├── .ert-runner ├── .gitignore ├── Cask ├── .travis.yml ├── test ├── test-helper.el ├── elixir-quoted-minor-mode-test.el ├── elixir-mode-font-test.el └── elixir-mode-indentation-test.el ├── elixir-deprecated.el ├── release.py ├── Rakefile ├── CONTRIBUTING.md ├── README.md ├── CHANGELOG.md ├── elixir-smie.el └── elixir-mode.el /.ert-runner: -------------------------------------------------------------------------------- 1 | -L . 2 | --quiet 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *\~ 2 | ._* 3 | /.cask/ 4 | /dist/ 5 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa) 3 | 4 | (package-file "elixir-mode.el") 5 | 6 | (files "*.el") 7 | 8 | (development 9 | (depends-on "ert-runner")) 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 17.1 4 | env: 5 | - EMACS=emacs24 EMACS_REPO=cassou/emacs TEST_RUN="rake test" 6 | - EMACS=emacs-snapshot EMACS_REPO=ubuntu-elisp/ppa TEST_RUN="rake test-no-gui" 7 | before_install: 8 | - sudo add-apt-repository -y ppa:$EMACS_REPO 9 | - sudo apt-get update 10 | - sudo apt-get -y install $EMACS 11 | - cd /tmp 12 | - wget https://github.com/elixir-lang/elixir/releases/download/v1.0.0/Precompiled.zip 13 | - unzip Precompiled.zip 14 | - export PATH="$PWD/bin:$PATH" 15 | - cd - 16 | - curl -fsSkL https://raw.github.com/cask/cask/master/go | python 17 | - export PATH="/home/travis/.cask/bin:$PATH" 18 | script: 19 | - $TEST_RUN -------------------------------------------------------------------------------- /test/test-helper.el: -------------------------------------------------------------------------------- 1 | ;;; test-helper.el --- Test helper 2 | 3 | ;;; Commentary: 4 | ;; 5 | 6 | ;;; Code: 7 | 8 | (require 'ert-x) 9 | 10 | (message "Running tests on Emacs %s" emacs-version) 11 | 12 | ;; The test fixtures assume an indentation width of 2, so we need to set that 13 | ;; up for the tests. 14 | (setq-default default-tab-width 2 15 | indent-tabs-mode nil) 16 | 17 | ;; Load the elixir-mode under test 18 | (require 'elixir-mode) 19 | 20 | ;; Helpers 21 | 22 | (defmacro* elixir-deftest (name args &body body) 23 | (declare (indent 2) 24 | (&define :name test name sexp 25 | [&optional [":documentation" stringp]] 26 | [&optional [":expected-result" sexp]] 27 | def-body)) 28 | `(ert-deftest ,(intern (format "elixir-ert-%s" name)) () 29 | "" 30 | ,@args 31 | (let ((elixir-smie-verbose-p t)) 32 | ,@body))) 33 | 34 | (defmacro* elixir-ert-with-test-buffer ((&rest args) initial-contents &body body) 35 | (declare (indent 2)) 36 | `(ert-with-test-buffer (,@args) 37 | (elixir-mode) 38 | (insert ,initial-contents) 39 | ,@body)) 40 | 41 | (defmacro elixir-test-with-temp-buffer (content &rest body) 42 | "Evaluate BODY in a temporary buffer with CONTENT." 43 | (declare (debug t) 44 | (indent 1)) 45 | `(with-temp-buffer 46 | (insert ,content) 47 | (elixir-mode) 48 | (font-lock-fontify-buffer) 49 | (goto-char (point-min)) 50 | ,@body)) 51 | 52 | (defmacro* elixir-def-indentation-test (name args initial-contents expected-output) 53 | (declare (indent 2)) 54 | `(elixir-deftest ,name ,args 55 | (elixir-ert-with-test-buffer (:name ,(format "(Expected)" name)) 56 | ,initial-contents 57 | (let ((indented (ert-buffer-string-reindented))) 58 | (delete-region (point-min) (point-max)) 59 | (insert ,expected-output) 60 | (ert-with-test-buffer (:name ,(format "(Actual)" name)) 61 | (elixir-mode) 62 | (insert indented) 63 | (should (equal indented ,expected-output))))))) 64 | 65 | (when (s-contains? "--win" (getenv "ERT_RUNNER_ARGS")) 66 | (defun ert-runner/run-tests-batch-and-exit (selector) 67 | (ert-run-tests-interactively selector))) 68 | 69 | (provide 'test-helper) 70 | 71 | ;;; test-helper.el ends here 72 | -------------------------------------------------------------------------------- /test/elixir-quoted-minor-mode-test.el: -------------------------------------------------------------------------------- 1 | ;;; elixir-quoted-minor-mode-test.el --- Quoted minor mode testsuite 2 | 3 | ;;; Commentary: 4 | ;; 5 | 6 | ;;; Code: 7 | 8 | (ert-deftest elixir-quoted-minor-mode/base () 9 | (elixir-test-with-temp-buffer 10 | "sum(1, 2)" 11 | (elixir-mode-string-to-quoted-on-current-line) 12 | (should (string= (buffer-name) elixir-quoted--buffer-name)) 13 | (should (search-forward "{:sum, [line: 1], [1, 2]}" nil t)) 14 | (should (eq 'quit-window (key-binding "q"))) 15 | (call-interactively (key-binding "q")) 16 | (should-not (string= (buffer-name) elixir-quoted--buffer-name)))) 17 | 18 | (ert-deftest elixir-quoted-minor-mode/read-only () 19 | (elixir-test-with-temp-buffer 20 | "sum(1,2)" 21 | (elixir-mode-string-to-quoted-on-current-line) 22 | (should buffer-read-only) 23 | (should-error (call-interactively (key-binding "w"))))) 24 | 25 | (ert-deftest elixir-quoted-minor-mode/multiple-invocation () 26 | (let ((test-buffer (current-buffer))) 27 | (elixir-test-with-temp-buffer 28 | "sum(1,2)" 29 | (elixir-mode-string-to-quoted-on-current-line) 30 | (should (search-forward "{:sum, [line: 1], [1, 2]}" nil t)) 31 | (pop-to-buffer test-buffer) 32 | (erase-buffer) 33 | (insert "sum(3, 2)") 34 | (elixir-mode-string-to-quoted-on-current-line) 35 | (should-not (search-forward "{:sum, [line: 1], [1, 2]}" nil t)) 36 | (should (search-forward "{:sum, [line: 1], [3, 2]}" nil t))))) 37 | 38 | (ert-deftest elixir-quoted-minor-mode/indentation () 39 | (elixir-test-with-temp-buffer 40 | "" 41 | (insert "if a do\n") 42 | (insert " b\n") 43 | (insert "else\n") 44 | (insert " c\n") 45 | (insert "end") 46 | (elixir-mode-string-to-quoted-on-region (point-min) (point-max)) 47 | (should (search-forward "{:if, [line: 1],\n" nil t)) 48 | (should (search-forward " [{:a, [line: 1], nil}" nil t)))) 49 | 50 | (ert-deftest elixir-quoted-minor-mode/undo () 51 | (let ((test-buffer (current-buffer))) 52 | (elixir-test-with-temp-buffer 53 | "sum(1, 2)" 54 | (elixir-mode-string-to-quoted-on-current-line) 55 | (should-error (undo)) 56 | (pop-to-buffer test-buffer) 57 | (erase-buffer) 58 | (insert "sum(3, 2)") 59 | (elixir-mode-string-to-quoted-on-current-line) 60 | (should-error (undo))))) 61 | 62 | (provide 'elixir-quoted-minor-mode-test) 63 | 64 | ;;; elixir-quoted-minor-mode-test.el ends here 65 | -------------------------------------------------------------------------------- /elixir-deprecated.el: -------------------------------------------------------------------------------- 1 | ;;; elixir-deprecated.el --- Functionality to display deprecated messages 2 | 3 | ;; Copyright 2011-2014 secondplanet 4 | ;; 2013-2014 Matt DeBoard, Samuel Tonini, Andreas Fuchs 5 | ;; Authors: Humza Yaqoob, 6 | ;; Andreas Fuchs , 7 | ;; Matt DeBoard 8 | ;; Samuel Tonini 9 | 10 | ;; This file is not part of GNU Emacs. 11 | 12 | ;; This program is free software: you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; This program is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with this program. If not, see . 24 | 25 | ;;; Commentary: 26 | 27 | ;; Functionality to display deprecated messages 28 | 29 | ;;; Code: 30 | 31 | (defface elixir-deprecated--warning-face 32 | '((t (:inherit font-lock-variable-name-face :bold t :foreground "red"))) 33 | "" 34 | :group 'elixir-deprecated) 35 | 36 | (defface elixir-deprecated--function-face 37 | '((t (:inherit font-lock-variable-name-face :bold t :foreground "green"))) 38 | "" 39 | :group 'elixir-deprecated) 40 | 41 | (defun elixir-deprecated--warning (function-name message) 42 | (let ((buffer (get-buffer "*Warnings*"))) 43 | (when buffer 44 | (kill-buffer buffer)) 45 | (display-warning :deprecated 46 | (concat "\n\n" 47 | (propertize "DEPRECATION WARNING: " 48 | 'face 'elixir-deprecated--warning-face) 49 | (propertize (format "[ %s ]\n\n" function-name) 50 | 'face 'elixir-deprecated--function-face) 51 | message)) :warning)) 52 | 53 | ;; DEPRECATED MESSAGES FOR RELEASE 3.0.0 54 | 55 | (defvar elixir-deprecated--alchemist-message "This function will be removed in version 3.0.0.\n 56 | Please use the package *alchemist.el* for compilation functionality.\n 57 | Alchemist: http://www.github.com/tonini/alchemist.el") 58 | 59 | (defun elixir-deprecated-use-alchemist (function-name) 60 | (elixir-deprecated--warning function-name 61 | elixir-deprecated--alchemist-message)) 62 | 63 | (provide 'elixir-deprecated) 64 | -------------------------------------------------------------------------------- /release.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """This script generates release notes for each merged pull request from 3 | git merge-commit messages. 4 | 5 | Usage: 6 | 7 | `python release.py [--output {file,stdout}]` 8 | 9 | For example, if you wanted to find the diff between version 1.0 and 1.2, 10 | and write the output to the release notes file, you would type the 11 | following: 12 | 13 | `python release.py 1.0 1.2 -o file` 14 | 15 | """ 16 | import os.path as op 17 | import re 18 | import subprocess 19 | from collections import deque 20 | 21 | 22 | def commit_msgs(start_commit, end_commit): 23 | """Run the git command that outputs the merge commits (both subject 24 | and body) to stdout, and return the output. 25 | 26 | """ 27 | fmt_string = ("'%s%n* [#{pr_num}]" 28 | "(https://github.com/elixir-lang/emacs-elixir/pull/{pr_num}) - %b'") 29 | return subprocess.check_output([ 30 | "git", 31 | "log", 32 | "--pretty=format:%s" % fmt_string, 33 | "--merges", "%s..%s" % (start_commit, end_commit)]) 34 | 35 | 36 | def release_note_lines(msgs): 37 | """Parse the lines from git output and format the strings using the 38 | pull request number. 39 | 40 | """ 41 | ptn = r"Merge pull request #(\d+).*\n([^\n]*)'$" 42 | pairs = re.findall(ptn, msgs, re.MULTILINE) 43 | return deque(body.format(pr_num=pr_num) for pr_num, body in pairs) 44 | 45 | 46 | def release_header_line(version, release_date=None): 47 | release_date = release_date or datetime.date.today().strftime('%Y/%m/%d') 48 | return "## %s - %s" % (version, release_date) 49 | 50 | 51 | def prepend(filename, lines, release_header=False): 52 | """Write `lines` (i.e. release notes) to file `filename`.""" 53 | if op.exists(filename): 54 | with open(filename, 'r+') as f: 55 | first_line = f.read() 56 | f.seek(0, 0) 57 | f.write('\n\n'.join([lines, first_line])) 58 | else: 59 | with open(filename, 'w') as f: 60 | f.write(lines) 61 | f.write('\n') 62 | 63 | 64 | if __name__ == "__main__": 65 | import argparse 66 | import datetime 67 | 68 | parser = argparse.ArgumentParser() 69 | parser.add_argument('start_commit', metavar='START_COMMIT_OR_TAG') 70 | parser.add_argument('end_commit', metavar='END_COMMIT_OR_TAG') 71 | parser.add_argument('--filepath', '-f', 72 | help="Absolute path to output file.") 73 | parser.add_argument('--tag', '-t', metavar='NEW_TAG') 74 | parser.add_argument( 75 | '--date', '-d', metavar='RELEASE_DATE', 76 | help="Date of release for listed patch notes. Use yyyy/mm/dd format.") 77 | args = parser.parse_args() 78 | start, end = args.start_commit, args.end_commit 79 | lines = release_note_lines(commit_msgs(start, end)) 80 | 81 | if args.tag: 82 | lines.appendleft(release_header_line(args.tag, args.date)) 83 | 84 | lines = '\n'.join(lines) 85 | 86 | if args.filepath: 87 | filename = op.abspath(args.filepath) 88 | prepend(filename, lines) 89 | else: 90 | print lines 91 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | EMACS = "emacs" 2 | CASK = "cask" 3 | 4 | task default: %w[test] 5 | 6 | desc "Create a new release." 7 | task release: [:test] do 8 | current_version = run('git tag').split(/\n/).last.strip[1..-1] 9 | version = ask("What version do you want to release? (current: #{current_version}): ") 10 | version_tag = "v%s" % version 11 | 12 | if run('git tag').split(/\n/).include?(version_tag) 13 | raise("This tag has already been committed to the repo.") 14 | end 15 | 16 | run "./release.py v#{current_version} HEAD -t #{version_tag} -f CHANGELOG.md" 17 | 18 | elixir_mode_contents = File.read('elixir-mode.el') 19 | File.write('elixir-mode.el', update_version(elixir_mode_contents, current_version, version)) 20 | git_changes(version, version_tag) 21 | end 22 | 23 | desc "Installed Emacs information" 24 | task 'info' do 25 | process_info "Install Emacs information" 26 | say "" 27 | say "PATH: ", :green, false 28 | system "which #{EMACS}" 29 | say "VERSION: ", :green, false 30 | system "#{CASK} exec #{EMACS} --version | head -1" 31 | end 32 | 33 | desc "Run test suite" 34 | task "test" do 35 | process_info "Install package dependencies" 36 | say "" 37 | say "#{indent(3)}Command: ", :yellow, false 38 | sh "cask install" 39 | say "" 40 | 41 | process_info "Run test suite" 42 | say "" 43 | system "#{CASK} exec ert-runner" 44 | 45 | exit_when_failed!("Test suite failed!\n") 46 | end 47 | 48 | desc "Run test suite with Emacs without GUI window" 49 | task "test-no-gui" do 50 | process_info "Install package dependencies" 51 | say "" 52 | say "#{indent(3)}Command: ", :yellow, false 53 | sh "cask install" 54 | say "" 55 | 56 | process_info "Run test suite with --no-win" 57 | say "" 58 | system "#{CASK} exec ert-runner --no-win" 59 | 60 | print_when_success("Test suite success\n") 61 | exit_when_failed!("Test suite failed!\n") 62 | end 63 | 64 | namespace :testing do 65 | desc "Run indentation test suite" 66 | task "indentation" do 67 | process_info "Install package dependencies" 68 | say "" 69 | say "#{indent(3)}Command: ", :yellow, false 70 | sh "cask install" 71 | say "" 72 | 73 | process_info "Run test suite" 74 | say "" 75 | system "#{CASK} exec ert-runner -t indentation" 76 | 77 | exit_when_failed!("Test suite failed!\n") 78 | end 79 | 80 | desc "Run font highlighting test suite" 81 | task "fontification" do 82 | process_info "Install package dependencies" 83 | say "" 84 | say "#{indent(3)}Command: ", :yellow, false 85 | sh "cask install" 86 | say "" 87 | 88 | process_info "Run test suite" 89 | say "" 90 | system "#{CASK} exec ert-runner -t fontification,syntax-table" 91 | 92 | exit_when_failed!("Test suite failed!\n") 93 | end 94 | end 95 | 96 | def git_changes(version, version_tag) 97 | run "git commit -a -m \"prepare #{version}\"" 98 | run "git tag -a -m \"Version #{version}\" #{version_tag}" 99 | run "git push origin" 100 | run "git push origin --tags" 101 | end 102 | 103 | def update_version(content, from, to) 104 | content = content.gsub("Version: #{from}", "Version: #{to}") 105 | end 106 | 107 | def exit_when_failed!(message) 108 | if $?.exitstatus != 0 109 | warning(message) 110 | exit(1) 111 | end 112 | end 113 | 114 | def print_when_success(message) 115 | if $?.exitstatus == 0 116 | info(message) 117 | end 118 | end 119 | 120 | def ansi 121 | { 122 | green: "\e[32m", 123 | red: "\e[31m", 124 | white: "\e[37m", 125 | bold: "\e[1m", 126 | on_green: "\e[42m", 127 | ending: "\e[0m" 128 | } 129 | end 130 | 131 | def run(command) 132 | `#{command}` 133 | end 134 | 135 | def say(message, type=:white, newline=true) 136 | message = "#{ansi[type]}#{message}#{ansi[:ending]}" 137 | if newline 138 | puts message 139 | else 140 | print message 141 | end 142 | end 143 | 144 | def process_info(message) 145 | puts "#{ansi[:bold]}#{ansi[:green]}#{message}#{ansi[:ending]}#{ansi[:ending]}" 146 | end 147 | 148 | def info(message) 149 | puts "#{ansi[:green]}#{ansi[:bold]}#{message}#{ansi[:ending]}#{ansi[:ending]}" 150 | end 151 | 152 | def warning(message) 153 | puts "#{ansi[:red]}#{ansi[:bold]}#{message}#{ansi[:ending]}#{ansi[:ending]}" 154 | end 155 | 156 | def ask(question, type=:white) 157 | say(question, type, false) 158 | answer = STDIN.gets.chomp 159 | if answer == "" || answer.nil? 160 | return nil 161 | else 162 | return answer 163 | end 164 | end 165 | 166 | def indent(count) 167 | " " * count 168 | end 169 | 170 | def colorize(string, color) 171 | "#{ansi[color]}#{string}#{ansi[:ending]}" 172 | end 173 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Guidelines For Reporting An Issue/Feature 2 | 3 | So you've found a bug or have a great idea for a feature. Here's the steps you 4 | should take to help get it added/fixed in emacs-elixir 5 | 6 | * First, check to see if there's an existing issue/pull request for the 7 | bug/feature. All issues are at https://github.com/elixir-lang/emacs-elixir/issues 8 | and pull reqs are at https://github.com/elixir-lang/emacs-elixir/pulls. 9 | * If there isn't one there, please file an issue. The ideal report includes: 10 | 11 | * A description of the problem/suggestion. 12 | * How to recreate the bug. 13 | * Versions of your: 14 | 15 | * operating system 16 | * elixir-mode 17 | * emacs 18 | 19 | * Ideally, creating a pull request with a (failing) test case demonstrating 20 | what's wrong. This makes it easy for us to reproduce & fix the problem. 21 | 22 | You might also hop into the IRC channel (``#elixir-lang`` on ``irc.freenode.net``) 23 | & raise your question there, as there may be someone who can help you with a 24 | work-around. 25 | 26 | 27 | ## Guidelines For Contributing Code 28 | 29 | If you're ready to take the plunge & contribute back some code, the 30 | process should look like: 31 | 32 | * Fork the project on GitHub into your own account. 33 | * Clone your copy of emacs-elixir. 34 | * Make a new branch in git & commit your changes there. 35 | * Push your new branch up to GitHub. 36 | * Again, ensure there isn't already an issue or pull request out there on it. 37 | If there is & you feel you have a better fix, please take note of the issue 38 | number & mention it in your pull request. 39 | * Create a new pull request (based on your branch), including what the 40 | problem/feature is, versions of your software & referencing any related 41 | issues/pull requests. 42 | 43 | In order to be merged into emacs-elixir, contributions must have the following: 44 | 45 | * A solid patch that: 46 | 47 | * is clear. 48 | * works across all supported versions of Emacs (24+). 49 | * follows the existing style of the code base. 50 | * comments included as needed. 51 | 52 | * A test case that demonstrates the previous flaw that now passes 53 | with the included patch. 54 | 55 | If your contribution lacks any of these things, they will have to be added 56 | by a core contributor before being merged into emacs-elixir proper, which may take 57 | substantial time for the all-volunteer team to get to. 58 | 59 | ## How to run tests 60 | 61 | There are three tools that helps us to test emacs-elixir: 62 | 63 | * [EVM](https://github.com/rejeep/evm) - a command-line tool which allows you to easily install, manage, and work with multiple Emacs versions. 64 | * [Cask](https://github.com/cask/cask) - a project management tool for Emacs that helps automate the package development cycle. 65 | * [Ert-runner](https://github.com/rejeep/ert-runner.el) - a tool for Emacs projects tested using Ert. 66 | 67 | ### Emacs Version Manager 68 | 69 | To install [EVM](https://github.com/rejeep/evm), run: 70 | 71 | ```bash 72 | $ sudo mkdir /usr/local/evm 73 | $ sudo chown $USER: /usr/local/evm 74 | $ curl -fsSkL https://raw.github.com/rejeep/evm/master/go | bash 75 | $ export PATH="~/.evm/bin:$PATH" # Add it to your .bashrc or analogue 76 | ``` 77 | 78 | To list all available Emacs versions you can install, run: 79 | 80 | ```bash 81 | $ evm list 82 | ``` 83 | 84 | To install a version (for example `emacs-24.3-bin`), run: 85 | 86 | ```bash 87 | $ evm install emacs-24.3-bin 88 | ``` 89 | 90 | Read more about [EVM](https://github.com/rejeep/evm). 91 | 92 | ### Cask and ert-runner 93 | 94 | To install Cask, run: 95 | 96 | ```bash 97 | $ curl -fsSkL https://raw.github.com/cask/cask/master/go | python 98 | $ export PATH="~/.cask/bin:$PATH" # Add it to your .bashrc or analogue 99 | ``` 100 | 101 | To install [Ert-runner](https://github.com/rejeep/ert-runner.el), run: 102 | 103 | ```bash 104 | $ cd path/to/emacs-elixir 105 | $ cask install # install ert-runner 106 | $ EMACS=`evm bin emacs-24.3-bin` cask install # install ert-runner for Emacs 24.3 107 | ``` 108 | 109 | #### Examples of usage 110 | 111 | * Run all tests: 112 | 113 | ```bash 114 | $ cask exec ert-runner 115 | ``` 116 | 117 | * Run all tests for Emacs 24.3: 118 | 119 | ```bash 120 | $ EMACS=`evm bin emacs-24.3-bin` cask exec ert-runner 121 | ``` 122 | 123 | Run all tests which are tagged `fontification`: 124 | 125 | ```bash 126 | $ cask exec ert-runner -t fontification 127 | ``` 128 | 129 | Run all tests with `elixir-smie-verbose-p` equal to `t`: 130 | 131 | ```bash 132 | $ cask exec ert-runner --verbose 133 | ``` 134 | 135 | Run all tests interactively: 136 | 137 | ```bash 138 | $ cask exec ert-runner --win 139 | ``` 140 | 141 | Run all tests which are tagged `fontification` for Emacs 24.3 interactively: 142 | 143 | ```bash 144 | $ EMACS=`evm bin emacs-24.3-bin` cask exec ert-runner -t fontification --win 145 | ``` 146 | 147 | Read more about [Cask](https://github.com/cask/cask) and [Ert-runner](https://github.com/rejeep/ert-runner.el). 148 | -------------------------------------------------------------------------------- /test/elixir-mode-font-test.el: -------------------------------------------------------------------------------- 1 | ;;; elixir-mode-font-test.el --- Font highlighting testsuite 2 | 3 | ;;; Commentary: 4 | ;; 5 | ;; `elixir-test-with-temp-buffer' and `elixir-test-face-at' are both slightly 6 | ;; modified versions of the original at 7 | ;; https://github.com/lunaryorn/puppet-mode/blob/master/test/puppet-mode-test.el 8 | 9 | ;;; Code: 10 | 11 | (defun elixir-test-face-at (pos &optional content) 12 | "Get the face at POS in CONTENT. 13 | 14 | If CONTENT is not given, return the face at POS in the current 15 | buffer." 16 | (if content 17 | (elixir-test-with-temp-buffer content 18 | (get-text-property pos 'face)) 19 | (get-text-property pos 'face))) 20 | 21 | (ert-deftest elixir-mode-syntax-table/fontify-regex () 22 | :tags '(fontification syntax-table) 23 | (elixir-test-with-temp-buffer 24 | "match = ~r/foo/" 25 | (should (eq (elixir-test-face-at 1) 'font-lock-variable-name-face)) 26 | (should (eq (elixir-test-face-at 9) 'font-lock-builtin-face)) 27 | (should (eq (elixir-test-face-at 12) 'font-lock-string-face)) 28 | ;; no face for regex delimiters 29 | (should (eq (elixir-test-face-at 15) nil)))) 30 | 31 | (ert-deftest elixir-mode-syntax-table/fontify-modules-and-types () 32 | :tags '(fontification syntax-table) 33 | (elixir-test-with-temp-buffer 34 | "defmodule Application.Behavior do 35 | use Application.Behaviour" 36 | (should (eq (elixir-test-face-at 1) 'font-lock-keyword-face)) 37 | (should (eq (elixir-test-face-at 11) 'font-lock-type-face)) 38 | (should (eq (elixir-test-face-at 22) 'font-lock-type-face)) 39 | (should (eq (elixir-test-face-at 23) 'font-lock-type-face)) 40 | (should (eq (elixir-test-face-at 32) 'font-lock-keyword-face)) 41 | (should (eq (elixir-test-face-at 37) 'font-lock-keyword-face)) 42 | (should (eq (elixir-test-face-at 41) 'font-lock-type-face)) 43 | (should (eq (elixir-test-face-at 52) 'font-lock-type-face)) 44 | (should (eq (elixir-test-face-at 53) 'font-lock-type-face)))) 45 | 46 | (ert-deftest elixir-mode-syntax-table/fontify-regex-with-quote () 47 | "https://github.com/elixir-lang/emacs-elixir/issues/23" 48 | :tags '(fontification syntax-table) 49 | :expected-result :failed 50 | (elixir-test-with-temp-buffer 51 | "~r/\"/ 52 | x = 15" 53 | (should (eq (elixir-test-face-at 7) 'font-lock-variable-name-face)))) 54 | 55 | (ert-deftest elixir-mode-syntax-table/fontify-regex-with-question/1 () 56 | "https://github.com/elixir-lang/emacs-elixir/issues/36" 57 | :tags '(fontification syntax-table) 58 | (elixir-test-with-temp-buffer 59 | "~r/^matt: (?\d+)$/mg 60 | x = 15" 61 | (should (eq (elixir-test-face-at 4) 'font-lock-string-face)) 62 | (should (eq (elixir-test-face-at 25) 'font-lock-variable-name-face)))) 63 | 64 | (ert-deftest elixir-mode-syntax-table/fontify-regex-with-question/2 () 65 | "https://github.com/elixir-lang/emacs-elixir/issues/29" 66 | :tags '(fontification syntax-table) 67 | (elixir-test-with-temp-buffer 68 | "a = \"\" <> \"?\" 69 | x = 15" 70 | (should (eq (elixir-test-face-at 15) 'font-lock-variable-name-face)))) 71 | 72 | (ert-deftest elixir-mode-syntax-table/fontify-function-name/1 () 73 | :tags '(fontification syntax-table) 74 | (elixir-test-with-temp-buffer 75 | "def fooBar do 76 | :foo 77 | end" 78 | (should (eq (elixir-test-face-at 5) 'font-lock-function-name-face)) 79 | (should (eq (elixir-test-face-at 8) 'font-lock-function-name-face)))) 80 | 81 | (ert-deftest elixir-mode-syntax-table/fontify-function-name/2 () 82 | :tags '(fontification syntax-table) 83 | (elixir-test-with-temp-buffer 84 | "def foo? do 85 | :foo 86 | end" 87 | (should (eq (elixir-test-face-at 5) 'font-lock-function-name-face)) 88 | (should (eq (elixir-test-face-at 8) 'font-lock-function-name-face)))) 89 | 90 | (ert-deftest elixir-mode-syntax-table/fontify-function-name/3 () 91 | :tags '(fontification syntax-table) 92 | (elixir-test-with-temp-buffer 93 | "def foo! do 94 | :foo 95 | end" 96 | (should (eq (elixir-test-face-at 5) 'font-lock-function-name-face)) 97 | (should (eq (elixir-test-face-at 8) 'font-lock-function-name-face)))) 98 | 99 | (ert-deftest elixir-mode-syntax-table/fontify-heredoc/1 () 100 | :tags '(fontification heredoc syntax-table) 101 | (elixir-test-with-temp-buffer 102 | "@doc \"\"\"" 103 | (should (eq (elixir-test-face-at 1) 'elixir-attribute-face)) 104 | (should (eq (elixir-test-face-at 2) 'elixir-attribute-face)) 105 | (should (eq (elixir-test-face-at 6) 'font-lock-string-face)))) 106 | 107 | (ert-deftest elixir-mode-syntax-table/fontify-heredoc/2 () 108 | :tags '(fontification heredoc syntax-table) 109 | (elixir-test-with-temp-buffer 110 | "@moduledoc \"\"\"" 111 | (should (eq (elixir-test-face-at 1) 'elixir-attribute-face)) 112 | (should (eq (elixir-test-face-at 2) 'elixir-attribute-face)) 113 | (should (eq (elixir-test-face-at 12) 'font-lock-string-face)))) 114 | 115 | (ert-deftest elixir-mode-syntax-table/fontify-heredoc/3 () 116 | :tags '(fontification heredoc syntax-table) 117 | (elixir-test-with-temp-buffer 118 | "~s\"\"\"" 119 | (should (eq (elixir-test-face-at 1) 'elixir-attribute-face)) 120 | (should (eq (elixir-test-face-at 2) 'elixir-attribute-face)) 121 | (should (eq (elixir-test-face-at 3) 'font-lock-string-face)))) 122 | 123 | (ert-deftest elixir-mode-syntax-table/fontify-atoms () 124 | :tags '(fontification atom syntax-table) 125 | (elixir-test-with-temp-buffer 126 | ":oriole 127 | :andale" 128 | (should (eq (elixir-test-face-at 3) 'elixir-atom-face)) 129 | (should (eq (elixir-test-face-at 5) 'elixir-atom-face)) 130 | (should (eq (elixir-test-face-at 10) 'elixir-atom-face)) 131 | (should (eq (elixir-test-face-at 13) 'elixir-atom-face)))) 132 | 133 | (ert-deftest elixir-mode-syntax-table/fontify-map-keys () 134 | :tags '(fontification map syntax-table) 135 | (elixir-test-with-temp-buffer 136 | "%{a: 1, b: 2}" 137 | (should (eq (elixir-test-face-at 3) 'elixir-atom-face)) 138 | (should (eq (elixir-test-face-at 4) 'elixir-atom-face)) 139 | (should (eq (elixir-test-face-at 9) 'elixir-atom-face)) 140 | (should (eq (elixir-test-face-at 10) 'elixir-atom-face)))) 141 | 142 | (ert-deftest elixir-mode-syntax-table/fontify-interpolation () 143 | :tags '(fontification interpolation syntax-table) 144 | (elixir-test-with-temp-buffer 145 | "\"#{1 + 2} is 3.\"" 146 | (should (eq (elixir-test-face-at 1) 'font-lock-string-face)) 147 | (should (eq (elixir-test-face-at 3) 'font-lock-variable-name-face)) 148 | (should (eq (elixir-test-face-at 11) 'font-lock-string-face)))) 149 | 150 | (ert-deftest elixir-mode-syntax-table/fontify-continuation-lines-assignment () 151 | :tags '(fontification syntax-table) 152 | (elixir-test-with-temp-buffer 153 | "some_var = 154 | some_expr" 155 | (should (eq (elixir-test-face-at 1) 'font-lock-variable-name-face)))) 156 | 157 | (ert-deftest elixir-mode-syntax-table/fontify-assignment-with-pattern/1 () 158 | :expected-result :failed 159 | :tags '(fontification syntax-table) 160 | (elixir-test-with-temp-buffer 161 | "{x, y} = some_expr" 162 | (should (eq (elixir-test-face-at 2) 'font-lock-variable-name-face)) 163 | (should (eq (elixir-test-face-at 5) 'font-lock-variable-name-face)))) 164 | 165 | (ert-deftest elixir-mode-syntax-table/fontify-assignment-with-pattern/2 () 166 | :expected-result :failed 167 | :tags '(fontification syntax-table) 168 | (elixir-test-with-temp-buffer 169 | "[h|t] = some_expr" 170 | (should (eq (elixir-test-face-at 2) 'font-lock-variable-name-face)) 171 | (should (eq (elixir-test-face-at 4) 'font-lock-variable-name-face)))) 172 | 173 | (provide 'elixir-mode-font-test) 174 | 175 | ;;; elixir-mode-font-test.el ends here 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License GPL 3][badge-license]](http://www.gnu.org/licenses/gpl-3.0.txt) 2 | [![Build Status](https://travis-ci.org/elixir-lang/emacs-elixir.svg?branch=master)](https://travis-ci.org/elixir-lang/emacs-elixir) 3 | [![MELPA Stable](http://stable.melpa.org/packages/elixir-mode-badge.svg)](http://stable.melpa.org/#/elixir-mode) 4 | 5 | # Elixir Mode 6 | 7 | Provides font-locking, indentation and navigation support for the 8 | [Elixir programming language.](http://elixir-lang.org/) 9 | 10 | - [Installation](#installation) 11 | - [Via package.el](#via-packageel) 12 | - [Via el-get](#via-el-get) 13 | - [Manual](#manual) 14 | - [Usage](#usage) 15 | - [Interactive Commands](#interactive-commands) 16 | - [Configuration](#configuration) 17 | - [Hooks](#hooks) 18 | - [Keymapping](#keymapping) 19 | - [Notes](#notes) 20 | - [Elixir Tooling Integration](#elixir-tooling-integration) 21 | - [History](#history) 22 | - [Contributing](#contributing) 23 | 24 | ## Installation 25 | 26 | ### Via package.el 27 | 28 | `package.el` is the built-in package manager in Emacs. 29 | 30 | `Elixir-Mode` is available on the two major community maintained repositories - 31 | [MELPA STABLE](melpa-stable.milkbox.net) and [MELPA](http://melpa.milkbox.net). 32 | 33 | You can install `Elixir-Mode` with the following command: 34 | 35 | M-x package-install [RET] elixir-mode [RET] 36 | 37 | or by adding this bit of Emacs Lisp code to your Emacs initialization file 38 | (`.emacs` or `init.el`): 39 | 40 | ```el 41 | (unless (package-installed-p 'elixir-mode) 42 | (package-install 'elixir-mode)) 43 | ``` 44 | 45 | If the installation doesn't work try refreshing the package list: 46 | 47 | M-x package-refresh-contents [RET] 48 | 49 | Keep in mind that MELPA packages are built automatically from 50 | the `master` branch, meaning bugs might creep in there from time to 51 | time. Never-the-less, installing from MELPA is the recommended way of 52 | obtaining `Elixir-Mode`, as the `master` branch is normally quite stable and 53 | "stable" (tagged) builds are released somewhat infrequently. 54 | 55 | With the most recent builds of Emacs, you can pin `Elixir-Mode` to always 56 | use MELPA Stable by adding this to your Emacs initialization: 57 | 58 | ```el 59 | (add-to-list 'package-pinned-packages '(elixir-mode . "melpa-stable") t) 60 | ``` 61 | 62 | ### Via el-get 63 | 64 | [el-get](https://github.com/dimitri/el-get) is another popular package manager for Emacs. If you're an el-get 65 | user just do M-x el-get-install [RET] elixir-mode [RET]. 66 | 67 | ### Manual 68 | 69 | You can install `Elixir-Mode` manually by placing `Elixir-Mode` on your `load-path` and 70 | `require` ing it. Many people favour the folder `~/.emacs.d/vendor`. 71 | 72 | ```el 73 | (add-to-list 'load-path "~/.emacs.d/vendor") 74 | (require 'elixir-mode) 75 | ``` 76 | 77 | ## Usage 78 | 79 | ### Interactive Commands 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 104 | 105 | 106 | 107 | 109 | 110 | 111 | 112 | 114 | 115 | 116 | 117 | 119 | 120 | 121 | 122 | 124 | 125 | 126 | 127 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 |
Command (For the M-x prompt.)Description
elixir-modeSwitches to elixir-mode.
elixir-mode-compile-fileCompile Elixir files. Works fine on *.exs files, too, if needed.
elixir-cos-modeApplies compile-on-save minor mode.
elixir-mode-iex 100 | Launch iex inside Emacs. Use C-u 101 | univesal-argument 102 | to run iex with some additional arguments. 103 |
elixir-mode-eval-on-regionEvaluates the Elixir code on the marked region. 108 | This is bound to C-c ,r while in elixir-mode.
elixir-mode-eval-on-current-lineEvaluates the Elixir code on the current line. 113 | This is bound to C-c ,c while in elixir-mode.
elixir-mode-eval-on-current-bufferEvaluates the Elixir code in the current buffer. 118 | This is bound to C-c ,b while in elixir-mode.
elixir-mode-string-to-quoted-on-regionGet the representation of the expression on the marked region. 123 | This is bound to C-c ,a while in elixir-mode.
elixir-mode-string-to-quoted-on-current-lineGet the representation of the expression on the current line. 128 | This is bound to C-c ,l while in elixir-mode.
elixir-mode-opengithubOpen the GitHub page for Elixir.
elixir-mode-open-elixir-homeGo to Elixir README in the browser.
elixir-mode-open-docs-masterOpen the Elixir documentation for the master.
elixir-mode-open-docs-stableOpen the Elixir documentation for the latest stable release.
elixir-mode-show-versionPrint version info for elixir-mode.
152 | 153 | ### Configuration 154 | 155 | Any file that matches the glob `*.ex[s]` or `*.elixir` is 156 | automatically opened in elixir-mode, but you can change this 157 | functionality easily. 158 | 159 | ```lisp 160 | ;; Highlights *.elixir2 as well 161 | (add-to-list 'auto-mode-alist '("\\.elixir2\\'" . elixir-mode)) 162 | ``` 163 | 164 | Custom variables for elixir-mode. 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 |
VariableDefaultDescription
elixir-compiler-command (string)"elixirc"Command to compile Elixir code.
elixir-iex-command (string)"iex"Command to start an interactive REPL in IEX.
elixir-mode-cygwin-paths (boolean)tShould Cygwin paths be used on Windows?
elixir-mode-cygwin-prefix (string)"/cygdrive/C"The prefix for Cygwin-style paths.
193 | 194 | ### Hooks 195 | 196 | Hooks can be used to add functionality to elixir-mode. This example 197 | adds compile on save. 198 | 199 | ```lisp 200 | (defun elixir-mode-compile-on-save () 201 | "Elixir mode compile files on save." 202 | (and (file-exists (buffer-file-name)) 203 | (file-exists (elixir-mode-compiled-file-name)) 204 | (elixir-cos-mode t))) 205 | (add-hook 'elixir-mode-hook 'elixir-mode-compile-on-save) 206 | ``` 207 | 208 | ### Keymapping 209 | 210 | Keymaps can be added to the `elixir-mode-map` variable. 211 | 212 | ## Notes 213 | 214 | If you want to use `ruby-end-mode` for a more comfortable editing 215 | experience, you can add the following to your `elixir-mode-hook`: 216 | 217 | ```lisp 218 | (add-to-list 'elixir-mode-hook 219 | (defun auto-activate-ruby-end-mode-for-elixir-mode () 220 | (set (make-variable-buffer-local 'ruby-end-expand-keywords-before-re) 221 | "\\(?:^\\|\\s-+\\)\\(?:do\\)") 222 | (set (make-variable-buffer-local 'ruby-end-check-statement-modifiers) nil) 223 | (ruby-end-mode +1))) 224 | ``` 225 | 226 | ## Elixir Tooling Integration 227 | 228 | If you looking for elixir tooling integration for emacs, check: [alchemist.el](https://github.com/tonini/alchemist.el) 229 | 230 | ## History 231 | 232 | This mode is based on the 233 | [Emacs mode by secondplanet](https://github.com/secondplanet/elixir-mode). 234 | 235 | ## Contributing 236 | 237 | Please read [CONTRIBUTING.md](https://github.com/elixir-lang/emacs-elixir/blob/master/CONTRIBUTING.md) for guidelines on how to contribute to this project. 238 | 239 | [badge-license]: https://img.shields.io/badge/license-GPL_3-green.svg 240 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v2.3.0-dev (unreleased) 4 | 5 | ### Bugfixes 6 | 7 | * [Syntax Highlighting] Modules don't start with underscore. This fix corrects the fontification of private function. 8 | 9 | ## v2.2.0 2014/12/31 10 | 11 | ### Enhancements 12 | 13 | * [Indentation] Indent listing inside square brackets. (#160) 14 | * [Indentation] Indent of binary sequence inside match block 15 | * [Indentation] Indent correct after a binary sequence `<<1,2,3,4>>`. 16 | * [Indentation] Indent correct after oneline `def ... do:` function 17 | * [Indentation] Correct behavior after last line in buffer. (#145) 18 | 19 | ## v2.1.1 - 2014/12/24 20 | 21 | ### Enhancements 22 | 23 | * [Indentation] Indent block inside a fn match. 24 | 25 | ### Bugfixes 26 | 27 | * [Indentation] #152 Fix indentation inside parens wrapped around def 28 | 29 | ## v2.1.0 - 2014/12/23 30 | 31 | ### Enhancements 32 | 33 | * [Indentation] Continue of indentation in multiple line assignment. 34 | * [Indentation] Always indent with only 2 spaces except when function arguments span multiple lines. 35 | * [Indentation] Fix the indentation for mixed matchings. 36 | * [Indentation] Pipe |> indentation works correctly. 37 | * [Syntax Highlighting] Fontify continuation lines assignment. 38 | 39 | ### Changes 40 | 41 | * [Deprecated] Add deprecated message for eval and quoted functions. 42 | 43 | ## v2.0.2 - 2014/10/29 44 | * [#136](https://github.com/elixir-lang/emacs-elixir/pull/136) - Expand def of block operator regex to include non-newlines. 45 | * [#137](https://github.com/elixir-lang/emacs-elixir/pull/137) - update rake tasks 46 | * [#135](https://github.com/elixir-lang/emacs-elixir/pull/135) - Update README so the MELPA badge points to stable build. 47 | * [#133](https://github.com/elixir-lang/emacs-elixir/pull/133) - refine readme 48 | * [#134](https://github.com/elixir-lang/emacs-elixir/pull/134) - refine the ability to show the current elixir-mode version 49 | * [#132](https://github.com/elixir-lang/emacs-elixir/pull/132) - Added: test coverage with undercover.el 50 | * [#131](https://github.com/elixir-lang/emacs-elixir/pull/131) - Fix documentation url 51 | * [#127](https://github.com/elixir-lang/emacs-elixir/pull/127) - Add failing test for comment in cond expression (fixed) 52 | * [#128](https://github.com/elixir-lang/emacs-elixir/pull/128) - Added: collection of failing tests 53 | * [#124](https://github.com/elixir-lang/emacs-elixir/pull/124) - Fixed: Build Status badge 54 | * [#123](https://github.com/elixir-lang/emacs-elixir/pull/123) - TravisCI: add-apt-repository and apt-get install fix 55 | * [#121](https://github.com/elixir-lang/emacs-elixir/pull/121) - Add TravisCI support 56 | * [#122](https://github.com/elixir-lang/emacs-elixir/pull/122) - Remove unused code 57 | 58 | ## v2.0.1 - 2014/09/11 59 | * [#119](https://github.com/elixir-lang/emacs-elixir/pull/119) - Fixed: indent-inside-parens 60 | * [#117](https://github.com/elixir-lang/emacs-elixir/pull/117) - Fixed: emacs 24.3 tests 61 | * [#116](https://github.com/elixir-lang/emacs-elixir/pull/116) - Add "How to run tests" section to CONTRIBUTING.md 62 | * [#118](https://github.com/elixir-lang/emacs-elixir/pull/118) - Added: try/rescue rule 63 | * [#114](https://github.com/elixir-lang/emacs-elixir/pull/114) - Fixed: run tests interectively for terminal 64 | 65 | ## v2.0.0 - 2014/09/08 66 | * [#113](https://github.com/elixir-lang/emacs-elixir/pull/113) - Cask and ert-runner support 67 | * [#110](https://github.com/elixir-lang/emacs-elixir/pull/110) - Added: ability to run tests via EVM 68 | * [#111](https://github.com/elixir-lang/emacs-elixir/pull/111) - Fixed: elixir-quoted-minor-mode tests for ert-run-tests-interactively 69 | * [#108](https://github.com/elixir-lang/emacs-elixir/pull/108) - Fix various issues caused by code followed by inline comments 70 | 71 | ## v1.5.0 - 2014/08/27 72 | * [#103](https://github.com/elixir-lang/emacs-elixir/pull/103) - Add elixir-quoted-minor-mode. 73 | 74 | ## v1.4.10 - 2014/08/26 75 | * [#102](https://github.com/elixir-lang/emacs-elixir/pull/102) - Add support for syntax highlighting for variable interpolation. Fixes #93 76 | * [#101](https://github.com/elixir-lang/emacs-elixir/pull/101) - Fix indentation after inline comment. Fixes #95 77 | 78 | ## v1.4.9 - 2014/08/25 79 | * [#100](https://github.com/elixir-lang/emacs-elixir/pull/100) - Fix indentation in multi-line match expressions. Fixes #98 80 | * [#99](https://github.com/elixir-lang/emacs-elixir/pull/99) - Tokenize trailing whitespace properly. Fixes #97 81 | * [#96](https://github.com/elixir-lang/emacs-elixir/pull/96) - Remove syntax highlighting for operators. 82 | 83 | ## v1.4.8 - 2014/08/19 84 | * [#92](https://github.com/elixir-lang/emacs-elixir/pull/92) - Update Rakefile to also run the release.py script. Refs #88. 85 | * [#91](https://github.com/elixir-lang/emacs-elixir/pull/91) - Updates to regexes for various lexemes 86 | * [#90](https://github.com/elixir-lang/emacs-elixir/pull/90) - Fix bug in atom highlighting. 87 | 88 | ## v1.4.7 - 2014/08/18 89 | * [#87](https://github.com/elixir-lang/emacs-elixir/pull/87) - Add syntax highlighting for heredocs & failing tests for indentation. 90 | * [#85](https://github.com/elixir-lang/emacs-elixir/pull/85) - Improve regex for defmodule highlighting. 91 | * [#84](https://github.com/elixir-lang/emacs-elixir/pull/84) - Improve fontification for identifiers. 92 | 93 | ## v1.4.6 - 2014/08/18 94 | * [#82](https://github.com/elixir-lang/emacs-elixir/pull/82) - Remove broken SMIE rule. 95 | 96 | ## v1.4.5 - 2014/08/18 97 | * [#81](https://github.com/elixir-lang/emacs-elixir/pull/81) - Rewrite token emitting functions 98 | 99 | ## v1.4.4 - 2014/08/18 100 | * [#79](https://github.com/elixir-lang/emacs-elixir/pull/79) - Remove erroneous defrecord syntax. 101 | 102 | ## v1.4.3 - 2014/08/16 103 | * [#75](https://github.com/elixir-lang/emacs-elixir/pull/75) - Clean up several minor bugbears in elixir-smie. 104 | * [#74](https://github.com/elixir-lang/emacs-elixir/pull/74) - Remove special indentation rules for operators, except booleans. 105 | 106 | ## v1.4.2 - 2014/08/15 107 | * [#73](https://github.com/elixir-lang/emacs-elixir/pull/73) - Fix buggy syntax highlighting behavior involving "?" 108 | * [#71](https://github.com/elixir-lang/emacs-elixir/pull/71) - Need two backslashes in regex string. 109 | * [#70](https://github.com/elixir-lang/emacs-elixir/pull/70) - Use define-derived-mode to define elixir-mode 110 | * [#69](https://github.com/elixir-lang/emacs-elixir/pull/69) - Remove unused variable 111 | 112 | ## v1.4.1 - 2014/08/11 113 | * [#66](https://github.com/elixir-lang/emacs-elixir/pull/66) - Indent correctly after one-liner if/do: statements. Fixes #65 114 | * [#64](https://github.com/elixir-lang/emacs-elixir/pull/64) - wrong indentation if space between if and statement 115 | * [#63](https://github.com/elixir-lang/emacs-elixir/pull/63) - Correctly indent one-line anon fns AND block fns. Fixes #59 116 | 117 | ## v1.4.0 - 2014/07/09 118 | * [#62](https://github.com/elixir-lang/emacs-elixir/pull/62) - Remove grammar entry causing erroneous alignment to ".". Fixes #49 119 | * [#61](https://github.com/elixir-lang/emacs-elixir/pull/61) - Remove "=" & left-assoc opers from "OP" regex. Fixes #18. 120 | * [#60](https://github.com/elixir-lang/emacs-elixir/pull/60) - Refactor font face defaults. 121 | * [#58](https://github.com/elixir-lang/emacs-elixir/pull/58) - Use string syntax highlighting for regex patterns. 122 | * [#57](https://github.com/elixir-lang/emacs-elixir/pull/57) - Add beginnings of font-face testing. 123 | 124 | ## v1.3.1 - 2014/07/05 125 | * [#52](https://github.com/elixir-lang/emacs-elixir/pull/52) - Add CLI for running elixir-mode tests. 126 | * [#48](https://github.com/elixir-lang/emacs-elixir/pull/48) - Add a SMIE rule function for "def". Fixes #38 and #41 127 | * [#50](https://github.com/elixir-lang/emacs-elixir/pull/50) - Remove grammar clause for "fn" terminal. Fixes #7 128 | * [#44](https://github.com/elixir-lang/emacs-elixir/pull/44) - sigil % to ~ 129 | * [#46](https://github.com/elixir-lang/emacs-elixir/pull/46) - Added highlight for unless and Task module 130 | * [#45](https://github.com/elixir-lang/emacs-elixir/pull/45) - Highlight defstruct and Actor, Base modules 131 | * [#40](https://github.com/elixir-lang/emacs-elixir/pull/40) - IMenu: Show ExUnit tests under heading "Tests". 132 | * [#37](https://github.com/elixir-lang/emacs-elixir/pull/37) - Remove needless statement 133 | * [#35](https://github.com/elixir-lang/emacs-elixir/pull/35) - Added simple imenu support. 134 | * [#32](https://github.com/elixir-lang/emacs-elixir/pull/32) - Properly make variables local 135 | * [#31](https://github.com/elixir-lang/emacs-elixir/pull/31) - needed for effective code-navigation via syntax-ppss 136 | * [#28](https://github.com/elixir-lang/emacs-elixir/pull/28) - Recognize ? char syntax 137 | * [#24](https://github.com/elixir-lang/emacs-elixir/pull/24) - elixir-mode-eval-on-current-buffer binding comment incorrect 138 | * [#22](https://github.com/elixir-lang/emacs-elixir/pull/22) - Enhance `elixir-mode-iex` to accept additional arguments 139 | 140 | ## 1.3.0 (June 24, 2013) 141 | * Add `elixir-mode-eval-on-region` to evalute Elixir code on the 142 | marked region. 143 | * Add `elixir-mode-eval-on-current-buffer` to evalute Elixir code in the current buffer. 144 | * Add `elixir-mode-eval-on-current-line` to evalute Elixir code on the current line. 145 | * Add `elixir-mode-string-to-quoted-on-region` to get the representation of the expression on the marked region. 146 | * Add `elixir-mode-string-to-quoted-on-current-line` to get the 147 | representation of the expression on the current line. 148 | -------------------------------------------------------------------------------- /elixir-smie.el: -------------------------------------------------------------------------------- 1 | ;;; elixir-smie.el --- Structural syntax support for elixir-mode 2 | 3 | ;; Copyright 2011-2015 secondplanet 4 | ;; 2013-2015 Samuel Tonini, Matt DeBoard, Andreas Fuchs 5 | 6 | ;; This file is not a part of GNU Emacs. 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation; either version 2, or (at your option) 11 | ;; any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program; if not, write to the Free Software 20 | ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 | 22 | ;;; Commentary: 23 | 24 | ;; Structural syntax support for elixir-mode 25 | 26 | ;;; Code: 27 | 28 | (require 'smie) 29 | 30 | ;; HACK: Patch for Emacs 24.3 smie that fix 31 | ;; https://github.com/elixir-lang/emacs-elixir/issues/107. 32 | ;; 33 | ;; defadvice is used to change the behavior only for elixir-mode. 34 | ;; Definition of advice is a definition of corresponding function 35 | ;; in Emacs 24.4. 36 | (when (and (= 24 emacs-major-version) 37 | (= 3 emacs-minor-version)) 38 | (defadvice smie-rule-parent (around elixir-mode-patch activate) 39 | (if (not (eq major-mode 'elixir-mode)) 40 | (progn ad-do-it) 41 | (setq ad-return-value 42 | (save-excursion 43 | (goto-char (cadr (smie-indent--parent))) 44 | (cons 'column 45 | (+ (or offset 0) 46 | (smie-indent-virtual))))))) 47 | 48 | (defadvice smie-indent-comment (around elixir-mode-patch activate) 49 | (if (not (eq major-mode 'elixir-mode)) 50 | (progn ad-do-it) 51 | (setq ad-return-value 52 | (and (smie-indent--bolp) 53 | (let ((pos (point))) 54 | (save-excursion 55 | (beginning-of-line) 56 | (and (re-search-forward comment-start-skip (line-end-position) t) 57 | (eq pos (or (match-end 1) (match-beginning 0)))))) 58 | (save-excursion 59 | (forward-comment (point-max)) 60 | (skip-chars-forward " \t\r\n") 61 | (unless 62 | (save-excursion 63 | (let ((next (funcall smie-forward-token-function))) 64 | (or (if (zerop (length next)) 65 | (or (eobp) (eq (car (syntax-after (point))) 5))) 66 | (rassoc next smie-closer-alist)))) 67 | (smie-indent-calculate)))))))) 68 | 69 | ;; FIXME: This is me being lazy. CL is a compile-time dep only. 70 | ;; (But for now, there is no real file-compilation plot, so let's 71 | ;; scrape by with the runtime dep.) 72 | (require 'cl) 73 | 74 | (defvar elixir-smie-verbose-p nil 75 | "Emit context information about the current syntax state.") 76 | 77 | (defvar elixir-mode-syntax-table 78 | (let ((table (make-syntax-table))) 79 | 80 | ;; Note that ?_ might be better as class "_", but either seems to 81 | ;; work: 82 | (modify-syntax-entry ?_ "_" table) 83 | (modify-syntax-entry ?? "w" table) 84 | (modify-syntax-entry ?~ "w" table) 85 | (modify-syntax-entry ?! "_" table) 86 | (modify-syntax-entry ?' "\"'" table) 87 | (modify-syntax-entry ?\" "\"\"" table) 88 | (modify-syntax-entry ?# "<" table) 89 | (modify-syntax-entry ?\n ">" table) 90 | (modify-syntax-entry ?\( "()" table) 91 | (modify-syntax-entry ?\) ")(" table) 92 | (modify-syntax-entry ?\{ "(}" table) 93 | (modify-syntax-entry ?\} "){" table) 94 | (modify-syntax-entry ?\[ "(]" table) 95 | (modify-syntax-entry ?\] ")[" table) 96 | (modify-syntax-entry ?: "_" table) 97 | (modify-syntax-entry ?@ "_" table) 98 | table) 99 | "Elixir mode syntax table.") 100 | 101 | (defconst elixir-smie-grammar 102 | (smie-prec2->grammar 103 | (smie-merge-prec2s 104 | (smie-bnf->prec2 105 | '((id) 106 | (statements (statement) 107 | (statement ";" statements)) 108 | (statement ("def" non-block-expr "do" statements "end") 109 | (non-block-expr "fn" match-statements "end") 110 | (non-block-expr "do" statements "end") 111 | ("if" non-block-expr "do" statements "else" statements "end") 112 | ("if" non-block-expr "do" statements "end") 113 | ("if" non-block-expr "COMMA" "do:" non-block-expr) 114 | ("if" non-block-expr "COMMA" 115 | "do:" non-block-expr "COMMA" 116 | "else:" non-block-expr) 117 | ("try" "do" statements "after" statements "end") 118 | ("try" "do" statements "catch" match-statements "end") 119 | ("try" "do" statements "rescue" match-statements "end") 120 | ("try" "do" statements "end") 121 | ("case" non-block-expr "do" match-statements "end")) 122 | (non-block-expr (non-block-expr "OP" non-block-expr) 123 | (non-block-expr "COMMA" non-block-expr) 124 | ("(" non-block-expr ")") 125 | ("{" non-block-expr "}") 126 | ("[" non-block-expr "]") 127 | ("STRING")) 128 | (match-statements (match-statement "MATCH-STATEMENT-DELIMITER" 129 | match-statements) 130 | (match-statement)) 131 | (match-statement (non-block-expr "->" statements))) 132 | '((assoc "if" "do:" "else:") 133 | (assoc "COMMA") 134 | (left "OP"))) 135 | 136 | (smie-precs->prec2 137 | '((left "||") 138 | (left "&&") 139 | (nonassoc "=~" "===" "!==" "==" "!=" "<=" ">=" "<" ">") 140 | (left "+" "-" "<<<" ">>>" "^^^" "~~~" "&&&" "|||") 141 | (left "*" "/")))))) 142 | 143 | (defvar elixir-smie--operator-regexp 144 | (rx (or "<<<" ">>>" "^^^" "~~~" "&&&" "|||" "===" "!==" "==" "!=" "<=" 145 | "=" ">=" "<" ">" "&&" "||" "<>" "++" "--" "//" "/>" "=~" "|>"))) 146 | 147 | (defvar elixir-smie--binary-sequence-regexp 148 | (rx (or "<<" ">>"))) 149 | 150 | (defvar elixir-smie--block-operator-regexp 151 | (rx "->" (0+ nonl))) 152 | 153 | (defvar elixir-smie--spaces-til-eol-regexp 154 | (rx (and (1+ space) eol)) 155 | "Regex representing one or more whitespace characters concluding with eol.") 156 | 157 | (defvar elixir-smie--comment-regexp 158 | (rx (and (0+ space) "#" (0+ not-newline))) 159 | "Regex matching comments.") 160 | 161 | (defvar elixir-smie-indent-basic 2) 162 | 163 | (defmacro elixir-smie-debug (message &rest format-args) 164 | `(progn 165 | (when elixir-smie-verbose-p 166 | (message (format ,message ,@format-args))) 167 | nil)) 168 | 169 | (defun elixir-smie--implicit-semi-p () 170 | (not (or (memq (char-before) '(?\{ ?\[)) 171 | (looking-back elixir-smie--operator-regexp (- (point) 3) t)))) 172 | 173 | (defun elixir-smie--semi-ends-match () 174 | "Return non-nil if the current line concludes a match block." 175 | (when (not (eobp)) 176 | (save-excursion 177 | ;; Warning: Recursion. 178 | ;; This is easy though. 179 | 180 | ;; 1. If we're at a blank line, move forward a character. This takes us to 181 | ;; the next line. 182 | ;; 2. If we're not at the end of the buffer, call this function again. 183 | ;; (Otherwise, return nil.) 184 | 185 | ;; The point here is that we want to treat blank lines as a single semi- 186 | ;; colon when it comes to detecting the end of match statements. This could 187 | ;; also be handled by a `while' expression or some other looping mechanism. 188 | (cl-flet ((self-call () 189 | (if (< (point) (point-max)) 190 | (elixir-smie--semi-ends-match) 191 | nil))) 192 | (cond 193 | ((and (eolp) (bolp)) 194 | (forward-char) 195 | (self-call)) 196 | ((looking-at elixir-smie--spaces-til-eol-regexp) 197 | (move-beginning-of-line 2) 198 | (self-call)) 199 | ;; And if we're NOT on a blank line, move to the end of the line, and see 200 | ;; if we're looking back at a block operator. 201 | (t (move-end-of-line 1) 202 | (looking-back elixir-smie--block-operator-regexp))))))) 203 | 204 | (defun elixir-smie--same-line-as-parent (parent-pos child-pos) 205 | "Return non-nil if `child-pos' is on same line as `parent-pos'." 206 | (= (line-number-at-pos parent-pos) (line-number-at-pos child-pos))) 207 | 208 | (defun elixir-smie-forward-token () 209 | (cond 210 | ;; If there is nothing but whitespace between the last token and eol, emit 211 | ;; a semicolon. 212 | ((looking-at elixir-smie--spaces-til-eol-regexp) 213 | (goto-char (match-end 0)) 214 | ";") 215 | ((and (or (looking-at elixir-smie--comment-regexp) 216 | (looking-at "[\n#]")) 217 | (elixir-smie--implicit-semi-p)) 218 | (when (not (save-excursion 219 | (forward-comment 1) 220 | (eobp))) 221 | (if (eolp) (forward-char 1) (forward-comment 1))) 222 | ;; Note: `elixir-smie--semi-ends-match' will be called when the point is at 223 | ;; the beginning of a new line. Keep that in mind. 224 | (if (elixir-smie--semi-ends-match) 225 | "MATCH-STATEMENT-DELIMITER" 226 | ";")) 227 | ((looking-at elixir-smie--block-operator-regexp) 228 | (goto-char (match-end 0)) 229 | "->") 230 | ((looking-at elixir-smie--operator-regexp) 231 | (goto-char (match-end 0)) 232 | "OP") 233 | (t (smie-default-forward-token)))) 234 | 235 | (defun elixir-smie-backward-token () 236 | (let ((pos (point))) 237 | (forward-comment (- (point))) 238 | (cond 239 | ((and (> pos (line-end-position)) 240 | (elixir-smie--implicit-semi-p)) 241 | (if (elixir-smie--semi-ends-match) 242 | "MATCH-STATEMENT-DELIMITER" 243 | ";")) 244 | ((looking-back elixir-smie--block-operator-regexp (- (point) 3) t) 245 | (goto-char (match-beginning 0)) 246 | "->") 247 | ((looking-back elixir-smie--binary-sequence-regexp (- (point) 3) t) 248 | (goto-char (match-beginning 0)) 249 | "OP") 250 | ((looking-back elixir-smie--operator-regexp (- (point) 3) t) 251 | (goto-char (match-beginning 0)) 252 | "OP") 253 | (t (smie-default-backward-token))))) 254 | 255 | (defun verbose-elixir-smie-rules (kind token) 256 | (let ((value (elixir-smie-rules kind token))) 257 | (elixir-smie-debug "%s '%s'; sibling-p:%s parent:%s prev-is-OP:%s hanging:%s == %s" kind token 258 | (ignore-errors (smie-rule-sibling-p)) 259 | (ignore-errors smie--parent) 260 | (ignore-errors (smie-rule-prev-p "OP")) 261 | (ignore-errors (smie-rule-hanging-p)) 262 | value) 263 | value)) 264 | 265 | (defun elixir-smie-rules (kind token) 266 | (pcase (cons kind token) 267 | (`(:before . "OP") 268 | (when (and (not (smie-rule-hanging-p)) 269 | (smie-rule-prev-p "OP")) 270 | -2)) 271 | (`(:after . "OP") 272 | (cond 273 | ((smie-rule-sibling-p) nil) 274 | ((smie-rule-hanging-p) (smie-rule-parent elixir-smie-indent-basic)) 275 | (t (smie-rule-parent)))) 276 | (`(:before . "MATCH-STATEMENT-DELIMITER") 277 | (cond 278 | ((and (not (smie-rule-sibling-p)) 279 | (smie-rule-hanging-p)) 280 | (smie-rule-parent elixir-smie-indent-basic)))) 281 | (`(:before . "fn") 282 | (smie-rule-parent)) 283 | (`(:before . "end") 284 | (smie-rule-parent)) 285 | ;; Closing paren on the other line 286 | (`(:before . "(") 287 | (smie-rule-parent)) 288 | (`(:before . "[") 289 | (cond 290 | ((smie-rule-hanging-p) 291 | (smie-rule-parent)))) 292 | (`(:after . "[") 293 | (cond 294 | ((smie-rule-hanging-p) 295 | (smie-rule-parent elixir-smie-indent-basic)))) 296 | (`(:before . "->") 297 | (cond 298 | ((smie-rule-hanging-p) 299 | (smie-rule-parent elixir-smie-indent-basic)))) 300 | (`(:after . "->") 301 | (cond 302 | ;; This first condition is kind of complicated so I'll try to make this 303 | ;; comment as clear as possible. 304 | 305 | ;; "If `->' is the last thing on the line, and its parent token 306 | ;; is `fn' ..." 307 | ((and (smie-rule-hanging-p) 308 | (smie-rule-parent-p "fn")) 309 | ;; "... and if: 310 | 311 | ;; 1. `smie--parent' is non-nil 312 | ;; 2. the `->' token in question is on the same line as its parent (if 313 | ;; the logic has gotten this far, its parent will be `fn') 314 | 315 | ;; ... then indent the line after the `->' aligned with the 316 | ;; parent, offset by `elixir-smie-indent-basic'." 317 | (if (and smie--parent (elixir-smie--same-line-as-parent 318 | (nth 1 smie--parent) 319 | (point))) 320 | (smie-rule-parent elixir-smie-indent-basic) 321 | elixir-smie-indent-basic)) 322 | ;; Otherwise, if just indent by two. 323 | ((smie-rule-hanging-p) 324 | (cond 325 | ((smie-rule-parent-p "after" "catch" "do" "rescue" "try") 326 | elixir-smie-indent-basic) 327 | (t 328 | (smie-rule-parent elixir-smie-indent-basic)))))) 329 | (`(:before . ";") 330 | (cond 331 | ((smie-rule-parent-p "after" "catch" "def" "defmodule" "defp" "do" "else" 332 | "fn" "if" "rescue" "try" "unless") 333 | (smie-rule-parent elixir-smie-indent-basic)))) 334 | (`(:after . ";") 335 | (cond 336 | ((smie-rule-parent-p "def") 337 | (smie-rule-parent)) 338 | ((smie-rule-parent-p "if") 339 | (smie-rule-parent)) 340 | ((and (smie-rule-parent-p "(") 341 | (save-excursion 342 | (goto-char (cadr smie--parent)) 343 | (smie-rule-hanging-p))) 344 | (smie-rule-parent elixir-smie-indent-basic)))))) 345 | 346 | (provide 'elixir-smie) 347 | 348 | ;;; elixir-smie.el ends here 349 | -------------------------------------------------------------------------------- /test/elixir-mode-indentation-test.el: -------------------------------------------------------------------------------- 1 | ;;; elixir-mode-indentation-test.el --- Indentation testsuite 2 | 3 | ;;; Commentary: 4 | ;; 5 | ;; Expected test failures indicates that the code tested by that test case is 6 | ;; indeed broken. My intention is that while working on a specific problem, 7 | ;; the failure expectation will be removed so that we know when the test case 8 | ;; passes. 9 | 10 | ;;; Code: 11 | 12 | (elixir-def-indentation-test indent-use-dot-module-newline 13 | (:tags '(indentation)) 14 | "defmodule Foo do 15 | use GenServer.Behaviour 16 | 17 | def foobar do 18 | if true, do: IO.puts \"yay\" 19 | end 20 | end" 21 | "defmodule Foo do 22 | use GenServer.Behaviour 23 | 24 | def foobar do 25 | if true, do: IO.puts \"yay\" 26 | end 27 | end") 28 | 29 | (elixir-def-indentation-test indent-use-dot-module 30 | (:tags '(indentation)) 31 | " 32 | defmodule Foo do 33 | use GenServer.Behaviour 34 | def foobar do 35 | if true, do: IO.puts \"yay\" 36 | end 37 | end" 38 | " 39 | defmodule Foo do 40 | use GenServer.Behaviour 41 | def foobar do 42 | if true, do: IO.puts \"yay\" 43 | end 44 | end") 45 | 46 | (elixir-def-indentation-test indent-do-blocks 47 | (:tags '(indentation)) 48 | " 49 | defmodule Foo do 50 | def foobar do 51 | if true, do: IO.puts \"yay\" 52 | 20 53 | end 54 | end" 55 | " 56 | defmodule Foo do 57 | def foobar do 58 | if true, do: IO.puts \"yay\" 59 | 20 60 | end 61 | end") 62 | 63 | (elixir-def-indentation-test indent-do-blocks-after-linebreak-two 64 | (:tags '(indentation)) 65 | " 66 | defmodule FooBar do 67 | def foo do 68 | if true, do: IO.puts \"yay\" 69 | 20 70 | end 71 | 72 | def bar do 73 | if true, do: IO.puts \"yay\" 74 | 20 75 | end 76 | end" 77 | " 78 | defmodule FooBar do 79 | def foo do 80 | if true, do: IO.puts \"yay\" 81 | 20 82 | end 83 | 84 | def bar do 85 | if true, do: IO.puts \"yay\" 86 | 20 87 | end 88 | end") 89 | 90 | (elixir-def-indentation-test indent-do-blocks-after-linebreak-three 91 | (:tags '(indentation)) 92 | " 93 | defmodule FooBar do 94 | def foo do 95 | if true, do: IO.puts \"yay\" 96 | 20 97 | end 98 | 99 | def bar do 100 | if true, do: IO.puts \"yay\" 101 | 20 102 | end 103 | 104 | def baz do 105 | if true, do: IO.puts \"yay\" 106 | 20 107 | end 108 | end" 109 | " 110 | defmodule FooBar do 111 | def foo do 112 | if true, do: IO.puts \"yay\" 113 | 20 114 | end 115 | 116 | def bar do 117 | if true, do: IO.puts \"yay\" 118 | 20 119 | end 120 | 121 | def baz do 122 | if true, do: IO.puts \"yay\" 123 | 20 124 | end 125 | end") 126 | 127 | (elixir-def-indentation-test indent-do-blocks-with-space-after-inline 128 | (:tags '(indentation)) 129 | "defmodule Foo do 130 | def foobar do 131 | if true, do: IO.puts \"yay\" 132 | 133 | 20 134 | end 135 | end" 136 | "defmodule Foo do 137 | def foobar do 138 | if true, do: IO.puts \"yay\" 139 | 140 | 20 141 | end 142 | end") 143 | 144 | (elixir-def-indentation-test indent-after-empty-line 145 | (:tags '(indentation)) 146 | " 147 | def foo do 148 | a = 2 149 | 150 | b = a + 3 151 | 152 | c = a * b 153 | end" 154 | " 155 | def foo do 156 | a = 2 157 | 158 | b = a + 3 159 | 160 | c = a * b 161 | end") 162 | 163 | (elixir-def-indentation-test indent-function-calls-without-parens 164 | (:tags '(indentation)) 165 | " 166 | test \"foo\" do 167 | assert true, \"should be true\" 168 | assert !false, \"should still be true\" 169 | end 170 | " 171 | " 172 | test \"foo\" do 173 | assert true, \"should be true\" 174 | assert !false, \"should still be true\" 175 | end 176 | ") 177 | 178 | (elixir-def-indentation-test indent-records-correctly 179 | (:tags '(indentation)) 180 | " 181 | defmodule MyModule do 182 | require Record 183 | Record.defrecord :money, [:currency_unit, :amount] 184 | 185 | Record.defrecord :animal, [:species, :name] 186 | end 187 | " 188 | " 189 | defmodule MyModule do 190 | require Record 191 | Record.defrecord :money, [:currency_unit, :amount] 192 | 193 | Record.defrecord :animal, [:species, :name] 194 | end 195 | ") 196 | 197 | (elixir-def-indentation-test indent-continuation-lines 198 | (:tags '(indentation)) 199 | " 200 | def foo do 201 | has_something(x) && 202 | has_something(y) || 203 | has_something(z) 204 | end 205 | " 206 | " 207 | def foo do 208 | has_something(x) && 209 | has_something(y) || 210 | has_something(z) 211 | end 212 | ") 213 | 214 | (elixir-def-indentation-test indent-continuation-lines-with-comments/1 215 | (:tags '(indentation)) 216 | " 217 | has_something(x) && # foo 218 | has_something(y) || 219 | has_something(z) 220 | " 221 | " 222 | has_something(x) && # foo 223 | has_something(y) || 224 | has_something(z) 225 | ") 226 | 227 | (elixir-def-indentation-test indent-continuation-lines-with-comments/2 228 | (:tags '(indentation)) 229 | " 230 | has_something(x) && 231 | has_something(y) || # foo 232 | has_something(z) 233 | " 234 | " 235 | has_something(x) && 236 | has_something(y) || # foo 237 | has_something(z) 238 | ") 239 | 240 | (elixir-def-indentation-test indent-continuation-lines-with-comments/3 241 | (:tags '(indentation)) 242 | " 243 | def str(s, sub, start_pos, end_pos) when is_binary(s) and is_binary(sub) do # and start_pos <= end_pos do 244 | len = end_pos-start_pos 245 | end 246 | " 247 | " 248 | def str(s, sub, start_pos, end_pos) when is_binary(s) and is_binary(sub) do # and start_pos <= end_pos do 249 | len = end_pos-start_pos 250 | end 251 | ") 252 | 253 | (elixir-def-indentation-test indent-continuation-lines-assignment 254 | (:tags '(indentation)) 255 | " 256 | some_var = 257 | some_expr 258 | " " 259 | some_var = 260 | some_expr 261 | ") 262 | 263 | (elixir-def-indentation-test indent-continuation-lines-assignment/2 264 | (:tags '(indentation)) 265 | " 266 | next_fun = 267 | case raw do 268 | true -> &IO.each_binstream(&1, line_or_bytes) 269 | false -> &IO.each_stream(&1, line_or_bytes) 270 | end 271 | " " 272 | next_fun = 273 | case raw do 274 | true -> &IO.each_binstream(&1, line_or_bytes) 275 | false -> &IO.each_stream(&1, line_or_bytes) 276 | end 277 | ") 278 | 279 | (elixir-def-indentation-test indent-continuation-lines-assignment/3 280 | (:expected-result :failed :tags '(indentation)) 281 | " 282 | start_fun = 283 | fn -> 284 | case :file.open(path, modes) do 285 | {:ok, device} -> device 286 | {:error, reason} -> 287 | raise File.Error, reason: reason, action: \"stream\", path: path 288 | end 289 | end 290 | " " 291 | start_fun = 292 | fn -> 293 | case :file.open(path, modes) do 294 | {:ok, device} -> device 295 | {:error, reason} -> 296 | raise File.Error, reason: reason, action: \"stream\", path: path 297 | end 298 | end 299 | ") 300 | 301 | 302 | (elixir-def-indentation-test indent-last-commented-line 303 | (:tags '(indentation)) 304 | " 305 | defmodule Foo do 306 | def bar do 307 | 2 308 | end 309 | 310 | # last line 311 | end 312 | " 313 | " 314 | defmodule Foo do 315 | def bar do 316 | 2 317 | end 318 | 319 | # last line 320 | end 321 | ") 322 | 323 | (elixir-def-indentation-test indent-if 324 | (:tags '(indentation)) 325 | " 326 | if condition do 327 | yes 328 | end" 329 | " 330 | if condition do 331 | yes 332 | end") 333 | 334 | (elixir-def-indentation-test indent-if-else 335 | (:tags '(indentation)) 336 | " 337 | if condition do 338 | yes 339 | else 340 | no 341 | end" 342 | " 343 | if condition do 344 | yes 345 | else 346 | no 347 | end") 348 | 349 | (elixir-def-indentation-test indent-try 350 | (:tags '(indentation)) 351 | " 352 | try do 353 | foo 354 | bar 355 | end" 356 | " 357 | try do 358 | foo 359 | bar 360 | end") 361 | 362 | (elixir-def-indentation-test indent-try/after 363 | (:tags '(indentation)) 364 | " 365 | try do 366 | foo 367 | bar 368 | after 369 | after_everything() 370 | post_that() 371 | end" 372 | " 373 | try do 374 | foo 375 | bar 376 | after 377 | after_everything() 378 | post_that() 379 | end") 380 | 381 | (elixir-def-indentation-test indent-try/catch/after 382 | (:tags '(indentation)) 383 | " 384 | try do 385 | foo 386 | bar 387 | catch 388 | baz -> 389 | nope 390 | [yeah] -> 391 | maybe 392 | after 393 | after_everything() 394 | post_that() 395 | end 396 | " 397 | " 398 | try do 399 | foo 400 | bar 401 | catch 402 | baz -> 403 | nope 404 | [yeah] -> 405 | maybe 406 | after 407 | after_everything() 408 | post_that() 409 | end 410 | ") 411 | 412 | (elixir-def-indentation-test indent-try/rescue/1 413 | (:tags '(indentation)) 414 | " 415 | try do 416 | raise 'some error' 417 | rescue 418 | RuntimeError -> 'rescued a runtime error' 419 | end 420 | " 421 | " 422 | try do 423 | raise 'some error' 424 | rescue 425 | RuntimeError -> 'rescued a runtime error' 426 | end 427 | ") 428 | 429 | (elixir-def-indentation-test indent-try/rescue/2 430 | (:tags '(indentation)) 431 | " 432 | try do 433 | raise 'some error' 434 | rescue 435 | x in [RuntimeError] -> 436 | x.message 437 | end 438 | " 439 | " 440 | try do 441 | raise 'some error' 442 | rescue 443 | x in [RuntimeError] -> 444 | x.message 445 | end 446 | ") 447 | 448 | (elixir-def-indentation-test indent-block-inside-fn-match 449 | (:tags '(indentation)) 450 | " 451 | defp into(stream, device, raw) do 452 | fn 453 | :ok, {:cont, x} -> 454 | case raw do 455 | true -> IO.binwrite(device, x) 456 | false -> IO.write(device, x) 457 | end 458 | :ok, _ -> stream 459 | end 460 | end" 461 | " 462 | defp into(stream, device, raw) do 463 | fn 464 | :ok, {:cont, x} -> 465 | case raw do 466 | true -> IO.binwrite(device, x) 467 | false -> IO.write(device, x) 468 | end 469 | :ok, _ -> stream 470 | end 471 | end") 472 | 473 | (elixir-def-indentation-test indent-fn-in-assignment 474 | (:tags '(indentation)) 475 | " 476 | f = fn x, y -> 477 | x + y 478 | end" 479 | " 480 | f = fn x, y -> 481 | x + y 482 | end") 483 | 484 | (elixir-def-indentation-test indent-fn-as-arguments 485 | (:tags '(indentation)) 486 | " 487 | Enum.map 1..10, fn x -> 488 | x + 1 489 | end" 490 | " 491 | Enum.map 1..10, fn x -> 492 | x + 1 493 | end") 494 | 495 | (elixir-def-indentation-test indent-list-argument-continuation-lines-nicely 496 | (:tags '(indentation)) 497 | " 498 | to_process = [27, 33, 35, 11, 36, 29, 18, 37, 21, 31, 19, 10, 14, 30, 499 | 15, 17, 23, 28, 25, 34, 22, 20, 13, 16, 32, 12, 26, 24] 500 | " 501 | " 502 | to_process = [27, 33, 35, 11, 36, 29, 18, 37, 21, 31, 19, 10, 14, 30, 503 | 15, 17, 23, 28, 25, 34, 22, 20, 13, 16, 32, 12, 26, 24] 504 | ") 505 | 506 | (elixir-def-indentation-test indent-nested-fn 507 | (:tags '(indentation)) 508 | "defmodule FooModule do 509 | def foo do 510 | x = fn(a, b) -> a + b end 511 | end 512 | end" 513 | "defmodule FooModule do 514 | def foo do 515 | x = fn(a, b) -> a + b end 516 | end 517 | end") 518 | 519 | (elixir-def-indentation-test indent-list-of-floats-aligns 520 | (:tags '(indentation)) 521 | " 522 | [1.2, 523 | 3.4]" 524 | " 525 | [1.2, 526 | 3.4]") 527 | 528 | (elixir-def-indentation-test indent-after-operator 529 | (:tags '(indentation)) 530 | " 531 | defmodule Banana do 532 | def start do 533 | a = \"\" <> \"?\" 534 | 535 | case bar do 536 | z -> 1 537 | end 538 | 539 | case foo do 540 | ?x -> x 541 | end 542 | 543 | end 544 | end 545 | " 546 | " 547 | defmodule Banana do 548 | def start do 549 | a = \"\" <> \"?\" 550 | 551 | case bar do 552 | z -> 1 553 | end 554 | 555 | case foo do 556 | ?x -> x 557 | end 558 | 559 | end 560 | end 561 | ") 562 | 563 | (elixir-def-indentation-test nested-modules 564 | (:tags '(indentation)) 565 | "defmodule Mod1 do 566 | defmodule Mod1a do 567 | def start do 568 | foo() 569 | end 570 | end 571 | end" 572 | "defmodule Mod1 do 573 | defmodule Mod1a do 574 | def start do 575 | foo() 576 | end 577 | end 578 | end") 579 | 580 | (elixir-def-indentation-test cond-comment 581 | (:tags '(indentation)) 582 | " 583 | def foo() do 584 | cond do 585 | yadda -> 586 | :ok 587 | badda -> # comment throws this off 588 | :what 589 | end 590 | end 591 | " 592 | " 593 | def foo() do 594 | cond do 595 | yadda -> 596 | :ok 597 | badda -> # comment throws this off 598 | :what 599 | end 600 | end 601 | ") 602 | 603 | (elixir-def-indentation-test indent-heredoc 604 | (:expected-result :failed :tags '(indentation)) 605 | " 606 | defmodule Foo do 607 | @doc \"\"\" 608 | this is a heredoc string 609 | 610 | \"\"\" 611 | def convert do 612 | x = 15 613 | end 614 | end 615 | " 616 | " 617 | defmodule Foo do 618 | @doc \"\"\" 619 | this is a heredoc string 620 | 621 | \"\"\" 622 | def convert do 623 | x = 15 624 | end 625 | end 626 | ") 627 | 628 | (elixir-def-indentation-test indent-pipes 629 | (:tags '(indentation)) 630 | " 631 | def foo(x) do 632 | a = x 633 | |> Enum.reverse 634 | end" 635 | " 636 | def foo(x) do 637 | a = x 638 | |> Enum.reverse 639 | end") 640 | 641 | (elixir-def-indentation-test indent-inside-parens 642 | (:tags '(indentation)) 643 | " 644 | x = do_something( 645 | :foo, 646 | :bar 647 | )" 648 | " 649 | x = do_something( 650 | :foo, 651 | :bar 652 | )") 653 | 654 | (elixir-def-indentation-test indent-inside-parens/2 655 | (:tags '(indentation)) 656 | " 657 | x = do_something(:foo, 658 | :bar)" 659 | " 660 | x = do_something(:foo, 661 | :bar)") 662 | 663 | (elixir-def-indentation-test indent-inside-parens/3 664 | (:tags '(indentation)) 665 | " 666 | x = do_something(:foo, fn (arg) -> 667 | do_another(arg) 668 | end)" 669 | " 670 | x = do_something(:foo, fn (arg) -> 671 | do_another(arg) 672 | end)") 673 | 674 | (elixir-def-indentation-test indent-inside-parens/4 675 | (:tags '(indentation)) 676 | " 677 | defmodule Something do 678 | def something do 679 | x = do_something(:foo, fn (arg) -> 680 | do_another(arg) 681 | end) 682 | end 683 | end" 684 | " 685 | defmodule Something do 686 | def something do 687 | x = do_something(:foo, fn (arg) -> 688 | do_another(arg) 689 | end) 690 | end 691 | end") 692 | 693 | (elixir-def-indentation-test indent-inside-parens/5 694 | (:tags '(indentation)) 695 | " 696 | defmodule IndentPlayground do 697 | def my_func(arr) do 698 | Enum.map(arr, fn(x) -> 699 | x * 2 700 | end) 701 | #back here 702 | end 703 | end" 704 | " 705 | defmodule IndentPlayground do 706 | def my_func(arr) do 707 | Enum.map(arr, fn(x) -> 708 | x * 2 709 | end) 710 | #back here 711 | end 712 | end") 713 | 714 | (elixir-def-indentation-test indent-lone-keyword 715 | (:tags '(indentation)) 716 | " 717 | def foo do #comment 718 | :bar 719 | end" 720 | " 721 | def foo do #comment 722 | :bar 723 | end") 724 | 725 | (elixir-def-indentation-test indent-single-line-match 726 | (:tags '(indentation)) 727 | " 728 | case x do 729 | a -> b 730 | c -> d 731 | end 732 | " " 733 | case x do 734 | a -> b 735 | c -> d 736 | end 737 | ") 738 | 739 | (elixir-def-indentation-test indent-multiline-match 740 | (:tags '(indentation)) 741 | " 742 | def foo do 743 | case is_string(x) do 744 | true -> 745 | x2 = \" one\" 746 | x <> x2 747 | false -> 748 | x2 = \" two\" 749 | x <> x2 750 | end 751 | end" 752 | " 753 | def foo do 754 | case is_string(x) do 755 | true -> 756 | x2 = \" one\" 757 | x <> x2 758 | false -> 759 | x2 = \" two\" 760 | x <> x2 761 | end 762 | end" 763 | ) 764 | 765 | (elixir-def-indentation-test indent-multiline-match/2 766 | (:tags '(indentation)) 767 | " 768 | def foo do 769 | case is_string(x) do 770 | true -> 771 | x2 = \" one\" 772 | x <> x2 773 | 774 | false -> 775 | x2 = \" two\" 776 | x <> x2 777 | end 778 | end" 779 | " 780 | def foo do 781 | case is_string(x) do 782 | true -> 783 | x2 = \" one\" 784 | x <> x2 785 | 786 | false -> 787 | x2 = \" two\" 788 | x <> x2 789 | end 790 | end" 791 | ) 792 | 793 | (elixir-def-indentation-test indent-mixed-match 794 | (:tags '(indentation)) 795 | " 796 | case x do 797 | a -> b 798 | c -> 799 | d 800 | e -> f 801 | end 802 | " " 803 | case x do 804 | a -> b 805 | c -> 806 | d 807 | e -> f 808 | end 809 | ") 810 | 811 | (elixir-def-indentation-test indent-after-require-Record 812 | (:tags '(indentation)) 813 | ;; Mind the significant whitespace after `Record' in each case. There should 814 | ;; be two spaces after `Record', otherwise this test is meaningless. 815 | " 816 | defmodule RSS do 817 | require Record 818 | 819 | def zip(list1, list2) when length(list1) == length(list2) do 820 | x = 1 821 | end 822 | end" 823 | " 824 | defmodule RSS do 825 | require Record 826 | 827 | def zip(list1, list2) when length(list1) == length(list2) do 828 | x = 1 829 | end 830 | end") 831 | 832 | (elixir-def-indentation-test indent-fn-in-multiline-assignment 833 | (:expected-result :failed :tags '(indentation)) 834 | " 835 | variable = 836 | fn -> 837 | case :file.open(path, modes) do 838 | {:ok, device} -> device 839 | {:error, reason} -> 840 | raise File.Error, reason: reason 841 | end 842 | end" 843 | 844 | " 845 | variable = 846 | fn -> 847 | case :file.open(path, modes) do 848 | {:ok, device} -> device 849 | {:error, reason} -> 850 | raise File.Error, reason: reason 851 | end 852 | end") 853 | 854 | (elixir-def-indentation-test indent-after-def-do-online 855 | (:tags '(indentation)) 856 | " 857 | defmodule Greeter do 858 | def hello, do: IO.puts \"hello\" 859 | def bye, do: IO.puts \"bye\" 860 | 861 | def hi(name) do 862 | IO.puts \"Hi #{name}\" 863 | end 864 | end" 865 | 866 | " 867 | defmodule Greeter do 868 | def hello, do: IO.puts \"hello\" 869 | def bye, do: IO.puts \"bye\" 870 | 871 | def hi(name) do 872 | IO.puts \"Hi #{name}\" 873 | end 874 | end") 875 | 876 | (elixir-def-indentation-test indent-binary-sequence 877 | (:tags '(indentation)) 878 | " 879 | defmodule ExampleTest do 880 | test \"the truth\" do 881 | assert <<1,2>> == <<1,2>> 882 | assert 1 + 1 == 2 883 | end 884 | end" 885 | 886 | " 887 | defmodule ExampleTest do 888 | test \"the truth\" do 889 | assert <<1,2>> == <<1,2>> 890 | assert 1 + 1 == 2 891 | end 892 | end") 893 | 894 | (elixir-def-indentation-test indent-binary-sequence-inside-match-block/2 895 | (:tags '(indentation)) 896 | " 897 | case asd do 898 | <> -> 899 | <> 902 | <> -> 903 | <> 906 | <> -> 907 | <> 910 | <> -> 911 | <> 914 | <<>> -> 915 | main 916 | end" 917 | 918 | " 919 | case asd do 920 | <> -> 921 | <> 924 | <> -> 925 | <> 928 | <> -> 929 | <> 932 | <> -> 933 | <> 936 | <<>> -> 937 | main 938 | end") 939 | 940 | (elixir-def-indentation-test indent-inside-square-brackets 941 | (:tags '(indentation)) 942 | " 943 | children = [ 944 | supervisor(Task.Supervisor, [[name: KVServer.TaskSupervisor]]), 945 | worker(Task, [KVServer, :accept, [4040]]) 946 | ]" 947 | 948 | " 949 | children = [ 950 | supervisor(Task.Supervisor, [[name: KVServer.TaskSupervisor]]), 951 | worker(Task, [KVServer, :accept, [4040]]) 952 | ]") 953 | 954 | ;; We don't want automatic whitespace cleanup here because of the significant 955 | ;; whitespace after `Record' above. By setting `whitespace-action' to nil, 956 | ;; `whitespace-mode' won't automatically clean up trailing whitespace (in my 957 | ;; config, anyway). 958 | 959 | ;;; Local Variables: 960 | ;;; whitespace-action: nil 961 | ;;; End: 962 | 963 | (provide 'elixir-mode-indentation-test) 964 | 965 | ;;; elixir-mode-indentation-test.el ends here 966 | -------------------------------------------------------------------------------- /elixir-mode.el: -------------------------------------------------------------------------------- 1 | ;;; elixir-mode.el --- Major mode for editing Elixir files 2 | 3 | ;; Copyright 2011-2015 secondplanet 4 | ;; 2013-2015 Samuel Tonini, Matt DeBoard, Andreas Fuchs 5 | ;; Authors: Humza Yaqoob, 6 | ;; Andreas Fuchs , 7 | ;; Matt DeBoard 8 | ;; Samuel Tonini 9 | 10 | ;; URL: https://github.com/elixir-lang/emacs-elixir 11 | ;; Created: Mon Nov 7 2011 12 | ;; Keywords: languages elixir 13 | ;; Version: 2.3.0-cvs 14 | 15 | ;; This file is not a part of GNU Emacs. 16 | 17 | ;; This program is free software; you can redistribute it and/or modify 18 | ;; it under the terms of the GNU General Public License as published by 19 | ;; the Free Software Foundation; either version 2, or (at your option) 20 | ;; any later version. 21 | 22 | ;; This program is distributed in the hope that it will be useful, 23 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 | ;; GNU General Public License for more details. 26 | 27 | ;; You should have received a copy of the GNU General Public License 28 | ;; along with this program; if not, write to the Free Software 29 | ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 30 | 31 | ;;; Commentary: 32 | 33 | ;; Provides font-locking, indentation and navigation support 34 | ;; for the Elixir programming language. 35 | 36 | ;;; Code: 37 | 38 | (require 'comint) ; for interactive REPL 39 | (require 'easymenu) ; for menubar features 40 | 41 | (require 'elixir-smie) ; syntax and indentation support 42 | (require 'elixir-deprecated) ; deprecated messages 43 | 44 | (defgroup elixir-mode nil 45 | "Provides font-locking, indentation and navigation support 46 | for the Elixir programming language." 47 | :prefix "elixir-mode-" 48 | :group 'applications 49 | :link '(url-link :tag "Github" "https://github.com/elixir-lang/emacs-elixir") 50 | :link '(emacs-commentary-link :tag "Commentary" "elixir-mode")) 51 | 52 | (defvar elixir-mqode--website-url 53 | "http://elixir-lang.org") 54 | 55 | (defvar elixir-mode-hook nil) 56 | 57 | (defvar elixir-mode-map 58 | (let ((map (make-sparse-keymap))) 59 | (define-key map (kbd "C-c ,r") 'elixir-mode-eval-on-region) 60 | (define-key map (kbd "C-c ,c") 'elixir-mode-eval-on-current-line) 61 | (define-key map (kbd "C-c ,b") 'elixir-mode-eval-on-current-buffer) 62 | (define-key map (kbd "C-c ,a") 'elixir-mode-string-to-quoted-on-region) 63 | (define-key map (kbd "C-c ,l") 'elixir-mode-string-to-quoted-on-current-line) 64 | map) 65 | "Keymap used in `elixir-mode'.") 66 | 67 | (defvar elixir-imenu-generic-expression 68 | '(("Modules" "^\\s-*defmodule[ \n\t]+\\([A-Z][A-Za-z0-9._]+\\)\\s-+do.*$" 1) 69 | ("Public Functions" "^\\s-*def[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*[ \t\n]+do.*" 1) 70 | ("Private Functions" "^\\s-*defp[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*[ \t\n]+do.*" 1) 71 | ("Public Macros" "^\\s-*defmacro[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*[ \t\n]+do.*" 1) 72 | ("Private Macros" "^\\s-*defmacrop[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*[ \t\n]+do.*" 1) 73 | ("Delegates" "^\\s-*defdelegate[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*[ \t\n]+do.*" 1) 74 | ("Overridables" "^\\s-*defoverridable[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*[ \t\n]+do.*" 1) 75 | ("Tests" "^\\s-*test[ \t\n]+\"?\\(:?[a-z0-9_@+() \t-]+\\)\"?[ \t\n]+do.*" 1)) 76 | "Imenu pattern for `elixir-mode'.") 77 | 78 | (defgroup elixir nil 79 | "Elixir major mode." 80 | :group 'languages) 81 | 82 | (defcustom elixir-compiler-command "elixirc" 83 | "Elixir mode command to compile code. Must be in your path." 84 | :type 'string 85 | :group 'elixir) 86 | 87 | (defcustom elixir-mode-command "elixir" 88 | "The command for elixir." 89 | :type 'string 90 | :group 'elixir) 91 | 92 | (defcustom elixir-iex-command "iex" 93 | "Elixir mode command for interactive REPL. Must be in your path." 94 | :type 'string 95 | :group 'elixir) 96 | 97 | (defcustom elixir-mode-cygwin-paths t 98 | "Elixir mode use Cygwin style paths on Windows operating systems." 99 | :type 'boolean 100 | :group 'elixir) 101 | 102 | (defcustom elixir-mode-cygwin-prefix "/cygdrive/C" 103 | "Elixir mode Cygwin prefix." 104 | :type 'string 105 | :group 'elixir) 106 | 107 | (defvar elixir-mode--eval-filename "elixir-mode-tmp-eval-file.exs") 108 | 109 | (defvar elixir-quoted--buffer-name "*elixir-quoted*") 110 | 111 | (defvar elixir-basic-offset 2) 112 | (defvar elixir-key-label-offset 0) 113 | (defvar elixir-match-label-offset 2) 114 | 115 | (defvar elixir-operator-face 'elixir-operator-face) 116 | (defface elixir-operator-face 117 | '((((class color) (min-colors 88) (background light)) 118 | :foreground "darkred") 119 | (((class color) (background dark)) 120 | (:foreground "lemonchiffon1")) 121 | (t nil)) 122 | "For use with operators." 123 | :group 'font-lock-faces) 124 | 125 | (defvar elixir-negation-face 'elixir-negation-face) 126 | (defface elixir-negation-face 127 | '((((class color) (min-colors 88) (background light)) 128 | :foreground "#ff4500") 129 | (((class color) (background dark)) 130 | (:foreground "#ff4500")) 131 | (t nil)) 132 | "For use with standalone \"?\" to indicate code point." 133 | :group 'font-lock-faces) 134 | 135 | (defvar elixir-attribute-face 'elixir-attribute-face) 136 | (defface elixir-attribute-face 137 | '((((class color) (min-colors 88) (background light)) 138 | :foreground "MediumPurple4") 139 | (((class color) (background dark)) 140 | (:foreground "thistle")) 141 | (t nil)) 142 | "For use with module attribute tokens." 143 | :group 'font-lock-faces) 144 | 145 | (defvar elixir-atom-face 'elixir-atom-face) 146 | (defface elixir-atom-face 147 | '((((class color) (min-colors 88) (background light)) 148 | :foreground "RoyalBlue4") 149 | (((class color) (background dark)) 150 | (:foreground "light sky blue")) 151 | (t nil)) 152 | "For use with atoms & map keys." 153 | :group 'font-lock-faces) 154 | 155 | (defun elixir-syntax-propertize-interpolation () 156 | (let* ((beg (match-beginning 0)) 157 | (context (save-excursion (save-match-data (syntax-ppss beg))))) 158 | (put-text-property beg (1+ beg) 'elixir-interpolation 159 | (cons (nth 3 context) (match-data))))) 160 | 161 | (defun elixir-syntax-propertize-function (start end) 162 | (let ((case-fold-search nil)) 163 | (goto-char start) 164 | (remove-text-properties start end '(elixir-interpolation)) 165 | (funcall 166 | (syntax-propertize-rules 167 | ((rx (group "#{" (0+ (not (any "}"))) "}")) 168 | (0 (ignore (elixir-syntax-propertize-interpolation))))) 169 | start end))) 170 | 171 | (defun elixir-match-interpolation (limit) 172 | (let ((pos (next-single-char-property-change (point) 'elixir-interpolation 173 | nil limit))) 174 | (when (and pos (> pos (point))) 175 | (goto-char pos) 176 | (let ((value (get-text-property pos 'elixir-interpolation))) 177 | (if (eq (car value) ?\") 178 | (progn 179 | (set-match-data (cdr value)) 180 | t) 181 | (elixir-match-interpolation limit)))))) 182 | 183 | (eval-when-compile 184 | (defconst elixir-rx-constituents 185 | `( 186 | (atoms . ,(rx ":" 187 | (or 188 | (one-or-more (any "a-z" "A-Z" "_" "\"" "'")) 189 | (and "\"" (one-or-more (not (any "\""))) "\"") 190 | (and "'" (one-or-more (not (any "'"))) "'")))) 191 | (builtin . ,(rx symbol-start 192 | (or "case" "cond" "for" "if" "unless" "try" "receive" 193 | "raise" "quote" "unquote" "unquote_splicing" "throw" 194 | "super") 195 | symbol-end)) 196 | (builtin-declaration . ,(rx symbol-start 197 | (or "def" "defp" "defmodule" "defprotocol" 198 | "defmacro" "defmacrop" "defdelegate" 199 | "defexception" "defstruct" "defimpl" 200 | "defcallback") 201 | symbol-end)) 202 | (builtin-modules . ,(rx symbol-start 203 | (or "Agent" "Application" "Atom" "Base" 204 | "Behaviour" "Bitwise" "Builtin" "Code" "Dict" 205 | "EEx" "Elixir" "Enum" "ExUnit" "Exception" 206 | "File" "File.Stat" "File.Stream" "Float" 207 | "Function" "GenEvent" "GenServer" "GenTCP" 208 | "HashDict" "HashSet" "IO" "IO.ANSI" 209 | "IO.Stream" "Inspect.Algebra" "Inspect.Opts" 210 | "Integer" "Kernel" "Kernel.ParallelCompiler" 211 | "Kernel.ParallelRequire" "Kernel.SpecialForms" 212 | "Kernel.Typespec" "Keyword" "List" "Macro" 213 | "Macro.Env" "Map" "Math" "Module" "Node" 214 | "OptionParser" "OrdDict" "Path" "Port" 215 | "Process" "Protocol" "Range" "Record" "Regex" 216 | "Set" "Stream" "String" "StringIO" 217 | "Supervisor" "Supervisor.Spec" "System" "Task" 218 | "Task.Supervisor" "Tuple" "URI" 219 | "UnboundMethod" "Version") 220 | symbol-end)) 221 | (builtin-namespace . ,(rx symbol-start 222 | (or "import" "require" "use" "alias") 223 | symbol-end)) 224 | ;; Set aside code point syntax for `elixir-negation-face'. 225 | (code-point . ,(rx symbol-start 226 | "?" 227 | anything 228 | symbol-end)) 229 | (function-declaration . ,(rx symbol-start 230 | (or "def" "defp") 231 | symbol-end)) 232 | ;; Match `@doc' or `@moduledoc' syntax, with or without triple quotes. 233 | (heredocs . ,(rx symbol-start 234 | (or "@doc" "@moduledoc" "~s") 235 | symbol-end)) 236 | ;; The first character of an identifier must be a letter or an underscore. 237 | ;; After that, they may contain any alphanumeric character + underscore. 238 | ;; Additionally, the final character may be either `?' or `!'. 239 | (identifiers . ,(rx (one-or-more (any "A-Z" "a-z" "_")) 240 | (zero-or-more (any "A-Z" "a-z" "0-9" "_")) 241 | (optional (or "?" "!")))) 242 | (keyword . ,(rx symbol-start 243 | (or "fn" "do" "end" "after" "else" "rescue" "catch") 244 | symbol-end)) 245 | (keyword-operator . ,(rx symbol-start 246 | (or "not" "and" "or" "when" "in") 247 | symbol-end)) 248 | ;; Module and submodule names start with upper case letter. This 249 | ;; can then be followed by any combination of alphanumeric chars. 250 | ;; In turn, this can be followed by a `.' which begins the notation of 251 | ;; a submodule, which follows the same naming pattern of the module. 252 | ;; Finally, like other identifiers, it can be terminated with either `?' 253 | ;; or `!'. 254 | (module-names . ,(rx symbol-start 255 | (one-or-more (any "A-Z")) 256 | (zero-or-more (any "A-Z" "a-z" "_" "0-9")) 257 | (zero-or-more 258 | (and "." 259 | (one-or-more (any "A-Z" "_")) 260 | (zero-or-more (any "A-Z" "a-z" "_" "0-9")))) 261 | (optional (or "!" "?")) 262 | symbol-end)) 263 | (operators1 . ,(rx symbol-start 264 | (or "<" ">" "+" "-" "*" "/" "!" "^" "&") 265 | symbol-end)) 266 | (operators2 . ,(rx symbol-start 267 | (or 268 | "==" "!=" "<=" ">=" "&&" "||" "<>" "++" "--" "|>" "=~" 269 | "->" "<-" "|" "." "=") 270 | symbol-end)) 271 | (operators3 . ,(rx symbol-start 272 | (or "<<<" ">>>" "|||" "&&&" "^^^" "~~~" "===" "!==") 273 | symbol-end)) 274 | (pseudo-var . ,(rx symbol-start 275 | (or "_" "__MODULE__" "__DIR__" "__ENV__" "__CALLER__" 276 | "__block__" "__aliases__") 277 | symbol-end)) 278 | (punctuation . ,(rx symbol-start 279 | (or "\\" "<<" ">>" "=>" "(" ")" ":" ";" "" "[" "]") 280 | symbol-end)) 281 | (sigils . ,(rx "~" (or "B" "C" "R" "S" "b" "c" "r" "s" "w"))))) 282 | 283 | (defmacro elixir-rx (&rest sexps) 284 | (let ((rx-constituents (append elixir-rx-constituents rx-constituents))) 285 | (cond ((null sexps) 286 | (error "No regexp")) 287 | ((cdr sexps) 288 | (rx-to-string `(and ,@sexps) t)) 289 | (t 290 | (rx-to-string (car sexps) t)))))) 291 | 292 | (defconst elixir-font-lock-keywords 293 | `( 294 | ;; String interpolation 295 | (elixir-match-interpolation 0 font-lock-variable-name-face t) 296 | 297 | ;; Module-defining & namespace builtins 298 | (,(elixir-rx (or builtin-declaration builtin-namespace) 299 | space 300 | (group module-names)) 301 | 1 font-lock-type-face) 302 | 303 | ;; Module attributes 304 | (,(elixir-rx (group (or heredocs 305 | (and "@" (1+ identifiers))))) 306 | 1 elixir-attribute-face) 307 | 308 | ;; Keywords 309 | (,(elixir-rx (group (or builtin builtin-declaration builtin-namespace 310 | keyword keyword-operator))) 311 | 1 font-lock-keyword-face) 312 | 313 | ;; Function names, i.e. `def foo do'. 314 | (,(elixir-rx (group function-declaration) 315 | space 316 | (group identifiers)) 317 | 2 font-lock-function-name-face) 318 | 319 | ;; Variable definitions 320 | (,(elixir-rx (group identifiers) 321 | (one-or-more space) 322 | "=" 323 | (or (one-or-more space) 324 | (one-or-more "\n"))) 325 | 1 font-lock-variable-name-face) 326 | 327 | ;; Sigils 328 | (,(elixir-rx (group sigils)) 329 | 1 font-lock-builtin-face) 330 | 331 | ;; Regex patterns. Elixir has support for eight different regex delimiters. 332 | ;; This isn't a very DRY approach here but it gets the job done. 333 | (,(elixir-rx "~r" 334 | (and "/" (group (one-or-more (not (any "/")))) "/")) 335 | 1 font-lock-string-face) 336 | (,(elixir-rx "~r" 337 | (and "[" (group (one-or-more (not (any "]")))) "]")) 338 | 1 font-lock-string-face) 339 | (,(elixir-rx "~r" 340 | (and "{" (group (one-or-more (not (any "}")))) "}")) 341 | 1 font-lock-string-face) 342 | (,(elixir-rx "~r" 343 | (and "(" (group (one-or-more (not (any ")")))) ")")) 344 | 1 font-lock-string-face) 345 | (,(elixir-rx "~r" 346 | (and "|" (group (one-or-more (not (any "|")))) "|")) 347 | 1 font-lock-string-face) 348 | (,(elixir-rx "~r" 349 | (and "\"" (group (one-or-more (not (any "\"")))) "\"")) 350 | 1 font-lock-string-face) 351 | (,(elixir-rx "~r" 352 | (and "'" (group (one-or-more (not (any "'")))) "'")) 353 | 1 font-lock-string-face) 354 | (,(elixir-rx "~r" 355 | (and "<" (group (one-or-more (not (any ">")))) ">")) 356 | 1 font-lock-string-face) 357 | 358 | ;; Atoms and singleton-like words like true/false/nil. 359 | (,(elixir-rx (group atoms)) 360 | 1 elixir-atom-face) 361 | 362 | ;; Map keys 363 | (,(elixir-rx (group (and (one-or-more identifiers) ":"))) 364 | 1 elixir-atom-face) 365 | 366 | ;; Built-in modules and pseudovariables 367 | (,(elixir-rx (group (or builtin-modules pseudo-var))) 368 | 1 font-lock-constant-face) 369 | 370 | ;; Code points 371 | (,(elixir-rx (group code-point)) 372 | 1 elixir-negation-face))) 373 | 374 | (defun elixir-mode-cygwin-path (expanded-file-name) 375 | "Elixir mode get Cygwin absolute path name. 376 | Argument EXPANDED-FILE-NAME ." 377 | (replace-regexp-in-string "^[a-zA-Z]:" elixir-mode-cygwin-prefix expanded-file-name t)) 378 | 379 | (defun elixir-mode-universal-path (file-name) 380 | "Elixir mode multi-OS path handler. 381 | Argument FILE-NAME ." 382 | (let ((full-file-name (expand-file-name file-name))) 383 | (if (and (equal system-type 'windows-nt) 384 | elixir-mode-cygwin-paths) 385 | (elixir-mode-cygwin-path full-file-name) 386 | full-file-name))) 387 | 388 | (defun elixir-mode-command-compile (file-name) 389 | "Elixir mode command to compile a file. 390 | Argument FILE-NAME ." 391 | (let ((full-file-name (elixir-mode-universal-path file-name))) 392 | (mapconcat 'identity (append (list elixir-compiler-command) (list full-file-name)) " "))) 393 | 394 | (defun elixir-mode-compiled-file-name (&optional filename) 395 | "Elixir mode compiled FILENAME." 396 | (concat (file-name-sans-extension (or filename (buffer-file-name))) ".beam")) 397 | 398 | (defun elixir-mode-compile-file () 399 | "Elixir mode compile and save current file." 400 | (interactive) 401 | (elixir-deprecated-use-alchemist "elixir-mode-compile-file") 402 | (let ((compiler-output (shell-command-to-string (elixir-mode-command-compile (buffer-file-name))))) 403 | (when (string= compiler-output "") 404 | (message "Compiled and saved as %s" (elixir-mode-compiled-file-name))))) 405 | 406 | (defun elixir-quoted--initialize-buffer (quoted) 407 | (pop-to-buffer elixir-quoted--buffer-name) 408 | (setq buffer-undo-list nil) ; Get rid of undo information from 409 | ; previous expansions 410 | (let ((inhibit-read-only t) 411 | (buffer-undo-list t)) ; Ignore undo information 412 | (erase-buffer) 413 | (insert quoted) 414 | (goto-char (point-min)) 415 | (elixir-mode) 416 | (elixir-quoted-minor-mode 1))) 417 | 418 | ;;;###autoload 419 | (defun elixir-mode-iex (&optional args-p) 420 | "Elixir mode interactive REPL. 421 | Optional argument ARGS-P ." 422 | (interactive "P") 423 | (let ((switches (if (equal args-p nil) 424 | '() 425 | (split-string (read-string "Additional args: "))))) 426 | (unless (comint-check-proc "*IEX*") 427 | (set-buffer 428 | (apply 'make-comint "IEX" 429 | elixir-iex-command nil switches)))) 430 | (pop-to-buffer "*IEX*") 431 | (elixir-deprecated-use-alchemist "elixir-mode-iex")) 432 | 433 | ;;;###autoload 434 | (defun elixir-mode-open-modegithub () 435 | "Elixir mode open GitHub page." 436 | (interactive) 437 | (browse-url "https://github.com/elixir-lang/emacs-elixir")) 438 | 439 | ;;;###autoload 440 | (defun elixir-mode-open-elixir-home () 441 | "Elixir mode go to language home." 442 | (interactive) 443 | (browse-url elixir-mode--website-url)) 444 | 445 | ;;;###autoload 446 | (defun elixir-mode-open-docs-master () 447 | "Elixir mode go to master documentation." 448 | (interactive) 449 | (browse-url (concat elixir-mode--website-url "/docs/master/elixir"))) 450 | 451 | ;;;###autoload 452 | (defun elixir-mode-open-docs-stable () 453 | "Elixir mode go to stable documentation." 454 | (interactive) 455 | (browse-url (concat elixir-mode--website-url "/docs/stable/elixir"))) 456 | 457 | ;;;###autoload 458 | (defun elixir-mode-version (&optional show-version) 459 | "Get the Elixir-Mode version as string. 460 | 461 | If called interactively or if SHOW-VERSION is non-nil, show the 462 | version in the echo area and the messages buffer. 463 | 464 | The returned string includes both, the version from package.el 465 | and the library version, if both a present and different. 466 | 467 | If the version number could not be determined, signal an error, 468 | if called interactively, or if SHOW-VERSION is non-nil, otherwise 469 | just return nil." 470 | (interactive (list t)) 471 | (let ((version (pkg-info-version-info 'elixir-mode))) 472 | (when show-version 473 | (message "Elixir-Mode version: %s" version)) 474 | version)) 475 | 476 | (defun elixir-mode--code-eval-string-command (file) 477 | (format "%s -e 'IO.puts inspect(elem(Code.eval_string(File.read!(\"%s\")), 0))'" 478 | elixir-mode-command 479 | file)) 480 | 481 | (defun elixir-mode--code-string-to-quoted-command (file) 482 | (format "%s -e 'IO.puts inspect(elem(Code.string_to_quoted(File.read!(\"%s\")), 1), pretty: true)'" 483 | elixir-mode-command 484 | file)) 485 | 486 | (defun elixir-mode--execute-elixir-with-code-eval-string (string) 487 | (with-temp-file elixir-mode--eval-filename 488 | (insert string)) 489 | (let ((output (shell-command-to-string (elixir-mode--code-eval-string-command elixir-mode--eval-filename)))) 490 | (delete-file elixir-mode--eval-filename) 491 | output)) 492 | 493 | (defun elixir-mode--execute-elixir-with-code-string-to-quoted (string) 494 | (with-temp-file elixir-mode--eval-filename 495 | (insert string)) 496 | (let ((output (shell-command-to-string (elixir-mode--code-string-to-quoted-command elixir-mode--eval-filename)))) 497 | (delete-file elixir-mode--eval-filename) 498 | output)) 499 | 500 | (defun elixir-mode--eval-string (string) 501 | (let ((output (elixir-mode--execute-elixir-with-code-eval-string string))) 502 | (message output))) 503 | 504 | (defun elixir-mode--string-to-quoted (string) 505 | (let* ((output (elixir-mode--execute-elixir-with-code-string-to-quoted string))) 506 | (elixir-quoted--initialize-buffer output))) 507 | 508 | (defun elixir-mode-eval-on-region (beg end) 509 | "Evaluate the Elixir code on the marked region. 510 | Argument BEG Start of the region. 511 | Argument END End of the region." 512 | (interactive (list (point) (mark))) 513 | (elixir-deprecated-use-alchemist "elixir-mode-eval-on-region") 514 | (unless (and beg end) 515 | (error "The mark is not set now, so there is no region")) 516 | (let* ((region (buffer-substring-no-properties beg end))) 517 | (elixir-mode--eval-string region))) 518 | 519 | (defun elixir-mode-eval-on-current-line () 520 | "Evaluate the Elixir code on the current line." 521 | (interactive) 522 | (elixir-deprecated-use-alchemist "elixir-mode-eval-on-current-line") 523 | (let ((current-line (thing-at-point 'line))) 524 | (elixir-mode--eval-string current-line))) 525 | 526 | (defun elixir-mode-eval-on-current-buffer () 527 | "Evaluate the Elixir code on the current buffer." 528 | (interactive) 529 | (elixir-deprecated-use-alchemist "elixir-mode-eval-on-current-buffer") 530 | (let ((current-buffer (buffer-substring-no-properties (point-max) (point-min)))) 531 | (elixir-mode--eval-string current-buffer))) 532 | 533 | (defun elixir-mode-string-to-quoted-on-region (beg end) 534 | "Get the representation of the expression on the marked region. 535 | Argument BEG Start of the region. 536 | Argument END End of the region." 537 | (interactive (list (point) (mark))) 538 | (elixir-deprecated-use-alchemist "elixir-mode-string-to-quoted-on-region") 539 | (unless (and beg end) 540 | (error "The mark is not set now, so there is no region")) 541 | (let ((region (buffer-substring-no-properties beg end))) 542 | (elixir-mode--string-to-quoted region))) 543 | 544 | (defun elixir-mode-string-to-quoted-on-current-line () 545 | "Get the representation of the expression on the current line." 546 | (interactive) 547 | (elixir-deprecated-use-alchemist "elixir-mode-string-to-quoted-on-current-line") 548 | (let ((current-line (thing-at-point 'line))) 549 | (elixir-mode--string-to-quoted current-line))) 550 | 551 | (easy-menu-define elixir-mode-menu elixir-mode-map 552 | "Elixir mode menu." 553 | '("Elixir" 554 | ["Indent line" smie-indent-line] 555 | ["Compile file" elixir-mode-compile-file] 556 | ["IEX" elixir-mode-iex] 557 | "---" 558 | ["elixir-mode on GitHub" elixir-mode-open-modegithub] 559 | ["Elixir homepage" elixir-mode-open-elixirhome] 560 | ["About" elixir-mode-version] 561 | )) 562 | 563 | ;;;###autoload 564 | (define-derived-mode elixir-mode prog-mode "Elixir" 565 | "Major mode for editing Elixir code. 566 | 567 | \\{elixir-mode-map}" 568 | (set (make-local-variable 'font-lock-defaults) 569 | '(elixir-font-lock-keywords)) 570 | (set (make-local-variable 'comment-start) "# ") 571 | (set (make-local-variable 'comment-end) "") 572 | (set (make-local-variable 'comment-start-skip) "#+ *") 573 | (set (make-local-variable 'comment-use-syntax) t) 574 | (set (make-local-variable 'tab-width) elixir-basic-offset) 575 | (set (make-local-variable 'syntax-propertize-function) 576 | #'elixir-syntax-propertize-function) 577 | (set (make-local-variable 'imenu-generic-expression) 578 | elixir-imenu-generic-expression) 579 | (smie-setup elixir-smie-grammar 'verbose-elixir-smie-rules 580 | :forward-token 'elixir-smie-forward-token 581 | :backward-token 'elixir-smie-backward-token)) 582 | 583 | (define-minor-mode elixir-cos-mode 584 | "Elixir mode toggle compile on save." 585 | :group 'elixir-cos :lighter " CoS" 586 | (cond 587 | (elixir-cos-mode 588 | (add-hook 'after-save-hook 'elixir-mode-compile-file nil t)) 589 | (t 590 | (remove-hook 'after-save-hook 'elixir-mode-compile-file t)))) 591 | 592 | (define-minor-mode elixir-quoted-minor-mode 593 | "Minor mode for displaying elixir quoted expressions" 594 | :group 'elixir-quoted :lighter " quoted" 595 | :keymap '(("q" . quit-window)) 596 | (setq buffer-read-only t)) 597 | 598 | ;; Invoke elixir-mode when appropriate 599 | 600 | ;;;###autoload 601 | (progn 602 | (add-to-list 'auto-mode-alist '("\\.elixir\\'" . elixir-mode)) 603 | (add-to-list 'auto-mode-alist '("\\.ex\\'" . elixir-mode)) 604 | (add-to-list 'auto-mode-alist '("\\.exs\\'" . elixir-mode))) 605 | 606 | (setq-default indent-tabs-mode nil) 607 | (setq-default tab-width 2) 608 | 609 | (provide 'elixir-mode) 610 | 611 | ;;; elixir-mode.el ends here 612 | --------------------------------------------------------------------------------