├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Eldev
├── README.md
├── elixir-format.el
├── elixir-mode.el
├── elixir-smie.el
└── tests
├── elixir-format-test.el
├── elixir-mode-font-test.el
├── elixir-mode-helper-test.el
├── elixir-mode-indentation-test.el
├── elixir-mode-moving-test.el
└── test-helper.el
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - '**.md'
7 | pull_request:
8 | paths-ignore:
9 | - '**.md'
10 |
11 | jobs:
12 | test:
13 | name: test (Emacs ${{matrix.emacs_version}} | Elixir ${{matrix.elixir}} | Erlang/OTP ${{matrix.otp}})
14 | runs-on: ubuntu-20.04
15 | strategy:
16 | matrix:
17 | emacs_version: ['27.2', '26.3', '25.3', 'snapshot']
18 | otp: ['23.3']
19 | elixir: ['1.11.4', '1.12.3', '1.13.4', '1.14.3']
20 |
21 | steps:
22 | - name: Setup Emacs
23 | uses: purcell/setup-emacs@master
24 | with:
25 | version: ${{matrix.emacs_version}}
26 |
27 | - name: Setup OTP + Elixir
28 | uses: erlef/setup-beam@v1
29 | with:
30 | version-type: strict
31 | otp-version: ${{matrix.otp}}
32 | elixir-version: ${{matrix.elixir}}
33 |
34 | - name: Install Eldev
35 | run: curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/github-eldev | sh
36 |
37 | - name: Check out the source code
38 | uses: actions/checkout@v2
39 |
40 | - name: Test the project
41 | run: |
42 | eldev -p -dtT test
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *\~
2 | ._*
3 | /.cask/
4 | /dist/
5 | *-emacs-elixir-format.ex
6 | elixir-mode-autoloads.el
7 |
8 | # Added automatically by ‘eldev init’.
9 | /.eldev
10 | /Eldev-local
11 |
12 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v2.6.0 - Unreleased
2 |
3 | ## v2.5.0 - 2023-06-26
4 |
5 | * [#505](https://github.com/elixir-editors/emacs-elixir/pull/505) - Update markdown, add Tips & Tricks
6 | * [#504](https://github.com/elixir-editors/emacs-elixir/pull/504) - Add guards to easymenu
7 | * [#502](https://github.com/elixir-editors/emacs-elixir/pull/502) - add H to list of sigil characters
8 | * [#499](https://github.com/elixir-editors/emacs-elixir/pull/499) - Fix CI
9 | * [#498](https://github.com/elixir-editors/emacs-elixir/pull/498) - Delete -emacs-elixir-format files when elixir-format is called uninteractively
10 | * [#496](https://github.com/elixir-editors/emacs-elixir/pull/496) - Add mention of Tree-Sitter WIP work
11 | * [#493](https://github.com/elixir-editors/emacs-elixir/pull/493) - Support ~H heex sigil
12 | * [#490](https://github.com/elixir-editors/emacs-elixir/pull/490) - chore(cleanup): remove Emacs 24 hack not supported anymore
13 | * [#489](https://github.com/elixir-editors/emacs-elixir/pull/489) - fix(tests): remove duplicated tests
14 | * [#487](https://github.com/elixir-editors/emacs-elixir/pull/487) - Bump otp and elixir versions for ci testing
15 |
16 | ## v2.4.0 - 2021-10-05
17 | * [#485](https://github.com/elixir-editors/emacs-elixir/pull/485) - Add require for cl-lib
18 | * [#482](https://github.com/elixir-editors/emacs-elixir/pull/482) - Remove pkg-info dependency
19 | * [#481](https://github.com/elixir-editors/emacs-elixir/pull/481) - Enable elixir-mode by default when opening mix.lock file.
20 | * [#475](https://github.com/elixir-editors/emacs-elixir/pull/475) - Backport ppss accessors, use setq-local & more
21 | * [#472](https://github.com/elixir-editors/emacs-elixir/pull/472) - chore: update guides with new build system
22 | * [#471](https://github.com/elixir-editors/emacs-elixir/pull/471) - feat: switch CI and build system
23 | * [#470](https://github.com/elixir-editors/emacs-elixir/pull/470) - Highlight atom map keys that end with a newline
24 | * [#468](https://github.com/elixir-editors/emacs-elixir/pull/468) - Sigil heredoc support
25 | * [#459](https://github.com/elixir-editors/emacs-elixir/pull/459) - Customizable face for numbers
26 |
27 | ## v2.3.2 - 2020-11-16
28 | * [#460](https://github.com/elixir-editors/emacs-elixir/pull/460) - @typedoc recognized as heredoc
29 | * [#454](https://github.com/elixir-editors/emacs-elixir/pull/454) - Add ~L, ~E, and ~e sigils for EEx and LiveView
30 | * [#449](https://github.com/elixir-editors/emacs-elixir/pull/449) - Disable smie-blink-matching-inners-locally
31 | * [#448](https://github.com/elixir-editors/emacs-elixir/pull/448) - make usable again in Emacs 27
32 | * [#438](https://github.com/elixir-editors/emacs-elixir/pull/438) - Run mix directly
33 | * [#442](https://github.com/elixir-editors/emacs-elixir/pull/442) - Add support for ~U sigil
34 | * [#441](https://github.com/elixir-editors/emacs-elixir/pull/441) - Inherit faces from appropriate built-in ones
35 | * [#433](https://github.com/elixir-editors/emacs-elixir/pull/433) - Special case for indentation of fat arrow in map literals
36 | * [#420](https://github.com/elixir-editors/emacs-elixir/pull/420) - change to mix.exs directory before format
37 | * [#425](https://github.com/elixir-editors/emacs-elixir/pull/425) - fontify defguard and defguardp
38 | * [#418](https://github.com/elixir-editors/emacs-elixir/pull/418) - locate closest formatter.exs on save
39 | * [#406](https://github.com/elixir-editors/emacs-elixir/pull/406) - add elixir-format function
40 | * [#381](https://github.com/elixir-editors/emacs-elixir/pull/381) - Fontify ~N and ~T sigils
41 | * [#383](https://github.com/elixir-editors/emacs-elixir/pull/383) - Fix @dox highlight
42 | * [#386](https://github.com/elixir-editors/emacs-elixir/pull/387) - Fix a broken command name
43 | * [#374](https://github.com/elixir-editors/emacs-elixir/pull/374) - Fix highlighting ignored variable in pattern match
44 | * [#373](https://github.com/elixir-editors/emacs-elixir/pull/373) - Fontify ~D sigil
45 | * [#368](https://github.com/elixir-editors/emacs-elixir/pull/368) - Implement moving defun command
46 | * [#364](https://github.com/elixir-editors/emacs-elixir/pull/364) - Fix issue that emacs hangs after def? statement
47 | * [#307](https://github.com/elixir-editors/emacs-elixir/pull/307) - Include - in punctuation class
48 | * [#357](https://github.com/elixir-editors/emacs-elixir/pull/357) - remove elixir-negation-face in favor of font-lock
49 | * [#358](https://github.com/elixir-editors/emacs-elixir/pull/358) - remove ignore-var-face in favor of comment-face
50 | * [#350](https://github.com/elixir-editors/emacs-elixir/pull/350) - Correct implementation for with keyword
51 | * [#345](https://github.com/elixir-editors/emacs-elixir/pull/345) - Correct default indent after match
52 | * [#343](https://github.com/elixir-editors/emacs-elixir/pull/343) - Fix for do oneline blocks
53 | * [#342](https://github.com/elixir-editors/emacs-elixir/pull/342) - Complex comment indentation
54 | * [#340](https://github.com/elixir-editors/emacs-elixir/pull/340) - Fix invalid highlighting question quote
55 | * [#339](https://github.com/elixir-editors/emacs-elixir/pull/339) - Fix heredoc indentation on first line inside block
56 | * [#338](https://github.com/elixir-editors/emacs-elixir/pull/338) - Fix "variable binding depth exceeds max-specpdl-size" error
57 |
58 | ## v2.3.1 - 2016/04/19
59 | * [#337](https://github.com/elixir-editors/emacs-elixir/pull/337) - Fix indentation issue after COMMA token
60 | * [#333](https://github.com/elixir-editors/emacs-elixir/pull/333) - Fix indentation of second element inside list of tuples
61 | * [#332](https://github.com/elixir-editors/emacs-elixir/pull/332) - Correct indent after using 'for' as function name
62 | * [#329](https://github.com/elixir-editors/emacs-elixir/pull/329) - Indent by one level if current line belongs to function call
63 |
64 | ## v2.3.0 - 2016/04/13
65 | * [#327](https://github.com/elixir-editors/emacs-elixir/pull/327) - Correct indentation of maps inside lists
66 | * [#326](https://github.com/elixir-editors/emacs-elixir/pull/326) - Correct anonymous fun indent inside block
67 | * [#325](https://github.com/elixir-editors/emacs-elixir/pull/325) - Fix indentation of statement keywords on dot call
68 | * [#324](https://github.com/elixir-editors/emacs-elixir/pull/324) - added failing tests for named functions in if and case
69 | * [#322](https://github.com/elixir-editors/emacs-elixir/pull/322) - added a failing indentation test for cond within with
70 | * [#321](https://github.com/elixir-editors/emacs-elixir/pull/321) - Fix invalid highlighting '::' in binaries
71 | * [#318](https://github.com/elixir-editors/emacs-elixir/pull/318) - Fix indent of pipes inside blocks of 'def' for example
72 |
73 | ## v2.2.9 - 2016/04/03
74 | * [#317](https://github.com/elixir-editors/emacs-elixir/pull/317) - Correct pipeline indentation
75 | * [#316](https://github.com/elixir-editors/emacs-elixir/pull/316) - Fix indentation of if within an else block
76 | * [#315](https://github.com/elixir-editors/emacs-elixir/pull/315) - Correct indentation with for-comprehensions within blocks
77 | * [#314](https://github.com/elixir-editors/emacs-elixir/pull/314) - Fix highlighting triple single quote(heredoc)
78 | * [#313](https://github.com/elixir-editors/emacs-elixir/pull/313) - Correct indentation after a one line 'fn' definition
79 | * [#305](https://github.com/elixir-editors/emacs-elixir/pull/305) - Added test case for for-comprehensions within case
80 | * [#303](https://github.com/elixir-editors/emacs-elixir/pull/303) - Fix escaped delimiter in sigil issue
81 | * [#295](https://github.com/elixir-editors/emacs-elixir/pull/295) - Demonstrate defstruct indention in a test case
82 | * [#261](https://github.com/elixir-editors/emacs-elixir/pull/261) - Test for multi-line function calls without parenthesis
83 | * [#299](https://github.com/elixir-editors/emacs-elixir/pull/299) - Added `with/1` to the highlighted keywords
84 | * [#298](https://github.com/elixir-editors/emacs-elixir/pull/298) - Added a test for alignment of the last key in multiline maps in cases
85 | * [#296](https://github.com/elixir-editors/emacs-elixir/pull/296) - Gray out ignored variables
86 | * [#291](https://github.com/elixir-editors/emacs-elixir/pull/291) - Added a test for indenting non-finished one-line if-else
87 | * [#289](https://github.com/elixir-editors/emacs-elixir/pull/289) - Added a test case for if within an else
88 | * [#287](https://github.com/elixir-editors/emacs-elixir/pull/287) - Fix sigil triple quotes
89 | * [#284](https://github.com/elixir-editors/emacs-elixir/pull/284) - Added a test for highlighting end after comment
90 | * [#285](https://github.com/elixir-editors/emacs-elixir/pull/285) - Don't capture '(or line-start (not (any ".")))'
91 | * [#282](https://github.com/elixir-editors/emacs-elixir/pull/282) - Indent multiple macro calls with do colon correct
92 | * [#280](https://github.com/elixir-editors/emacs-elixir/pull/280) - Fix one line definitions with equal char inside guard
93 | * [#279](https://github.com/elixir-editors/emacs-elixir/pull/279) - Fix indentation of single line fun declarations after single line fun declarations with when clauses
94 | * [#277](https://github.com/elixir-editors/emacs-elixir/pull/277) - Fix syntax highlighting sigils in string
95 | * [#273](https://github.com/elixir-editors/emacs-elixir/pull/273) - Removed send_after from highlighted keywords
96 | * [#272](https://github.com/elixir-editors/emacs-elixir/pull/272) - Added `send` and `send_after` to font lock
97 | * [#271](https://github.com/elixir-editors/emacs-elixir/pull/271) - Highlight module if preceded by a pipe
98 |
99 | ## v2.2.8 - 2015/10/19
100 | * [#270](https://github.com/elixir-editors/emacs-elixir/pull/270) - Fix highlighting hashmark in sigil
101 | * [#269](https://github.com/elixir-editors/emacs-elixir/pull/269) - Fix string interpolation
102 | * [#268](https://github.com/elixir-editors/emacs-elixir/pull/268) - added font-lock to defoverridable
103 | * [#262](https://github.com/elixir-editors/emacs-elixir/pull/262) - Add indentation tests for comprehensions
104 | * [#267](https://github.com/elixir-editors/emacs-elixir/pull/267) - ~s is sigil, not attribute
105 | * [#266](https://github.com/elixir-editors/emacs-elixir/pull/266) - Fix quotes in sigils
106 | * [#264](https://github.com/elixir-editors/emacs-elixir/pull/264) - Fix string interpolation
107 |
108 | ## v2.2.7 - 2015/09/17
109 | * [#260](https://github.com/elixir-editors/emacs-elixir/pull/260) - Correct indentation after "for" comprehension
110 | * [#259](https://github.com/elixir-editors/emacs-elixir/pull/259) - Indent receive/after matches correct
111 | * [#258](https://github.com/elixir-editors/emacs-elixir/pull/258) - Emacs hangs if `elixir-smie-forward-token` returns an empty string
112 | * [#253](https://github.com/elixir-editors/emacs-elixir/pull/253) - Fix Highlight atom issue(atom contains '!', '?', '@')
113 | * [#252](https://github.com/elixir-editors/emacs-elixir/pull/252) - Fix after dot highlighting
114 | * [#249](https://github.com/elixir-editors/emacs-elixir/pull/249) - Add correct indent for "if" inside a "->" block
115 | * [#246](https://github.com/elixir-editors/emacs-elixir/pull/246) - Fix highlighting true, false, nil
116 | * [#244](https://github.com/elixir-editors/emacs-elixir/pull/244) - True,false, nil are highlighted as atoms
117 | * [#241](https://github.com/elixir-editors/emacs-elixir/pull/241) - correct indent for oneline `do:` when moved to next line
118 | * [#240](https://github.com/elixir-editors/emacs-elixir/pull/240) - Correct indent in case expression when returning a tuple
119 | * [#236](https://github.com/elixir-editors/emacs-elixir/pull/236) - fontify special macros with prefix like '%' and '&'
120 | * [#235](https://github.com/elixir-editors/emacs-elixir/pull/235) - correct indentation for identifiers which contains built in words after a dot
121 |
122 | ## v2.2.6 - 2015/08/05
123 | * [#234](https://github.com/elixir-editors/emacs-elixir/pull/234) - don't highlights LHS as a variable in `==` case fixes #225
124 | * [#233](https://github.com/elixir-editors/emacs-elixir/pull/233) - module syntax highlighting also works correctly with &
125 | * [#232](https://github.com/elixir-editors/emacs-elixir/pull/232) - correct indentation for closing Map curly bracket fixes #223
126 | * [#231](https://github.com/elixir-editors/emacs-elixir/pull/231) - correct indentation for block with multiple matches
127 | * [#230](https://github.com/elixir-editors/emacs-elixir/pull/230) - update travis setup
128 | * [#228](https://github.com/elixir-editors/emacs-elixir/pull/228) - clear elixir-mode from deprecated functions
129 |
130 | ## v2.2.5 - 2015/06/18
131 | * [#222](https://github.com/elixir-editors/emacs-elixir/pull/222) - correct indentation inside heredoc strings
132 | * [#221](https://github.com/elixir-editors/emacs-elixir/pull/221) - highlight atoms correctly in a pattern match
133 | * [#220](https://github.com/elixir-editors/emacs-elixir/pull/220) - Correct indentation of parenthesized expression inside blocks
134 | * [#214](https://github.com/elixir-editors/emacs-elixir/pull/214) - correct indentation after one line definition with if keyword
135 | * [#213](https://github.com/elixir-editors/emacs-elixir/pull/213) - remove do keyword to handle also oneline definitions
136 |
137 | ## v2.2.4 - 2015/05/26
138 | * [#203](https://github.com/elixir-editors/emacs-elixir/pull/203) - Implement triple-quoted strings.
139 |
140 | ## v2.2.3 - 2015/05/26
141 | * [#202](https://github.com/elixir-editors/emacs-elixir/pull/202) - correct indentation after oneline def/if definition
142 | * [#200](https://github.com/elixir-editors/emacs-elixir/pull/200) - correct elements indentation for structs, maps and tuples
143 | * [#198](https://github.com/elixir-editors/emacs-elixir/pull/198) - indenting elements of a list correctly
144 | * [#196](https://github.com/elixir-editors/emacs-elixir/pull/196) - correct indentation outside of blocks
145 |
146 | ## v2.2.2 - 2015/05/22
147 | * [#193](https://github.com/elixir-editors/emacs-elixir/pull/193) - fix wrong indentation on empty line between existing code
148 | * [#195](https://github.com/elixir-editors/emacs-elixir/pull/195) - highlighting of capitalized modules when used as structs
149 | * [#192](https://github.com/elixir-editors/emacs-elixir/pull/192) - Fix (error "Lisp nesting exceeds `max-lisp-eval-depth'") which crashes correct indentation
150 | * [#190](https://github.com/elixir-editors/emacs-elixir/pull/190) - correct indentation for multiclause anonymous functions
151 | * [#189](https://github.com/elixir-editors/emacs-elixir/pull/189) - Modify indentation rules for one-line functions ending with bitstrings.
152 | * [#179](https://github.com/elixir-editors/emacs-elixir/pull/179) - Modify syntax highlighting so there is no differentiating between built-in and user-defined modules.
153 | * [#188](https://github.com/elixir-editors/emacs-elixir/pull/188) - Fix changelog.
154 | * [#187](https://github.com/elixir-editors/emacs-elixir/pull/187) - Add unresolved test case.
155 | * [#178](https://github.com/elixir-editors/emacs-elixir/pull/178) - Factor out `cask install` as its own task in Rakefile
156 |
157 | ## v2.2.1 - 2015/05/19
158 | * [#186](https://github.com/elixir-editors/emacs-elixir/pull/186) - Remove undocumented failing tests.
159 | * [#182](https://github.com/elixir-editors/emacs-elixir/pull/182) - Remove redundant local-pair tip for smartparens users
160 | * [#183](https://github.com/elixir-editors/emacs-elixir/pull/183) - Fix typos in README.
161 | * [#176](https://github.com/elixir-editors/emacs-elixir/pull/176) - Highlight digits in atoms
162 | * [#173](https://github.com/elixir-editors/emacs-elixir/pull/173) - Add function to apply `fill-region` in doc strings
163 | * [#175](https://github.com/elixir-editors/emacs-elixir/pull/175) - Add tips for smartparens users
164 | * [#177](https://github.com/elixir-editors/emacs-elixir/pull/177) - Prefer Emacs over emacs.
165 | * [#141](https://github.com/elixir-editors/emacs-elixir/pull/141) - Add documentation line explaining how to edit Elixir templates.
166 | * [Syntax Highlighting] Modules don't start with underscore. This fix corrects the fontification of private function.
167 |
168 | ## v2.2.0 2014/12/31
169 |
170 | ### Enhancements
171 |
172 | * [Indentation] Indent listing inside square brackets. (#160)
173 | * [Indentation] Indent of binary sequence inside match block
174 | * [Indentation] Indent correct after a binary sequence `<<1,2,3,4>>`.
175 | * [Indentation] Indent correct after oneline `def ... do:` function
176 | * [Indentation] Correct behavior after last line in buffer. (#145)
177 |
178 | ## v2.1.1 - 2014/12/24
179 |
180 | ### Enhancements
181 |
182 | * [Indentation] Indent block inside a fn match.
183 |
184 | ### Bugfixes
185 |
186 | * [Indentation] #152 Fix indentation inside parens wrapped around def
187 |
188 | ## v2.1.0 - 2014/12/23
189 |
190 | ### Enhancements
191 |
192 | * [Indentation] Continue of indentation in multiple line assignment.
193 | * [Indentation] Always indent with only 2 spaces except when function arguments span multiple lines.
194 | * [Indentation] Fix the indentation for mixed matchings.
195 | * [Indentation] Pipe |> indentation works correctly.
196 | * [Syntax Highlighting] Fontify continuation lines assignment.
197 |
198 | ### Changes
199 |
200 | * [Deprecated] Add deprecated message for eval and quoted functions.
201 |
202 | ## v2.0.2 - 2014/10/29
203 | * [#136](https://github.com/elixir-editors/emacs-elixir/pull/136) - Expand def of block operator regex to include non-newlines.
204 | * [#137](https://github.com/elixir-editors/emacs-elixir/pull/137) - update rake tasks
205 | * [#135](https://github.com/elixir-editors/emacs-elixir/pull/135) - Update README so the MELPA badge points to stable build.
206 | * [#133](https://github.com/elixir-editors/emacs-elixir/pull/133) - refine readme
207 | * [#134](https://github.com/elixir-editors/emacs-elixir/pull/134) - refine the ability to show the current elixir-mode version
208 | * [#132](https://github.com/elixir-editors/emacs-elixir/pull/132) - Added: test coverage with undercover.el
209 | * [#131](https://github.com/elixir-editors/emacs-elixir/pull/131) - Fix documentation url
210 | * [#127](https://github.com/elixir-editors/emacs-elixir/pull/127) - Add failing test for comment in cond expression (fixed)
211 | * [#128](https://github.com/elixir-editors/emacs-elixir/pull/128) - Added: collection of failing tests
212 | * [#124](https://github.com/elixir-editors/emacs-elixir/pull/124) - Fixed: Build Status badge
213 | * [#123](https://github.com/elixir-editors/emacs-elixir/pull/123) - TravisCI: add-apt-repository and apt-get install fix
214 | * [#121](https://github.com/elixir-editors/emacs-elixir/pull/121) - Add TravisCI support
215 | * [#122](https://github.com/elixir-editors/emacs-elixir/pull/122) - Remove unused code
216 |
217 | ## v2.0.1 - 2014/09/11
218 | * [#119](https://github.com/elixir-editors/emacs-elixir/pull/119) - Fixed: indent-inside-parens
219 | * [#117](https://github.com/elixir-editors/emacs-elixir/pull/117) - Fixed: emacs 24.3 tests
220 | * [#116](https://github.com/elixir-editors/emacs-elixir/pull/116) - Add "How to run tests" section to CONTRIBUTING.md
221 | * [#118](https://github.com/elixir-editors/emacs-elixir/pull/118) - Added: try/rescue rule
222 | * [#114](https://github.com/elixir-editors/emacs-elixir/pull/114) - Fixed: run tests interectively for terminal
223 |
224 | ## v2.0.0 - 2014/09/08
225 | * [#113](https://github.com/elixir-editors/emacs-elixir/pull/113) - Cask and ert-runner support
226 | * [#110](https://github.com/elixir-editors/emacs-elixir/pull/110) - Added: ability to run tests via EVM
227 | * [#111](https://github.com/elixir-editors/emacs-elixir/pull/111) - Fixed: elixir-quoted-minor-mode tests for ert-run-tests-interactively
228 | * [#108](https://github.com/elixir-editors/emacs-elixir/pull/108) - Fix various issues caused by code followed by inline comments
229 |
230 | ## v1.5.0 - 2014/08/27
231 | * [#103](https://github.com/elixir-editors/emacs-elixir/pull/103) - Add elixir-quoted-minor-mode.
232 |
233 | ## v1.4.10 - 2014/08/26
234 | * [#102](https://github.com/elixir-editors/emacs-elixir/pull/102) - Add support for syntax highlighting for variable interpolation. Fixes #93
235 | * [#101](https://github.com/elixir-editors/emacs-elixir/pull/101) - Fix indentation after inline comment. Fixes #95
236 |
237 | ## v1.4.9 - 2014/08/25
238 | * [#100](https://github.com/elixir-editors/emacs-elixir/pull/100) - Fix indentation in multi-line match expressions. Fixes #98
239 | * [#99](https://github.com/elixir-editors/emacs-elixir/pull/99) - Tokenize trailing whitespace properly. Fixes #97
240 | * [#96](https://github.com/elixir-editors/emacs-elixir/pull/96) - Remove syntax highlighting for operators.
241 |
242 | ## v1.4.8 - 2014/08/19
243 | * [#92](https://github.com/elixir-editors/emacs-elixir/pull/92) - Update Rakefile to also run the release.py script. Refs #88.
244 | * [#91](https://github.com/elixir-editors/emacs-elixir/pull/91) - Updates to regexes for various lexemes
245 | * [#90](https://github.com/elixir-editors/emacs-elixir/pull/90) - Fix bug in atom highlighting.
246 |
247 | ## v1.4.7 - 2014/08/18
248 | * [#87](https://github.com/elixir-editors/emacs-elixir/pull/87) - Add syntax highlighting for heredocs & failing tests for indentation.
249 | * [#85](https://github.com/elixir-editors/emacs-elixir/pull/85) - Improve regex for defmodule highlighting.
250 | * [#84](https://github.com/elixir-editors/emacs-elixir/pull/84) - Improve fontification for identifiers.
251 |
252 | ## v1.4.6 - 2014/08/18
253 | * [#82](https://github.com/elixir-editors/emacs-elixir/pull/82) - Remove broken SMIE rule.
254 |
255 | ## v1.4.5 - 2014/08/18
256 | * [#81](https://github.com/elixir-editors/emacs-elixir/pull/81) - Rewrite token emitting functions
257 |
258 | ## v1.4.4 - 2014/08/18
259 | * [#79](https://github.com/elixir-editors/emacs-elixir/pull/79) - Remove erroneous defrecord syntax.
260 |
261 | ## v1.4.3 - 2014/08/16
262 | * [#75](https://github.com/elixir-editors/emacs-elixir/pull/75) - Clean up several minor bugbears in elixir-smie.
263 | * [#74](https://github.com/elixir-editors/emacs-elixir/pull/74) - Remove special indentation rules for operators, except booleans.
264 |
265 | ## v1.4.2 - 2014/08/15
266 | * [#73](https://github.com/elixir-editors/emacs-elixir/pull/73) - Fix buggy syntax highlighting behavior involving "?"
267 | * [#71](https://github.com/elixir-editors/emacs-elixir/pull/71) - Need two backslashes in regex string.
268 | * [#70](https://github.com/elixir-editors/emacs-elixir/pull/70) - Use define-derived-mode to define elixir-mode
269 | * [#69](https://github.com/elixir-editors/emacs-elixir/pull/69) - Remove unused variable
270 |
271 | ## v1.4.1 - 2014/08/11
272 | * [#66](https://github.com/elixir-editors/emacs-elixir/pull/66) - Indent correctly after one-liner if/do: statements. Fixes #65
273 | * [#64](https://github.com/elixir-editors/emacs-elixir/pull/64) - wrong indentation if space between if and statement
274 | * [#63](https://github.com/elixir-editors/emacs-elixir/pull/63) - Correctly indent one-line anon fns AND block fns. Fixes #59
275 |
276 | ## v1.4.0 - 2014/07/09
277 | * [#62](https://github.com/elixir-editors/emacs-elixir/pull/62) - Remove grammar entry causing erroneous alignment to ".". Fixes #49
278 | * [#61](https://github.com/elixir-editors/emacs-elixir/pull/61) - Remove "=" & left-assoc opers from "OP" regex. Fixes #18.
279 | * [#60](https://github.com/elixir-editors/emacs-elixir/pull/60) - Refactor font face defaults.
280 | * [#58](https://github.com/elixir-editors/emacs-elixir/pull/58) - Use string syntax highlighting for regex patterns.
281 | * [#57](https://github.com/elixir-editors/emacs-elixir/pull/57) - Add beginnings of font-face testing.
282 |
283 | ## v1.3.1 - 2014/07/05
284 | * [#52](https://github.com/elixir-editors/emacs-elixir/pull/52) - Add CLI for running elixir-mode tests.
285 | * [#48](https://github.com/elixir-editors/emacs-elixir/pull/48) - Add a SMIE rule function for "def". Fixes #38 and #41
286 | * [#50](https://github.com/elixir-editors/emacs-elixir/pull/50) - Remove grammar clause for "fn" terminal. Fixes #7
287 | * [#44](https://github.com/elixir-editors/emacs-elixir/pull/44) - sigil % to ~
288 | * [#46](https://github.com/elixir-editors/emacs-elixir/pull/46) - Added highlight for unless and Task module
289 | * [#45](https://github.com/elixir-editors/emacs-elixir/pull/45) - Highlight defstruct and Actor, Base modules
290 | * [#40](https://github.com/elixir-editors/emacs-elixir/pull/40) - IMenu: Show ExUnit tests under heading "Tests".
291 | * [#37](https://github.com/elixir-editors/emacs-elixir/pull/37) - Remove needless statement
292 | * [#35](https://github.com/elixir-editors/emacs-elixir/pull/35) - Added simple imenu support.
293 | * [#32](https://github.com/elixir-editors/emacs-elixir/pull/32) - Properly make variables local
294 | * [#31](https://github.com/elixir-editors/emacs-elixir/pull/31) - needed for effective code-navigation via syntax-ppss
295 | * [#28](https://github.com/elixir-editors/emacs-elixir/pull/28) - Recognize ? char syntax
296 | * [#24](https://github.com/elixir-editors/emacs-elixir/pull/24) - elixir-mode-eval-on-current-buffer binding comment incorrect
297 | * [#22](https://github.com/elixir-editors/emacs-elixir/pull/22) - Enhance `elixir-mode-iex` to accept additional arguments
298 |
299 | ## 1.3.0 (June 24, 2013)
300 | * Add `elixir-mode-eval-on-region` to evalute Elixir code on the
301 | marked region.
302 | * Add `elixir-mode-eval-on-current-buffer` to evalute Elixir code in the current buffer.
303 | * Add `elixir-mode-eval-on-current-line` to evalute Elixir code on the current line.
304 | * Add `elixir-mode-string-to-quoted-on-region` to get the representation of the expression on the marked region.
305 | * Add `elixir-mode-string-to-quoted-on-current-line` to get the
306 | representation of the expression on the current line.
307 |
--------------------------------------------------------------------------------
/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-editors/emacs-elixir/issues
8 | and pull reqs are at https://github.com/elixir-editors/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 test case demonstrating what's wrong.
20 | This makes it easy for us to review, 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 (25+).
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 | We use [Eldev](https://github.com/doublep/eldev) as the project management tool and its built-in ERT integration. Our build matrix can be seen in the workflow file in `.github/workflows/ci.yml`. This ensure we run tests in all of our build matrix.
62 |
63 | Read more about Eldev in its home page.
64 |
65 | #### Examples of usage
66 |
67 | * Run all tests:
68 |
69 | ```bash
70 | $ eldev test
71 | ```
72 |
73 | * Check dependencies
74 |
75 | ```bash
76 | $ eldev deps
77 | ```
78 |
79 | ```bash
80 | $ eldev deps test
81 | ```
82 |
83 | * Run linters
84 |
85 | ```bash
86 | $ eldev lint
87 | ```
88 |
89 | * Compile project (byte compilation)
90 |
91 | ```bash
92 | $ eldev compile
93 | ```
94 |
95 | * Clean-up, for example, after compilation
96 |
97 | ```bash
98 | $ eldev clean
99 | ```
100 |
--------------------------------------------------------------------------------
/Eldev:
--------------------------------------------------------------------------------
1 | ; -*- mode: emacs-lisp; lexical-binding: t; no-byte-compile: t -*-
2 |
3 | ;; Autodetermined by `eldev init'.
4 | (eldev-use-package-archive 'melpa)
5 |
6 | (eldev-use-plugin 'autoloads)
7 | (eldev-use-plugin 'undercover)
8 |
9 | (eldev-add-loading-roots 'test "tests")
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![License GPL 3][badge-license]](http://www.gnu.org/licenses/gpl-3.0.txt)
2 | [](https://github.com/elixir-editors/emacs-elixir/actions)
3 | [](https://elpa.nongnu.org/nongnu/elixir-mode.html)
4 | [](http://stable.melpa.org/#/elixir-mode)
5 | [](http://melpa.org/#/elixir-mode)
6 |
7 | > **WARNING**
8 | > There is a built-in Elixir mode with tree-sitter support from Emacs 30+ that can also be used with Emacs 29
9 | > This repository is for an older elixir-mode built with SMIE (Simple Minded Indentation Engine) which is not
10 | > as advanced as tree-sitter for a language like Elixir.
11 |
12 | # Elixir Mode
13 |
14 | Provides font-locking, indentation and navigation support for the
15 | [Elixir programming language.](http://elixir-lang.org/)
16 |
17 | ## Installation
18 |
19 | `elixir-mode` is available on [NON-GNU ELPA](https://elpa.nongnu.org/),
20 | [MELPA STABLE](https://stable.melpa.org/) and [MELPA](https://melpa.org/).
21 |
22 | ### Via package.el
23 |
24 | `package.el` is the built-in package manager in Emacs.
25 |
26 | You can install `elixir-mode` with the following command:
27 |
28 | M-x package-install [RET] elixir-mode [RET]
29 |
30 | or by adding this bit of Emacs Lisp code to your Emacs initialization file
31 | (`.emacs` or `init.el`):
32 |
33 | ```el
34 | (unless (package-installed-p 'elixir-mode)
35 | (package-install 'elixir-mode))
36 | ```
37 |
38 | If the installation doesn't work try refreshing the package list:
39 |
40 | M-x package-refresh-contents [RET]
41 |
42 | Keep in mind that MELPA packages are built automatically from
43 | the `master` branch, meaning bugs might creep in there from time to
44 | time. Never-the-less, installing from MELPA is the recommended way of
45 | obtaining `Elixir-Mode`.
46 |
47 | MELPA Stable contains packages released from our tags.
48 |
49 | ### Via use-package
50 |
51 | Since Emacs 29, `use-package` is a built-in feature. For versions prior to 29
52 | one can also install it from MELPA (and MELPA Stable).
53 |
54 | To install elixir-mode using `use-package` one can:
55 |
56 | ``` elisp
57 | (use-package elixir-mode
58 | :ensure t)
59 | ```
60 |
61 | ## Usage
62 |
63 | ### Interactive Commands
64 |
65 |
66 |
67 | Command (For the M-x prompt.) |
68 | Description |
69 |
70 |
71 | elixir-mode |
72 | Switches to elixir-mode. |
73 |
74 |
75 | elixir-mode-open-github |
76 | Open the GitHub page for Elixir. |
77 |
78 |
79 |
80 | elixir-mode-open-elixir-home |
81 | Go to Elixir README in the browser. |
82 |
83 |
84 | elixir-mode-open-docs-master |
85 | Open the Elixir documentation for the master. |
86 |
87 |
88 | elixir-mode-open-docs-stable |
89 | Open the Elixir documentation for the latest stable release. |
90 |
91 |
92 | elixir-mode-show-version |
93 | Print version info for elixir-mode. |
94 |
95 |
96 |
97 | ### Configuration
98 |
99 | Any file that matches the glob `*.ex[s]` or `*.elixir` is
100 | automatically opened in elixir-mode, but you can change this
101 | functionality easily.
102 |
103 | ```lisp
104 | ;; Highlights *.elixir2 as well
105 | (add-to-list 'auto-mode-alist '("\\.elixir2\\'" . elixir-mode))
106 | ```
107 |
108 | ### Keymapping
109 |
110 | Keymaps can be added to the `elixir-mode-map` variable.
111 |
112 | ### Pairing
113 |
114 | [Smartparens](https://github.com/Fuco1/smartparens) has direct support for Elixir.
115 |
116 | Alternatively, if you want to use `ruby-end-mode`, you can add the following to your `elixir-mode-hook`:
117 |
118 | ```lisp
119 | (add-to-list 'elixir-mode-hook
120 | (defun auto-activate-ruby-end-mode-for-elixir-mode ()
121 | (set (make-variable-buffer-local 'ruby-end-expand-keywords-before-re)
122 | "\\(?:^\\|\\s-+\\)\\(?:do\\)")
123 | (set (make-variable-buffer-local 'ruby-end-check-statement-modifiers) nil)
124 | (ruby-end-mode +1)))
125 | ```
126 |
127 | ## Notes
128 |
129 | This package is tested only with a single version of OTP and 3 versions of Elixir. Please, always report versions
130 | (Emacs, Elixir and Erlang/OTP) when raising issues.
131 |
132 | ## Elixir Tooling Integration
133 |
134 | You can use [web-mode.el](http://web-mode.org) to edit elixir templates (eex files).
135 |
136 | [mix.el](https://github.com/ayrat555/mix.el) provides a minor mode for integration with Mix, a build tool that ships with Elixir.
137 |
138 | [exunit.el](https://github.com/ananthakumaran/exunit.el) provides `ExUnit` integration.
139 |
140 | ## Elixir Format
141 |
142 | This mode can call mix for formatting code. When inside an elixir buffer, just type `M-x elixir-format`.
143 |
144 | To automate that, you can add this command to the `before-save` hook.
145 |
146 | ``` elisp
147 | ;; Create a buffer-local hook to run elixir-format on save, only when we enable elixir-mode.
148 | (add-hook 'elixir-mode-hook
149 | (lambda () (add-hook 'before-save-hook 'elixir-format nil t)))
150 | ```
151 |
152 | To use a `.formatter.exs` you can either set `elixir-format-arguments` globally to a path like this:
153 |
154 | ``` elisp
155 | (setq elixir-format-arguments (list "--dot-formatter" "/path/to/.formatter.exs"))
156 | ```
157 |
158 | or you set `elixir-format-arguments` in a hook like this:
159 |
160 | ``` elisp
161 | (add-hook 'elixir-format-hook (lambda ()
162 | (if (projectile-project-p)
163 | (setq elixir-format-arguments
164 | (list "--dot-formatter"
165 | (concat (locate-dominating-file buffer-file-name ".formatter.exs") ".formatter.exs")))
166 | (setq elixir-format-arguments nil))))
167 | ```
168 |
169 | In this example we use [Projectile](https://github.com/bbatsov/projectile) to determine if we are in a project and then set `elixir-format-arguments` accordingly.
170 |
171 | Please note that this code snippet may cause unhappiness if there is no `.formatter.exs` file available.
172 |
173 | ## Tips & Tricks
174 |
175 | ### Prettify symbols
176 |
177 | Emacs supports [font ligatures](https://en.wikipedia.org/wiki/Ligature_(writing)). For enabling it for Elixir
178 | you can add it to your configuration:
179 |
180 | ``` elisp
181 | (add-hook
182 | 'elixir-mode-hook
183 | (lambda ()
184 | (push '(">=" . ?\u2265) prettify-symbols-alist)
185 | (push '("<=" . ?\u2264) prettify-symbols-alist)
186 | (push '("!=" . ?\u2260) prettify-symbols-alist)
187 | (push '("==" . ?\u2A75) prettify-symbols-alist)
188 | (push '("=~" . ?\u2245) prettify-symbols-alist)
189 | (push '("<-" . ?\u2190) prettify-symbols-alist)
190 | (push '("->" . ?\u2192) prettify-symbols-alist)
191 | (push '("<-" . ?\u2190) prettify-symbols-alist)
192 | (push '("|>" . ?\u25B7) prettify-symbols-alist)))
193 |
194 | ;; Or if you use use-packge
195 |
196 | (use-package elixir-mode
197 | :hook (elixir-mode . (lambda ()
198 | (push '(">=" . ?\u2265) prettify-symbols-alist)
199 | (push '("<=" . ?\u2264) prettify-symbols-alist)
200 | (push '("!=" . ?\u2260) prettify-symbols-alist)
201 | (push '("==" . ?\u2A75) prettify-symbols-alist)
202 | (push '("=~" . ?\u2245) prettify-symbols-alist)
203 | (push '("<-" . ?\u2190) prettify-symbols-alist)
204 | (push '("->" . ?\u2192) prettify-symbols-alist)
205 | (push '("<-" . ?\u2190) prettify-symbols-alist)
206 | (push '("|>" . ?\u25B7) prettify-symbols-alist))))
207 | ```
208 |
209 | ### Formatting
210 |
211 | If you have issues with the formatter provided by this package, you can try
212 | using the format function of language servers. Elixir-ls supports this.
213 |
214 | One way to configure this with `eglot` and `use-package` would be:
215 |
216 | ``` elisp
217 | (use-package elixir-mode
218 | :hook (elixir-mode . eglot-ensure)
219 | (before-save . eglot-format))
220 | ```
221 |
222 | ### Syntax highlighting for LiveView (and similar techniques for other syntaxes)
223 |
224 | When you need different major modes in the SAME buffer, Emacs do not provide a standard way.
225 | There is an external package that can help here by adding more than one major mode called `poly-mode`.
226 |
227 | A possible configuration would be:
228 |
229 | ``` elisp
230 | ;; Assumes web-mode and elixir-mode are already set up
231 | ;;
232 | (use-package polymode
233 | :mode ("\.ex$" . poly-elixir-web-mode)
234 | :config
235 | (define-hostmode poly-elixir-hostmode :mode 'elixir-mode)
236 | (define-innermode poly-liveview-expr-elixir-innermode
237 | :mode 'web-mode
238 | :head-matcher (rx line-start (* space) "~H" (= 3 (char "\"'")) line-end)
239 | :tail-matcher (rx line-start (* space) (= 3 (char "\"'")) line-end)
240 | :head-mode 'host
241 | :tail-mode 'host
242 | :allow-nested nil
243 | :keep-in-mode 'host
244 | :fallback-mode 'host)
245 | (define-polymode poly-elixir-web-mode
246 | :hostmode 'poly-elixir-hostmode
247 | :innermodes '(poly-liveview-expr-elixir-innermode))
248 | )
249 | (setq web-mode-engines-alist '(("elixir" . "\\.ex\\'")))
250 | ```
251 |
252 | ## History
253 |
254 | This mode is based on the [Emacs mode by secondplanet](https://github.com/secondplanet/elixir-mode).
255 |
256 | ## Contributing
257 |
258 | Please read [CONTRIBUTING.md](https://github.com/elixir-editors/emacs-elixir/blob/master/CONTRIBUTING.md) for guidelines on how to contribute to this project.
259 |
260 | ## License
261 |
262 | Copyright © 2011-2017 Samuel Tonini, Matt DeBoard, Andreas Fuchs, secondplanet and [contributors](https://github.com/elixir-editors/emacs-elixir/contributors).
263 |
264 | Distributed under the GNU General Public License, version 3
265 |
266 | [badge-license]: https://img.shields.io/badge/license-GPL_3-green.svg
267 |
--------------------------------------------------------------------------------
/elixir-format.el:
--------------------------------------------------------------------------------
1 | ;;; elixir-format.el --- Emacs plugin to mix format Elixir files
2 |
3 | ;; Copyright 2017-2018 Anil Wadghule, Christian Kruse
4 |
5 | ;; This file is NOT part of GNU Emacs.
6 |
7 | ;; This program is free software; you can redistribute it and/or modify
8 | ;; it under the terms of the GNU General Public License as published by
9 | ;; the Free Software Foundation; either version 2, or (at your option)
10 | ;; any later version.
11 |
12 | ;; This program is distributed in the hope that it will be useful,
13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | ;; GNU General Public License for more details.
16 |
17 | ;;; Commentary:
18 |
19 | ;; The elixir-format function formats the elixir files with Elixir's `mix format`
20 | ;; command
21 |
22 | ;; e.g.
23 | ;; M-x elixir-format
24 | ;;
25 |
26 | (require 'ansi-color)
27 | (require 'cl-lib)
28 |
29 | (defcustom elixir-format-arguments nil
30 | "Additional arguments to 'mix format'."
31 | :type '(repeat string)
32 | :group 'elixir
33 | :group 'elixir-format)
34 |
35 | (defcustom elixir-format-hook nil
36 | "Hook called by `elixir-format'."
37 | :type 'hook
38 | :group 'elixir
39 | :group 'elixir-format)
40 |
41 |
42 | ;;; Code:
43 |
44 | (defun elixir-format--errbuff ()
45 | (get-buffer-create "*elixir-format-errors*"))
46 |
47 | (defun elixir-format--outbuff ()
48 | (get-buffer-create "*elixir-format-output*"))
49 |
50 | (defun elixir-format--elixir-executable ()
51 | (executable-find "elixir"))
52 |
53 | (defun elixir-format--mix-executable ()
54 | (executable-find "mix"))
55 |
56 | ;;;###autoload
57 | (defun elixir-format (&optional called-interactively-p)
58 | (interactive "p")
59 | (if (not (elixir-format--elixir-and-mix-path-set-p))
60 | (elixir-format--display-missing-executables-error called-interactively-p)
61 | (unwind-protect
62 | (save-restriction
63 | (elixir-format--clean-output-buffers)
64 | (elixir-format--run-format called-interactively-p)))))
65 |
66 | (defun elixir-format--elixir-and-mix-path-set-p ()
67 | (and (elixir-format--elixir-executable)
68 | (elixir-format--mix-executable)))
69 |
70 | (defun elixir-format--display-missing-executables-error (called-interactively-p)
71 | (with-current-buffer (elixir-format--errbuff)
72 | (setq buffer-read-only nil)
73 | (erase-buffer)
74 | (insert "Emacs is unable to find the executables for elixir and/or mix. Either they are not installed on your system or emacs' PATH is not as wide as it needs to be. The latter is most likely to happen on OSX, in which case the simplest answer may be to add the exec-path-from-shell package to your configuration.")
75 | (setq buffer-read-only t)
76 | (ansi-color-apply-on-region (point-min) (point-max))
77 | (special-mode)
78 | (if called-interactively-p
79 | (display-buffer (elixir-format--errbuff))
80 | (error "Elixir Format error see %s" (elixir-format--errbuff)))))
81 |
82 | (defun elixir-format--clean-output-buffers ()
83 | (with-current-buffer (elixir-format--outbuff)
84 | (erase-buffer))
85 |
86 | (with-current-buffer (elixir-format--errbuff)
87 | (setq buffer-read-only nil)
88 | (erase-buffer)))
89 |
90 | (defun elixir-format--target-file-name ()
91 | "Returns the file name of current visited file.
92 |
93 | If the buffer is not visiting any file (like during tests) then
94 | it returns a file name based on the name of the buffer."
95 | (or buffer-file-name (concat (secure-hash 'md5 (buffer-name)) ".ex")))
96 |
97 | (defun elixir-format--temp-file-path ()
98 | "Make a temp file in the current directory, because mix format
99 | applies rules based on path patterns and looks for .formatter.exs
100 | files in subdirectories."
101 | (let ((target-file-name (elixir-format--target-file-name)))
102 | (concat (file-name-sans-extension target-file-name)
103 | "-emacs-elixir-format."
104 | (file-name-extension target-file-name))))
105 |
106 | (defun elixir-format--run-format (called-interactively-p)
107 | (let ((tmpfile (elixir-format--temp-file-path))
108 | (our-elixir-format-arguments (list "format")))
109 |
110 | (write-region nil nil tmpfile)
111 | (run-hooks 'elixir-format-hook)
112 |
113 | (when elixir-format-arguments
114 | (setq our-elixir-format-arguments (append our-elixir-format-arguments elixir-format-arguments)))
115 | (setq our-elixir-format-arguments (append our-elixir-format-arguments (list tmpfile)))
116 |
117 | (unwind-protect
118 | (if (zerop (elixir-format--from-mix-root (elixir-format--mix-executable) (elixir-format--errbuff) our-elixir-format-arguments))
119 | (elixir-format--call-format-command tmpfile)
120 | (elixir-format--failed-to-format called-interactively-p))
121 | (delete-file tmpfile)
122 | (kill-buffer (elixir-format--outbuff)))))
123 |
124 | (defun elixir-format--call-format-command (tmpfile)
125 | (if (zerop (call-process-region (point-min) (point-max) "diff" nil (elixir-format--outbuff) nil "-n" "-" tmpfile))
126 | (message "File is already formatted")
127 | (elixir-format--apply-rcs-patch (elixir-format--outbuff))
128 | (message "elixir-format format applied"))
129 | (kill-buffer (elixir-format--errbuff)))
130 |
131 | (defun elixir-format--failed-to-format (called-interactively-p)
132 | (with-current-buffer (elixir-format--errbuff)
133 | (setq buffer-read-only t)
134 | (ansi-color-apply-on-region (point-min) (point-max))
135 | (special-mode))
136 |
137 | (if called-interactively-p
138 | (display-buffer (elixir-format--errbuff))
139 | (error "elixir-format failed: see %s" (buffer-name (elixir-format--errbuff)))))
140 |
141 | (defun elixir-format--apply-rcs-patch (patch-buffer)
142 | "Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer.
143 | Shamelessly stolen from go-mode (https://github.com/dominikh/go-mode.el)"
144 |
145 | (let ((target-buffer (current-buffer))
146 | ;; Relative offset between buffer line numbers and line numbers
147 | ;; in patch.
148 | ;;
149 | ;; Line numbers in the patch are based on the source file, so
150 | ;; we have to keep an offset when making changes to the
151 | ;; buffer.
152 | ;;
153 | ;; Appending lines decrements the offset (possibly making it
154 | ;; negative), deleting lines increments it. This order
155 | ;; simplifies the forward-line invocations.
156 | (line-offset 0))
157 | (save-excursion
158 | (with-current-buffer patch-buffer
159 | (goto-char (point-min))
160 | (while (not (eobp))
161 | (unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
162 | (error "Invalid rcs patch or internal error in elixir-format--apply-rcs-patch"))
163 | (forward-line)
164 | (let ((action (match-string 1))
165 | (from (string-to-number (match-string 2)))
166 | (len (string-to-number (match-string 3))))
167 | (cond
168 | ((equal action "a")
169 | (let ((start (point)))
170 | (forward-line len)
171 | (let ((text (buffer-substring start (point))))
172 | (with-current-buffer target-buffer
173 | (cl-decf line-offset len)
174 | (goto-char (point-min))
175 | (forward-line (- from len line-offset))
176 | (insert text)))))
177 | ((equal action "d")
178 | (with-current-buffer target-buffer
179 | (elixir-format--goto-line (- from line-offset))
180 | (cl-incf line-offset len)
181 | (elixir-format--delete-whole-line len)))
182 | (t
183 | (error "Invalid rcs patch or internal error in elixir-format--apply-rcs-patch")))))))))
184 |
185 | (defun elixir-format--goto-line (line)
186 | (goto-char (point-min))
187 | (forward-line (1- line)))
188 |
189 | (defun elixir-format--delete-whole-line (&optional arg)
190 | "Delete the current line without putting it in the `kill-ring'.
191 | Derived from function `kill-whole-line'. ARG is defined as for that
192 | function.
193 |
194 | Shamelessly stolen from go-mode (https://github.com/dominikh/go-mode.el)"
195 | (setq arg (or arg 1))
196 | (if (and (> arg 0)
197 | (eobp)
198 | (save-excursion (forward-visible-line 0) (eobp)))
199 | (signal 'end-of-buffer nil))
200 | (if (and (< arg 0)
201 | (bobp)
202 | (save-excursion (end-of-visible-line) (bobp)))
203 | (signal 'beginning-of-buffer nil))
204 | (cond ((zerop arg)
205 | (delete-region (progn (forward-visible-line 0) (point))
206 | (progn (end-of-visible-line) (point))))
207 | ((< arg 0)
208 | (delete-region (progn (end-of-visible-line) (point))
209 | (progn (forward-visible-line (1+ arg))
210 | (unless (bobp)
211 | (backward-char))
212 | (point))))
213 | (t
214 | (delete-region (progn (forward-visible-line 0) (point))
215 | (progn (forward-visible-line arg) (point))))))
216 |
217 |
218 | (defun elixir-format--from-mix-root (mix-path errbuff format-arguments)
219 | "Run mix format where `mix.exs' is located, because mix is
220 | meant to be run from the project root. Otherwise, run in the
221 | current directory."
222 | (let ((original-default-directory default-directory)
223 | (mix-dir (locate-dominating-file (elixir-format--target-file-name) "mix.exs")))
224 |
225 | (when mix-dir
226 | (setq default-directory (expand-file-name mix-dir)))
227 |
228 | (message (concat "Run "
229 | (abbreviate-file-name default-directory) ": "
230 | (mapconcat 'identity format-arguments " ")))
231 |
232 | (let ((result (apply #'call-process
233 | mix-path nil errbuff nil format-arguments)))
234 | (setq default-directory original-default-directory)
235 | result)))
236 |
237 | (provide 'elixir-format)
238 |
239 | ;;; elixir-format.el ends here
240 |
--------------------------------------------------------------------------------
/elixir-mode.el:
--------------------------------------------------------------------------------
1 | ;;; elixir-mode.el --- Major mode for editing Elixir files -*- lexical-binding: t -*-
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-editors/emacs-elixir
11 | ;; Created: Mon Nov 7 2011
12 | ;; Keywords: languages elixir
13 | ;; Version: 2.5.0
14 | ;; Package-Requires: ((emacs "25"))
15 |
16 | ;; This file is not a part of GNU Emacs.
17 |
18 | ;; This program is free software; you can redistribute it and/or modify
19 | ;; it under the terms of the GNU General Public License as published by
20 | ;; the Free Software Foundation; either version 2, or (at your option)
21 | ;; any later version.
22 |
23 | ;; This program is distributed in the hope that it will be useful,
24 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
25 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 | ;; GNU General Public License for more details.
27 |
28 | ;; You should have received a copy of the GNU General Public License
29 | ;; along with this program; if not, write to the Free Software
30 | ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
31 |
32 | ;;; Commentary:
33 |
34 | ;; Provides font-locking, indentation and navigation support
35 | ;; for the Elixir programming language.
36 |
37 | ;;; Code:
38 |
39 | (require 'easymenu) ; Elixir Mode menu definition
40 | (require 'elixir-smie) ; Syntax and indentation support
41 | (require 'elixir-format) ; Elixir Format functions
42 |
43 | (defgroup elixir nil
44 | "Major mode for editing Elixir code."
45 | :prefix "elixir-"
46 | :group 'languages
47 | :link '(url-link :tag "Github" "https://github.com/elixir-editors/emacs-elixir")
48 | :link '(emacs-commentary-link :tag "Commentary" "elixir-mode"))
49 |
50 | (defcustom elixir-mode-website-url "http://elixir-lang.org"
51 | "Official url of Elixir programming website."
52 | :type 'string)
53 |
54 | (defcustom elixir-mode-doc-url "https://hexdocs.pm/elixir"
55 | "Official documentation for the Elixir programming language."
56 | :type 'string)
57 |
58 | (defcustom elixir-mode-hook nil
59 | "Hook that runs when switching to major mode"
60 | :type 'hook)
61 |
62 | (defvar elixir-mode-map
63 | (let ((map (make-sparse-keymap)))
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-+.*$" 1)
69 | ("Public Functions" "^\\s-*def[ \n\t]+\\([a-z0-9_!\\?]+\\)\\(([^)]*)\\)*.*" 1)
70 | ("Private Functions" "^\\s-*defp[ \n\t]+\\([a-z0-9_!\\?]+\\)\\(([^)]*)\\)*.*" 1)
71 | ("Public Macros" "^\\s-*defmacro[ \n\t]+\\([a-z0-9_!\\?]+\\)\\(([^)]*)\\)*.*" 1)
72 | ("Private Macros" "^\\s-*defmacrop[ \n\t]+\\([a-z0-9_!\\?]+\\)\\(([^)]*)\\)*.*" 1)
73 | ("Public Guards" "^\\s-*defguard[ \n\t]+\\([a-z0-9_!\\?]+\\)\\(([^)]*)\\)*.*" 1)
74 | ("Private Guards" "^\\s-*defguardp[ \n\t]+\\([a-z0-9_!\\?]+\\)\\(([^)]*)\\)*.*" 1)
75 | ("Delegates" "^\\s-*defdelegate[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*.*" 1)
76 | ("Overridables" "^\\s-*defoverridable[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*.*" 1)
77 | ("Tests" "^\\s-*test[ \t\n]+\"?\\(:?[a-z0-9_@+() \t-]+\\)\"?[ \t\n]+.*" 1))
78 | "Imenu pattern for `elixir-mode'.")
79 |
80 | (defcustom elixir-basic-offset 2
81 | "Basic offset."
82 | :type 'integer)
83 | (defcustom elixir-key-label-offset 0
84 | "Offset used for key label."
85 | :type 'integer)
86 | (defcustom elixir-match-label-offset 2
87 | "Offset for a match label."
88 | :type 'integer)
89 |
90 | (defgroup elixir-faces nil
91 | "Font-lock faces for `elixir'."
92 | :group 'elixir
93 | :group 'faces)
94 |
95 | (defvar elixir-attribute-face 'elixir-attribute-face)
96 | (defface elixir-attribute-face
97 | '((t (:inherit font-lock-preprocessor-face)))
98 | "For use with module attribute tokens.")
99 |
100 | (defvar elixir-atom-face 'elixir-atom-face)
101 | (defface elixir-atom-face
102 | '((t (:inherit font-lock-builtin-face)))
103 | "For use with atoms & map keys.")
104 |
105 | (defvar elixir-number-face 'elixir-number-face)
106 | (defface elixir-number-face
107 | '((t (:inherit default)))
108 | "For use with numbers.")
109 |
110 |
111 | (eval-when-compile
112 | (defconst elixir-rx-constituents
113 | `(
114 | (string-delimiter . ,(rx (and
115 | ;; Match even number of backslashes.
116 | (or (not (any ?\\ ?\' ?\")) point
117 | ;; Quotes might be preceded by escaped quote
118 | (and (or (not (any ?\\)) point) ?\\
119 | (* ?\\ ?\\) (any ?\' ?\")))
120 | (* ?\\ ?\\)
121 | ;; Match single or triple quotes of any kind.
122 | (group (or "\"" "\"\"\"" "'" "'''")))))
123 | (atoms . ,(rx ":"
124 | (or
125 | (and
126 | (any "a-z" "A-Z" "_" "\"" "'")
127 | (zero-or-more (any "a-z" "A-Z" "0-9" "_" "\"" "'" "!" "@" "?")))
128 | (and "\"" (one-or-more (not (any "\""))) "\"")
129 | (and "'" (one-or-more (not (any "'"))) "'"))))
130 | (numbers . ,(rx (and symbol-start
131 | (? "-")
132 | (+ digit)
133 | (0+ (and "_" (= 3 digit)))
134 | symbol-end)))
135 | (builtin . ,(rx symbol-start
136 | (or "case" "cond" "for" "if" "quote" "raise" "receive" "send"
137 | "super" "throw" "try" "unless" "unquote" "unquote_splicing"
138 | "with")
139 | symbol-end))
140 | (builtin-declaration . ,(rx symbol-start
141 | (or "def" "defp" "defmodule" "defprotocol"
142 | "defmacro" "defmacrop" "defdelegate"
143 | "defexception" "defstruct" "defimpl"
144 | "defguard" "defguardp" "defcallback"
145 | "defoverridable")
146 | symbol-end))
147 | (builtin-namespace . ,(rx symbol-start
148 | (or "import" "require" "use" "alias")
149 | symbol-end))
150 | ;; Set aside code point syntax for negation face.
151 | (code-point . ,(rx symbol-start
152 | "?"
153 | anything
154 | symbol-end))
155 | (function-declaration . ,(rx (or line-start (not (any ".")))
156 | symbol-start
157 | (or "def" "defp")
158 | symbol-end))
159 | ;; The first character of an identifier must be a letter or an underscore.
160 | ;; After that, they may contain any alphanumeric character + underscore.
161 | ;; Additionally, the final character may be either `?' or `!'.
162 | (identifiers . ,(rx (any "A-Z" "a-z" "_")
163 | (zero-or-more (any "A-Z" "a-z" "0-9" "_"))
164 | (optional (or "?" "!"))))
165 | (keyword . ,(rx symbol-start
166 | (or "fn" "do" "end" "after" "else" "rescue" "catch")
167 | symbol-end))
168 | (keyword-operator . ,(rx symbol-start
169 | (or "not" "and" "or" "when" "in")
170 | symbol-end))
171 | ;; Module and submodule names start with upper case letter. This
172 | ;; can then be followed by any combination of alphanumeric chars.
173 | ;; In turn, this can be followed by a `.' which begins the notation of
174 | ;; a submodule, which follows the same naming pattern of the module.
175 | ;; Finally, like other identifiers, it can be terminated with either `?'
176 | ;; or `!'.
177 | (module-names . ,(rx symbol-start
178 | (optional (or "%" "&"))
179 | (any "A-Z")
180 | (zero-or-more (any "A-Z" "a-z" "_" "0-9"))
181 | (zero-or-more
182 | (and "."
183 | (any "A-Z" "_")
184 | (zero-or-more (any "A-Z" "a-z" "_" "0-9"))))
185 | (optional (or "!" "?"))
186 | symbol-end))
187 | (arrows . ,(rx (or "->" "<-" "=>" "|>")))
188 | (pseudo-var . ,(rx symbol-start
189 | (optional (or "%" "&"))
190 | (or "_" "__MODULE__" "__DIR__" "__ENV__" "__CALLER__"
191 | "__block__" "__aliases__")
192 | symbol-end))
193 | (sigils . ,(rx "~" (or "B" "C" "D" "E" "H" "L" "N" "R" "S" "T" "U" "b" "c" "e" "r" "s" "w")))))
194 |
195 | (defmacro elixir-rx (&rest sexps)
196 | (let ((rx-constituents (append elixir-rx-constituents rx-constituents)))
197 | (cond ((null sexps)
198 | (error "No regexp"))
199 | ((cdr sexps)
200 | (rx-to-string `(and ,@sexps) t))
201 | (t
202 | (rx-to-string (car sexps) t))))))
203 |
204 | (defsubst elixir-syntax-in-string-or-comment-p ()
205 | (elixir-ppss-comment-or-string-start (syntax-ppss)))
206 |
207 | (defsubst elixir-syntax-count-quotes (quote-char &optional point limit)
208 | "Count number of quotes around point (max is 3).
209 | QUOTE-CHAR is the quote char to count. Optional argument POINT is
210 | the point where scan starts (defaults to current point), and LIMIT
211 | is used to limit the scan."
212 | (let ((i 0))
213 | (while (and (< i 3)
214 | (or (not limit) (< (+ point i) limit))
215 | (eq (char-after (+ point i)) quote-char))
216 | (setq i (1+ i)))
217 | i))
218 |
219 | (defun elixir-syntax-stringify ()
220 | "Put `syntax-table' property correctly on single/triple quotes."
221 | (let* ((num-quotes (length (match-string-no-properties 1)))
222 | (quote-starting-pos (- (point) num-quotes))
223 | (quote-ending-pos (point))
224 | (ppss (save-excursion
225 | (syntax-ppss quote-starting-pos)))
226 | (string-start (and (not (elixir-ppss-comment-depth ppss))
227 | (elixir-ppss-comment-or-string-start ppss)))
228 | (num-closing-quotes
229 | (and string-start
230 | (elixir-syntax-count-quotes
231 | (char-before) string-start quote-starting-pos))))
232 | (cond ((and string-start (= num-closing-quotes 0))
233 | ;; This set of quotes doesn't match the string starting
234 | ;; kind. Do nothing.
235 | nil)
236 | ((not string-start)
237 | ;; This set of quotes delimit the start of a string.
238 | (put-text-property quote-starting-pos (1+ quote-starting-pos)
239 | 'syntax-table (string-to-syntax "|")))
240 | ((= num-quotes num-closing-quotes)
241 | ;; This set of quotes delimit the end of a string.
242 | (put-text-property (1- quote-ending-pos) quote-ending-pos
243 | 'syntax-table (string-to-syntax "|")))
244 | ((> num-quotes num-closing-quotes)
245 | ;; This may only happen whenever a triple quote is closing
246 | ;; a single quoted string. Add string delimiter syntax to
247 | ;; all three quotes.
248 | (put-text-property quote-starting-pos quote-ending-pos
249 | 'syntax-table (string-to-syntax "|"))))))
250 |
251 |
252 | (defun elixir-syntax-propertize-interpolation ()
253 | (let* ((beg (match-beginning 0))
254 | (context (save-excursion (save-match-data (syntax-ppss beg)))))
255 | (put-text-property beg (1+ beg) 'syntax-table (string-to-syntax "w"))
256 | (put-text-property beg (1+ beg) 'elixir-interpolation
257 | (cons (elixir-ppss-string-terminator context)
258 | (match-data)))))
259 |
260 | (defconst elixir-sigil-delimiter-pair
261 | '((?\( . ")")
262 | (?\{ . "}")
263 | (?\< . ">")
264 | (?\[ . "]")))
265 |
266 | (defun elixir-syntax-replace-property-in-sigil ()
267 | (unless (elixir-syntax-in-string-or-comment-p)
268 | (let ((heredoc-p (save-excursion
269 | (goto-char (match-beginning 0))
270 | (looking-at-p "~[BCDEHLNRSTUbcersw]\\(?:'''\\|\"\"\"\\)"))))
271 | (unless heredoc-p
272 | (forward-char 1)
273 | (let* ((start-delim (char-after (1- (point))))
274 | (end-delim (or (assoc-default start-delim elixir-sigil-delimiter-pair)
275 | (char-to-string start-delim)))
276 | (end (save-excursion
277 | (let (finish)
278 | (while (not finish)
279 | (skip-chars-forward (concat "^" end-delim))
280 | (if (or (not (eq (char-before) ?\\))
281 | (eq (char-before (1- (point))) ?\\)
282 | (eobp))
283 | (setq finish t)
284 | (forward-char 1)))
285 | (point))))
286 | (word-syntax (string-to-syntax "w")))
287 | (when (memq start-delim '(?' ?\"))
288 | (setq end (1+ end))
289 | (forward-char -1))
290 | (while (re-search-forward "[\"'#]" end 'move)
291 | (put-text-property (1- (point)) (point) 'syntax-table word-syntax)))))))
292 |
293 | (defun elixir-syntax-propertize-function (start end)
294 | (let ((case-fold-search nil))
295 | (goto-char start)
296 | (funcall
297 | (syntax-propertize-rules
298 | ("\\(\\?\\)[\"']"
299 | (1 (if (save-excursion
300 | (elixir-ppss-string-terminator
301 | (syntax-ppss (match-beginning 0))))
302 | ;; Within a string, skip.
303 | (ignore
304 | (goto-char (match-end 1)))
305 | (put-text-property (match-end 1) (match-end 0)
306 | 'syntax-table (string-to-syntax "_"))
307 | (string-to-syntax "'"))))
308 | ((elixir-rx string-delimiter)
309 | (0 (ignore (elixir-syntax-stringify))))
310 | ((elixir-rx sigils)
311 | (0 (ignore (elixir-syntax-replace-property-in-sigil))))
312 | ((rx (group "#{" (0+ (not (any "}"))) "}"))
313 | (0 (ignore (elixir-syntax-propertize-interpolation)))))
314 | start end)))
315 |
316 | (defun elixir-match-interpolation (limit)
317 | (let ((pos (next-single-char-property-change (point) 'elixir-interpolation
318 | nil limit)))
319 | (when (and pos (> pos (point)))
320 | (goto-char pos)
321 | (let ((value (get-text-property pos 'elixir-interpolation)))
322 | (if (car value)
323 | (progn
324 | (set-match-data (cdr value))
325 | t)
326 | (elixir-match-interpolation limit))))))
327 |
328 |
329 | (defconst elixir-font-lock-keywords
330 | `(
331 | ;; String interpolation
332 | (elixir-match-interpolation 0 font-lock-variable-name-face t)
333 |
334 | ;; Module attributes
335 | (,(elixir-rx (and "@" identifiers))
336 | 0 elixir-attribute-face)
337 |
338 | ;; Keywords
339 | (,(elixir-rx (and (or line-start (not (any ".")))
340 | (group (or builtin builtin-declaration builtin-namespace
341 | keyword keyword-operator))))
342 | 1 font-lock-keyword-face)
343 |
344 | ;; Function names, i.e. `def foo do'.
345 | (,(elixir-rx (group function-declaration)
346 | space
347 | (group identifiers))
348 | 2 font-lock-function-name-face)
349 |
350 | ;; Sigil patterns. Elixir has support for eight different sigil delimiters.
351 | ;; This isn't a very DRY approach here but it gets the job done.
352 | (,(elixir-rx (group sigils)
353 | (and "/"
354 | (group (zero-or-more (or (and "\\" "/") (not (any "/" "\n" "\r")))))
355 | "/"))
356 | (1 font-lock-builtin-face)
357 | (2 font-lock-string-face))
358 | (,(elixir-rx (group sigils)
359 | (and "["
360 | (group (zero-or-more (or (and "\\" "]") (not (any "]" "\n" "\r")))))
361 | "]"))
362 | (1 font-lock-builtin-face)
363 | (2 font-lock-string-face))
364 | (,(elixir-rx (group sigils)
365 | (and "{"
366 | (group (zero-or-more (or (and "\\" "}") (not (any "}" "\n" "\r")))))
367 | "}"))
368 | (1 font-lock-builtin-face)
369 | (2 font-lock-string-face))
370 | (,(elixir-rx (group sigils)
371 | (and "("
372 | (group (zero-or-more (or (and "\\" ")") (not (any ")" "\n" "\r")))))
373 | ")"))
374 | (1 font-lock-builtin-face)
375 | (2 font-lock-string-face))
376 | (,(elixir-rx (group sigils)
377 | (and "|"
378 | (group (zero-or-more (or (and "\\" "|") (not (any "|" "\n" "\r")))))
379 | "|"))
380 | (1 font-lock-builtin-face)
381 | (2 font-lock-string-face))
382 | (,(elixir-rx (group sigils)
383 | (and "\""
384 | (group (zero-or-more (or (and "\\" "\"") (not (any "\"" "\n" "\r")))))
385 | "\""))
386 | (1 font-lock-builtin-face)
387 | (2 font-lock-string-face))
388 | (,(elixir-rx (group sigils)
389 | (and "'"
390 | (group (zero-or-more (or (and "\\" "'") (not (any "'" "\n" "\r")))))
391 | "'"))
392 | (1 font-lock-builtin-face)
393 | (2 font-lock-string-face))
394 | (,(elixir-rx (group sigils)
395 | (and "<"
396 | (group (zero-or-more (or (and "\\" ">") (not (any ">" "\n" "\r")))))
397 | ">"))
398 | (1 font-lock-builtin-face)
399 | (2 font-lock-string-face))
400 |
401 | ;; Modules
402 | (,(elixir-rx (group module-names))
403 | 1 font-lock-type-face)
404 |
405 | ;; Atoms and singleton-like words like true/false/nil.
406 | (,(elixir-rx symbol-start
407 | (group (or atoms "true" "false" "nil"))
408 | symbol-end
409 | (zero-or-more space)
410 | (optional "="))
411 | 1 elixir-atom-face)
412 |
413 | ;; Numbers
414 | (,(elixir-rx (group numbers))
415 | 1 elixir-number-face)
416 |
417 | ;; Gray out variables starting with "_"
418 | (,(elixir-rx symbol-start
419 | (group (and "_"
420 | (any "A-Z" "a-z" "0-9"))
421 | (zero-or-more (any "A-Z" "a-z" "0-9" "_"))
422 | (optional (or "?" "!"))))
423 | 1 font-lock-comment-face)
424 |
425 | ;; Variable definitions
426 | (,(elixir-rx (group identifiers)
427 | (zero-or-more space)
428 | (repeat 1 "=")
429 | (or (or sigils identifiers space)
430 | (one-or-more "\n")))
431 | 1 font-lock-variable-name-face)
432 |
433 | ;; Map keys
434 | (,(elixir-rx (group (and identifiers ":")) (or space "\n"))
435 | 1 elixir-atom-face)
436 |
437 | ;; Pseudovariables
438 | (,(elixir-rx (group pseudo-var))
439 | 1 font-lock-constant-face)
440 |
441 | ;; Arrows
442 | (,(elixir-rx (group arrows))
443 | 1 font-lock-keyword-face)
444 |
445 | ;; Code points
446 | (,(elixir-rx (group code-point))
447 | 1 font-lock-negation-char-face)))
448 |
449 | ;;;###autoload
450 | (defun elixir-mode-open-github ()
451 | "Elixir mode open GitHub page."
452 | (interactive)
453 | (browse-url "https://github.com/elixir-editors/emacs-elixir"))
454 |
455 | ;;;###autoload
456 | (defun elixir-mode-open-elixir-home ()
457 | "Elixir mode go to language home."
458 | (interactive)
459 | (browse-url elixir-mode-website-url))
460 |
461 | ;;;###autoload
462 | (defun elixir-mode-open-docs-master ()
463 | "Elixir mode go to master documentation."
464 | (interactive)
465 | (browse-url (concat elixir-mode-doc-url "/master")))
466 |
467 | ;;;###autoload
468 | (defun elixir-mode-open-docs-stable ()
469 | "Elixir mode go to stable documentation."
470 | (interactive)
471 | (browse-url elixir-mode-doc-url))
472 |
473 | (defconst elixir--version
474 | (eval-when-compile
475 | (require 'lisp-mnt)
476 | (let ((file (or byte-compile-current-file
477 | load-file-name
478 | (buffer-file-name))))
479 | (if file
480 | (with-temp-buffer
481 | (insert-file-contents file)
482 | (lm-version))
483 | "Unknown")))
484 | "The current version of `elixir-mode'.")
485 |
486 | ;;;###autoload
487 | (defun elixir-mode-version (&optional show-version)
488 | "Get the Elixir-Mode version as string.
489 |
490 | If called interactively or if SHOW-VERSION is non-nil, show the
491 | version in the echo area and the messages buffer.
492 |
493 | The returned string includes both, the version from package.el
494 | and the library version, if both a present and different.
495 |
496 | If the version number could not be determined, signal an error,
497 | if called interactively, or if SHOW-VERSION is non-nil, otherwise
498 | just return nil."
499 | (interactive (list t))
500 | (when show-version
501 | (message "Elixir-Mode version: %s" elixir--version))
502 | elixir--version)
503 |
504 | (defun elixir-mode-fill-doc-string ()
505 | (interactive)
506 | (save-excursion
507 | (re-search-backward (rx "@" (or "moduledoc" "typedoc" "doc") space "\"\"\"") nil t)
508 | (re-search-forward "\"\"\"" nil t)
509 | (set-mark (point))
510 | (re-search-forward "\"\"\"" nil t)
511 | (re-search-backward "^ *\"\"\"" nil t)
512 | (backward-char)
513 | (fill-region (point) (mark))))
514 |
515 | (defun elixir-beginning-of-defun (&optional arg)
516 | (interactive "p")
517 | (let ((regexp (concat "^\\s-*" (elixir-rx builtin-declaration)))
518 | case-fold-search)
519 | (while (and (re-search-backward regexp nil t (or arg 1))
520 | (elixir-syntax-in-string-or-comment-p)))
521 | (goto-char (line-beginning-position))))
522 |
523 | (defun elixir-end-of-defun ()
524 | (interactive)
525 | (goto-char (line-beginning-position))
526 | (if (re-search-forward "\\_" nil t))
534 | (when (and (not (elixir-syntax-in-string-or-comment-p))
535 | (= (current-indentation) level))
536 | (setq finish t)))
537 | (when (looking-back "^\\s-*\\_ "." table)
85 | (modify-syntax-entry ?_ "_" table)
86 | (modify-syntax-entry ?? "w" table)
87 | (modify-syntax-entry ?~ "w" table)
88 | (modify-syntax-entry ?! "_" table)
89 | (modify-syntax-entry ?' "\"'" table)
90 | (modify-syntax-entry ?\" "\"\"" table)
91 | (modify-syntax-entry ?# "<" table)
92 | (modify-syntax-entry ?\n ">" 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 | (modify-syntax-entry ?\] ")[" table)
99 | (modify-syntax-entry ?: "_" table)
100 | (modify-syntax-entry ?@ "_" table)
101 | table)
102 | "Elixir mode syntax table.")
103 |
104 | (defconst elixir-smie-grammar
105 | (smie-prec2->grammar
106 | (smie-merge-prec2s
107 | (smie-bnf->prec2
108 | '((id)
109 | (statements (statement)
110 | (statement ";" statements))
111 | (statement (non-block-expr "fn" match-statements "end")
112 | (non-block-expr "do" statements "end")
113 | ("if" non-block-expr "do" statements "else" statements "end")
114 | ("if" non-block-expr "do" statements "end")
115 | ("if" non-block-expr "COMMA" "do:" non-block-expr)
116 | ("if" non-block-expr "COMMA"
117 | "do:" non-block-expr "COMMA"
118 | "else:" non-block-expr)
119 | ("try" "do" statements "after" statements "end")
120 | ("try" "do" statements "catch" match-statements "end")
121 | ("try" "do" statements "rescue" match-statements "end")
122 | ("try" "do" statements "end")
123 | ("case" non-block-expr "do" match-statements "end")
124 | ("for" non-block-expr "COMMA" "do:" non-block-expr)
125 | ("for" non-block-expr "COMMA" "into:" non-block-expr "COMMA" "do" statements "end")
126 | ("for" non-block-expr "COMMA" "into:" non-block-expr "COMMA" "do:" non-block-expr)
127 | ("with" non-block-expr "do" statements "else" statements "end")
128 | ("with" non-block-expr "do:" non-block-expr "COMMA" "else:" non-block-expr))
129 | (non-block-expr (non-block-expr "OP" non-block-expr)
130 | ("(" non-block-expr ")")
131 | ("{" non-block-expr "}")
132 | ("[" non-block-expr "]")
133 | ("STRING"))
134 | (match-statements (match-statement "MATCH-STATEMENT-DELIMITER"
135 | match-statements)
136 | (match-statement))
137 | (match-statement (non-block-expr "->" statements)))
138 | '((assoc "if" "do:" "else:")
139 | (assoc "COMMA")
140 | (left "OP")))
141 |
142 | (smie-precs->prec2
143 | '((left "||")
144 | (left "&&")
145 | (nonassoc "=~" "===" "!==" "==" "!=" "<=" ">=" "<" ">")
146 | (left "+" "-" "<<<" ">>>" "^^^" "~~~" "&&&" "|||")
147 | (left "*" "/"))))))
148 |
149 | (defvar elixir-smie--operator-regexp
150 | (rx (or "<<<" ">>>" "^^^" "~~~" "&&&" "|||" "===" "!==" "==" "!=" "<="
151 | "=" ">=" "<" ">" "&&" "||" "<>" "++" "--" "//" "/>" "=~" "|>")))
152 |
153 | (defvar elixir-smie--binary-sequence-regexp
154 | (rx (or "<<" ">>")))
155 |
156 | (defvar elixir-smie--block-operator-regexp
157 | (rx "->" (0+ nonl)))
158 |
159 | (defvar elixir-smie--oneline-def-operator-regexp
160 | (rx "do:" (0+ nonl)))
161 |
162 | (defvar elixir-smie--spaces-til-eol-regexp
163 | (rx (and (1+ space) eol))
164 | "Regex representing one or more whitespace characters concluding with eol.")
165 |
166 | (defvar elixir-smie--comment-regexp
167 | (rx (and (0+ space) "#" (0+ not-newline)))
168 | "Regex matching comments.")
169 |
170 | (defvar elixir-smie-indent-basic 2)
171 |
172 | (defmacro elixir-smie-debug (message &rest format-args)
173 | `(progn
174 | (when elixir-smie-verbose-p
175 | (message (format ,message ,@format-args)))
176 | nil))
177 |
178 | (defun elixir-smie--implicit-semi-p ()
179 | (not (or (memq (char-before) '(?\{ ?\[))
180 | (looking-back elixir-smie--operator-regexp (- (point) 3) t))))
181 |
182 | (defun elixir-smie-current-line-contains-built-in-keyword-p ()
183 | "Return non-nil if the current line contains built in keywords with a \".\"."
184 | (save-excursion
185 | (beginning-of-line)
186 | (looking-at ".+\\.\\(case\\|try\\|if\\|rescue\\)")))
187 |
188 | (defun elixir-smie-last-line-end-with-block-operator-p ()
189 | "Return non-nil if the previous line ends with a `->' operator."
190 | (save-excursion
191 | (forward-line -1)
192 | (move-end-of-line 1)
193 | (looking-back elixir-smie--block-operator-regexp (- (point) 3) t)))
194 |
195 | (defun elixir-smie-last-line-start-with-block-operator-p ()
196 | (save-excursion
197 | (forward-line -1)
198 | (beginning-of-line)
199 | (looking-at "^\s+->.+$")))
200 |
201 | (defun elixir-smie-current-line-start-with-pipe-operator-p ()
202 | (save-excursion
203 | (beginning-of-line)
204 | (looking-at "^\s*|>.+$")))
205 |
206 | (defun elixir-smie-last-line-is-assignment-p ()
207 | (save-excursion
208 | (forward-line -1)
209 | (beginning-of-line)
210 | (looking-at "^.+=.+$")))
211 |
212 | (defun elixir-smie-last-line-start-with-pipe-operator-p ()
213 | (save-excursion
214 | (forward-line -1)
215 | (beginning-of-line)
216 | (looking-at "^\s*|>.+$")))
217 |
218 | (defun elixir-smie-line-starts-with-do-colon-p ()
219 | (save-excursion
220 | (beginning-of-line)
221 | (looking-at "^\s+do:")))
222 |
223 | (defun elixir-smie--semi-ends-match ()
224 | "Return non-nil if the current line concludes a match block."
225 | (when (not (eobp))
226 | (save-excursion
227 | ;; Warning: Recursion.
228 | ;; This is easy though.
229 |
230 | ;; 1. If we're at a blank line, move forward a character. This takes us to
231 | ;; the next line.
232 | ;; 2. If we're not at the end of the buffer, call this function again.
233 | ;; (Otherwise, return nil.)
234 |
235 | ;; The point here is that we want to treat blank lines as a single semi-
236 | ;; colon when it comes to detecting the end of match statements. This could
237 | ;; also be handled by a `while' expression or some other looping mechanism.
238 | (cl-flet ((self-call ()
239 | (if (< (point) (point-max))
240 | (elixir-smie--semi-ends-match)
241 | nil)))
242 | (cond
243 | ((and (eolp) (bolp))
244 | (forward-char)
245 | (self-call))
246 | ((looking-at elixir-smie--spaces-til-eol-regexp)
247 | (forward-char)
248 | (self-call))
249 | ;; And if we're NOT on a blank line, move to the end of the line, and see
250 | ;; if we're looking back at a block operator.
251 | (t (move-end-of-line 1)
252 | (and (looking-back elixir-smie--block-operator-regexp)
253 | (not (looking-back ".+fn.+")))))))))
254 |
255 | (defun elixir-smie--same-line-as-parent (parent-pos child-pos)
256 | "Return non-nil if CHILD-POS is on same line as PARENT-POS."
257 | (= (line-number-at-pos parent-pos) (line-number-at-pos child-pos)))
258 |
259 | (defun elixir-smie-forward-token ()
260 | (cond
261 | ;; If there is nothing but whitespace between the last token and eol, emit
262 | ;; a semicolon.
263 | ((looking-at elixir-smie--spaces-til-eol-regexp)
264 | (goto-char (match-end 0))
265 | ";")
266 | ((and (or (looking-at elixir-smie--comment-regexp)
267 | (looking-at "[\n#]"))
268 | (elixir-smie--implicit-semi-p))
269 | (if (eolp) (forward-char 1) (forward-comment 1))
270 | ;; Note: `elixir-smie--semi-ends-match' will be called when the point is at
271 | ;; the beginning of a new line. Keep that in mind.
272 | (if (elixir-smie--semi-ends-match)
273 | "MATCH-STATEMENT-DELIMITER"
274 | (if (and (looking-at ".+,$")
275 | (not (> (elixir-ppss-depth (syntax-ppss)) 0)))
276 | "COMMA"
277 | ";")))
278 | ((looking-at elixir-smie--block-operator-regexp)
279 | (goto-char (match-end 0))
280 | "->")
281 | ((looking-at elixir-smie--operator-regexp)
282 | (goto-char (match-end 0))
283 | "OP")
284 | (t
285 | (let ((token (smie-default-forward-token)))
286 | (unless (or (elixir-smie-empty-string-p token)
287 | (elixir-smie--at-dot-call))
288 | token)))))
289 |
290 | (defun elixir-smie--at-dot-call ()
291 | (and (eq ?w (char-syntax (following-char)))
292 | (eq (char-before) ?.)
293 | (not (eq (char-before (1- (point))) ?.))))
294 |
295 | (defun elixir-smie-backward-token ()
296 | (let ((pos (point)))
297 | (forward-comment (- (point)))
298 | (cond
299 | ((and (> pos (line-end-position))
300 | (elixir-smie--implicit-semi-p))
301 | (if (elixir-smie--semi-ends-match)
302 | "MATCH-STATEMENT-DELIMITER"
303 | (if (and (looking-back ",$" (- (point) 3) t)
304 | (not (> (elixir-ppss-depth (syntax-ppss)) 0)))
305 | "COMMA"
306 | ";")))
307 | ((looking-back elixir-smie--block-operator-regexp (- (point) 3) t)
308 | (goto-char (match-beginning 0))
309 | "->")
310 | ((looking-back elixir-smie--binary-sequence-regexp (- (point) 3) t)
311 | (goto-char (match-beginning 0))
312 | "OP")
313 | ((looking-back elixir-smie--operator-regexp (- (point) 3) t)
314 | (goto-char (match-beginning 0))
315 | "OP")
316 | (t (let ((token (smie-default-backward-token)))
317 | (unless (or (elixir-smie-empty-string-p token)
318 | (elixir-smie--at-dot-call))
319 | token))))))
320 |
321 | (defun verbose-elixir-smie-rules (kind token)
322 | (let ((value (elixir-smie-rules kind token)))
323 | (elixir-smie-debug "%s '%s'; sibling-p:%s parent:%s prev-is-OP:%s hanging:%s == %s" kind token
324 | (ignore-errors (smie-rule-sibling-p))
325 | (ignore-errors smie--parent)
326 | (ignore-errors (smie-rule-prev-p "OP"))
327 | (ignore-errors (smie-rule-hanging-p))
328 | value)
329 | value))
330 |
331 | (defun elixir-smie-rules (kind token)
332 | (pcase (cons kind token)
333 | (`(:list-intro . ";")
334 | -4)
335 | (`(:list-intro . nil)
336 | -4)
337 | (`(:elem . args)
338 | -4)
339 | (`(:before . "COMMA")
340 | (cond
341 | ((and (smie-rule-parent-p "with")
342 | (smie-rule-hanging-p))
343 | (smie-rule-parent 5))
344 | ((and (smie-rule-parent-p ";")
345 | (smie-rule-hanging-p))
346 | (smie-rule-parent))
347 | ((and (smie-rule-parent-p "COMMA")
348 | (smie-rule-hanging-p))
349 | (if (save-excursion
350 | (forward-line 1)
351 | (move-beginning-of-line 1)
352 | (looking-at "^.+do:.+$"))
353 | (smie-rule-parent -5)
354 | (smie-rule-parent)))
355 | ((and (smie-rule-parent-p "COMMA")
356 | (not (smie-rule-hanging-p)))
357 | (smie-rule-parent elixir-smie-indent-basic))
358 | ((smie-rule-parent-p "(")
359 | (smie-rule-parent))
360 | ((smie-rule-parent-p "if")
361 | (smie-rule-parent))
362 | ((smie-rule-parent-p "->")
363 | (smie-rule-parent))))
364 | (`(:after . "COMMA")
365 | (cond
366 | ((and (smie-rule-parent-p ";")
367 | (smie-rule-hanging-p))
368 | (smie-rule-parent elixir-smie-indent-basic))
369 | ((and (smie-rule-parent-p "with")
370 | (smie-rule-hanging-p))
371 | (smie-rule-parent 5))
372 | ((and (smie-rule-parent-p "{")
373 | (smie-rule-hanging-p))
374 | (smie-rule-parent elixir-smie-indent-basic))
375 | ((and (smie-rule-parent-p "[")
376 | (smie-rule-hanging-p))
377 | 0)
378 | ((smie-rule-parent-p "COMMA")
379 | (smie-rule-parent elixir-smie-indent-basic))
380 | ((smie-rule-parent-p "->")
381 | (smie-rule-parent elixir-smie-indent-basic))
382 | (t (smie-rule-parent elixir-smie-indent-basic))))
383 | (`(:before . "OP")
384 | (cond
385 | ((smie-rule-parent-p "for")
386 | (smie-rule-parent))
387 | ((and (not (smie-rule-hanging-p))
388 | (elixir-smie-current-line-start-with-pipe-operator-p)
389 | (elixir-smie-last-line-is-assignment-p))
390 | (smie-rule-parent))
391 | ((and (not (smie-rule-hanging-p))
392 | (elixir-smie-current-line-start-with-pipe-operator-p))
393 | (cons 'column (elixir-smie--previous-line-indentation)))
394 | ((and (not (smie-rule-hanging-p))
395 | (smie-rule-prev-p "OP"))
396 | (- elixir-smie-indent-basic))
397 | ((smie-rule-parent-p "def" "defp" "defmacro" "defmacrop")
398 | (smie-rule-parent))
399 | (t (smie-rule-parent))))
400 | (`(:after . "OP")
401 | (cond
402 | ((smie-rule-sibling-p) nil)
403 | ((smie-rule-hanging-p)
404 | (smie-rule-parent elixir-smie-indent-basic))
405 | ((and (not (smie-rule-sibling-p))
406 | (not (smie-rule-hanging-p))
407 | (smie-rule-parent-p "do:"))
408 | (smie-rule-parent))
409 | ((smie-rule-parent-p ";")
410 | (smie-rule-parent))
411 | ((smie-rule-parent-p "{")
412 | (smie-rule-parent elixir-smie-indent-basic))
413 | (t (smie-rule-parent (- elixir-smie-indent-basic)))))
414 | (`(:before . "MATCH-STATEMENT-DELIMITER")
415 | (cond
416 | ((and (smie-rule-parent-p "do")
417 | (smie-rule-hanging-p))
418 | (smie-rule-parent))
419 | ((and (smie-rule-parent-p "do")
420 | (not (smie-rule-hanging-p)))
421 | (smie-rule-parent elixir-smie-indent-basic))
422 | ((and (smie-rule-parent-p "fn"))
423 | (smie-rule-parent elixir-smie-indent-basic))
424 | ;; There is a case when between two line inside a def block
425 | ;; when jumping to the next line and indent, where the cursor
426 | ;; jumps too much in front.
427 | ;;
428 | ;; Example:
429 | ;; def generate_pkg(path, opts) do
430 | ;; name = Path.basename(Path.expand(path))
431 | ;;
432 | ;; File.mkdir_p!(path)
433 | ;; <-
434 | ;; File.cd! path, fn ->
435 | ;; _generate_pkg(name, opts)
436 | ;; end
437 | ;; end
438 | ((and (smie-rule-parent-p "do")
439 | (not (smie-rule-hanging-p)))
440 | 0)
441 | ((and (not (smie-rule-sibling-p))
442 | (elixir-ppss-last-complete-sexp-start smie--parent)
443 | (smie-rule-hanging-p))
444 | (smie-rule-parent elixir-smie-indent-basic))
445 | ((and (not (smie-rule-sibling-p))
446 | (not (elixir-ppss-last-complete-sexp-start smie--parent))
447 | (smie-rule-hanging-p))
448 | (smie-rule-parent))))
449 | (`(:after . "MATCH-STATEMENT-DELIMITER")
450 | (cond
451 | ((and (smie-rule-parent-p "MATCH-STATEMENT-DELIMITER")
452 | (smie-rule-hanging-p)
453 | (smie-rule-sibling-p))
454 | (smie-rule-parent))
455 | ((and (smie-rule-parent-p "after")
456 | (smie-rule-hanging-p))
457 | (smie-rule-parent elixir-smie-indent-basic))
458 | ;; Correct indentation after a one-line fn definition
459 | ;; Example:
460 | ;;
461 | ;; sum = Enum.reduce(dbms, fn(x, sum) -> x + sum end)
462 | ;; average_dbm = sum / length(addresses)
463 | ((smie-rule-parent-p "fn")
464 | (smie-rule-parent elixir-smie-indent-basic))
465 | (t
466 | (smie-rule-parent))))
467 | (`(:before . "fn")
468 | (cond
469 | ((smie-rule-parent-p "(")
470 | (smie-rule-parent))
471 | (t (smie-rule-parent))))
472 | (`(:before . "for")
473 | (cond
474 | ((elixir-smie-last-line-end-with-block-operator-p)
475 | (smie-rule-parent elixir-smie-indent-basic))
476 | ((and (smie-rule-parent-p ";")
477 | (smie-rule-prev-p "OP"))
478 | (smie-rule-parent))
479 | ((smie-rule-prev-p "OP" "def")
480 | (smie-rule-parent -2))))
481 | (`(:before . "into:")
482 | (cond
483 | ((smie-rule-parent-p "COMMA")
484 | (smie-rule-parent elixir-smie-indent-basic))))
485 | (`(:before . "do:")
486 | (cond
487 | ((smie-rule-parent-p "def" "defp" "defmacro" "defmacrop")
488 | (if (save-excursion
489 | (move-beginning-of-line 1)
490 | (looking-at "^\s*do:.+$"))
491 | (smie-rule-parent)
492 | (smie-rule-parent)))
493 | ;; Example
494 | ;;
495 | ;; hi = for i <- list, do: i
496 | ;; # weird spacing now <- Indent
497 | ;;
498 | ;; for i <- list, do: i
499 | ;; IO.puts 'WORKED' <- Indent
500 | ((and (smie-rule-parent-p "for")
501 | (not (smie-rule-hanging-p)))
502 | (smie-rule-parent))
503 | ((and (smie-rule-parent-p ";")
504 | (not (smie-rule-hanging-p))
505 | (save-excursion
506 | (move-beginning-of-line 1)
507 | (looking-at "^\s*do:.+$")))
508 | (if (> (elixir-ppss-depth (syntax-ppss)) 0)
509 | (smie-rule-parent (- 3))
510 | (smie-rule-parent elixir-smie-indent-basic)))
511 | ((and (smie-rule-parent-p ";")
512 | (not (smie-rule-hanging-p)))
513 | (if (> (elixir-ppss-depth (syntax-ppss)) 0)
514 | (smie-rule-parent (- elixir-smie-indent-basic))
515 | (smie-rule-parent)))
516 | ((and (smie-rule-parent-p "OP")
517 | (not (smie-rule-hanging-p)))
518 | (smie-rule-parent elixir-smie-indent-basic))
519 | ((and (smie-rule-parent-p "COMMA")
520 | (not (smie-rule-hanging-p)))
521 | (smie-rule-parent elixir-smie-indent-basic))))
522 | (`(:before . "do")
523 | (cond
524 | ((and (smie-rule-parent-p "for")
525 | (smie-rule-hanging-p))
526 | (if (save-excursion
527 | (move-beginning-of-line 1)
528 | (looking-at "^.+\sfor\s.+\sdo\s*"))
529 | (smie-rule-parent elixir-smie-indent-basic)
530 | (smie-rule-parent (+ elixir-smie-indent-basic
531 | elixir-smie-indent-basic))))
532 | ((and (smie-rule-parent-p "case")
533 | (smie-rule-hanging-p))
534 | (smie-rule-parent elixir-smie-indent-basic))
535 | ;; There is a case when between two line inside a def block
536 | ;; when jumping to the next line and indent, where the cursor
537 | ;; jumps too much in front.
538 | ;;
539 | ;; Example:
540 | ;; def generate_pkg(path, opts) do
541 | ;; name = Path.basename(Path.expand(path))
542 | ;;
543 | ;; File.mkdir_p!(path)
544 | ;; <-
545 | ;; File.cd! path, fn ->
546 | ;; _generate_pkg(name, opts)
547 | ;; end
548 | ;; end
549 | ((and (smie-rule-parent-p "def")
550 | (smie-rule-hanging-p))
551 | (smie-rule-parent elixir-smie-indent-basic))
552 | (t elixir-smie-indent-basic)))
553 | (`(:before . "end")
554 | (cond
555 | ((smie-rule-parent-p "for")
556 | (smie-rule-parent))
557 | ((smie-rule-parent-p "(")
558 | (smie-rule-parent))
559 | (t (smie-rule-parent))))
560 | (`(:before . "else:")
561 | (cond
562 | ((smie-rule-parent-p ";")
563 | (if (> (elixir-ppss-depth (syntax-ppss)) 0)
564 | (smie-rule-parent elixir-smie-indent-basic)
565 | (smie-rule-parent)))
566 | ((smie-rule-parent-p "if")
567 | (smie-rule-parent elixir-smie-indent-basic))
568 | (t (smie-rule-parent))))
569 | ;; Closing paren on the other line
570 | (`(:before . "(")
571 | (cond
572 | ((smie-rule-parent-p "fn")
573 | (smie-rule-parent elixir-smie-indent-basic))
574 | ;; Indent parenthesis correctly inside a block
575 | ;;
576 | ;; Example:
577 | ;;
578 | ;; def bar do
579 | ;; ()
580 | ;; .....
581 | ((smie-rule-parent-p "do")
582 | (smie-rule-parent))
583 | ((smie-rule-parent-p "OP")
584 | (smie-rule-parent))
585 | ((and (smie-rule-parent-p "with")
586 | (smie-rule-hanging-p))
587 | (smie-rule-parent))
588 | ((and (smie-rule-parent-p "with")
589 | (not (smie-rule-hanging-p)))
590 | (smie-rule-parent 3))
591 | ((smie-rule-parent-p ";")
592 | (smie-rule-parent))
593 | (t (smie-rule-parent))))
594 | (`(:before . "[")
595 | (cond
596 | ((smie-rule-hanging-p)
597 | (smie-rule-parent))))
598 | (`(:before . "{")
599 | (cond
600 | ((smie-rule-parent-p "COMMA")
601 | (smie-rule-parent))
602 | ;; Example
603 | ;;
604 | ;; case parse do
605 | ;; { [ help: true ], _, _ }
606 | ;; -> :help
607 | ;; { _, [ user, project, count ], _ }
608 | ((and (not (smie-rule-hanging-p))
609 | (smie-rule-parent-p "do"))
610 | ;; If the last line ends with a block operator `->'
611 | ;; indent two spaces more
612 | ;;
613 | ;; Example
614 | ;;
615 | ;; case File.read("/usr/share/dict/words") do
616 | ;; {:ok, contents} ->
617 | ;; {:something, contents} <- Indent here two spaces
618 | ;; ...
619 | (if (elixir-smie-last-line-end-with-block-operator-p)
620 | (smie-rule-parent elixir-smie-indent-basic)))
621 | ((and (smie-rule-parent-p "MATCH-STATEMENT-DELIMITER")
622 | (not (smie-rule-hanging-p)))
623 | (if (elixir-smie-last-line-end-with-block-operator-p)
624 | (smie-rule-parent elixir-smie-indent-basic)
625 | (if (elixir-smie-last-line-start-with-block-operator-p)
626 | (smie-rule-parent (- elixir-smie-indent-basic))
627 | (smie-rule-parent))))
628 | ((and (smie-rule-parent-p "OP")
629 | (smie-rule-hanging-p))
630 | (smie-rule-parent))
631 | ((smie-rule-parent-p ";")
632 | (if (save-excursion
633 | (move-end-of-line 1)
634 | (looking-back elixir-smie--block-operator-regexp (- (point) 3) t))
635 | (smie-rule-parent (- elixir-smie-indent-basic))
636 | (if (save-excursion
637 | (move-beginning-of-line 1)
638 | (looking-at "^.+->.+$"))
639 | (smie-rule-parent (- elixir-smie-indent-basic))
640 | (smie-rule-parent))))))
641 | (`(:after . "{")
642 | (cond
643 | ((smie-rule-hanging-p)
644 | (smie-rule-parent elixir-smie-indent-basic))
645 | (t elixir-smie-indent-basic)))
646 | (`(:after . "[")
647 | (cond
648 | ((smie-rule-hanging-p)
649 | (smie-rule-parent elixir-smie-indent-basic))
650 | (t elixir-smie-indent-basic)))
651 | (`(:before . "if")
652 | (cond
653 | ;; Indent when if is inside a `->' block
654 | ;;
655 | ;; Example:
656 | ;;
657 | ;; whatever ->
658 | ;; if true do <-
659 | ;; :foo
660 | ;; end
661 | ;; ....
662 | ((elixir-smie-last-line-end-with-block-operator-p)
663 | (smie-rule-parent elixir-smie-indent-basic))
664 | ;; Indent if inside else
665 | ;;
666 | ;; Example:
667 | ;;
668 | ;; else
669 | ;; if condition, do: :bar <-
670 | ;; end
671 | ((smie-rule-parent-p "else")
672 | (smie-rule-parent elixir-smie-indent-basic))
673 | (t (smie-rule-parent))))
674 | (`(:before . "->")
675 | (cond
676 | ;; Example
677 | ;;
678 | ;; receive do
679 | ;; after
680 | ;; 2000 ->
681 | ;; IO.puts 'hello'
682 | ;; IO.puts 'status 2000 ends' <- Indent second line
683 | ;; { :ok } ->
684 | ;; ....
685 | ((and (smie-rule-parent-p "after")
686 | (not (smie-rule-sibling-p)))
687 | (smie-rule-parent (+ elixir-smie-indent-basic
688 | elixir-smie-indent-basic)))
689 | ;; Example
690 | ;;
691 | ;; case parse do
692 | ;; { [ help: true ], _, _ }
693 | ;; -> :help
694 | ;; ...
695 | ((and (not (smie-rule-hanging-p))
696 | (smie-rule-parent-p "do"))
697 | elixir-smie-indent-basic)
698 | ((and (not (smie-rule-hanging-p))
699 | (smie-rule-parent-p "MATCH-STATEMENT-DELIMITER"))
700 | (smie-rule-parent))
701 | (t (smie-rule-parent elixir-smie-indent-basic))))
702 | (`(:after . "->")
703 | (cond
704 | ;; This first condition is kind of complicated so I'll try to make this
705 | ;; comment as clear as possible.
706 |
707 | ;; "If `->' is the last thing on the line, and its parent token
708 | ;; is `fn' ..."
709 | ((and (smie-rule-hanging-p)
710 | (smie-rule-parent-p "fn"))
711 | ;; "... and if:
712 |
713 | ;; 1. `smie--parent' is non-nil
714 | ;; 2. the `->' token in question is on the same line as its parent (if
715 | ;; the logic has gotten this far, its parent will be `fn')
716 |
717 | ;; ... then indent the line after the `->' aligned with the
718 | ;; parent, offset by `elixir-smie-indent-basic'."
719 | (if (and smie--parent (elixir-smie--same-line-as-parent
720 | (elixir-ppss-innermost-start smie--parent)
721 | (point)))
722 | (smie-rule-parent elixir-smie-indent-basic)
723 | elixir-smie-indent-basic))
724 | ;; Otherwise, if just indent by two.
725 | ((smie-rule-hanging-p)
726 | (cond
727 | ((smie-rule-parent-p "catch" "rescue" "else")
728 | (smie-rule-parent (+ elixir-smie-indent-basic
729 | elixir-smie-indent-basic)))
730 | ((smie-rule-parent-p "do" "try")
731 | (smie-rule-parent elixir-smie-indent-basic))
732 | ;; Example
733 | ;;
734 | ;; receive do
735 | ;; after
736 | ;; 2000 ->
737 | ;; IO.puts 'hello' <- Indent two spaces
738 | ((and (smie-rule-parent-p "after")
739 | (smie-rule-hanging-p)
740 | (not (smie-rule-sibling-p)))
741 | (smie-rule-parent (+ elixir-smie-indent-basic
742 | elixir-smie-indent-basic)))
743 | (t (smie-rule-parent elixir-smie-indent-basic))))))
744 | (`(:before . ";")
745 | (cond
746 | ;; Handle cases where built in keywords are used
747 | ;; as function names.
748 | ;;
749 | ;; Example:
750 | ;;
751 | ;; def foo(test) do
752 | ;; test_case = test.case
753 | ;; run(test_case)
754 | ;; end
755 | ((and (smie-rule-parent-p "case" "try" "rescue")
756 | (smie-rule-hanging-p)
757 | (elixir-smie-current-line-contains-built-in-keyword-p))
758 | (+ (- (cdr (smie-rule-parent))) (+ elixir-smie-indent-basic
759 | elixir-smie-indent-basic)))
760 | ;; There is a case after an one line definition of functions/macros
761 | ;; when an `if' keyword token is involved, where the next block `end'
762 | ;; token will have a `if' as parent and it's hanging.
763 | ;;
764 | ;; Example:
765 | ;;
766 | ;; defmacro my_if(expr, do: if_block), do: if(expr, do: if_block, else: nil)
767 | ;; defmacro my_if(expr, do: if_block, else: else_block) do
768 | ;; ...
769 | ;; end <- parent is `if`
770 | ((and (smie-rule-parent-p "if")
771 | (smie-rule-hanging-p))
772 | (smie-rule-parent))
773 | ((and (smie-rule-parent-p "else")
774 | (smie-rule-hanging-p))
775 | (smie-rule-parent elixir-smie-indent-basic))
776 | ((smie-rule-parent-p "catch" "def" "defmodule" "defp" "do" "else"
777 | "fn" "if" "rescue" "try" "unless" "defmacro" "defmacrop")
778 | (smie-rule-parent))
779 | ((smie-rule-parent-p "after")
780 | (smie-rule-parent elixir-smie-indent-basic))
781 | ;; Example
782 | ;;
783 | ;; case parse do
784 | ;; { [ help: true ], _, _ }
785 | ;; -> :help
786 | ;; { _, [ user, project, count ], _ }
787 | ;; -> { user, project, count }
788 | ;; ...
789 | ((and (smie-rule-parent-p "->")
790 | (smie-rule-hanging-p))
791 | (smie-rule-parent))
792 | ((and (smie-rule-parent-p ";")
793 | (smie-rule-hanging-p)
794 | (save-excursion
795 | (move-beginning-of-line 1)
796 | (looking-at "^\s+else:.+$"))
797 | (not (save-excursion
798 | (move-beginning-of-line 1)
799 | (looking-at "^\s+else:.+)$"))))
800 | (smie-rule-parent (- elixir-smie-indent-basic)))
801 | ((and (smie-rule-parent-p ";")
802 | (save-excursion
803 | (move-beginning-of-line 1)
804 | (looking-at "^.+,$")))
805 | (smie-rule-parent))
806 | ((and (smie-rule-parent-p ";")
807 | (smie-rule-hanging-p)
808 | (save-excursion
809 | (move-beginning-of-line 1)
810 | (looking-at "^\s+do:.+$"))
811 | (not (save-excursion
812 | (move-beginning-of-line 1)
813 | (looking-at "^\s+do:.+)$"))))
814 | (smie-rule-parent))
815 | ((elixir-smie-current-line-start-with-pipe-operator-p)
816 | (smie-rule-parent))
817 | ((smie-rule-parent-p "(")
818 | (smie-rule-parent elixir-smie-indent-basic))))
819 | (`(:after . ";")
820 | (cond
821 | ((smie-rule-parent-p "def")
822 | (smie-rule-parent))
823 | ((and (smie-rule-parent-p "if")
824 | (elixir-smie-current-line-contains-built-in-keyword-p))
825 | (+ (- (cdr (smie-rule-parent))) (+ elixir-smie-indent-basic
826 | elixir-smie-indent-basic)))
827 | ((smie-rule-parent-p "if")
828 | (smie-rule-parent))
829 | ((smie-rule-parent-p "after")
830 | (smie-rule-parent elixir-smie-indent-basic))
831 | ((and (smie-rule-parent-p "(")
832 | (boundp 'smie--parent)
833 | (save-excursion
834 | (goto-char (cadr smie--parent))
835 | (smie-rule-hanging-p)))
836 | (smie-rule-parent elixir-smie-indent-basic))))))
837 |
838 | (defun elixir-smie--heredoc-at-current-point-p ()
839 | "Return non-nil if cursor is at a string."
840 | (save-excursion
841 | (or (save-excursion
842 | (let ((parse-data (parse-partial-sexp 1 (point))))
843 | (and (elixir-ppss-string-terminator parse-data)
844 | (elixir-ppss-comment-or-string-start parse-data))))
845 | (and (looking-at "\"\"\"")
846 | (match-beginning 0)))))
847 |
848 | (defun elixir-smie--previous-line-empty-p ()
849 | "Return non-nil if the previous line is blank."
850 | (save-excursion
851 | (forward-line -1)
852 | (move-beginning-of-line 1)
853 | (looking-at "[[:space:]]*$")))
854 |
855 | (defun elixir-smie--previous-line-indentation ()
856 | "Return the indentation of the previous line."
857 | (save-excursion
858 | (forward-line -1)
859 | (current-indentation)))
860 |
861 | ;; Add the custom function to handle indentation inside heredoc to the
862 | ;; smie-indent-functions list. The indentation function will only be
863 | ;; process inside an elixir-mode.
864 | (defun elixir-smie--indent-inside-heredoc ()
865 | "Handle indentation inside Elixir heredocs.
866 |
867 | Rules:
868 | 1. If the previous line is empty, indent as the basic indentation
869 | at the beginning of the heredoc.
870 | 2. If the previous line is not empty, indent as the previous line."
871 | (if (eq major-mode 'elixir-mode)
872 | (if (elixir-smie--heredoc-at-current-point-p)
873 | (let ((indent
874 | (save-excursion
875 | (when (re-search-backward "^\\(\s+\\)\\(@doc\\|@moduledoc\\|.*\\)\"\"\"" nil t)
876 | (string-width (match-string 1))))))
877 | (cond
878 | ((elixir-smie--previous-line-empty-p)
879 | (goto-char indent))
880 | ((and (not (save-excursion (looking-at "\"\"\"")))
881 | (not (elixir-smie--previous-line-empty-p)))
882 | (goto-char (elixir-smie--previous-line-indentation)))
883 | (indent
884 | (goto-char indent)))))))
885 |
886 | (defun elixir-smie-empty-string-p (string)
887 | "Return non-nil if STRING is null, blank or whitespace only."
888 | (or (null string)
889 | (string= string "")
890 | (if (string-match-p "^\s+$" string) t)))
891 |
892 | (add-to-list 'smie-indent-functions 'elixir-smie--indent-inside-heredoc)
893 |
894 | (provide 'elixir-smie)
895 |
896 | ;;; elixir-smie.el ends here
897 |
--------------------------------------------------------------------------------
/tests/elixir-format-test.el:
--------------------------------------------------------------------------------
1 | ;;; elixir-format-test.el --- Basic tests for elixir-format
2 |
3 | ;;; Code:
4 | (require 'test-helper)
5 |
6 | (ert-deftest elixir-format-indents-a-buffer ()
7 | (when elixir-formatter-supported
8 | (ert-with-test-buffer (:name "(Expected)indents-a-buffer")
9 | (insert elixir-format-test-example)
10 | (elixir-format)
11 | (should (equal (buffer-string) elixir-format-formatted-test-example)))))
12 |
13 | (ert-deftest elixir-format-indents-a-buffer-and-undoes-changes ()
14 | (when elixir-formatter-supported
15 | (ert-with-test-buffer ()
16 | (buffer-enable-undo)
17 | (setq buffer-undo-list nil)
18 |
19 | (insert elixir-format-test-example)
20 |
21 | (undo-boundary)
22 | (elixir-format)
23 |
24 | (should (equal (buffer-string) elixir-format-formatted-test-example))
25 | (undo 0)
26 | (should (equal (buffer-string) elixir-format-test-example)))))
27 |
28 | (ert-deftest elixir-format-should-run-hook-before-formatting ()
29 | (when elixir-formatter-supported
30 | (ert-with-test-buffer ()
31 | (let ((has-been-run nil))
32 | (insert elixir-format-test-example)
33 | (add-hook 'elixir-format-hook (lambda () (setq has-been-run t)))
34 | (elixir-format)
35 | (should (equal has-been-run t))))))
36 |
37 | (ert-deftest elixir-format-should-message-on-error ()
38 | (when elixir-formatter-supported
39 | (ert-with-test-buffer ()
40 | (insert elixir-format-wrong-test-example)
41 | (should-error
42 | (elixir-format)))))
43 |
44 | (provide 'elixir-format-test)
45 |
46 | ;;; elixir-format-test.el ends here.
47 |
--------------------------------------------------------------------------------
/tests/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 | (require 'test-helper)
11 |
12 | (defun elixir-test-face-at (pos &optional content)
13 | "Get the face at POS in CONTENT.
14 |
15 | If CONTENT is not given, return the face at POS in the current
16 | buffer."
17 | (if content
18 | (elixir-test-with-temp-buffer content
19 | (get-text-property pos 'face))
20 | (get-text-property pos 'face)))
21 |
22 | (ert-deftest elixir-mode-syntax-table/fontify-regex ()
23 | :tags '(fontification syntax-table)
24 | (elixir-test-with-temp-buffer
25 | "match = ~r/foo/
26 | match=~r/foo/"
27 | (should (eq (elixir-test-face-at 1) 'font-lock-variable-name-face))
28 | (should (eq (elixir-test-face-at 9) 'font-lock-builtin-face))
29 | (should (eq (elixir-test-face-at 12) 'font-lock-string-face))
30 | (should (eq (elixir-test-face-at 18) 'font-lock-variable-name-face))
31 | ;; no face for regex delimiters
32 | (should (eq (elixir-test-face-at 15) nil))))
33 |
34 | (ert-deftest elixir-mode-syntax-table/sigils ()
35 | :tags '(fontification syntax-table)
36 | (elixir-test-with-temp-buffer
37 | "asdfg = ~s{Capitalized noncapitalized}"
38 | (should (eq (elixir-test-face-at 1) 'font-lock-variable-name-face))
39 | (should (eq (elixir-test-face-at 9) 'font-lock-builtin-face))
40 | (should (eq (elixir-test-face-at 12) 'font-lock-string-face))
41 | (should (eq (elixir-test-face-at 26) 'font-lock-string-face))
42 | ;; no face for regex delimiters
43 | (should (eq (elixir-test-face-at 38) nil))))
44 |
45 | (ert-deftest elixir-mode-syntax-table/fontify-special-macros ()
46 | :tags '(fontification syntax-table)
47 | (elixir-test-with-temp-buffer
48 | "__MODULE__
49 | __DIR__
50 | __aliases__
51 | %__MODULE__
52 | &__MODULE__
53 | &abc__DIR__"
54 | (should (eq (elixir-test-face-at 4) 'font-lock-constant-face))
55 | (should (eq (elixir-test-face-at 14) 'font-lock-constant-face))
56 | (should (eq (elixir-test-face-at 24) 'font-lock-constant-face))
57 | (should (eq (elixir-test-face-at 34) 'font-lock-constant-face))
58 | (should (eq (elixir-test-face-at 44) 'font-lock-constant-face))
59 | (should-not (eq (elixir-test-face-at 60) 'font-lock-constant-face))))
60 |
61 | (ert-deftest elixir-mode-syntax-table/fontify-modules-and-types ()
62 | :tags '(fontification syntax-table)
63 | (elixir-test-with-temp-buffer
64 | "defmodule Application.Behavior do
65 | use Application.Behaviour
66 | Stand.Alone.call
67 | %RuntimeError{message: msg}
68 | &Enum"
69 | (should (eq (elixir-test-face-at 1) 'font-lock-keyword-face))
70 | (should (eq (elixir-test-face-at 11) 'font-lock-type-face))
71 | (should (eq (elixir-test-face-at 22) 'font-lock-type-face))
72 | (should (eq (elixir-test-face-at 23) 'font-lock-type-face))
73 | (should (eq (elixir-test-face-at 32) 'font-lock-keyword-face))
74 | (should (eq (elixir-test-face-at 37) 'font-lock-keyword-face))
75 | (should (eq (elixir-test-face-at 41) 'font-lock-type-face))
76 | (should (eq (elixir-test-face-at 52) 'font-lock-type-face))
77 | (should (eq (elixir-test-face-at 53) 'font-lock-type-face))
78 | (should (eq (elixir-test-face-at 68) 'font-lock-type-face))
79 | (should (eq (elixir-test-face-at 72) 'font-lock-type-face))
80 | (should (eq (elixir-test-face-at 114) 'font-lock-type-face))
81 | (should (eq (elixir-test-face-at 117) 'font-lock-type-face))
82 | ;; no face for function call
83 | (should (eq (elixir-test-face-at 79) nil))
84 | (should (eq (elixir-test-face-at 84) 'font-lock-type-face))
85 | ;; no face for curly braces
86 | (should (eq (elixir-test-face-at 97) nil))))
87 |
88 | (ert-deftest elixir-mode-syntax-table/fontify-regex-with-quote ()
89 | "https://github.com/elixir-editors/emacs-elixir/issues/23"
90 | :tags '(fontification syntax-table)
91 | :expected-result :failed
92 | (elixir-test-with-temp-buffer
93 | "~r/\"/
94 | x = 15"
95 | (should (eq (elixir-test-face-at 8) 'font-lock-variable-name-face))))
96 |
97 | (ert-deftest elixir-mode-syntax-table/fontify-regex-with-question/1 ()
98 | "https://github.com/elixir-editors/emacs-elixir/issues/36"
99 | :tags '(fontification syntax-table)
100 | (elixir-test-with-temp-buffer
101 | "~r/^matt: (?\d+)$/mg
102 | x = 15"
103 | (should (eq (elixir-test-face-at 4) 'font-lock-string-face))
104 | (should (eq (elixir-test-face-at 25) 'font-lock-variable-name-face))))
105 |
106 | (ert-deftest elixir-mode-syntax-table/fontify-regex-with-question/2 ()
107 | "https://github.com/elixir-editors/emacs-elixir/issues/29"
108 | :tags '(fontification syntax-table)
109 | (elixir-test-with-temp-buffer
110 | "a = \"\" <> \"?\"
111 | x = 15"
112 | (should (eq (elixir-test-face-at 15) 'font-lock-variable-name-face))))
113 |
114 | (ert-deftest elixir-mode-syntax-table/fontify-defguard ()
115 | :tags '(fontification syntax-table)
116 | (elixir-test-with-temp-buffer
117 | "defmodule Foo do
118 | defguard is_foo(arg) when arg == true
119 | end"
120 | (should (eq (elixir-test-face-at 18) 'font-lock-keyword-face))))
121 |
122 | (ert-deftest elixir-mode-syntax-table/fontify-defguardp ()
123 | :tags '(fontification syntax-table)
124 | (elixir-test-with-temp-buffer
125 | "defmodule Foo do
126 | defguardp is_foo(arg) when arg == true
127 | end"
128 | (should (eq (elixir-test-face-at 18) 'font-lock-keyword-face))))
129 |
130 | (ert-deftest elixir-mode-syntax-table/fontify-function-name/1 ()
131 | :tags '(fontification syntax-table)
132 | (elixir-test-with-temp-buffer
133 | "def fooBar do
134 | :foo
135 | end"
136 | (should (eq (elixir-test-face-at 5) 'font-lock-function-name-face))
137 | (should (eq (elixir-test-face-at 8) 'font-lock-function-name-face))))
138 |
139 | (ert-deftest elixir-mode-syntax-table/fontify-function-name/2 ()
140 | :tags '(fontification syntax-table)
141 | (elixir-test-with-temp-buffer
142 | "def foo? do
143 | :foo
144 | end"
145 | (should (eq (elixir-test-face-at 5) 'font-lock-function-name-face))
146 | (should (eq (elixir-test-face-at 8) 'font-lock-function-name-face))))
147 |
148 | (ert-deftest elixir-mode-syntax-table/fontify-function-name/3 ()
149 | :tags '(fontification syntax-table)
150 | (elixir-test-with-temp-buffer
151 | "def foo! do
152 | :foo
153 | end"
154 | (should (eq (elixir-test-face-at 5) 'font-lock-function-name-face))
155 | (should (eq (elixir-test-face-at 8) 'font-lock-function-name-face))))
156 |
157 | (ert-deftest elixir-mode-syntax-table/fontify-defoverridable/1 ()
158 | :tags '(fontification syntax-table)
159 | (elixir-test-with-temp-buffer
160 | "defmodule Foo do
161 | defmacro __using__(_opts) do
162 | quote do
163 | def bar, do: :ok
164 | defoverridable [bar: 0]
165 | end
166 | end
167 | end"
168 | (should (eq (elixir-test-face-at 91) 'font-lock-keyword-face))))
169 |
170 | (ert-deftest elixir-mode-syntax-table/fontify-end-if-the-last-line-in-a-module-is-a-comment ()
171 | "https://github.com/elixir-editors/emacs-elixir/issues/283"
172 | :tags '(fontification syntax-table)
173 | (elixir-test-with-temp-buffer
174 | "defmodule Foo do
175 | # foo
176 | end"
177 | (should (eq (elixir-test-face-at 26) 'font-lock-keyword-face))))
178 |
179 | (ert-deftest elixir-mode-syntax-table/fontify-heredoc/1 ()
180 | :tags '(fontification heredoc syntax-table)
181 | (elixir-test-with-temp-buffer
182 | "@doc \"\"\""
183 | (should (eq (elixir-test-face-at 1) 'elixir-attribute-face))
184 | (should (eq (elixir-test-face-at 2) 'elixir-attribute-face))
185 | (should (eq (elixir-test-face-at 6) 'font-lock-doc-face))))
186 |
187 | (ert-deftest elixir-mode-syntax-table/fontify-heredoc/2 ()
188 | :tags '(fontification heredoc syntax-table)
189 | (elixir-test-with-temp-buffer
190 | "@moduledoc \"\"\""
191 | (should (eq (elixir-test-face-at 1) 'elixir-attribute-face))
192 | (should (eq (elixir-test-face-at 2) 'elixir-attribute-face))
193 | (should (eq (elixir-test-face-at 12) 'font-lock-doc-face))))
194 |
195 | (ert-deftest elixir-mode-syntax-table/fontify-heredoc/3 ()
196 | :tags '(fontification heredoc syntax-table)
197 | (elixir-test-with-temp-buffer
198 | "~s\"\"\""
199 | (should (eq (elixir-test-face-at 1) 'font-lock-builtin-face))
200 | (should (eq (elixir-test-face-at 2) 'font-lock-builtin-face))
201 | (should (eq (elixir-test-face-at 3) 'font-lock-string-face))))
202 |
203 | (ert-deftest elixir-mode-syntax-table/fontify-heredoc/4 ()
204 | :tags '(fontification heredoc syntax-table)
205 | (elixir-test-with-temp-buffer
206 | "@typedoc \"\"\""
207 | (should (eq (elixir-test-face-at 1) 'elixir-attribute-face))
208 | (should (eq (elixir-test-face-at 2) 'elixir-attribute-face))
209 | (should (eq (elixir-test-face-at 10) 'font-lock-doc-face))))
210 |
211 | (ert-deftest elixir-mode-syntax-table/fontify-atoms ()
212 | :tags '(fontification atom syntax-table)
213 | (elixir-test-with-temp-buffer
214 | ":oriole
215 | :andale
216 | :ms2pid
217 | :CapitalizedAtom
218 | true
219 | false
220 | nil
221 | true_false_nil
222 | :insert!
223 | :insert@
224 | :insert?
225 | "
226 | (should (eq (elixir-test-face-at 3) 'elixir-atom-face))
227 | (should (eq (elixir-test-face-at 5) 'elixir-atom-face))
228 | (should (eq (elixir-test-face-at 10) 'elixir-atom-face))
229 | (should (eq (elixir-test-face-at 13) 'elixir-atom-face))
230 | (should (eq (elixir-test-face-at 18) 'elixir-atom-face))
231 | (should (eq (elixir-test-face-at 23) 'elixir-atom-face))
232 | (should (eq (elixir-test-face-at 26) 'elixir-atom-face))
233 | (should (eq (elixir-test-face-at 43) 'elixir-atom-face))
234 | (should (eq (elixir-test-face-at 48) 'elixir-atom-face))
235 | (should (eq (elixir-test-face-at 54) 'elixir-atom-face))
236 | (should-not (eq (elixir-test-face-at 57) 'elixir-atom-face))
237 | (should (eq (elixir-test-face-at 74) 'elixir-atom-face))
238 | (should (eq (elixir-test-face-at 82) 'elixir-atom-face))
239 | (should (eq (elixir-test-face-at 97) 'elixir-atom-face))))
240 |
241 | (ert-deftest elixir-mode-syntax-table/fontify-map-keys ()
242 | :tags '(fontification map syntax-table)
243 | (elixir-test-with-temp-buffer
244 | "%{a: 1, b: 2}"
245 | (should (eq (elixir-test-face-at 3) 'elixir-atom-face))
246 | (should (eq (elixir-test-face-at 4) 'elixir-atom-face))
247 | (should (eq (elixir-test-face-at 9) 'elixir-atom-face))
248 | (should (eq (elixir-test-face-at 10) 'elixir-atom-face)))
249 |
250 | ;; https://github.com/elixir-editors/emacs-elixir/issues/320
251 | (elixir-test-with-temp-buffer
252 | "<>"
253 | (should-not (eq (elixir-test-face-at 3) 'elixir-atom-face)))
254 |
255 | (elixir-test-with-temp-buffer
256 | "%{key:
257 | a_very_long_value_that_needed_to_wrap}"
258 | (should (eq (elixir-test-face-at 3) 'elixir-atom-face))))
259 |
260 | (ert-deftest elixir-mode-syntax-table/fontify-interpolation ()
261 | :tags '(fontification interpolation syntax-table)
262 | (elixir-test-with-temp-buffer
263 | "\"#{1 + 2} is 3.\""
264 | (should (eq (elixir-test-face-at 1) 'font-lock-string-face))
265 | (should (eq (elixir-test-face-at 11) 'font-lock-string-face))))
266 |
267 | (ert-deftest elixir-mode-syntax-table/fontify-continuation-lines-assignment ()
268 | :tags '(fontification syntax-table)
269 | (elixir-test-with-temp-buffer
270 | "some_var =
271 | some_expr"
272 | (should (eq (elixir-test-face-at 1) 'font-lock-variable-name-face))))
273 |
274 | (ert-deftest elixir-mode-syntax-table/dont-fontify-equal-match ()
275 | :tags '(fontification syntax-table)
276 | (elixir-test-with-temp-buffer
277 | "this == that"
278 | (should-not (eq (elixir-test-face-at 2) 'font-lock-variable-name-face))))
279 |
280 | (ert-deftest elixir-mode-syntax-table/fontify-triple-quoted-string ()
281 | :tags '(fontification syntax-table)
282 | (elixir-test-with-temp-buffer
283 | "\"\"\"foo\"bar\"baz #{1 + 2} is 3.\"\"\""
284 | (should (eq (elixir-test-face-at 1) 'font-lock-string-face))
285 | (should (eq (elixir-test-face-at 5) 'font-lock-string-face))
286 | (should (eq (elixir-test-face-at 19) 'font-lock-variable-name-face))
287 | (should (eq (elixir-test-face-at 31) 'font-lock-string-face))))
288 |
289 | (ert-deftest elixir-mode-syntax-table/fontify-atom-in-pattern-match ()
290 | :tags '(fontification atom syntax-table)
291 | (elixir-test-with-temp-buffer
292 | ":any = into_to_type(type)
293 | :another=into_to_type(type)"
294 | (should (eq (elixir-test-face-at 3) 'elixir-atom-face))))
295 |
296 | (ert-deftest elixir-mode-syntax-table/fontify-assignment-with-pattern/1 ()
297 | :expected-result :failed
298 | :tags '(fontification syntax-table)
299 | (elixir-test-with-temp-buffer
300 | "{x, y} = some_expr"
301 | (should (eq (elixir-test-face-at 2) 'font-lock-variable-name-face))
302 | (should (eq (elixir-test-face-at 5) 'font-lock-variable-name-face))))
303 |
304 | (ert-deftest elixir-mode-syntax-table/fontify-assignment-with-pattern/2 ()
305 | :expected-result :failed
306 | :tags '(fontification syntax-table)
307 | (elixir-test-with-temp-buffer
308 | "[h|t] = some_expr"
309 | (should (eq (elixir-test-face-at 2) 'font-lock-variable-name-face))
310 | (should (eq (elixir-test-face-at 4) 'font-lock-variable-name-face))))
311 |
312 | (ert-deftest elixir-mode-syntax-table/fontify-assignment-with-singleton ()
313 | "https://github.com/elixir-editors/emacs-elixir/issues/245"
314 | :tags '(fontification syntax-table)
315 | (elixir-test-with-temp-buffer
316 | "true_false_nil = 1"
317 | (should (eq (elixir-test-face-at 1) 'font-lock-variable-name-face))
318 | (should (eq (elixir-test-face-at 6) 'font-lock-variable-name-face))
319 | (should (eq (elixir-test-face-at 12) 'font-lock-variable-name-face))))
320 |
321 | (ert-deftest elixir-mode-syntax-table/fontify-keyword-after-dot ()
322 | "https://github.com/elixir-editors/emacs-elixir/issues/250"
323 | :tags '(fontification syntax-table)
324 | (elixir-test-with-temp-buffer
325 | "Mix.raise
326 | raise
327 | Mix.def foo
328 | Mix.import
329 | import
330 | Mix.after
331 | after
332 | Mix.when
333 | when"
334 | (should-not (eq (elixir-test-face-at 5) 'font-lock-keyword-face))
335 | (should (eq (elixir-test-face-at 11) 'font-lock-keyword-face))
336 | (should-not (eq (elixir-test-face-at 21) 'font-lock-keyword-face))
337 | (should-not (eq (elixir-test-face-at 25) 'font-lock-function-name-face))
338 | (should-not (eq (elixir-test-face-at 33) 'font-lock-keyword-face))
339 | (should (eq (elixir-test-face-at 40) 'font-lock-keyword-face))
340 |
341 | (should-not (eq (elixir-test-face-at 51) 'font-lock-keyword-face))
342 | (should (eq (elixir-test-face-at 57) 'font-lock-keyword-face))
343 | (should-not (eq (elixir-test-face-at 67) 'font-lock-keyword-face))
344 | (should (eq (elixir-test-face-at 72) 'font-lock-keyword-face))))
345 |
346 | (ert-deftest elixir-mode-syntax-table/highlight-send ()
347 | "Highlight send as a keyword"
348 | :tags '(fontification syntax-table)
349 | (elixir-test-with-temp-buffer
350 | "defmodule Foo do
351 | def bar(pid) do
352 | send pid, :baz
353 | end
354 | end"
355 | (should (eq (elixir-test-face-at 40) 'font-lock-keyword-face))))
356 |
357 | (ert-deftest elixir-mode-syntax-table/highlight-with ()
358 | "Highlight with as a keyword"
359 | :tags '(fontification syntax-table)
360 | (elixir-test-with-temp-buffer
361 | "defmodule Foo do
362 | def bar(opts) do
363 | with(
364 | {:ok, width} <- Map.fetch(opts, :width),
365 | {:ok, height} <- Map.fetch(opts, :height),
366 | do: {:ok, width * height}
367 | )
368 | end
369 | end"
370 | (should (eq (elixir-test-face-at 41) 'font-lock-keyword-face))))
371 |
372 | (ert-deftest elixir-mode-syntax-table/string-interpolation-in-words-list ()
373 | "https://github.com/elixir-editors/emacs-elixir/issues/263"
374 | :tags '(fontification syntax-table)
375 | (elixir-test-with-temp-buffer
376 | "~w(SADD users #{user_id})"
377 | (should (eq (elixir-test-face-at 4) 'font-lock-string-face))
378 |
379 | (should-not (eq (elixir-test-face-at 15) 'font-lock-comment-face))
380 | (should-not (eq (elixir-test-face-at 17) 'font-lock-comment-face))
381 | (should-not (eq (elixir-test-face-at 25) 'font-lock-comment-face))))
382 |
383 | (ert-deftest elixir-mode-syntax-table/quotes-in-sigils ()
384 | "https://github.com/elixir-editors/emacs-elixir/issues/265"
385 | :tags '(fontification syntax-table)
386 | (elixir-test-with-temp-buffer
387 | "~s/\"/
388 | ~r|'|
389 | ~c\"'\"
390 | ~w'\"'
391 | ~s(\")
392 | ~r[\"]
393 | ~c{\"}
394 | ~w<\">
395 | ~s\"\"\"
396 | foo
397 | \"\"\"
398 | ~D(\")
399 | ~N(\")
400 | ~T(\")"
401 | (should-not (eq (elixir-test-face-at 5) 'font-lock-string-face)) ; ~s//
402 |
403 | (should-not (eq (elixir-test-face-at 7) 'font-lock-string-face)) ; ~r||
404 | (should (eq (elixir-test-face-at 7) 'font-lock-builtin-face))
405 | (should-not (eq (elixir-test-face-at 11) 'font-lock-string-face))
406 |
407 | (should-not (eq (elixir-test-face-at 13) 'font-lock-string-face)) ; ~c""
408 | (should (eq (elixir-test-face-at 13) 'font-lock-builtin-face))
409 | (should-not (eq (elixir-test-face-at 17) 'font-lock-string-face))
410 |
411 | (should-not (eq (elixir-test-face-at 19) 'font-lock-string-face)) ; ~w''
412 | (should (eq (elixir-test-face-at 19) 'font-lock-builtin-face))
413 | (should-not (eq (elixir-test-face-at 23) 'font-lock-string-face))
414 |
415 | (should-not (eq (elixir-test-face-at 25) 'font-lock-string-face)) ; ~s()
416 | (should (eq (elixir-test-face-at 25) 'font-lock-builtin-face)) ; ~s()
417 | (should-not (eq (elixir-test-face-at 29) 'font-lock-string-face))
418 |
419 | (should-not (eq (elixir-test-face-at 31) 'font-lock-string-face)) ; ~r[]
420 | (should (eq (elixir-test-face-at 31) 'font-lock-builtin-face))
421 | (should-not (eq (elixir-test-face-at 35) 'font-lock-string-face))
422 |
423 | (should-not (eq (elixir-test-face-at 37) 'font-lock-string-face)) ; ~c{}
424 | (should (eq (elixir-test-face-at 37) 'font-lock-builtin-face))
425 | (should-not (eq (elixir-test-face-at 41) 'font-lock-string-face))
426 |
427 | (should-not (eq (elixir-test-face-at 43) 'font-lock-string-face)) ; ~w<>
428 | (should (eq (elixir-test-face-at 43) 'font-lock-builtin-face))
429 | (should-not (eq (elixir-test-face-at 47) 'font-lock-string-face))
430 |
431 | (should (eq (elixir-test-face-at 51) 'font-lock-string-face)) ; ~s""" """
432 | (should (eq (elixir-test-face-at 52) 'font-lock-string-face))
433 | (should (eq (elixir-test-face-at 53) 'font-lock-string-face))
434 | (should (eq (elixir-test-face-at 55) 'font-lock-string-face))
435 |
436 | (should (eq (elixir-test-face-at 66) 'font-lock-string-face)) ; ~D()
437 |
438 | (should (eq (elixir-test-face-at 72) 'font-lock-string-face)) ; ~N()
439 |
440 | (should (eq (elixir-test-face-at 78) 'font-lock-string-face)))) ; ~T()
441 |
442 | (ert-deftest elixir-mode-syntax-table/hashmark-in-sigils ()
443 | "Don't treat hashmark in sigils as comment"
444 | :tags '(fontification syntax-table)
445 | (elixir-test-with-temp-buffer
446 | "~s(# foo)"
447 | (should-not (eq (elixir-test-face-at 4) 'font-lock-comment-face))
448 | (should (eq (elixir-test-face-at 6) 'font-lock-string-face))))
449 |
450 | (ert-deftest elixir-mode-syntax-table/highlight-modules-after-pipe ()
451 | "Module names must be hightligthed if preceded by a pipe character."
452 | (elixir-test-with-temp-buffer
453 | "|List"
454 | (should-not (eq (elixir-test-face-at 1) 'font-lock-type-face))
455 | (should (eq (elixir-test-face-at 3) 'font-lock-type-face)))
456 | (elixir-test-with-temp-buffer
457 | "[req_code(op_name)|Enum.reverse(data)]"
458 | (should-not (eq (elixir-test-face-at 19) 'font-lock-type-face))
459 | (should (eq (elixir-test-face-at 21) 'font-lock-type-face))))
460 |
461 | (ert-deftest elixir-mode-syntax-table/sigils-in-string ()
462 | "https://github.com/elixir-editors/emacs-elixir/issues/275"
463 | :tags '(fontification syntax-table)
464 | (elixir-test-with-temp-buffer
465 | "@one 1
466 | @two \"~s\"
467 | @three :tre
468 | "
469 | (should-not (eq (elixir-test-face-at 18) 'font-lock-string-face))
470 | (should (eq (elixir-test-face-at 19) 'elixir-attribute-face))
471 | (should-not (eq (elixir-test-face-at 25) 'font-lock-string-face))
472 | (should (eq (elixir-test-face-at 26) 'elixir-atom-face))))
473 |
474 | (ert-deftest elixir-mode-syntax-table/sigil-triple-quote ()
475 | "https://github.com/elixir-editors/emacs-elixir/issues/286"
476 | :tags '(fontification syntax-table)
477 | (elixir-test-with-temp-buffer
478 | "defmodule IEx do
479 | @moduledoc ~S\"\"\"
480 | Elixir's interactive shell.
481 | \"\"\"
482 | end
483 | "
484 | (should (eq (elixir-test-face-at 33) 'font-lock-string-face))))
485 |
486 | (ert-deftest elixir-mode-syntax-table/single-triple-single-quote ()
487 | "https://github.com/elixir-editors/emacs-elixir/issues/309"
488 | :tags '(fontification syntax-table)
489 | (elixir-test-with-temp-buffer
490 | "defmodule Module do
491 | @moduledoc ~S'''
492 | foo's \"bar\"
493 | '''"
494 | (should (eq (elixir-test-face-at 34) 'font-lock-builtin-face)) ;; ~S
495 | (should (eq (elixir-test-face-at 35) 'font-lock-builtin-face))
496 | (should (eq (elixir-test-face-at 36) 'font-lock-string-face)) ;; '''
497 | (should (eq (elixir-test-face-at 37) 'font-lock-string-face))
498 | (should (eq (elixir-test-face-at 38) 'font-lock-string-face))
499 | (should (eq (elixir-test-face-at 54) 'font-lock-string-face)) ;; '''
500 | (should (eq (elixir-test-face-at 55) 'font-lock-string-face))
501 | (should (eq (elixir-test-face-at 56) 'font-lock-string-face))))
502 |
503 | (ert-deftest elixir-mode-syntax-table/comment-out-ignored-var ()
504 | "https://github.com/elixir-editors/emacs-elixir/issues/292"
505 | :tags '(fontification syntax-table)
506 | (elixir-test-with-temp-buffer
507 | "variable
508 | _var
509 | _x
510 | _x!
511 | _x?
512 | _
513 | __MODULE__
514 | "
515 | (should (eq (elixir-test-face-at 4) nil))
516 | (should (eq (elixir-test-face-at 14) 'font-lock-comment-face))
517 | (should (eq (elixir-test-face-at 15) 'font-lock-comment-face))
518 | (should (eq (elixir-test-face-at 23) 'font-lock-comment-face))
519 | (should (eq (elixir-test-face-at 24) 'font-lock-comment-face))
520 | (should (eq (elixir-test-face-at 30) 'font-lock-comment-face))
521 | (should (eq (elixir-test-face-at 32) 'font-lock-comment-face))
522 | (should (eq (elixir-test-face-at 38) 'font-lock-comment-face))
523 | (should (eq (elixir-test-face-at 40) 'font-lock-comment-face))
524 | (should (eq (elixir-test-face-at 46) 'font-lock-constant-face))
525 | (should (eq (elixir-test-face-at 46) 'font-lock-constant-face))
526 | (should (eq (elixir-test-face-at 52) 'font-lock-constant-face))
527 | (should (eq (elixir-test-face-at 53) 'font-lock-constant-face))
528 | (should (eq (elixir-test-face-at 55) 'font-lock-constant-face))))
529 |
530 | (ert-deftest elixir-mode-syntax-table/escaped-sigil-delimiter ()
531 | "https://github.com/elixir-editors/emacs-elixir/issues/302"
532 | :tags '(fontification syntax-table)
533 | (elixir-test-with-temp-buffer
534 | "def player_id(video) do
535 | ~r/^.*(?:youtu.be\\/|v\\/|e\\/|u\\/\\w+\\/|embed\\/|v=)(?[^#\\&\\?]*).*/
536 | |> Regex.named_captures(video.url)
537 | |> get_in([\"id\"])
538 | end
539 | "
540 | (should (eq (elixir-test-face-at 45) 'font-lock-string-face)) ;; escaped '/'
541 | (should (eq (elixir-test-face-at 84) 'font-lock-string-face)) ;; comment mark '#'
542 |
543 | (elixir-test-with-temp-buffer
544 | "~B/\\//
545 | ~C[\\]]
546 | ~R{\\}}
547 | ~C(\\))
548 | ~b|\\||
549 | ~c\"\\\"\"
550 | ~r'\\''
551 | ~s<\\>>"
552 | (should (eq (elixir-test-face-at 5) 'font-lock-string-face)) ;; '/'
553 | (should (eq (elixir-test-face-at 12) 'font-lock-string-face)) ;; '[]'
554 | (should (eq (elixir-test-face-at 19) 'font-lock-string-face)) ;; '{}'
555 | (should (eq (elixir-test-face-at 26) 'font-lock-string-face)) ;; '()'
556 | (should (eq (elixir-test-face-at 33) 'font-lock-string-face)) ;; '|'
557 | (should (eq (elixir-test-face-at 40) 'font-lock-string-face)) ;; '"'
558 | (should (eq (elixir-test-face-at 47) 'font-lock-string-face)) ;; '\''
559 | (should (eq (elixir-test-face-at 53) 'font-lock-string-face)) ;; '<>'
560 | )))
561 |
562 | (ert-deftest elixir-mode-syntax-table/question-quote ()
563 | "https://github.com/elixir-editors/emacs-elixir/issues/185"
564 | :tags '(fontification syntax-table)
565 | (elixir-test-with-temp-buffer
566 | "\"\\\"foo\\\"\" |> String.strip(?\")"
567 | (should-not (eq (elixir-test-face-at 28) 'font-lock-string-face)))
568 |
569 | (elixir-test-with-temp-buffer
570 | "\"\\\"foo\\\"\" |> String.strip(?')"
571 | (should-not (eq (elixir-test-face-at 28) 'font-lock-string-face))))
572 |
573 | (ert-deftest elixir-mode-syntax-table/ignored-variables-in-pattern-match ()
574 | "https://github.com/elixir-editors/emacs-elixir/issues/361"
575 | :tags '(fontification syntax-table)
576 | (elixir-test-with-temp-buffer
577 | "(_1_day = 86_400)
578 | _1_day"
579 | (should (eq (elixir-test-face-at 2) 'font-lock-comment-face))
580 | (should (eq (elixir-test-face-at 19) 'font-lock-comment-face))))
581 |
582 | (ert-deftest elixir-mode-syntax-table/arrows ()
583 | :tags '(fontification syntax-table)
584 |
585 | (elixir-test-with-temp-buffer
586 | "with {:ok, _} <- SomeModule.call(),
587 | :ok <- OtherModule.call() do
588 | :ok
589 | end"
590 | (should (eq (elixir-test-face-at 15) 'font-lock-keyword-face))
591 | (should (eq (elixir-test-face-at 50) 'font-lock-keyword-face)))
592 |
593 | (elixir-test-with-temp-buffer
594 | "%{
595 | \"\"foo\"\" => \"bar\"
596 | }"
597 | (should (eq (elixir-test-face-at 18) 'font-lock-keyword-face)))
598 |
599 | (elixir-test-with-temp-buffer
600 | "[] |> IO.inspect()"
601 | (should (eq (elixir-test-face-at 4) 'font-lock-keyword-face)))
602 |
603 | (elixir-test-with-temp-buffer
604 | "a = fn x -> x end"
605 | (should (eq (elixir-test-face-at 10) 'font-lock-keyword-face))))
606 |
607 | (ert-deftest elixir-mode-in-docstring ()
608 | "https://github.com/elixir-editors/emacs-elixir/issues/355"
609 | :tags 'fontification
610 | (elixir-test-with-temp-buffer
611 | "# https://github.com/elixir-editors/emacs-elixir/issues/355
612 |
613 | @moduledoc \"\"\"
614 | Everything in here should be gray, including the @moduledoc and triple-quotes
615 | \"\"\"
616 |
617 | @doc \"\"\"
618 | Everything in here should be gray, including the @doc and triple-quotes
619 | \"\"\""
620 | ;; (switch-to-buffer (current-buffer))
621 | (search-forward "Everything")
622 | (should (elixir--docstring-p))
623 | (search-forward "Everything")
624 | (should (elixir--docstring-p))))
625 |
626 | (ert-deftest elixir-mode-docstring-face ()
627 | "https://github.com/elixir-editors/emacs-elixir/issues/355"
628 | :tags 'fontification
629 | (elixir-test-with-temp-buffer
630 | "# https://github.com/elixir-editors/emacs-elixir/issues/355
631 |
632 | @moduledoc \"\"\"
633 | Everything in here should be gray, including the @moduledoc and triple-quotes
634 | \"\"\"
635 |
636 | @doc \"\"\"
637 | Everything in here should be gray, including the @doc and triple-quotes
638 | \"\"\""
639 | (switch-to-buffer (current-buffer))
640 | (search-forward "Everything")
641 | (should (eq 'font-lock-doc-face (get-char-property (point) 'face)))
642 | (search-forward "Everything")
643 | (should (eq 'font-lock-doc-face (get-char-property (point) 'face)))))
644 |
645 | (provide 'elixir-mode-font-test)
646 |
647 | ;;; elixir-mode-font-test.el ends here
648 |
--------------------------------------------------------------------------------
/tests/elixir-mode-helper-test.el:
--------------------------------------------------------------------------------
1 | ;;; elixir-mode-helper-test.el --- Tests for helper functions
2 |
3 | ;;; Code:
4 | (require 'test-helper)
5 |
6 | (ert-deftest check-if-currently-inside-heredoc ()
7 | (should (with-temp-buffer
8 | (elixir-mode)
9 | (insert "
10 | defmodule Module.Name do
11 |
12 | @moduledoc \"\"\"
13 | ## Examples
14 |
15 | ....
16 | \"\"\"
17 |
18 | end")
19 | (goto-line 7)
20 | (elixir-smie--heredoc-at-current-point-p)
21 | ))
22 | (should (not (with-temp-buffer
23 | (elixir-mode)
24 | (insert "
25 | defmodule Module.Name do
26 |
27 | @moduledoc \"\"\"
28 | ## Examples
29 |
30 | ....
31 | \"\"\"
32 |
33 | end")
34 | (goto-line 3)
35 | (elixir-smie--heredoc-at-current-point-p)))))
36 |
37 | (ert-deftest get-previous-line-indentation ()
38 | (should (equal 2
39 | (with-temp-buffer
40 | (elixir-mode)
41 | (insert "
42 | defmodule Module.Name do
43 | def what do
44 | 1 + 1
45 | end
46 | end")
47 | (goto-line 4)
48 | (elixir-smie--previous-line-indentation)))))
49 |
50 |
51 | (ert-deftest check-if-previous-line-blank ()
52 | (should (not (with-temp-buffer
53 | (elixir-mode)
54 | (insert "
55 | defmodule Module.Name do
56 |
57 | def what do
58 | 1 + 1
59 | end
60 | end")
61 | (goto-line 3)
62 | (elixir-smie--previous-line-empty-p))))
63 | (should (with-temp-buffer
64 | (elixir-mode)
65 | (insert "
66 | defmodule Module.Name do
67 |
68 |
69 | def what do
70 | 1 + 1
71 | end
72 | end")
73 | (goto-line 4)
74 | (elixir-smie--previous-line-empty-p))))
75 |
76 | (ert-deftest test-current-line-contains-built-in-keyword ()
77 | (should (not (with-temp-buffer
78 | (elixir-mode)
79 | (insert "
80 | defmodule Module.Name do
81 |
82 | def hey(test) do
83 | test.case
84 | end
85 | end")
86 | (goto-line 4)
87 | (elixir-smie-current-line-contains-built-in-keyword-p))))
88 | (should (not (with-temp-buffer
89 | (elixir-mode)
90 | (insert "
91 | defmodule Module.Name do
92 |
93 | def hey(test) do
94 | case do
95 | end
96 | end
97 | end")
98 | (goto-line 4)
99 | (elixir-smie-current-line-contains-built-in-keyword-p)))))
100 |
101 | (ert-deftest test-if-string-is-empty ()
102 | (should (equal (elixir-smie-empty-string-p nil)
103 | t))
104 | (should (equal (elixir-smie-empty-string-p "")
105 | t))
106 | (should (equal (elixir-smie-empty-string-p " ")
107 | t))
108 | (should (equal (elixir-smie-empty-string-p "story")
109 | nil))
110 | (should (equal (elixir-smie-empty-string-p " ")
111 | t)))
112 |
113 | (provide 'elixir-mode-helper-test)
114 |
115 | ;;; elixir-mode-helper-test.el ends here
116 |
--------------------------------------------------------------------------------
/tests/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 | (require 'test-helper)
12 |
13 | (elixir-def-indentation-test indent-use-dot-module-newline
14 | (:tags '(indentation))
15 | "
16 | defmodule Foo do
17 | use GenServer.Behaviour
18 |
19 | def foobar do
20 | if true, do: IO.puts \"yay\"
21 | end
22 | end
23 | "
24 |
25 | "
26 | defmodule Foo do
27 | use GenServer.Behaviour
28 |
29 | def foobar do
30 | if true, do: IO.puts \"yay\"
31 | end
32 | end
33 | ")
34 |
35 | (elixir-def-indentation-test indent-use-dot-module
36 | (:tags '(indentation))
37 | "
38 | defmodule Foo do
39 | use GenServer.Behaviour
40 | def foobar do
41 | if true, do: IO.puts \"yay\"
42 | end
43 | end
44 | "
45 |
46 | "
47 | defmodule Foo do
48 | use GenServer.Behaviour
49 | def foobar do
50 | if true, do: IO.puts \"yay\"
51 | end
52 | end
53 | ")
54 |
55 | (elixir-def-indentation-test indent-do-blocks
56 | (:tags '(indentation))
57 | "
58 | defmodule Foo do
59 | def foobar do
60 | if true, do: IO.puts \"yay\"
61 | 20
62 | end
63 | end
64 | "
65 |
66 | "
67 | defmodule Foo do
68 | def foobar do
69 | if true, do: IO.puts \"yay\"
70 | 20
71 | end
72 | end
73 | ")
74 |
75 | (elixir-def-indentation-test indent-do-blocks-after-linebreak-two
76 | (:tags '(indentation))
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 |
91 | "
92 | defmodule FooBar do
93 | def foo do
94 | if true, do: IO.puts \"yay\"
95 | 20
96 | end
97 |
98 | def bar do
99 | if true, do: IO.puts \"yay\"
100 | 20
101 | end
102 | end
103 | ")
104 |
105 | (elixir-def-indentation-test indent-do-blocks-after-linebreak-three
106 | (:tags '(indentation))
107 | "
108 | defmodule FooBar do
109 | def foo do
110 | if true, do: IO.puts \"yay\"
111 | 20
112 | end
113 |
114 | def bar do
115 | if true, do: IO.puts \"yay\"
116 | 20
117 | end
118 |
119 | def baz do
120 | if true, do: IO.puts \"yay\"
121 | 20
122 | end
123 | end
124 | "
125 |
126 | "
127 | defmodule FooBar do
128 | def foo do
129 | if true, do: IO.puts \"yay\"
130 | 20
131 | end
132 |
133 | def bar do
134 | if true, do: IO.puts \"yay\"
135 | 20
136 | end
137 |
138 | def baz do
139 | if true, do: IO.puts \"yay\"
140 | 20
141 | end
142 | end
143 | ")
144 |
145 | (elixir-def-indentation-test indent-do-blocks-with-space-after-inline
146 | (:tags '(indentation))
147 | "
148 | defmodule Foo do
149 | def foobar do
150 | if true, do: IO.puts \"yay\"
151 |
152 | 20
153 | end
154 | end
155 | "
156 |
157 | "
158 | defmodule Foo do
159 | def foobar do
160 | if true, do: IO.puts \"yay\"
161 |
162 | 20
163 | end
164 | end
165 | ")
166 |
167 | (elixir-def-indentation-test indent-after-empty-line
168 | (:tags '(indentation))
169 | "
170 | def foo do
171 | a = 2
172 |
173 | b = a + 3
174 |
175 | c = a * b
176 | end
177 | "
178 |
179 | "
180 | def foo do
181 | a = 2
182 |
183 | b = a + 3
184 |
185 | c = a * b
186 | end
187 | ")
188 |
189 | (elixir-def-indentation-test indent-function-calls-without-parens
190 | (:tags '(indentation))
191 | "
192 | test \"foo\" do
193 | assert true, \"should be true\"
194 | assert !false, \"should still be true\"
195 | end
196 | "
197 |
198 | "
199 | test \"foo\" do
200 | assert true, \"should be true\"
201 | assert !false, \"should still be true\"
202 | end
203 | ")
204 |
205 | (elixir-def-indentation-test indent-records-correctly
206 | (:tags '(indentation))
207 | "
208 | defmodule MyModule do
209 | require Record
210 | Record.defrecord :money, [:currency_unit, :amount]
211 |
212 | Record.defrecord :animal, [:species, :name]
213 | end
214 | "
215 |
216 | "
217 | defmodule MyModule do
218 | require Record
219 | Record.defrecord :money, [:currency_unit, :amount]
220 |
221 | Record.defrecord :animal, [:species, :name]
222 | end
223 | ")
224 |
225 | (elixir-def-indentation-test indent-continuation-lines
226 | (:tags '(indentation))
227 | "
228 | def foo do
229 | has_something(x) &&
230 | has_something(y) ||
231 | has_something(z)
232 | end
233 | "
234 |
235 | "
236 | def foo do
237 | has_something(x) &&
238 | has_something(y) ||
239 | has_something(z)
240 | end
241 | ")
242 |
243 | (elixir-def-indentation-test indent-continuation-lines-with-comments/1
244 | (:tags '(indentation))
245 | "
246 | has_something(x) && # foo
247 | has_something(y) ||
248 | has_something(z)
249 | "
250 |
251 | "
252 | has_something(x) && # foo
253 | has_something(y) ||
254 | has_something(z)
255 | ")
256 |
257 | (elixir-def-indentation-test indent-continuation-lines-with-comments/2
258 | (:tags '(indentation))
259 | "
260 | has_something(x) &&
261 | has_something(y) || # foo
262 | has_something(z)
263 | "
264 |
265 | "
266 | has_something(x) &&
267 | has_something(y) || # foo
268 | has_something(z)
269 | ")
270 |
271 | (elixir-def-indentation-test indent-continuation-lines-with-comments/3
272 | (:tags '(indentation))
273 | "
274 | def str(s, sub, start_pos, end_pos) when is_binary(s) and is_binary(sub) do # and start_pos <= end_pos do
275 | len = end_pos-start_pos
276 | end
277 | "
278 |
279 | "
280 | def str(s, sub, start_pos, end_pos) when is_binary(s) and is_binary(sub) do # and start_pos <= end_pos do
281 | len = end_pos-start_pos
282 | end
283 | ")
284 |
285 | (elixir-def-indentation-test indent-continuation-lines-assignment
286 | (:tags '(indentation))
287 | "
288 | some_var =
289 | some_expr
290 | "
291 |
292 | "
293 | some_var =
294 | some_expr
295 | ")
296 |
297 | (elixir-def-indentation-test indent-continuation-lines-assignment/2
298 | (:tags '(indentation))
299 | "
300 | next_fun =
301 | case raw do
302 | true -> &IO.each_binstream(&1, line_or_bytes)
303 | false -> &IO.each_stream(&1, line_or_bytes)
304 | end
305 | "
306 |
307 | "
308 | next_fun =
309 | case raw do
310 | true -> &IO.each_binstream(&1, line_or_bytes)
311 | false -> &IO.each_stream(&1, line_or_bytes)
312 | end
313 | ")
314 |
315 | (elixir-def-indentation-test indent-continuation-lines-assignment/3
316 | (:expected-result :failed :tags '(indentation))
317 | "
318 | start_fun =
319 | fn ->
320 | case :file.open(path, modes) do
321 | {:ok, device} -> device
322 | {:error, reason} ->
323 | raise File.Error, reason: reason, action: \"stream\", path: path
324 | end
325 | end
326 | "
327 |
328 | "
329 | start_fun =
330 | fn ->
331 | case :file.open(path, modes) do
332 | {:ok, device} -> device
333 | {:error, reason} ->
334 | raise File.Error, reason: reason, action: \"stream\", path: path
335 | end
336 | end
337 | ")
338 |
339 |
340 | (elixir-def-indentation-test indent-last-commented-line
341 | (:tags '(indentation))
342 | "
343 | defmodule Foo do
344 | def bar do
345 | 2
346 | end
347 |
348 | # last line
349 | end
350 | "
351 |
352 | "
353 | defmodule Foo do
354 | def bar do
355 | 2
356 | end
357 |
358 | # last line
359 | end
360 | ")
361 |
362 | (elixir-def-indentation-test indent-if
363 | (:tags '(indentation))
364 | "
365 | if condition do
366 | yes
367 | end
368 | "
369 |
370 | "
371 | if condition do
372 | yes
373 | end
374 | ")
375 |
376 | (elixir-def-indentation-test indent-if-else
377 | (:tags '(indentation))
378 | "
379 | if condition do
380 | yes
381 | else
382 | no
383 | end
384 | "
385 |
386 | "
387 | if condition do
388 | yes
389 | else
390 | no
391 | end
392 | ")
393 |
394 | (elixir-def-indentation-test indent-if-else/2
395 | (:tags '(indentation))
396 | "
397 | if condition do
398 | :foo
399 | else
400 | if condition, do: :bar
401 | end
402 | "
403 |
404 | "
405 | if condition do
406 | :foo
407 | else
408 | if condition, do: :bar
409 | end
410 | ")
411 |
412 | (elixir-def-indentation-test indent-tuple-after-if-else
413 | (:tags '(indentation))
414 | "
415 | if foo do
416 | :ok
417 | else
418 | {:tuple}
419 | end
420 | "
421 |
422 | "
423 | if foo do
424 | :ok
425 | else
426 | {:tuple}
427 | end
428 | ")
429 |
430 | (elixir-def-indentation-test indent-non-finished-one-line-if-else
431 | (:tags '(indentation))
432 | "
433 | if condition,
434 | do: :foo,
435 | else: :bar
436 | "
437 |
438 | "
439 | if condition,
440 | do: :foo,
441 | else: :bar
442 | ")
443 |
444 | (elixir-def-indentation-test indent-if-when-condition-is-a-named-function-on-a-module
445 | (:tags '(indentation))
446 | "
447 | defmodule Whois do
448 | def lookup2(domain) do
449 | if Server.for(domain) do
450 | :ok
451 | else
452 | :error
453 | end
454 | end
455 | end
456 | "
457 |
458 | "
459 | defmodule Whois do
460 | def lookup2(domain) do
461 | if Server.for(domain) do
462 | :ok
463 | else
464 | :error
465 | end
466 | end
467 | end
468 | ")
469 |
470 | (elixir-def-indentation-test indent-try
471 | (:tags '(indentation))
472 | "
473 | try do
474 | foo
475 | bar
476 | end
477 | "
478 |
479 | "
480 | try do
481 | foo
482 | bar
483 | end
484 | ")
485 |
486 | (elixir-def-indentation-test indent-try/after
487 | (:tags '(indentation))
488 | "
489 | try do
490 | foo
491 | bar
492 | after
493 | after_everything()
494 | post_that()
495 | end
496 | "
497 |
498 | "
499 | try do
500 | foo
501 | bar
502 | after
503 | after_everything()
504 | post_that()
505 | end
506 | ")
507 |
508 | (elixir-def-indentation-test indent-try/catch/after
509 | (:tags '(indentation))
510 | "
511 | try do
512 | foo
513 | bar
514 | catch
515 | baz ->
516 | nope
517 | [yeah] ->
518 | maybe
519 | after
520 | after_everything()
521 | post_that()
522 | end
523 | "
524 |
525 | "
526 | try do
527 | foo
528 | bar
529 | catch
530 | baz ->
531 | nope
532 | [yeah] ->
533 | maybe
534 | after
535 | after_everything()
536 | post_that()
537 | end
538 | ")
539 |
540 | (elixir-def-indentation-test indent-try/rescue/1
541 | (:tags '(indentation))
542 | "
543 | try do
544 | raise 'some error'
545 | rescue
546 | RuntimeError -> 'rescued a runtime error'
547 | end
548 | "
549 |
550 | "
551 | try do
552 | raise 'some error'
553 | rescue
554 | RuntimeError -> 'rescued a runtime error'
555 | end
556 | ")
557 |
558 | (elixir-def-indentation-test indent-try/rescue/2
559 | (:tags '(indentation))
560 | "
561 | try do
562 | raise 'some error'
563 | rescue
564 | x in [RuntimeError] ->
565 | x.message
566 | end
567 | "
568 |
569 | "
570 | try do
571 | raise 'some error'
572 | rescue
573 | x in [RuntimeError] ->
574 | x.message
575 | end
576 | ")
577 |
578 | (elixir-def-indentation-test indent-block-inside-fn-match
579 | (:tags '(indentation))
580 | "
581 | defp into(stream, device, raw) do
582 | fn
583 | :ok, {:cont, x} ->
584 | case raw do
585 | true -> IO.binwrite(device, x)
586 | false -> IO.write(device, x)
587 | end
588 | :ok, _ -> stream
589 | end
590 | end
591 | "
592 |
593 | "
594 | defp into(stream, device, raw) do
595 | fn
596 | :ok, {:cont, x} ->
597 | case raw do
598 | true -> IO.binwrite(device, x)
599 | false -> IO.write(device, x)
600 | end
601 | :ok, _ -> stream
602 | end
603 | end
604 | ")
605 |
606 | (elixir-def-indentation-test indent-fn-in-assignment
607 | (:tags '(indentation))
608 | "
609 | f = fn x, y ->
610 | x + y
611 | end
612 | "
613 |
614 | "
615 | f = fn x, y ->
616 | x + y
617 | end
618 | ")
619 |
620 | (elixir-def-indentation-test indent-fn-as-arguments
621 | (:tags '(indentation))
622 | "
623 | Enum.map 1..10, fn x ->
624 | x + 1
625 | end
626 | "
627 |
628 | "
629 | Enum.map 1..10, fn x ->
630 | x + 1
631 | end
632 | ")
633 |
634 | (elixir-def-indentation-test indent-list-argument-continuation-lines-nicely
635 | (:tags '(indentation))
636 | "
637 | to_process = [27, 33, 35, 11, 36, 29, 18, 37, 21, 31, 19, 10, 14, 30,
638 | 15, 17, 23, 28, 25, 34, 22, 20, 13, 16, 32, 12, 26, 24]
639 | "
640 |
641 | "
642 | to_process = [27, 33, 35, 11, 36, 29, 18, 37, 21, 31, 19, 10, 14, 30,
643 | 15, 17, 23, 28, 25, 34, 22, 20, 13, 16, 32, 12, 26, 24]
644 | ")
645 |
646 | (elixir-def-indentation-test indent-nested-fn
647 | (:tags '(indentation))
648 | "
649 | defmodule FooModule do
650 | def foo do
651 | x = fn(a, b) -> a + b end
652 | end
653 | end
654 | "
655 |
656 | "
657 | defmodule FooModule do
658 | def foo do
659 | x = fn(a, b) -> a + b end
660 | end
661 | end
662 | ")
663 |
664 | (elixir-def-indentation-test indent-list-of-floats-aligns
665 | (:tags '(indentation))
666 | "
667 | [1.2,
668 | 3.4]
669 | "
670 |
671 | "
672 | [1.2,
673 | 3.4]
674 | ")
675 |
676 | (elixir-def-indentation-test indent-after-operator
677 | (:tags '(indentation))
678 | "
679 | defmodule Banana do
680 | def start do
681 | a = \"\" <> \"?\"
682 |
683 | case bar do
684 | z -> 1
685 | end
686 |
687 | case foo do
688 | ?x -> x
689 | end
690 |
691 | end
692 | end
693 | "
694 |
695 | "
696 | defmodule Banana do
697 | def start do
698 | a = \"\" <> \"?\"
699 |
700 | case bar do
701 | z -> 1
702 | end
703 |
704 | case foo do
705 | ?x -> x
706 | end
707 |
708 | end
709 | end
710 | ")
711 |
712 | (elixir-def-indentation-test nested-modules
713 | (:tags '(indentation))
714 | "
715 | defmodule Mod1 do
716 | defmodule Mod1a do
717 | def start do
718 | foo()
719 | end
720 | end
721 | end
722 | "
723 |
724 | "
725 | defmodule Mod1 do
726 | defmodule Mod1a do
727 | def start do
728 | foo()
729 | end
730 | end
731 | end
732 | ")
733 |
734 | (elixir-def-indentation-test cond-comment
735 | (:tags '(indentation))
736 | "
737 | def foo() do
738 | cond do
739 | yadda ->
740 | :ok
741 | badda -> # comment throws this off
742 | :what
743 | end
744 | end
745 | "
746 |
747 | "
748 | def foo() do
749 | cond do
750 | yadda ->
751 | :ok
752 | badda -> # comment throws this off
753 | :what
754 | end
755 | end
756 | ")
757 |
758 | (elixir-def-indentation-test cond-within-with
759 | (:expected-result :failed :tags '(indentation))
760 | ;; https://github.com/elixir-editors/emacs-elixir/issues/319
761 | "
762 | with(
763 | foo <-
764 | cond do
765 | bar ->
766 | bar
767 |
768 | baz ->
769 | baz
770 | end,
771 | do: :ok
772 | )
773 | "
774 | "
775 | with(
776 | foo <-
777 | cond do
778 | bar ->
779 | bar
780 |
781 | baz ->
782 | baz
783 | end,
784 | do: :ok
785 | )
786 | ")
787 |
788 | (elixir-def-indentation-test with-statement
789 | (:tags '(indentation))
790 | "
791 | defmodule Foo do
792 |
793 | def bar do
794 | with {:ok, width} <- Map.fetch(opts, :width),
795 | {:ok, height} <- Map.fetch(opts, :height) do
796 | {:ok, width * height}
797 | else
798 | :error ->
799 | {:error, :wrong_data}
800 | end
801 | end
802 |
803 | end
804 | "
805 |
806 | "
807 | defmodule Foo do
808 |
809 | def bar do
810 | with {:ok, width} <- Map.fetch(opts, :width),
811 | {:ok, height} <- Map.fetch(opts, :height) do
812 | {:ok, width * height}
813 | else
814 | :error ->
815 | {:error, :wrong_data}
816 | end
817 | end
818 |
819 | end
820 | ")
821 |
822 | (elixir-def-indentation-test with-statement/2
823 | (:tags '(indentation))
824 | "
825 | defmodule Foo do
826 | def bar do
827 | with {:ok, width} <- Map.fetch(opts, :width),
828 | {:ok, height} <- Map.fetch(opts, :height),
829 | do: {:ok, width * height}
830 |
831 | with({:ok, width} <- Map.fetch(opts, :width),
832 | {:ok, height} <- Map.fetch(opts, :height),
833 | do: {:ok, width * height})
834 | end
835 | end
836 | "
837 |
838 | "
839 | defmodule Foo do
840 | def bar do
841 | with {:ok, width} <- Map.fetch(opts, :width),
842 | {:ok, height} <- Map.fetch(opts, :height),
843 | do: {:ok, width * height}
844 |
845 | with({:ok, width} <- Map.fetch(opts, :width),
846 | {:ok, height} <- Map.fetch(opts, :height),
847 | do: {:ok, width * height})
848 | end
849 | end
850 | ")
851 |
852 | (elixir-def-indentation-test indent-heredoc
853 | (:tags '(indentation))
854 | "
855 | defmodule Foo do
856 | @doc \"\"\"
857 | this is a heredoc string
858 |
859 | \"\"\"
860 | def convert do
861 | x = 15
862 | end
863 | end
864 | "
865 |
866 | "
867 | defmodule Foo do
868 | @doc \"\"\"
869 | this is a heredoc string
870 |
871 | \"\"\"
872 | def convert do
873 | x = 15
874 | end
875 | end
876 | ")
877 |
878 | (elixir-def-indentation-test indent-heredoc/2
879 | (:tags '(indentation))
880 | "
881 | defmodule Foo do
882 | @doc \"\"\"
883 | this is a heredoc string
884 |
885 | \"\"\"
886 | def convert do
887 | x = 15
888 | end
889 |
890 | defmodule Bar do
891 | @moduledoc \"\"\"
892 | this is a heredoc string
893 |
894 | last line
895 | \"\"\"
896 | end
897 | end
898 | "
899 |
900 | "
901 | defmodule Foo do
902 | @doc \"\"\"
903 | this is a heredoc string
904 |
905 | \"\"\"
906 | def convert do
907 | x = 15
908 | end
909 |
910 | defmodule Bar do
911 | @moduledoc \"\"\"
912 | this is a heredoc string
913 |
914 | last line
915 | \"\"\"
916 | end
917 | end
918 | ")
919 |
920 | (elixir-def-indentation-test indent-heredoc/3
921 | (:tags '(indentation))
922 | "
923 | def foo() do
924 | \"\"\"
925 | heredoc
926 | \"\"\"
927 | end
928 |
929 | for x <- [1, 2, 3] do
930 | \"\"\"
931 | heredoc
932 | \"\"\"
933 | end
934 | "
935 |
936 | "
937 | def foo() do
938 | \"\"\"
939 | heredoc
940 | \"\"\"
941 | end
942 |
943 | for x <- [1, 2, 3] do
944 | \"\"\"
945 | heredoc
946 | \"\"\"
947 | end
948 | ")
949 |
950 | (elixir-def-indentation-test indent-multiline-pipes-after-call
951 | (:tags '(indentation))
952 | "
953 | some_string
954 | |> String.downcase
955 | |> String.strip
956 | "
957 |
958 | "
959 | some_string
960 | |> String.downcase
961 | |> String.strip
962 | ")
963 |
964 | (elixir-def-indentation-test indent-multiline-on-the-right-of-pattern-match
965 | (:tags '(indentation))
966 | "
967 | sanitized_string =
968 | some_string
969 | |> String.downcase
970 | |> String.strip
971 | "
972 |
973 | "
974 | sanitized_string =
975 | some_string
976 | |> String.downcase
977 | |> String.strip
978 | ")
979 |
980 | (elixir-def-indentation-test indent-pipes-after-assignment
981 | (:tags '(indentation))
982 | "
983 | def foo(x) do
984 | a = x
985 | |> Enum.reverse
986 | end
987 | "
988 |
989 | "
990 | def foo(x) do
991 | a = x
992 | |> Enum.reverse
993 | end
994 | ")
995 |
996 | (elixir-def-indentation-test indent-pipes-inside-blocks
997 | (:tags '(indentation))
998 | "
999 | defmodule Foo do
1000 | def bar do
1001 | baz =
1002 | [1,2,3,4,5,6,7,8,9]
1003 | |> Enum.reverse
1004 | |> Enum.filter(&(&1 > 5))
1005 | end
1006 | end
1007 | "
1008 |
1009 | "
1010 | defmodule Foo do
1011 | def bar do
1012 | baz =
1013 | [1,2,3,4,5,6,7,8,9]
1014 | |> Enum.reverse
1015 | |> Enum.filter(&(&1 > 5))
1016 | end
1017 | end
1018 | ")
1019 |
1020 | (elixir-def-indentation-test indent-inside-parens
1021 | (:tags '(indentation))
1022 | "
1023 | x = do_something(
1024 | :foo,
1025 | :bar
1026 | )
1027 | "
1028 |
1029 | "
1030 | x = do_something(
1031 | :foo,
1032 | :bar
1033 | )
1034 | ")
1035 |
1036 | (elixir-def-indentation-test indent-inside-parens/2
1037 | (:tags '(indentation))
1038 | "
1039 | x = do_something(:foo,
1040 | :bar)
1041 | "
1042 |
1043 | "
1044 | x = do_something(:foo,
1045 | :bar)
1046 | ")
1047 |
1048 | (elixir-def-indentation-test indent-inside-parens/3
1049 | (:tags '(indentation))
1050 | "
1051 | x = do_something(:foo, fn (arg) ->
1052 | do_another(arg)
1053 | end)
1054 | "
1055 |
1056 | "
1057 | x = do_something(:foo, fn (arg) ->
1058 | do_another(arg)
1059 | end)
1060 | ")
1061 |
1062 | (elixir-def-indentation-test indent-inside-parens/4
1063 | (:tags '(indentation))
1064 | "
1065 | defmodule Something do
1066 | def something do
1067 | x = do_something(:foo, fn (arg) ->
1068 | do_another(arg)
1069 | end)
1070 | end
1071 | end
1072 | "
1073 |
1074 | "
1075 | defmodule Something do
1076 | def something do
1077 | x = do_something(:foo, fn (arg) ->
1078 | do_another(arg)
1079 | end)
1080 | end
1081 | end
1082 | ")
1083 |
1084 | (elixir-def-indentation-test indent-inside-parens/5
1085 | (:tags '(indentation))
1086 | "
1087 | defmodule IndentPlayground do
1088 | def my_func(arr) do
1089 | Enum.map(arr, fn(x) ->
1090 | x * 2
1091 | end)
1092 | #back here
1093 | end
1094 | end
1095 | "
1096 |
1097 | "
1098 | defmodule IndentPlayground do
1099 | def my_func(arr) do
1100 | Enum.map(arr, fn(x) ->
1101 | x * 2
1102 | end)
1103 | #back here
1104 | end
1105 | end
1106 | ")
1107 |
1108 | (elixir-def-indentation-test indent-lone-keyword
1109 | (:tags '(indentation))
1110 | "
1111 | def foo do #comment
1112 | :bar
1113 | end
1114 | "
1115 |
1116 | "
1117 | def foo do #comment
1118 | :bar
1119 | end
1120 | ")
1121 |
1122 | (elixir-def-indentation-test indent-single-line-match
1123 | (:tags '(indentation))
1124 | "
1125 | case x do
1126 | a -> b
1127 | c -> d
1128 | end
1129 | "
1130 |
1131 | "
1132 | case x do
1133 | a -> b
1134 | c -> d
1135 | end
1136 | ")
1137 |
1138 | (elixir-def-indentation-test indent-multiline-match
1139 | (:tags '(indentation))
1140 | "
1141 | def foo do
1142 | case is_string(x) do
1143 | true ->
1144 | x2 = \" one\"
1145 | x <> x2
1146 | false ->
1147 | x2 = \" two\"
1148 | x <> x2
1149 | end
1150 | end
1151 | "
1152 |
1153 | "
1154 | def foo do
1155 | case is_string(x) do
1156 | true ->
1157 | x2 = \" one\"
1158 | x <> x2
1159 | false ->
1160 | x2 = \" two\"
1161 | x <> x2
1162 | end
1163 | end
1164 | ")
1165 |
1166 | (elixir-def-indentation-test indent-multiline-match/2
1167 | (:tags '(indentation))
1168 | "
1169 | def foo do
1170 | case is_string(x) do
1171 | true ->
1172 | x2 = \" one\"
1173 | x <> x2
1174 | false ->
1175 | x2 = \" two\"
1176 | x <> x2
1177 | end
1178 | end
1179 | "
1180 |
1181 | "
1182 | def foo do
1183 | case is_string(x) do
1184 | true ->
1185 | x2 = \" one\"
1186 | x <> x2
1187 | false ->
1188 | x2 = \" two\"
1189 | x <> x2
1190 | end
1191 | end
1192 | ")
1193 |
1194 | (elixir-def-indentation-test indent-mixed-match
1195 | (:tags '(indentation))
1196 | "
1197 | case x do
1198 | a -> b
1199 | c ->
1200 | d
1201 | e -> f
1202 | end
1203 | "
1204 |
1205 | "
1206 | case x do
1207 | a -> b
1208 | c ->
1209 | d
1210 | e -> f
1211 | end
1212 | ")
1213 |
1214 | (elixir-def-indentation-test indent-after-require-Record
1215 | (:tags '(indentation))
1216 | ;; Mind the significant whitespace after `Record' in each case. There should
1217 | ;; be two spaces after `Record', otherwise this test is meaningless.
1218 | "
1219 | defmodule RSS do
1220 | require Record
1221 |
1222 | def zip(list1, list2) when length(list1) == length(list2) do
1223 | x = 1
1224 | end
1225 | end
1226 | "
1227 |
1228 | "
1229 | defmodule RSS do
1230 | require Record
1231 |
1232 | def zip(list1, list2) when length(list1) == length(list2) do
1233 | x = 1
1234 | end
1235 | end
1236 | ")
1237 |
1238 | (elixir-def-indentation-test indent-fn-in-multiline-assignment
1239 | (:expected-result :failed :tags '(indentation))
1240 | "
1241 | variable =
1242 | fn ->
1243 | case :file.open(path, modes) do
1244 | {:ok, device} -> device
1245 | {:error, reason} ->
1246 | raise File.Error, reason: reason
1247 | end
1248 | end
1249 | "
1250 |
1251 | "
1252 | variable =
1253 | fn ->
1254 | case :file.open(path, modes) do
1255 | {:ok, device} -> device
1256 | {:error, reason} ->
1257 | raise File.Error, reason: reason
1258 | end
1259 | end
1260 | ")
1261 |
1262 | (elixir-def-indentation-test indent-after-def-do-online
1263 | (:tags '(indentation))
1264 | "
1265 | defmodule Greeter do
1266 | def hello, do: IO.puts \"hello\"
1267 | def bye, do: IO.puts \"bye\"
1268 |
1269 | def hi(name) do
1270 | IO.puts \"Hi #{name}\"
1271 | end
1272 | end
1273 | "
1274 |
1275 | "
1276 | defmodule Greeter do
1277 | def hello, do: IO.puts \"hello\"
1278 | def bye, do: IO.puts \"bye\"
1279 |
1280 | def hi(name) do
1281 | IO.puts \"Hi #{name}\"
1282 | end
1283 | end
1284 | ")
1285 |
1286 | (elixir-def-indentation-test indent-after-def-do-online/2
1287 | (:tags '(indentation))
1288 |
1289 | "
1290 | defmodule ControlFlow do
1291 | defmacro my_if(expr, do: if_block), do: if(expr, do: if_block, else: nil)
1292 | defmacro my_if(expr, do: if_block, else: else_block) do
1293 | quote do
1294 | case unquote(expr) do
1295 | result when result in [false, nil] -> unquote(else_block)
1296 | _ -> unquote(if_block)
1297 | end
1298 | end
1299 | end
1300 | end
1301 | "
1302 |
1303 | "
1304 | defmodule ControlFlow do
1305 | defmacro my_if(expr, do: if_block), do: if(expr, do: if_block, else: nil)
1306 | defmacro my_if(expr, do: if_block, else: else_block) do
1307 | quote do
1308 | case unquote(expr) do
1309 | result when result in [false, nil] -> unquote(else_block)
1310 | _ -> unquote(if_block)
1311 | end
1312 | end
1313 | end
1314 | end
1315 | ")
1316 |
1317 | (elixir-def-indentation-test indent-after-def-do-online/3
1318 | (:tags '(indentation))
1319 | "
1320 | defmodule Foo do
1321 | def bar(baz, quun \\\\ nil)
1322 | def bar(baz, quun) when baz == quun, do: baz
1323 | def bar(baz, quun), do: quun
1324 |
1325 | defp bar(baz, quun \\\\ nil)
1326 | defp bar(baz, quun) when baz == quun, do: baz
1327 | defp bar(baz, quun), do: quun
1328 |
1329 | defmacro bar(baz, quun \\\\ nil)
1330 | defmacro bar(baz, quun) when baz == quun, do: baz
1331 | defmacro bar(baz, quun), do: quun
1332 |
1333 | defmacrop bar(baz, quun \\\\ nil)
1334 | defmacrop bar(baz, quun) when baz == quun, do: baz
1335 | defmacrop bar(baz, quun), do: quun
1336 | end
1337 | "
1338 |
1339 | "
1340 | defmodule Foo do
1341 | def bar(baz, quun \\\\ nil)
1342 | def bar(baz, quun) when baz == quun, do: baz
1343 | def bar(baz, quun), do: quun
1344 |
1345 | defp bar(baz, quun \\\\ nil)
1346 | defp bar(baz, quun) when baz == quun, do: baz
1347 | defp bar(baz, quun), do: quun
1348 |
1349 | defmacro bar(baz, quun \\\\ nil)
1350 | defmacro bar(baz, quun) when baz == quun, do: baz
1351 | defmacro bar(baz, quun), do: quun
1352 |
1353 | defmacrop bar(baz, quun \\\\ nil)
1354 | defmacrop bar(baz, quun) when baz == quun, do: baz
1355 | defmacrop bar(baz, quun), do: quun
1356 | end
1357 | ")
1358 |
1359 | (elixir-def-indentation-test indent-after-not-finished-one-line-def
1360 | (:tags '(indentation))
1361 | "
1362 | defmodule Hello do
1363 | defp skip,
1364 | do: true
1365 | def on?, do: true
1366 | defmacro switch,
1367 | do: on!
1368 | defp self, do: value
1369 | defmacrop whatever, do: do_it!
1370 | end
1371 | "
1372 |
1373 | "
1374 | defmodule Hello do
1375 | defp skip,
1376 | do: true
1377 | def on?, do: true
1378 | defmacro switch,
1379 | do: on!
1380 | defp self, do: value
1381 | defmacrop whatever, do: do_it!
1382 | end
1383 | ")
1384 |
1385 | (elixir-def-indentation-test indent-correct-with-multiple-one-line-macro-calls
1386 | (:tags '(indentation))
1387 | "
1388 | defmodule MyMacros do
1389 | mymacro x1, do: [:x1]
1390 | mymacro x2, do: [:x2]
1391 | mymacro x3, do: [:x3]
1392 | mymacro x1, do: [:x1]
1393 | mymacro x2, do: [:x2]
1394 | mymacro x3, do: [:x3]
1395 | end
1396 | "
1397 |
1398 | "
1399 | defmodule MyMacros do
1400 | mymacro x1, do: [:x1]
1401 | mymacro x2, do: [:x2]
1402 | mymacro x3, do: [:x3]
1403 | mymacro x1, do: [:x1]
1404 | mymacro x2, do: [:x2]
1405 | mymacro x3, do: [:x3]
1406 | end
1407 | ")
1408 |
1409 | (elixir-def-indentation-test indent-binary-sequence
1410 | (:tags '(indentation))
1411 | "
1412 | defmodule ExampleTest do
1413 | test \"the truth\" do
1414 | assert <<1,2>> == <<1,2>>
1415 | assert 1 + 1 == 2
1416 | end
1417 | end
1418 | "
1419 |
1420 | "
1421 | defmodule ExampleTest do
1422 | test \"the truth\" do
1423 | assert <<1,2>> == <<1,2>>
1424 | assert 1 + 1 == 2
1425 | end
1426 | end
1427 | ")
1428 |
1429 | (elixir-def-indentation-test indent-binary-sequence-inside-match-block/2
1430 | (:tags '(indentation))
1431 | "
1432 | case asd do
1433 | <> ->
1434 | <>
1437 | <> ->
1438 | <>
1441 | <> ->
1442 | <>
1445 | <> ->
1446 | <>
1449 | <<>> ->
1450 | main
1451 | end
1452 | "
1453 |
1454 | "
1455 | case asd do
1456 | <> ->
1457 | <>
1460 | <> ->
1461 | <>
1464 | <> ->
1465 | <>
1468 | <> ->
1469 | <>
1472 | <<>> ->
1473 | main
1474 | end
1475 | ")
1476 |
1477 | (elixir-def-indentation-test indent-inside-square-brackets
1478 | (:tags '(indentation))
1479 | "
1480 | children = [
1481 | supervisor(Task.Supervisor, [[name: KVServer.TaskSupervisor]]),
1482 | worker(Task, [KVServer, :accept, [4040]])
1483 | ]
1484 | "
1485 |
1486 | "
1487 | children = [
1488 | supervisor(Task.Supervisor, [[name: KVServer.TaskSupervisor]]),
1489 | worker(Task, [KVServer, :accept, [4040]])
1490 | ]
1491 | ")
1492 |
1493 | (elixir-def-indentation-test indent-after-reserved-word/case
1494 | (:tags '(indentation))
1495 | "
1496 | defmodule Application.Behavior do
1497 | def foo(test) do
1498 | test_case = test.case
1499 | run(test_case)
1500 | end
1501 | end
1502 | "
1503 |
1504 | "
1505 | defmodule Application.Behavior do
1506 | def foo(test) do
1507 | test_case = test.case
1508 | run(test_case)
1509 | end
1510 | end
1511 | ")
1512 |
1513 | (elixir-def-indentation-test indent-after-reserved-word/try
1514 | (:tags '(indentation))
1515 | "
1516 | defmodule Application.Behavior do
1517 | def foo(test) do
1518 | test_case = test.try
1519 | run(test_case)
1520 | end
1521 | end
1522 | "
1523 |
1524 | "
1525 | defmodule Application.Behavior do
1526 | def foo(test) do
1527 | test_case = test.try
1528 | run(test_case)
1529 | end
1530 | end
1531 | ")
1532 |
1533 | (elixir-def-indentation-test indent-after-reserved-word/rescue
1534 | (:tags '(indentation))
1535 | "
1536 | defmodule Application.Behavior do
1537 | def foo(test) do
1538 | test_case = test.rescue
1539 | run(test_case)
1540 | end
1541 | end
1542 | "
1543 |
1544 | "
1545 | defmodule Application.Behavior do
1546 | def foo(test) do
1547 | test_case = test.rescue
1548 | run(test_case)
1549 | end
1550 | end
1551 | ")
1552 |
1553 |
1554 | (elixir-def-indentation-test indent-after-reserved-word/if
1555 | (:tags '(indentation))
1556 | "
1557 | defmodule Application.Behavior do
1558 | def foo(test) do
1559 | test_case = test.if
1560 | run(test_case)
1561 | end
1562 | end
1563 | "
1564 |
1565 | "
1566 | defmodule Application.Behavior do
1567 | def foo(test) do
1568 | test_case = test.if
1569 | run(test_case)
1570 | end
1571 | end
1572 | ")
1573 |
1574 | (elixir-def-indentation-test indent-after-bitstring/1
1575 | (:tags '(indentation))
1576 | "
1577 | defmodule X do
1578 | def a, do: <<1 :: size(8)>>
1579 | def b, do: <<2 :: size(8)>>
1580 | def c, do: <<3 :: size(8)>>
1581 | end
1582 | "
1583 |
1584 | "
1585 | defmodule X do
1586 | def a, do: <<1 :: size(8)>>
1587 | def b, do: <<2 :: size(8)>>
1588 | def c, do: <<3 :: size(8)>>
1589 | end
1590 | ")
1591 |
1592 | (elixir-def-indentation-test indent-after-fn/1
1593 | (:tags '(indentation))
1594 | "
1595 | defmodule X do
1596 | def func do
1597 | Enum.filter([1,2,3],
1598 | fn(1) -> true
1599 | (2) -> false
1600 | (_) -> true
1601 | end)
1602 | end
1603 | end
1604 | "
1605 |
1606 | "
1607 | defmodule X do
1608 | def func do
1609 | Enum.filter([1,2,3],
1610 | fn(1) -> true
1611 | (2) -> false
1612 | (_) -> true
1613 | end)
1614 | end
1615 | end
1616 | ")
1617 |
1618 | (elixir-def-indentation-test indent-outside-block
1619 | (:tags '(indentation))
1620 | "
1621 | 1 + 1 # => 2
1622 |
1623 | sum = fn(a, b) ->
1624 | a + b
1625 | end
1626 |
1627 | sum.(1231, 3)
1628 |
1629 | a = 23
1630 | a = a
1631 |
1632 | 23 / 3
1633 | "
1634 |
1635 | "
1636 | 1 + 1 # => 2
1637 |
1638 | sum = fn(a, b) ->
1639 | a + b
1640 | end
1641 |
1642 | sum.(1231, 3)
1643 |
1644 | a = 23
1645 | a = a
1646 |
1647 | 23 / 3
1648 | ")
1649 |
1650 | (elixir-def-indentation-test indent-list-elements
1651 | (:tags '(indentation))
1652 | "
1653 | defmodule Foo do
1654 | def bar do
1655 | [
1656 | :foo,
1657 | :bar
1658 | ]
1659 | end
1660 | end
1661 | "
1662 |
1663 | "
1664 | defmodule Foo do
1665 | def bar do
1666 | [
1667 | :foo,
1668 | :bar
1669 | ]
1670 | end
1671 | end
1672 | ")
1673 |
1674 | (elixir-def-indentation-test indent-tuple-elements
1675 | (:tags '(indentation))
1676 | "
1677 | defmodule Foo do
1678 | def bar do
1679 | {
1680 | :foo,
1681 | :bar
1682 | }
1683 | end
1684 | end
1685 | "
1686 |
1687 | "
1688 | defmodule Foo do
1689 | def bar do
1690 | {
1691 | :foo,
1692 | :bar
1693 | }
1694 | end
1695 | end
1696 | ")
1697 |
1698 | (elixir-def-indentation-test indent-maps-with-stings-as-keys
1699 | (:tags '(indentation))
1700 | "
1701 | %{
1702 | \"data\" => %{
1703 | \"foo\" => %{
1704 | \"bar\" => nil
1705 | }
1706 | }
1707 | }
1708 | "
1709 |
1710 | "
1711 | %{
1712 | \"data\" => %{
1713 | \"foo\" => %{
1714 | \"bar\" => nil
1715 | }
1716 | }
1717 | }
1718 | ")
1719 |
1720 | (elixir-def-indentation-test indent-maps-with-multiple-string-keys
1721 | (:expected-result :failed :tags '(indentation))
1722 | "
1723 | Enum.map [], fn x ->
1724 | %{
1725 | \"a\" => 5555,
1726 | \"b\" => 5555,
1727 | \"c\" => x,
1728 | }
1729 | end
1730 | "
1731 |
1732 | "
1733 | Enum.map [], fn x ->
1734 | %{
1735 | \"a\" => 5555,
1736 | \"b\" => 5555,
1737 | \"c\" => x,
1738 | }
1739 | end
1740 | ")
1741 |
1742 | (elixir-def-indentation-test indent-maps-and-structs-elements
1743 | (:tags '(indentation))
1744 | "
1745 | {
1746 | :foo,
1747 | :bar
1748 | }
1749 |
1750 | %GenEvent.Stream{
1751 | manager: manager,
1752 | timeout: Keyword.get(options, :timeout, :infinity)}
1753 | "
1754 |
1755 | "
1756 | {
1757 | :foo,
1758 | :bar
1759 | }
1760 |
1761 | %GenEvent.Stream{
1762 | manager: manager,
1763 | timeout: Keyword.get(options, :timeout, :infinity)}
1764 | ")
1765 |
1766 | (elixir-def-indentation-test indent-parenthesis-inside-block
1767 | (:tags '(indentation))
1768 | "
1769 | defmodule Foo do
1770 | def bar do
1771 | ()
1772 | end
1773 | end
1774 | "
1775 |
1776 | "
1777 | defmodule Foo do
1778 | def bar do
1779 | ()
1780 | end
1781 | end
1782 | ")
1783 |
1784 | (elixir-def-indentation-test complex-case-with-matches/2
1785 | (:tags '(indentation))
1786 | "
1787 | case parse do
1788 | { [ help: true ], _, _ } -> :help
1789 | { _, [ user, project, count ], _ } ->
1790 | { user, project, count }
1791 | { _, [ user, project ], _ } -> { user, project, @default_count }
1792 | _ -> :help
1793 | end
1794 | "
1795 |
1796 | "
1797 | case parse do
1798 | { [ help: true ], _, _ } -> :help
1799 | { _, [ user, project, count ], _ } ->
1800 | { user, project, count }
1801 | { _, [ user, project ], _ } -> { user, project, @default_count }
1802 | _ -> :help
1803 | end
1804 | ")
1805 |
1806 | (elixir-def-indentation-test complex-case-with-matches/3
1807 | (:tags '(indentation))
1808 | "
1809 | defmodule MyModule do
1810 | case File.read(\"/usr/share/dict/words\") do
1811 | {:ok, contents} ->
1812 | {:something, contents}
1813 | {:error, reason} ->
1814 | {:error, reason}
1815 | end
1816 | end
1817 | "
1818 |
1819 | "
1820 | defmodule MyModule do
1821 | case File.read(\"/usr/share/dict/words\") do
1822 | {:ok, contents} ->
1823 | {:something, contents}
1824 | {:error, reason} ->
1825 | {:error, reason}
1826 | end
1827 | end
1828 | ")
1829 |
1830 | (elixir-def-indentation-test complex-case-with-matches/4
1831 | (:tags '(indentation))
1832 | "
1833 | case :foo do
1834 | 1 ->
1835 | try true do
1836 | :foo
1837 | end
1838 | 2 ->
1839 | false
1840 | end
1841 |
1842 | fn x ->
1843 | if true do
1844 | end
1845 | end
1846 | "
1847 |
1848 | "
1849 | case :foo do
1850 | 1 ->
1851 | try true do
1852 | :foo
1853 | end
1854 | 2 ->
1855 | false
1856 | end
1857 |
1858 | fn x ->
1859 | if true do
1860 | end
1861 | end
1862 | ")
1863 |
1864 | (elixir-def-indentation-test complex-case-with-matches/5
1865 | (:tags '(indentation))
1866 | "
1867 | case parse do
1868 | {} -> :help
1869 | {} -> :help
1870 | _
1871 | end
1872 | "
1873 |
1874 | "
1875 | case parse do
1876 | {} -> :help
1877 | {} -> :help
1878 | _
1879 | end
1880 | ")
1881 |
1882 | (elixir-def-indentation-test case-with-multiline-maps
1883 | (:tags '(indentation))
1884 | "
1885 | case statement do
1886 | %{\"foo\" => \"foo\",
1887 | \"baz\" => \"baz\"} ->
1888 | :ok
1889 | _ ->
1890 | :ok
1891 | end
1892 | "
1893 |
1894 | "
1895 | case statement do
1896 | %{\"foo\" => \"foo\",
1897 | \"baz\" => \"baz\"} ->
1898 | :ok
1899 | _ ->
1900 | :ok
1901 | end
1902 | ")
1903 |
1904 | (elixir-def-indentation-test case-with-for-comprehension
1905 | (:tags '(indentation))
1906 | "
1907 | case expression do
1908 | true ->
1909 | for _ <- [] do
1910 | :ok
1911 | end
1912 | end
1913 | "
1914 |
1915 | "
1916 | case expression do
1917 | true ->
1918 | for _ <- [] do
1919 | :ok
1920 | end
1921 | end
1922 | ")
1923 |
1924 | (elixir-def-indentation-test indent-case-when-condition-is-a-named-function-on-a-module
1925 | (:tags '(indentation))
1926 | "
1927 | defmodule Whois do
1928 | def lookup1(domain) do
1929 | case Server.for(domain) do
1930 | {:ok, server} -> server
1931 | :error -> {:error, :unsupported}
1932 | end
1933 | end
1934 | end
1935 | "
1936 |
1937 | "
1938 | defmodule Whois do
1939 | def lookup1(domain) do
1940 | case Server.for(domain) do
1941 | {:ok, server} -> server
1942 | :error -> {:error, :unsupported}
1943 | end
1944 | end
1945 | end
1946 | ")
1947 |
1948 | (elixir-def-indentation-test close-map-curly-brackt
1949 | (:tags '(indentation))
1950 | "
1951 | config = %{
1952 | async_cases: [],
1953 | exclude: opts[:exclude],
1954 | include: opts[:include],
1955 | timeout: opts[:timeout],
1956 | trace: opts[:trace]
1957 | }
1958 | "
1959 |
1960 | "
1961 | config = %{
1962 | async_cases: [],
1963 | exclude: opts[:exclude],
1964 | include: opts[:include],
1965 | timeout: opts[:timeout],
1966 | trace: opts[:trace]
1967 | }
1968 | ")
1969 |
1970 |
1971 | (elixir-def-indentation-test receive-after-block
1972 | (:tags '(indentation))
1973 | "
1974 | receive do
1975 | {:hello} -> :ok
1976 | other ->
1977 | other
1978 | after
1979 | 2000 ->
1980 | IO.puts 'hello'
1981 | IO.puts 'status 2000 ends'
1982 | { :ok } ->
1983 | IO.puts 'ok'
1984 | _ -> whatever
1985 | end
1986 | "
1987 |
1988 | "
1989 | receive do
1990 | {:hello} -> :ok
1991 | other ->
1992 | other
1993 | after
1994 | 2000 ->
1995 | IO.puts 'hello'
1996 | IO.puts 'status 2000 ends'
1997 | { :ok } ->
1998 | IO.puts 'ok'
1999 | _ -> whatever
2000 | end
2001 | ")
2002 |
2003 | (elixir-def-indentation-test indent-after-for-online-definition
2004 | (:tags '(indentation))
2005 | "
2006 | defmodule Hello do
2007 | def hi do
2008 | hi = for i <- list, do: i
2009 | # weird spacing now
2010 |
2011 | for i <- list, do: i
2012 | IO.puts 'WORKED'
2013 | end
2014 | end
2015 |
2016 | hi = for i <- list, do: i
2017 | # weird spacing now
2018 | "
2019 |
2020 | "
2021 | defmodule Hello do
2022 | def hi do
2023 | hi = for i <- list, do: i
2024 | # weird spacing now
2025 |
2026 | for i <- list, do: i
2027 | IO.puts 'WORKED'
2028 | end
2029 | end
2030 |
2031 | hi = for i <- list, do: i
2032 | # weird spacing now
2033 | ")
2034 |
2035 | (elixir-def-indentation-test indent-oneline-for-after-assignment
2036 | (:expected-result :failed :tags '(indentation))
2037 | "
2038 | hi =
2039 | for i <- list, do: i
2040 | "
2041 |
2042 | "
2043 | hi =
2044 | for i <- list, do: i
2045 | ")
2046 |
2047 | (elixir-def-indentation-test indent-multiline-for
2048 | (:tags '(indentation))
2049 | "
2050 | defmodule For do
2051 | def test do
2052 | for {k, v} <- keyword,
2053 | v = process_value(v),
2054 | into: %{}
2055 | do: {v, k}
2056 |
2057 | for {k, v} <- keyword,
2058 | v = process_value(v),
2059 | into: %{} do
2060 | {v, k}
2061 | end
2062 | end
2063 | end
2064 | "
2065 |
2066 | "
2067 | defmodule For do
2068 | def test do
2069 | for {k, v} <- keyword,
2070 | v = process_value(v),
2071 | into: %{}
2072 | do: {v, k}
2073 |
2074 | for {k, v} <- keyword,
2075 | v = process_value(v),
2076 | into: %{} do
2077 | {v, k}
2078 | end
2079 | end
2080 | end
2081 | ")
2082 |
2083 | (elixir-def-indentation-test indent-multiline-for-with-assignment
2084 | (:expected-result :failed :tags '(indentation))
2085 | "
2086 | result =
2087 | for {k, v} <- keyword,
2088 | v = process_value(v),
2089 | into: %{}
2090 | do: {v, k}
2091 | "
2092 |
2093 | "
2094 | result =
2095 | for {k, v} <- keyword,
2096 | v = process_value(v),
2097 | into: %{}
2098 | do: {v, k}
2099 | ")
2100 |
2101 | (elixir-def-indentation-test indent-multiline-for-do-end-with-assignment
2102 | (:expected-result :failed :tags '(indentation))
2103 | "
2104 | result =
2105 | for {k, v} <- keyword,
2106 | v = process_value(v),
2107 | into: %{}
2108 | do
2109 | {v, k}
2110 | end
2111 | "
2112 |
2113 | "
2114 | result =
2115 | for {k, v} <- keyword,
2116 | v = process_value(v),
2117 | into: %{} do
2118 | {v, k}
2119 | end
2120 | ")
2121 |
2122 | (elixir-def-indentation-test indent-multiline-function-calls-without-parenthesis
2123 | (:tags '(indentation))
2124 | "
2125 | some_method :arg1,
2126 | :arg2
2127 | other_method
2128 | "
2129 |
2130 | "
2131 | some_method :arg1,
2132 | :arg2
2133 | other_method
2134 | ")
2135 |
2136 | (elixir-def-indentation-test indent-multiline-function-calls-without-parenthesis/2
2137 | (:tags '(indentation))
2138 | "
2139 | some_method :arg1,
2140 | arg1: 1,
2141 | arg2: 2
2142 | other_method
2143 | "
2144 |
2145 | "
2146 | some_method :arg1,
2147 | arg1: 1,
2148 | arg2: 2
2149 | other_method
2150 | ")
2151 |
2152 | (elixir-def-indentation-test indent-correct-inside-fn-block
2153 | (:tags '(indentation))
2154 | "
2155 | Enum.map(addresses, fn({mac_address, dbms}) ->
2156 | sum = Enum.reduce(dbms, fn(x, sum) -> x + sum end)
2157 | average_dbm = sum / length(addresses)
2158 | end)
2159 | "
2160 |
2161 | "
2162 | Enum.map(addresses, fn({mac_address, dbms}) ->
2163 | sum = Enum.reduce(dbms, fn(x, sum) -> x + sum end)
2164 | average_dbm = sum / length(addresses)
2165 | end)
2166 | ")
2167 |
2168 | (elixir-def-indentation-test indent-statement-with-anonymous-fn
2169 | (:tags '(indentation))
2170 | "
2171 | cond do
2172 | is_nil(val) ->
2173 | IO.puts \"OK\"
2174 | Enum.any?(1..6, fn -> end)
2175 | true ->
2176 | end
2177 | "
2178 |
2179 | "
2180 | cond do
2181 | is_nil(val) ->
2182 | IO.puts \"OK\"
2183 | Enum.any?(1..6, fn -> end)
2184 | true ->
2185 | end
2186 | ")
2187 |
2188 | (elixir-def-indentation-test indent-maps-inside-list
2189 | (:tags '(indentation))
2190 | "
2191 | [{:earmark, \"~> 0.1\", only: :dev},
2192 | {:earmark, \"~> 0.1\", only: :dev},
2193 | {:ex_doc, \"~> 0.11\", only: :dev},
2194 | {:ex_doc, \"~> 0.11\", only: :dev}]
2195 |
2196 | [
2197 | {1, 2, 3},
2198 | {4, 5, 6},
2199 | {7, 8, 9}
2200 | ]
2201 |
2202 | [
2203 | %{
2204 | name: \"John Doe\",
2205 | email: \"john@doe.org\"
2206 | },
2207 | %{
2208 | name: \"Jane Doe\",
2209 | email: \"jane@doe.org\",
2210 | },
2211 | %{
2212 | name: \"Josie Doe\",
2213 | email: \"josie@doe.org\",
2214 | },
2215 | ]
2216 | "
2217 |
2218 | "
2219 | [{:earmark, \"~> 0.1\", only: :dev},
2220 | {:earmark, \"~> 0.1\", only: :dev},
2221 | {:ex_doc, \"~> 0.11\", only: :dev},
2222 | {:ex_doc, \"~> 0.11\", only: :dev}]
2223 |
2224 | [
2225 | {1, 2, 3},
2226 | {4, 5, 6},
2227 | {7, 8, 9}
2228 | ]
2229 |
2230 | [
2231 | %{
2232 | name: \"John Doe\",
2233 | email: \"john@doe.org\"
2234 | },
2235 | %{
2236 | name: \"Jane Doe\",
2237 | email: \"jane@doe.org\",
2238 | },
2239 | %{
2240 | name: \"Josie Doe\",
2241 | email: \"josie@doe.org\",
2242 | },
2243 | ]
2244 | ")
2245 |
2246 | (elixir-def-indentation-test indent-before-comma
2247 | (:tags '(indentation))
2248 | "
2249 | defmodule TestIndentation do
2250 | import Enum
2251 |
2252 | @att %{ab: 21,
2253 | de: 22}
2254 | IO.inspect @att
2255 | end
2256 | "
2257 |
2258 | "
2259 | defmodule TestIndentation do
2260 | import Enum
2261 |
2262 | @att %{ab: 21,
2263 | de: 22}
2264 | IO.inspect @att
2265 | end
2266 | ")
2267 |
2268 | (elixir-def-indentation-test indent-complex-comments
2269 | (:tags '(indentation))
2270 | "
2271 | defmodule Foo do
2272 | @initial_state %{
2273 | socket: nil,
2274 | opts: nil,
2275 | # TODO: x,
2276 | tail: \"\",
2277 | }
2278 |
2279 | def bar do
2280 |
2281 | first = {
2282 | asdasd,
2283 | asdasd,
2284 | asdad,
2285 | # comment
2286 | # another comment
2287 | value,
2288 | another
2289 | }
2290 |
2291 | another = [
2292 | asdasd,
2293 | asdasd,
2294 | asdad,
2295 | # comment
2296 | # another comment
2297 | value,
2298 | another
2299 | ]
2300 |
2301 | end
2302 |
2303 | # one
2304 | # two
2305 | # three
2306 | end
2307 | "
2308 |
2309 | "
2310 | defmodule Foo do
2311 | @initial_state %{
2312 | socket: nil,
2313 | opts: nil,
2314 | # TODO: x,
2315 | tail: \"\",
2316 | }
2317 |
2318 | def bar do
2319 |
2320 | first = {
2321 | asdasd,
2322 | asdasd,
2323 | asdad,
2324 | # comment
2325 | # another comment
2326 | value,
2327 | another
2328 | }
2329 |
2330 | another = [
2331 | asdasd,
2332 | asdasd,
2333 | asdad,
2334 | # comment
2335 | # another comment
2336 | value,
2337 | another
2338 | ]
2339 |
2340 | end
2341 |
2342 | # one
2343 | # two
2344 | # three
2345 | end
2346 | ")
2347 |
2348 | (elixir-def-indentation-test indent-tuples-in-case-statement
2349 | (:tags '(indentation))
2350 | "
2351 | defmodule Foo do
2352 | def bar do
2353 | case info do
2354 | :init ->
2355 | {:foo, :bar}
2356 | {:foo, :bar}
2357 | {:foo, :bar}
2358 | {:foo, :bar}
2359 | end
2360 | end
2361 | end
2362 | "
2363 |
2364 | "
2365 | defmodule Foo do
2366 | def bar do
2367 | case info do
2368 | :init ->
2369 | {:foo, :bar}
2370 | {:foo, :bar}
2371 | {:foo, :bar}
2372 | {:foo, :bar}
2373 | end
2374 | end
2375 | end
2376 | "
2377 | )
2378 |
2379 | (elixir-def-indentation-test indent-multiline-function-specs
2380 | (:expected-result :failed :tags '(indentation))
2381 | "
2382 | @callback init(args :: term) ::
2383 | {:ok, state} |
2384 | {:ok, state, timeout | :hibernate} |
2385 | :ignore |
2386 | {:stop, reason :: any} when state: any
2387 | "
2388 |
2389 | "
2390 | @callback init(args :: term) ::
2391 | {:ok, state} |
2392 | {:ok, state, timeout | :hibernate} |
2393 | :ignore |
2394 | {:stop, reason :: any} when state: any
2395 | ")
2396 |
2397 | (elixir-def-indentation-test indent-multiline-function-specs-followed-by-a-function-def
2398 | (:expected-result :failed :tags '(indentation))
2399 | "
2400 | @spec foo(args :: term) ::
2401 | {:ok, state} |
2402 | {:ok, state, timeout | :hibernate} |
2403 | :ignore |
2404 | {:stop, reason :: any} when state: any
2405 | def foo(_opts) do
2406 | :ignore
2407 | end
2408 | "
2409 |
2410 | "
2411 | @spec foo(args :: term) ::
2412 | {:ok, state} |
2413 | {:ok, state, timeout | :hibernate} |
2414 | :ignore |
2415 | {:stop, reason :: any} when state: any
2416 | def foo(_opts) do
2417 | :ignore
2418 | end
2419 | ")
2420 |
2421 | ;; We don't want automatic whitespace cleanup here because of the significant
2422 | ;; whitespace after `Record' above. By setting `whitespace-action' to nil,
2423 | ;; `whitespace-mode' won't automatically clean up trailing whitespace (in my
2424 | ;; config, anyway).
2425 |
2426 | ;;; Local Variables:
2427 | ;;; whitespace-action: nil
2428 | ;;; End:
2429 |
2430 | (provide 'elixir-mode-indentation-test)
2431 |
2432 | ;;; elixir-mode-indentation-test.el ends here
2433 |
--------------------------------------------------------------------------------
/tests/elixir-mode-moving-test.el:
--------------------------------------------------------------------------------
1 | ;;; elixir-mode-moving-test.el --- Tests for moving cursor functions
2 |
3 | ;;; Code:
4 | (require 'test-helper)
5 |
6 | (ert-deftest beginning-of-defun ()
7 | :tags '(moving)
8 | (elixir-test-with-temp-buffer
9 | "def foo do
10 | :bar
11 | end
12 | "
13 | (search-forward ":bar")
14 | (call-interactively 'beginning-of-defun)
15 | (should (= (point) (point-min)))))
16 |
17 | (ert-deftest beginning-of-defun-nested ()
18 | :tags '(moving)
19 | (elixir-test-with-temp-buffer
20 | "
21 | defmodule Foo do
22 | def bar do
23 | :baz
24 | end
25 | end
26 | "
27 | (search-forward ":baz")
28 | (call-interactively 'beginning-of-defun)
29 | (should (and (= (line-number-at-pos) 3) (bolp)))))
30 |
31 | (ert-deftest end-of-defun ()
32 | :tags '(moving)
33 | (elixir-test-with-temp-buffer
34 | "def foo do
35 | :bar
36 | end
37 | "
38 | (search-forward ":bar")
39 | (call-interactively 'end-of-defun)
40 | (should (= (point) (point-max)))))
41 |
42 | (ert-deftest end-of-defun-oneline ()
43 | :tags '(moving)
44 | (elixir-test-with-temp-buffer
45 | "def foo do: :bar"
46 | (search-forward ":bar")
47 | (call-interactively 'end-of-defun)
48 | (should (= (point) (line-end-position)))))
49 |
50 | (ert-deftest end-of-defun-nested ()
51 | :tags '(moving)
52 | (elixir-test-with-temp-buffer
53 | "
54 | defmodule Foo do
55 | def bar do
56 | :baz
57 | end
58 | end
59 | "
60 | (forward-line 1)
61 | (call-interactively 'end-of-defun)
62 | (should (= (point) (point-max)))
63 |
64 | (goto-char (point-min))
65 | (search-forward ":baz")
66 | (call-interactively 'end-of-defun)
67 | (should (and (= (line-number-at-pos) 6) (bolp)))))
68 |
69 | (ert-deftest end-of-mark-defun ()
70 | :tags '(moving)
71 | (elixir-test-with-temp-buffer
72 | "
73 | defmodule Foo do
74 | def bar do
75 | :baz
76 | end
77 | end
78 | "
79 | (goto-char (point-min))
80 | (search-forward ":baz")
81 | (call-interactively 'mark-defun)
82 | (should (= (count-lines (region-beginning) (region-end)) 3))))
83 |
84 | (provide 'elixir-mode-moving-test)
85 |
86 | ;;; elixir-mode-helper-test.el ends here
87 |
--------------------------------------------------------------------------------
/tests/test-helper.el:
--------------------------------------------------------------------------------
1 | ;;; test-helper.el --- Test helper
2 |
3 | ;;; Commentary:
4 | ;;
5 |
6 | ;;; Code:
7 |
8 | (require 'ert-x) ; `ert-with-test-buffer'
9 | (require 'cl-lib) ; `cl-defmacro'
10 |
11 | (message "Running tests on Emacs %s" emacs-version)
12 |
13 | ;; The test fixtures assume an indentation width of 2, so we need to set that
14 | ;; up for the tests.
15 | (setq-default default-tab-width 2
16 | indent-tabs-mode nil)
17 |
18 | ;; Load the elixir-mode under test
19 | (require 'elixir-mode)
20 |
21 | ;; Load elixir-format under test
22 | (require 'elixir-format)
23 |
24 | ;; Helpers
25 |
26 | (cl-defmacro elixir-deftest (name args &body body)
27 | (declare (indent 2))
28 | `(ert-deftest ,(intern (format "elixir-ert-%s" name)) ()
29 | ""
30 | ,@args
31 | (let ((elixir-smie-verbose-p t))
32 | ,@body)))
33 |
34 | (cl-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 | (cl-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 | (setq elixir-format-elixir-path (executable-find "elixir"))
66 | (setq elixir-format-mix-path (executable-find "mix"))
67 |
68 | (defconst elixir-format-test-example "defmodule Foo do
69 | use GenServer.Behaviour
70 | def foobar do
71 | if true, do: IO.puts \"yay\"
72 | end
73 | end")
74 |
75 | (defconst elixir-format-wrong-test-example "defmodule Foo do
76 | use GenServer.Behaviour
77 | def foobar do
78 | if true, do: IO.puts \"yay\"
79 | end")
80 |
81 | (setq elixir-version (let ((str (shell-command-to-string (concat elixir-format-elixir-path " --version"))))
82 | (car (when (string-match "\s\\(.+[.].+[.].+\\)[\s\n]" str)
83 | (list (match-string 1 str))))))
84 |
85 | (defconst elixir-formatter-supported
86 | (>= (string-to-number elixir-version) (string-to-number "1.6"))
87 | )
88 |
89 | (defconst elixir-format-formatted-test-example
90 | "defmodule Foo do
91 | use GenServer.Behaviour
92 |
93 | def foobar do
94 | if true, do: IO.puts(\"yay\")
95 | end
96 | end
97 | ")
98 |
99 | (provide 'test-helper)
100 | ;;; test-helper.el ends here
101 |
--------------------------------------------------------------------------------