├── .gitignore ├── bin ├── vim ├── nvim ├── test_directory ├── projects_to_test.txt ├── test_for_regressions ├── rspec ├── profile └── test_indent ├── Dockerfile ├── Gemfile ├── Dockerfile.nvim ├── spec ├── indent │ ├── cond_spec.rb │ ├── macro_spec.rb │ ├── quote_spec.rb │ ├── string_spec.rb │ ├── receive_spec.rb │ ├── tuples_spec.rb │ ├── struct_spec.rb │ ├── def_spec.rb │ ├── try_spec.rb │ ├── documentation_spec.rb │ ├── exunit_spec.rb │ ├── if_spec.rb │ ├── map_spec.rb │ ├── embedded_elixir_spec.rb │ ├── keyword_spec.rb │ ├── ecto_queries_spec.rb │ ├── binary_operator_spec.rb │ ├── comment_spec.rb │ ├── ecto_spec.rb │ ├── anonymous_functions_spec.rb │ ├── blocks_spec.rb │ ├── case_spec.rb │ ├── pipeline_spec.rb │ ├── with_spec.rb │ ├── lists_spec.rb │ ├── embedded_views_spec.rb │ └── basic_spec.rb ├── syntax │ ├── records_spec.rb │ ├── case_spec.rb │ ├── module_function_spec.rb │ ├── map_spec.rb │ ├── tuple_spec.rb │ ├── guard_spec.rb │ ├── kernel_function_spec.rb │ ├── function_spec.rb │ ├── keyword_spec.rb │ ├── operator_spec.rb │ ├── anonymous_function_spec.rb │ ├── comments_spec.rb │ ├── defmodule_spec.rb │ ├── list_spec.rb │ ├── numbers_spec.rb │ ├── alias_spec.rb │ ├── embedded_elixir_spec.rb │ ├── variable_spec.rb │ ├── atom_spec.rb │ ├── struct_spec.rb │ ├── strings_spec.rb │ ├── exunit_spec.rb │ ├── sigil_spec.rb │ └── doc_spec.rb ├── folding │ └── basic_spec.rb └── spec_helper.rb ├── ISSUE_TEMPLATE.md ├── .travis.yml ├── compiler ├── credo.vim ├── mix.vim └── exunit.vim ├── ftdetect └── elixir.vim ├── docker-compose.yml ├── indent ├── elixir.vim └── eelixir.vim ├── test.init.vim ├── test.vimrc ├── LICENSE ├── autoload ├── db │ └── adapter │ │ ├── ecto.vim │ │ └── get_repos.exs └── elixir │ ├── util.vim │ └── indent.vim ├── Gemfile.lock ├── ftplugin ├── elixir.vim └── eelixir.vim ├── manual_install.sh ├── syntax ├── eelixir.vim └── elixir.vim ├── doc └── elixir.txt ├── README.md └── large_file.ex /.gitignore: -------------------------------------------------------------------------------- 1 | profile.log 2 | test_indent.result 3 | doc/tags 4 | .gvim_path 5 | -------------------------------------------------------------------------------- /bin/vim: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | docker-compose build 3 | docker-compose run vim 4 | -------------------------------------------------------------------------------- /bin/nvim: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | docker-compose build 3 | docker-compose run nvim nvim $1 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | 3 | RUN apt-get update && apt-get install -yf vim 4 | 5 | COPY test.vimrc /root/.vimrc 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rspec' 4 | gem 'vimrunner' 5 | gem 'pry' 6 | gem 'diffy' 7 | gem 'parallel_tests' 8 | -------------------------------------------------------------------------------- /bin/test_directory: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | dirs=`find $1 -iname '*.ex' -o -iname '*.exs' | grep -v '/deps/'` 3 | bin/test_indent $dirs 4 | -------------------------------------------------------------------------------- /Dockerfile.nvim: -------------------------------------------------------------------------------- 1 | # vi: ft=dockerfile 2 | FROM ubuntu:latest 3 | 4 | RUN apt-get update && apt-get install -yf neovim 5 | 6 | RUN mkdir -p /root/.config/nvim 7 | 8 | COPY test.init.vim /root/.config/nvim/init.vim 9 | -------------------------------------------------------------------------------- /bin/projects_to_test.txt: -------------------------------------------------------------------------------- 1 | wistia/ttl_cache 2 | wistia/generational_cache 3 | wistia/gen_poller 4 | wistia/assignment_ex 5 | wistia/http_monitor 6 | wistia/impersonate_ex 7 | wistia/m3u8_parser 8 | wistia/simple_http_server 9 | -------------------------------------------------------------------------------- /bin/test_for_regressions: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | rm -rf tmp 3 | cat bin/projects_to_test.txt | xargs -n 1 -I{} sh -c 'git clone -q git@github.com:{}.git tmp && echo "Testing directory: {}..." && bin/test_directory tmp && rm -rf tmp' 4 | -------------------------------------------------------------------------------- /spec/indent/cond_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting cond statements' do 6 | i <<~EOF 7 | cond do 8 | foo -> 1 9 | bar -> 2 10 | end 11 | EOF 12 | end 13 | -------------------------------------------------------------------------------- /spec/indent/macro_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Macros' do 4 | i <<~EOF 5 | defmodule DeadboltTest do 6 | use ExUnit.Case 7 | doctest Deadbolt 8 | 9 | hello 10 | 11 | end 12 | EOF 13 | end 14 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Filing a bug? Have you already tried updating `vim-elixir`? For indentation/highlighting bugs, please use the following template: 2 | 3 | # Actual 4 | 5 | ```ex 6 | Example code 7 | ``` 8 | 9 | # Expected 10 | 11 | ```ex 12 | Example code 13 | ``` 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.3.1 4 | before_install: sudo apt-get install vim-gtk 5 | before_script: 6 | - "vim --version" 7 | - "export DISPLAY=:99.0" 8 | - "sh -e /etc/init.d/xvfb start" 9 | script: "grep 'focus:.*true' -R spec/indent spec/syntax && exit -1 || CI=true bin/rspec --color -b -f d" 10 | -------------------------------------------------------------------------------- /spec/syntax/records_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # encoding: utf-8 4 | require 'spec_helper' 5 | 6 | describe 'Record syntax' do 7 | it 'private record symbol' do 8 | expect(<<~EOF).to include_elixir_syntax('elixirAtom', ':user') 9 | defrecordp :user, name: "José", age: 25 10 | EOF 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /compiler/credo.vim: -------------------------------------------------------------------------------- 1 | if exists('current_compiler') 2 | finish 3 | endif 4 | let current_compiler = 'credo' 5 | 6 | if exists(":CompilerSet") != 2 7 | command -nargs=* CompilerSet setlocal 8 | endif 9 | 10 | CompilerSet errorformat=%f:%l:%c:\ %t:\ %m,%f:%l:\ %t:\ %m 11 | CompilerSet makeprg=mix\ credo\ suggest\ --format=flycheck 12 | -------------------------------------------------------------------------------- /ftdetect/elixir.vim: -------------------------------------------------------------------------------- 1 | au BufRead,BufNewFile *.lexs set filetype=elixir "File extension used by https://github.com/mhanberg/temple 2 | au BufRead,BufNewFile * call s:DetectElixir() 3 | 4 | function! s:DetectElixir() 5 | if (!did_filetype() || &filetype !=# 'elixir') && getline(1) =~# '^#!.*\' 6 | set filetype=elixir 7 | endif 8 | endfunction 9 | -------------------------------------------------------------------------------- /spec/syntax/case_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Syntax case statements' do 6 | it ':* is recognized as an atom' do 7 | expect(<<~EOF).to include_elixir_syntax('elixirAtom', '\*') 8 | case pattern do 9 | :* -> :ok 10 | _ -> :error 11 | end 12 | EOF 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | vim: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | volumes: 8 | - .:/root/vim-elixir 9 | working_dir: /root/vim-elixir 10 | nvim: 11 | build: 12 | context: . 13 | dockerfile: Dockerfile.nvim 14 | volumes: 15 | - .:/root/vim-elixir 16 | working_dir: /root/vim-elixir 17 | -------------------------------------------------------------------------------- /spec/indent/quote_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting quote statements' do 6 | i <<~EOF 7 | defmacro foo do 8 | quote do 9 | unquote(foo) 10 | end 11 | end 12 | EOF 13 | 14 | i <<~EOF 15 | defmacro foo do 16 | if 1 = 1 do 17 | quote do 18 | unquote(foo) 19 | end 20 | end 21 | end 22 | EOF 23 | end 24 | -------------------------------------------------------------------------------- /spec/indent/string_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting strings' do 6 | it "bulk indenting strings" do 7 | expect(<<~EOF).to be_elixir_indentation 8 | defp sql do 9 | """ 10 | SELECT * 11 | FROM table 12 | WHERE column = 123 13 | AND another_column = 456 14 | """ 15 | end 16 | EOF 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /indent/elixir.vim: -------------------------------------------------------------------------------- 1 | if exists("b:did_indent") 2 | finish 3 | end 4 | let b:did_indent = 1 5 | 6 | setlocal indentexpr=elixir#indent(v:lnum) 7 | 8 | setlocal indentkeys+==after,=catch,=do,=else,=end,=rescue, 9 | setlocal indentkeys+=*,=->,=\|>,=<>,0},0],0),> 10 | 11 | " TODO: @jbodah 2017-02-27: all operators should cause reindent when typed 12 | 13 | function! elixir#indent(lnum) 14 | return elixir#indent#indent(a:lnum) 15 | endfunction 16 | -------------------------------------------------------------------------------- /compiler/mix.vim: -------------------------------------------------------------------------------- 1 | if exists('current_compiler') 2 | finish 3 | endif 4 | let current_compiler = 'mix' 5 | 6 | if exists(":CompilerSet") != 2 7 | command -nargs=* CompilerSet setlocal 8 | endif 9 | 10 | CompilerSet makeprg=mix\ compile 11 | CompilerSet errorformat= 12 | \%Wwarning:\ %m, 13 | \%C%f:%l,%Z, 14 | \%E==\ Compilation\ error\ in\ file\ %f\ ==, 15 | \%C**\ (%\\w%\\+)\ %f:%l:\ %m,%Z 16 | 17 | -------------------------------------------------------------------------------- /spec/indent/receive_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'receive indent' do 4 | i <<~EOF 5 | receive do 6 | after 7 | end 8 | EOF 9 | 10 | i <<~EOF 11 | def obtain_lock(pid, key, timeout \\ 60_000) do 12 | case GenServer.call(pid, {:obtain_lock, key}) do 13 | :will_notify -> 14 | receive do 15 | after 16 | timeout -> 17 | end 18 | res -> res 19 | end 20 | end 21 | EOF 22 | end 23 | -------------------------------------------------------------------------------- /test.init.vim: -------------------------------------------------------------------------------- 1 | set runtimepath+=/root/vim-elixir 2 | 3 | runtime ftdetect/elixir.vim 4 | 5 | filetype plugin indent on 6 | 7 | set ruler 8 | set hidden 9 | 10 | let g:elixir_indent_debug=1 11 | 12 | let mapleader="," 13 | 14 | map syn :echo "hi<" . synIDattr(synID(line("."),col("."),1),"name") . '> trans<' 15 | \ . synIDattr(synID(line("."),col("."),0),"name") . "> lo<" 16 | \ . synIDattr(synIDtrans(synID(line("."),col("."),1)),"name") . ">" 17 | -------------------------------------------------------------------------------- /test.vimrc: -------------------------------------------------------------------------------- 1 | set runtimepath+=/root/vim-elixir 2 | 3 | runtime ftdetect/elixir.vim 4 | 5 | filetype plugin indent on 6 | 7 | set ruler 8 | set hidden 9 | 10 | let g:elixir_indent_debug=1 11 | 12 | let mapleader="," 13 | 14 | map syn :echo "hi<" . synIDattr(synID(line("."),col("."),1),"name") . '> trans<' 15 | \ . synIDattr(synID(line("."),col("."),0),"name") . "> lo<" 16 | \ . synIDattr(synIDtrans(synID(line("."),col("."),1)),"name") . ">" 17 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | # 4 | # This file was generated by Bundler. 5 | # 6 | # The application 'rspec' is installed as part of a gem, and 7 | # this file is here to facilitate running it. 8 | # 9 | 10 | require "pathname" 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 12 | Pathname.new(__FILE__).realpath) 13 | 14 | require "rubygems" 15 | require "bundler/setup" 16 | 17 | load Gem.bin_path("rspec-core", "rspec") 18 | -------------------------------------------------------------------------------- /spec/indent/tuples_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting tuples' do 6 | i <<~EOF 7 | def xpto do 8 | { :a, 9 | :b, 10 | :c } 11 | end 12 | EOF 13 | 14 | i <<~EOF 15 | def method do 16 | { 17 | :bar, 18 | path: "deps/umbrella/apps/bar" 19 | } 20 | end 21 | EOF 22 | 23 | i <<~EOF 24 | x = [ 25 | {:text, "asd {"}, 26 | {:text, "qwe"}, 27 | ] 28 | EOF 29 | end 30 | -------------------------------------------------------------------------------- /spec/syntax/module_function_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Module function syntax' do 6 | it 'for used as module function' do 7 | expect(<<~EOF).to include_elixir_syntax('elixirId', 'for') 8 | OverridesDefault.for 9 | EOF 10 | end 11 | 12 | it 'case used as module function' do 13 | expect(<<~EOF).to include_elixir_syntax('elixirId', 'case') 14 | OverridesDefault.case 15 | EOF 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/syntax/map_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Map syntax' do 6 | it 'maps' do 7 | str = %q(%{name: "one"}) 8 | expect(str).to include_elixir_syntax('elixirMapDelimiter', '%') 9 | expect(str).to include_elixir_syntax('elixirMapDelimiter', '{') 10 | expect(str).to include_elixir_syntax('elixirAtom', 'name:') 11 | expect(str).to include_elixir_syntax('elixirMap', 'name:') 12 | expect(str).to include_elixir_syntax('elixirMapDelimiter', '}') 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/indent/struct_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'defstruct' do 4 | i <<~EOF 5 | defmodule A do 6 | defmodule State do 7 | defstruct field: nil, field: nil, field: nil, 8 | field: [], field: nil, field: 0, 9 | field: false, field: %{} 10 | end 11 | 12 | defmodule State do 13 | defstruct field: nil, field: nil, field: nil 14 | end 15 | 16 | defmodule State do 17 | defstruct field: nil, 18 | field: [], 19 | field: false 20 | end 21 | end 22 | EOF 23 | end 24 | -------------------------------------------------------------------------------- /spec/syntax/tuple_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Tuple syntax' do 6 | it 'tuples' do 7 | str = %q({:name, "one"}) 8 | 9 | expect(str).to include_elixir_syntax('elixirTupleDelimiter', '{') 10 | expect(str).to include_elixir_syntax('elixirTuple', '{') 11 | 12 | expect(str).to include_elixir_syntax('elixirAtom', ':name') 13 | expect(str).to include_elixir_syntax('elixirTuple', ':name') 14 | 15 | expect(str).to include_elixir_syntax('elixirTupleDelimiter', '}') 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/syntax/guard_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'defguard syntax' do 6 | it 'defines `defguard` keyword as elixirGuard' do 7 | expect(<<~EOF).to include_elixir_syntax('elixirGuard', 'defguard') 8 | defguard some_guard(x) when is_integer(x) 9 | EOF 10 | end 11 | 12 | it 'defines `defguardp` keyword as elixirPrivateGuard' do 13 | expect(<<~EOF).to include_elixir_syntax('elixirPrivateGuard', 'defguardp') 14 | defguardp some_private_guard(x) when is_integer(x) 15 | EOF 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/indent/def_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'def indentation' do 4 | i <<~EOF 5 | def handle_call({:release_lock, key}, _from, state) do 6 | case get_lock(state, key) do 7 | nil -> 8 | {:reply, {:error, :already_unlocked}, state} 9 | 10 | _ -> 11 | new_state = delete_lock(state, key) 12 | {:reply, :ok, new_state} 13 | end 14 | end 15 | 16 | def 17 | EOF 18 | 19 | i <<~EOF 20 | defmodule Hello do 21 | def hello do 22 | end 23 | #{"\n" * 40} 24 | def world do 25 | end 26 | end 27 | EOF 28 | end 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 Plataformatec 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /spec/syntax/kernel_function_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Kernel function syntax' do 6 | it 'kernel function used as an atom key in a keyword list outside of a block' do 7 | expect(<<~EOF).not_to include_elixir_syntax('elixirKeyword', 'length') 8 | do 9 | plug Plug.Parsers, length: 400_000_000 10 | end 11 | EOF 12 | end 13 | 14 | it 'kernel function used as an atom key in a keyword list contained in a block' do 15 | expect(<<~EOF).not_to include_elixir_syntax('elixirKeyword', 'length') 16 | plug Plug.Parsers, length: 400_000_000 17 | EOF 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/indent/try_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'try indent' do 4 | i <<~EOF 5 | try do 6 | rescue 7 | end 8 | EOF 9 | 10 | i <<~EOF 11 | try do 12 | catch 13 | end 14 | EOF 15 | 16 | i <<~EOF 17 | try do 18 | after 19 | end 20 | EOF 21 | 22 | i <<~EOF 23 | test "it proceses the command" do 24 | out = "testfile" 25 | try do 26 | cmd = "thing \#{@test_file} \#{out}" 27 | {:ok, 0, _} = Thing.exec(cmd) 28 | after 29 | File.rm!(out) 30 | end 31 | end 32 | EOF 33 | 34 | i <<~EOF 35 | try do 36 | foo() 37 | else 38 | value -> value 39 | end 40 | EOF 41 | end 42 | -------------------------------------------------------------------------------- /compiler/exunit.vim: -------------------------------------------------------------------------------- 1 | if exists("current_compiler") 2 | finish 3 | endif 4 | let current_compiler = "exunit" 5 | 6 | if exists(":CompilerSet") != 2 " older Vim always used :setlocal 7 | command -nargs=* CompilerSet setlocal 8 | endif 9 | 10 | let s:cpo_save = &cpo 11 | set cpo-=C 12 | CompilerSet makeprg=mix\ test 13 | CompilerSet errorformat= 14 | \%E\ \ %n)\ %m, 15 | \%+G\ \ \ \ \ **\ %m, 16 | \%+G\ \ \ \ \ stacktrace:, 17 | \%C\ \ \ \ \ %f:%l, 18 | \%+G\ \ \ \ \ \ \ (%\\w%\\+)\ %f:%l:\ %m, 19 | \%+G\ \ \ \ \ \ \ %f:%l:\ %.%#, 20 | \**\ (%\\w%\\+)\ %f:%l:\ %m 21 | 22 | let &cpo = s:cpo_save 23 | unlet s:cpo_save 24 | 25 | " vim: nowrap sw=2 sts=2 ts=8: 26 | -------------------------------------------------------------------------------- /spec/indent/documentation_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting documentation' do 6 | i <<~EOF 7 | defmodule Test do 8 | @doc """ 9 | end 10 | """ 11 | end 12 | EOF 13 | 14 | it "bulk indenting doc blocks" do 15 | expect(<<~EOF).to be_elixir_indentation 16 | defmodule Test do 17 | @doc """ 18 | do not reindent 19 | any indent that i do 20 | please 21 | """ 22 | end 23 | EOF 24 | end 25 | 26 | i <<~EOF 27 | defmodule Test do 28 | @doc """ 29 | it should 30 | have reasonable 31 | default start indent when typed 32 | """ 33 | EOF 34 | end 35 | -------------------------------------------------------------------------------- /spec/syntax/function_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | # frozen_string_literal: true 3 | 4 | require 'spec_helper' 5 | 6 | describe 'function syntax' do 7 | it 'doesnt treat underscored functions like unsued variables' do 8 | expect(<<~EOF).to include_elixir_syntax('elixirId', '__ensure_defimpl__') 9 | defp derive(protocol, for, struct, opts, env) do 10 | # ... code ... 11 | __ensure_defimpl__(protocol, for, env) 12 | EOF 13 | 14 | expect(<<~EOF).not_to include_elixir_syntax('elixirUnusedVariable', '__ensure_defimpl__') 15 | defp derive(protocol, for, struct, opts, env) do 16 | # ... code ... 17 | __ensure_defimpl__(protocol, for, env) 18 | EOF 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /autoload/db/adapter/ecto.vim: -------------------------------------------------------------------------------- 1 | let s:path = expand(':h') 2 | let s:cmd = join(['mix', 'run', '--no-start', '--no-compile', shellescape(s:path.'/get_repos.exs')]) 3 | 4 | function! s:repo_list() abort 5 | return map(systemlist(s:cmd), 'split(v:val)') 6 | endfunction 7 | 8 | function! db#adapter#ecto#canonicalize(url) abort 9 | for l:item in s:repo_list() 10 | let l:name = get(l:item, 0) 11 | let l:url = get(l:item, 1) 12 | if !empty(l:name) && 'ecto:'.l:name ==# a:url 13 | return l:url 14 | endif 15 | endfor 16 | endfunction 17 | 18 | function! db#adapter#ecto#complete_opaque(url) abort 19 | return map(s:repo_list(), 'v:val[0]') 20 | endfunction 21 | -------------------------------------------------------------------------------- /spec/indent/exunit_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'exunit' do 4 | i <<~EOF 5 | test "test" do 6 | Mod.fun(fn -> 7 | map = %Mod.Map{ 8 | id: "abc123", 9 | state: "processing", 10 | submod: %Mod.Submod{ 11 | options: %{} 12 | } 13 | } 14 | EOF 15 | 16 | i <<~EOF 17 | test "test" do 18 | Mod.fun(fn -> 19 | map = %Mod.Map{ 20 | id: "abc123", 21 | fun: fn -> 22 | IO.inspect :hello 23 | IO.inspect %{ 24 | this_is: :a_map 25 | } 26 | end, 27 | submod: %Mod.Submod{ 28 | options: %{} 29 | } 30 | } 31 | EOF 32 | end 33 | -------------------------------------------------------------------------------- /spec/indent/if_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting if clauses' do 6 | i <<~EOF 7 | if foo do 8 | bar 9 | end 10 | EOF 11 | 12 | i <<~EOF 13 | if foo do 14 | bar 15 | else 16 | baz 17 | end 18 | EOF 19 | 20 | i <<~EOF 21 | def test do 22 | "else" 23 | end 24 | EOF 25 | 26 | i <<~EOF 27 | if true do 28 | else 29 | end 30 | EOF 31 | 32 | i <<~EOF 33 | def exec(command, progress_func \\ fn(_, state) -> state end, key \\ nil, output \\ nil) do 34 | if key do 35 | with_cache(key, output, fn -> do_exec(command, progress_func) end) 36 | else 37 | do_exec(command, progress_func) 38 | end 39 | end 40 | EOF 41 | end 42 | -------------------------------------------------------------------------------- /spec/indent/map_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Map indent' do 6 | i <<~'EOF' 7 | DrMock.mock(fn -> 8 | params = %{ 9 | 10 | } 11 | end) 12 | EOF 13 | 14 | i <<~EOF 15 | x = %{ 16 | foo: :bar 17 | } 18 | 19 | y = :foo 20 | EOF 21 | 22 | i <<~EOF 23 | x = 24 | %{ foo: :bar } 25 | 26 | y = :foo 27 | EOF 28 | 29 | i <<~EOF 30 | x = %{ 31 | foo: :bar } 32 | 33 | y = :foo 34 | EOF 35 | 36 | i <<~EOF 37 | test "test" do 38 | Mod.fun(fn -> 39 | map = %Mod.Map{ 40 | id: "abc123", 41 | state: "processing", 42 | submod: %Mod.Submod{ 43 | options: %{} 44 | } 45 | } 46 | EOF 47 | end 48 | -------------------------------------------------------------------------------- /spec/syntax/keyword_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Keyword syntax' do 6 | it 'for used as keyword' do 7 | expect(<<~EOF).to include_elixir_syntax('elixirKeyword', 'for') 8 | for v <- [1, 3, 3] 9 | EOF 10 | end 11 | 12 | it 'case used as keyword' do 13 | expect(<<~EOF).to include_elixir_syntax('elixirKeyword', 'case') 14 | case true do 15 | EOF 16 | end 17 | 18 | it 'raise used as keyword' do 19 | expect(<<~EOF).to include_elixir_syntax('elixirKeyword', 'raise') 20 | raise "oops" 21 | EOF 22 | 23 | expect(<<~EOF).to include_elixir_syntax('elixirKeyword', 'raise') 24 | raise ArgumentError, message: "invalid argument foo" 25 | EOF 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /bin/profile: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | require 'bundler/setup' 3 | require 'vimrunner' 4 | dir = File.expand_path('..', __dir__) 5 | plugin = 'ftdetect/elixir.vim' 6 | vim = Vimrunner.start_gvim 7 | vim.add_plugin(dir, plugin) 8 | vim.normal ':let g:elixir_indent_debug=0' 9 | vim.edit! 'large_file.ex' 10 | # remove all indentation 11 | vim.normal 'ggVG999<<' 12 | vim.normal ':profile start profile.log' 13 | vim.normal ':profile func *' 14 | vim.normal ':profile file *' 15 | 16 | t1 = Time.now 17 | # force vim to indent the file 18 | vim.normal 'gg=G' 19 | vim.normal ':profile pause' 20 | vim.normal ':q!' 21 | 22 | Process.wait vim.server.pid 23 | t2 = Time.now 24 | 25 | puts "Took #{t2-t1} seconds to indent large_file.ex. Profile logged to profile.log" 26 | -------------------------------------------------------------------------------- /spec/indent/embedded_elixir_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting eelixir' do 6 | it 'anonymous function' do 7 | expect(<<~EOF).to be_eelixir_indentation 8 | <%= form_for @changeset, user_path(@conn, :create), fn f -> %> 9 | It is obviously true 10 | <% end %> 11 | EOF 12 | end 13 | 14 | it 'if..do..end' do 15 | expect(<<~EOF).to be_eelixir_indentation 16 | <%= if true do %> 17 | It is obviously true 18 | <% end %> 19 | EOF 20 | end 21 | 22 | it 'if..do..else..end' do 23 | expect(<<~EOF).to be_eelixir_indentation 24 | <%= if true do %> 25 | It is obviously true 26 | <% else %> 27 | This will never appear 28 | <% end %> 29 | EOF 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/indent/keyword_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Keywords' do 4 | i <<~EOF 5 | def handle_call({:get_in_line_for_lock, key}, from, state) do 6 | queue = state[:queues][key] || :queue.new 7 | queue = queue.in(from, queue) 8 | hello 9 | end 10 | EOF 11 | 12 | # Has cond in milliseconds 13 | i <<~EOF 14 | if arg[:arg] do 15 | finish_time = Timex.Duration.now 16 | start_time = Mod.Mod.arg(@attr, fun(state)) 17 | duration = Timex.Duration.diff(finish_time, start_time, :milliseconds) 18 | Mod.fun(:arg, arg, arg: arg, arg: arg, arg) 19 | e 20 | EOF 21 | 22 | i <<~EOF 23 | Logger.metadata( 24 | task_id: state.recipe.task_id, 25 | hashed_id: state.recipe.config.some_id, 26 | task 27 | ) 28 | EOF 29 | end 30 | -------------------------------------------------------------------------------- /spec/syntax/operator_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Operators' do 6 | it 'default argument' do 7 | expect(<<~'EOF').to include_elixir_syntax('elixirOperator', '\\') 8 | def foo(bar \\ :baz) 9 | EOF 10 | 11 | expect(<<~EOF).to include_elixir_syntax('elixirOperator', '\/') 12 | def foo(bar // :baz) 13 | EOF 14 | end 15 | 16 | it 'in' do 17 | expect(<<~EOF).to include_elixir_syntax('elixirOperator', 'in') 18 | 'x' in ['x'] 19 | EOF 20 | 21 | expect(<<~EOF).not_to include_elixir_syntax('elixirOperator', 'in') 22 | :queue.in x, 5 23 | EOF 24 | end 25 | 26 | it 'does not highlight operators inside of elixirIds' do 27 | expect(<<~EOF).not_to include_elixir_syntax('elixirOperator', 'in') 28 | incoming 29 | EOF 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /autoload/elixir/util.vim: -------------------------------------------------------------------------------- 1 | function! elixir#util#get_filename(word) abort 2 | let word = a:word 3 | 4 | " get first thing that starts uppercase, until the first space or end of line 5 | let word = substitute(word,'^\s*\(\u[^ ]\+\).*$','\1','g') 6 | 7 | " remove any trailing characters that don't look like a nested module 8 | let word = substitute(word,'\.\U.*$','','g') 9 | 10 | " replace module dots with slash 11 | let word = substitute(word,'\.','/','g') 12 | 13 | " remove any special chars 14 | let word = substitute(word,'[^A-z0-9-_/]','','g') 15 | 16 | " convert to snake_case 17 | let word = substitute(word,'\(\u\+\)\(\u\l\)','\1_\2','g') 18 | let word = substitute(word,'\(\u\+\)\(\u\l\)','\1_\2','g') 19 | let word = substitute(word,'\(\l\|\d\)\(\u\)','\1_\2','g') 20 | let word = substitute(word,'-','_','g') 21 | let word = tolower(word) 22 | 23 | return word 24 | endfunction 25 | -------------------------------------------------------------------------------- /spec/indent/ecto_queries_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting Ecto queries' do 6 | i <<~EOF 7 | defmodule New do 8 | def do_query do 9 | from user in Users, 10 | select: user.name, 11 | join: signup in Signups, where: user.id == signup.user_id 12 | end 13 | end 14 | EOF 15 | 16 | i <<~EOF 17 | def smth do 18 | from = 1 19 | to = 7 20 | end 21 | EOF 22 | 23 | i <<~EOF 24 | fromin, 25 | EOF 26 | 27 | i <<~EOF 28 | query = from u in query, select: u.city 29 | EOF 30 | 31 | i <<~EOF 32 | def do_query do 33 | where = [category: "fresh and new"] 34 | order_by = [desc: :published_at] 35 | select = [:id, :title, :body] 36 | from Post, where: ^where, order_by: ^order_by, select: ^select 37 | end 38 | EOF 39 | 40 | i <<~EOF 41 | def alphabetical(query) do 42 | from c in query, order_by: c.name 43 | end 44 | EOF 45 | end 46 | -------------------------------------------------------------------------------- /spec/syntax/anonymous_function_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Anonymous function syntax' do 6 | it 'anonymous function' do 7 | expect(<<~'EOF').to include_elixir_syntax('elixirAnonymousFunction', 'fn') 8 | fn(_, state) -> state end 9 | EOF 10 | end 11 | 12 | it 'as a default argument' do 13 | expect(<<~'EOF').to include_elixir_syntax('elixirAnonymousFunction', 'fn') 14 | def exec(func \\ fn(_, state) -> state end) do 15 | end 16 | EOF 17 | end 18 | 19 | it 'as a default argument in a module' do 20 | str = <<~'EOF' 21 | defmodule HelloWorld do 22 | def exec(func \\ fn(_, state) -> state end) do 23 | end 24 | end 25 | EOF 26 | 27 | expect(str).to include_elixir_syntax('elixirAnonymousFunction', 'fn') 28 | 29 | # Test that the syntax properly closed 30 | expect(str).to include_elixir_syntax('elixirBlockDefinition', '^end') 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | coderay (1.1.1) 5 | diff-lcs (1.2.5) 6 | diffy (3.4.1) 7 | method_source (0.8.2) 8 | parallel (1.12.1) 9 | parallel_tests (2.21.2) 10 | parallel 11 | pry (0.10.4) 12 | coderay (~> 1.1.0) 13 | method_source (~> 0.8.1) 14 | slop (~> 3.4) 15 | rspec (3.5.0) 16 | rspec-core (~> 3.5.0) 17 | rspec-expectations (~> 3.5.0) 18 | rspec-mocks (~> 3.5.0) 19 | rspec-core (3.5.3) 20 | rspec-support (~> 3.5.0) 21 | rspec-expectations (3.5.0) 22 | diff-lcs (>= 1.2.0, < 2.0) 23 | rspec-support (~> 3.5.0) 24 | rspec-mocks (3.5.0) 25 | diff-lcs (>= 1.2.0, < 2.0) 26 | rspec-support (~> 3.5.0) 27 | rspec-support (3.5.0) 28 | slop (3.6.0) 29 | vimrunner (0.3.3) 30 | 31 | PLATFORMS 32 | ruby 33 | x86_64-darwin-19 34 | 35 | DEPENDENCIES 36 | diffy 37 | parallel_tests 38 | pry 39 | rspec 40 | vimrunner 41 | 42 | BUNDLED WITH 43 | 2.2.2 44 | -------------------------------------------------------------------------------- /spec/syntax/comments_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Comments syntax' do 6 | it 'full line comment' do 7 | expect(<<~EOF).to include_elixir_syntax('elixirComment', '#\ this\ is\ a\ comment') 8 | # this is a comment 9 | EOF 10 | end 11 | 12 | it 'end line comment' do 13 | expect(<<~EOF).to include_elixir_syntax('elixirComment', '#\ this\ is\ a\ comment') 14 | IO.puts "some text" # this is a comment 15 | EOF 16 | end 17 | 18 | it 'after arguments' do 19 | t = <<~EOF 20 | def foo(<< 21 | 0 :: 1, # Foo 22 | 1 :: size(1), # Bar 23 | # Blah 24 | baz :: 8, # Baz 25 | >>), do: baz 26 | EOF 27 | expect(t).to include_elixir_syntax('elixirComment', '#\ Foo') 28 | expect(t).to include_elixir_syntax('elixirComment', '#\ Bar') 29 | expect(t).to include_elixir_syntax('elixirComment', '#\ Blah') 30 | expect(t).to include_elixir_syntax('elixirComment', '#\ Baz') 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/indent/binary_operator_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Binary operators' do 6 | i <<~EOF 7 | word = 8 | "h" 9 | <> "e" 10 | <> "l" 11 | <> "l" 12 | <> "o" 13 | 14 | IO.puts word 15 | EOF 16 | 17 | i <<~EOF 18 | def hello do 19 | expected = "hello" 20 | <> "world" 21 | IO.puts expected 22 | end 23 | EOF 24 | 25 | i <<~EOF 26 | def hello do 27 | expected = 28 | "hello" 29 | <> "world" 30 | IO.puts expected 31 | end 32 | EOF 33 | 34 | i <<~EOF 35 | alias Rumbl.Repo 36 | alias Rumbl.Category 37 | 38 | for category <- ~w(Action Drama Romance Comedy Sci-fi) do 39 | Repo.get_by(Category, name: category) || 40 | Repo.insert!(%Category{name: category}) 41 | end 42 | EOF 43 | 44 | i <<~EOF 45 | data = [ 46 | "blah", 47 | "blah2", # * 48 | "blah3" 49 | ] 50 | EOF 51 | 52 | i <<~EOF 53 | data = [ 54 | "blah", 55 | # + 56 | "blah2", 57 | "blah3" 58 | ] 59 | EOF 60 | end 61 | -------------------------------------------------------------------------------- /spec/syntax/defmodule_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Defmodule syntax' do 6 | it 'defines `defmodule` keyword as elixirModuleDefine' do 7 | expect(<<~EOF).to include_elixir_syntax('elixirModuleDefine', 'defmodule') 8 | defmodule HelloPhoenix.HelloController do 9 | EOF 10 | end 11 | 12 | it 'defines module name as elixirModuleDeclaration' do 13 | str = "defmodule HelloPhoenix.HelloController do" 14 | expect(str).to include_elixir_syntax('elixirModuleDeclaration', 'HelloPhoenix') 15 | expect(str).to include_elixir_syntax('elixirModuleDeclaration', '\.') 16 | expect(str).to include_elixir_syntax('elixirModuleDeclaration', 'HelloController') 17 | end 18 | 19 | it 'does not define module name as elixirAlias' do 20 | expect(<<~EOF).not_to include_elixir_syntax('elixirAlias', 'HelloPhoenix.HelloController') 21 | defmodule HelloPhoenix.HelloController do 22 | EOF 23 | end 24 | 25 | it 'defines `do` keyword as elixirBlock' do 26 | expect(<<~EOF).to include_elixir_syntax('elixirBlock', 'do') 27 | defmodule HelloPhoenix.HelloController do 28 | EOF 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/syntax/list_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'List syntax' do 6 | it 'should properly handle "\\\\" inside' do 7 | syntax = <<~EOF 8 | '"\\\\' 9 | var = 1 10 | EOF 11 | expect(syntax).to include_elixir_syntax('elixirId', 'var') 12 | expect(syntax).not_to include_elixir_syntax('elixirString', 'var') 13 | end 14 | 15 | it 'recognizes lists' do 16 | syntax = <<~EOF 17 | [ 18 | :hello, 19 | :world 20 | ] 21 | EOF 22 | expect(syntax).to include_elixir_syntax('elixirListDelimiter', '[') 23 | expect(syntax).to include_elixir_syntax('elixirList', ':hello') 24 | expect(syntax).to include_elixir_syntax('elixirListDelimiter', ']') 25 | end 26 | 27 | it 'recognizes lists inside functions' do 28 | syntax = <<~EOF 29 | def hello_world do 30 | [ 31 | :hello, 32 | :world 33 | ] 34 | end 35 | EOF 36 | expect(syntax).to include_elixir_syntax('elixirListDelimiter', '[') 37 | expect(syntax).to include_elixir_syntax('elixirList', ':hello') 38 | expect(syntax).to include_elixir_syntax('elixirListDelimiter', ']') 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/indent/comment_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting *after* comments' do 6 | i <<~EOF 7 | # do 8 | IO.puts :test 9 | EOF 10 | 11 | i <<~EOF 12 | defmodule Foo do 13 | def run do 14 | list = 15 | File.read!("/path/to/file") 16 | |> String.split() 17 | # now start a new line 18 | # used to start here 19 | # but now starts here 20 | end 21 | end 22 | EOF 23 | 24 | i <<~EOF 25 | defmodule Foo do 26 | def run(task) when task in [:t1, :t2] do 27 | end 28 | 29 | # now starts a new line 30 | # use to start here 31 | # but now starts here 32 | end 33 | EOF 34 | 35 | i <<~EOF 36 | receive do 37 | {{:lock_ready, ^key}, ^pid} -> 38 | after 39 | # NOTE: @jbodah 2017-03-28: we should do some math to adjust the timeout 40 | timeout -> 41 | {:error, :timed_out_waiting_for_lock} 42 | end 43 | EOF 44 | 45 | it "bulk indenting comments" do 46 | expect(<<~EOF).to be_elixir_indentation 47 | defmodule Test do 48 | # SELECT * 49 | # FROM table 50 | # WHERE column = 123 51 | # AND another_column = 456 52 | end 53 | EOF 54 | end 55 | end 56 | 57 | -------------------------------------------------------------------------------- /bin/test_indent: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | require 'bundler/setup' 3 | require 'vimrunner' 4 | require 'diffy' 5 | require 'fileutils' 6 | 7 | dir = File.expand_path('..', __dir__) 8 | plugin = 'ftdetect/elixir.vim' 9 | 10 | def bm 11 | t1 = Time.now 12 | yield 13 | Time.now - t1 14 | end 15 | 16 | def detect_change(f) 17 | pre = File.read(f) 18 | pre = strip_doc_blocks(pre) 19 | yield 20 | post = File.read('test_indent.result') 21 | post = strip_doc_blocks(post) 22 | pre == post ? nil : Diffy::Diff.new(pre, post) 23 | end 24 | 25 | def strip_doc_blocks(body) 26 | body.gsub(/@\w+ """.*"""/m, '') 27 | end 28 | 29 | 30 | ARGV.each do |f| 31 | vim = Vimrunner.start_gvim 32 | vim.add_plugin(dir, plugin) 33 | 34 | vim.edit! f 35 | 36 | print "## Testing #{File.expand_path(f)} ... " 37 | time = nil 38 | diff = detect_change(f) do 39 | time = bm do 40 | vim.normal 'ggVG999<<' 41 | vim.normal 'gg=G' 42 | vim.normal ':w! test_indent.result' 43 | 44 | vim.normal ':q!' 45 | Process.wait vim.server.pid 46 | end 47 | end 48 | 49 | if diff 50 | puts "error [#{time}s]" 51 | puts diff 52 | else 53 | puts "ok [#{time}s]" 54 | end 55 | end 56 | 57 | FileUtils.rm 'test_indent.result' 58 | -------------------------------------------------------------------------------- /spec/syntax/numbers_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Numbers syntax' do 6 | describe 'decimal' do 7 | it 'positive is colorized' do 8 | expect('123').to include_elixir_syntax('elixirNumber', '123') 9 | end 10 | 11 | it 'negative is colorized' do 12 | expect('-123').to include_elixir_syntax('elixirNumber', '123') 13 | end 14 | end 15 | 16 | describe 'hexadecimal' do 17 | it 'positive is colorized' do 18 | expect('0xdeadbeaf').to include_elixir_syntax('elixirNumber', '0xdeadbeaf') 19 | end 20 | 21 | it 'negative is colorized' do 22 | expect('-0xdeadbeaf').to include_elixir_syntax('elixirNumber', '0xdeadbeaf') 23 | end 24 | end 25 | 26 | describe 'octal' do 27 | it 'positive is colorized' do 28 | expect('0o777').to include_elixir_syntax('elixirNumber', '0o777') 29 | end 30 | 31 | it 'negative is colorized' do 32 | expect('-0o777').to include_elixir_syntax('elixirNumber', '0o777') 33 | end 34 | end 35 | 36 | describe 'binary' do 37 | it 'positive is colorized' do 38 | expect('0b1011').to include_elixir_syntax('elixirNumber', '0b1011') 39 | end 40 | 41 | it 'negative is colorized' do 42 | expect('-0b1011').to include_elixir_syntax('elixirNumber', '0b1011') 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/indent/ecto_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'EctoEnum' do 4 | i <<~EOF 5 | defmodule Onemedical.Types do 6 | import EctoEnum 7 | defenum(Work.Occupation, :work_occupation, [ 8 | :actor, :architect, :athlete, :baker, :bank_clerk, :banker, :barber, :blogger, 9 | :bricklayer, :broadcaster, :builder, :captain, :carpenter, :choreographer, 10 | :computer_engineer, :computer_programmer, :custom_officer, :dancer, :designer, 11 | :director, :doctor, :driver, :editor, :entertainer, :engineer, :facility_manager, 12 | :farmer, :fashion_designer, :geologist, :goldsmith, :graphic_designer, :hairdresser, 13 | :host_hostess, :house_girl, :interior_designer, :judge, :land_surveyor, :lecturer, 14 | :make_up_artist, :manager, :mechanic, :midwife, :model, :music_director, :musician, 15 | :nanny, :nurse, :pastor, :paediatrician, :photographer, :physicist, :pilot, :plumber, 16 | :police_officer, :printer, :producer, :publisher, :quality_inspector, :radiographer, 17 | :real_estate_agent, :referee, :refuse_collector, :registrar, :safety_engineer, :sales_manager, 18 | :script_writer, :secretary, :security_guard, :shoemaker, :songwriter, :sound_engineer, 19 | :stock_broker, :surveyor, :tailor, :teacher, :telecommunications_engineer, :usher, 20 | :waiter, :writer, :zookeeper, :other]) 21 | defenum(Work.Type, :work_type, [ 22 | :full_time, :part_time, :volunteer, :temporary 23 | ]) 24 | end 25 | EOF 26 | end 27 | -------------------------------------------------------------------------------- /spec/indent/anonymous_functions_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting anonymous functions' do 6 | i <<~EOF 7 | def do 8 | some_func = fn x -> x end 9 | end 10 | EOF 11 | 12 | i <<~EOF 13 | def do 14 | some_func = function do x -> x end 15 | end 16 | EOF 17 | 18 | i <<~EOF 19 | def test do 20 | assert_raise Queue.Empty, fn -> 21 | Q.new |> Q.deq! 22 | end 23 | end 24 | EOF 25 | 26 | i <<~EOF 27 | defmodule Test do 28 | def lol do 29 | Enum.map([1,2,3], fn x -> 30 | x * 3 31 | end) 32 | end 33 | end 34 | EOF 35 | 36 | i <<~EOF 37 | fizzbuzz = fn 38 | 0, 0, _ -> "FizzBuzz" 39 | 0, _, _ -> "Fizz" 40 | _, 0, _ -> "Buzz" 41 | _, _, x -> x 42 | end 43 | EOF 44 | 45 | i <<~EOF 46 | fizzbuzz = function do 47 | 0, 0, _ -> "FizzBuzz" 48 | 0, _, _ -> "Fizz" 49 | _, 0, _ -> "Buzz" 50 | _, _, x -> x 51 | end 52 | EOF 53 | 54 | i <<~EOF 55 | {:ok, 0} = Mod.exec!(cmd, fn progress -> 56 | if event_handler do 57 | event_handler.({:progress_updated, progress}) 58 | end 59 | end 60 | ) 61 | EOF 62 | 63 | i <<~EOF 64 | defp handle_chunk(:err, line, state) do 65 | update_in(state[:stderr], fn 66 | true -> true 67 | false -> false 68 | end) 69 | 70 | Map.update(state, :stderr, [line], &(&1 ++ [line])) 71 | end 72 | EOF 73 | 74 | i <<~EOF 75 | defp handle_chunk(:err, line, state) do 76 | update_in(state[:stderr], fn 77 | hello -> :ok 78 | world -> :ok 79 | end) 80 | 81 | Map.update(state, :stderr, [line], &(&1 ++ [line])) 82 | end 83 | EOF 84 | 85 | i <<~EOF 86 | fn -> 87 | end 88 | EOF 89 | end 90 | -------------------------------------------------------------------------------- /spec/folding/basic_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Basic folding' do 6 | def self.fold(content) 7 | it("properly folds \n#{content}") do 8 | expect(content).to fold_lines 9 | end 10 | end 11 | 12 | fold <<~EOF 13 | defmodule M do # fold 14 | end # fold 15 | "not in fold" 16 | EOF 17 | 18 | fold <<~EOF 19 | defmodule M do # fold 20 | def some_func do # fold 21 | end # fold 22 | end # fold 23 | "not in fold" 24 | EOF 25 | 26 | fold <<~EOF 27 | defmodule M do 28 | def some_func do # fold 29 | end # fold 30 | end 31 | "not in fold" 32 | EOF 33 | 34 | fold <<~EOF 35 | if true do # fold 36 | end # fold 37 | "not in fold" 38 | EOF 39 | 40 | fold <<~EOF 41 | if true do # fold 42 | nil # fold 43 | else # fold 44 | nil # fold 45 | end # fold 46 | "not in fold" 47 | EOF 48 | 49 | fold <<~EOF 50 | defmodule M do 51 | def some_func do 52 | [ # fold 53 | :hello, # fold 54 | :world # fold 55 | ] # fold 56 | :hello_world 57 | end 58 | end 59 | EOF 60 | 61 | fold <<~EOF 62 | defmodule M do 63 | def some_func do 64 | { # fold 65 | :hello, # fold 66 | :world # fold 67 | } # fold 68 | :hello_world 69 | end 70 | end 71 | EOF 72 | 73 | fold <<~EOF 74 | defmodule M do 75 | def some_func do 76 | %{ # fold 77 | hello: "a", # fold 78 | world: "b" # fold 79 | } # fold 80 | :hello_world 81 | end 82 | end 83 | EOF 84 | 85 | fold <<~EOF 86 | defmodule M do 87 | def some_func do 88 | %User{ # fold 89 | hello: "a", # fold 90 | world: "b" # fold 91 | } # fold 92 | :hello_world 93 | end 94 | end 95 | EOF 96 | end 97 | -------------------------------------------------------------------------------- /spec/syntax/alias_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Alias syntax' do 6 | it 'colorize only module alias' do 7 | str = "Enum.empty?(...)" 8 | expect(str).to include_elixir_syntax('elixirAlias', 'Enum') 9 | expect(str).to include_elixir_syntax('elixirOperator', '\.') 10 | expect(str).to include_elixir_syntax('elixirId', 'empty?') 11 | end 12 | 13 | it 'colorize the module alias even if it starts with `!`' do 14 | expect(<<~EOF).to include_elixir_syntax('elixirAlias', 'Enum') 15 | !Enum.empty?(...) 16 | EOF 17 | end 18 | 19 | it 'does not colorize the preceding ! in an alias' do 20 | expect(<<~EOF).not_to include_elixir_syntax('elixirAlias', '!') 21 | !Enum.empty?(...) 22 | EOF 23 | end 24 | 25 | it 'does not colorize words starting with lowercase letters' do 26 | expect(<<~EOF).not_to include_elixir_syntax('elixirAlias', 'aEnum') 27 | aEnum.empty?(...) 28 | EOF 29 | end 30 | 31 | it 'colorizes numbers in aliases' do 32 | str = "S3Manager" 33 | expect(str).to include_elixir_syntax('elixirAlias', 'S') 34 | expect(str).to include_elixir_syntax('elixirAlias', '3') 35 | expect(str).to include_elixir_syntax('elixirAlias', 'Manager') 36 | end 37 | 38 | it 'colorize dots in module alias' do 39 | str = "Foo.Bar.Baz.fun(...)" 40 | expect(str).to include_elixir_syntax('elixirAlias', 'Foo') 41 | expect(str).to include_elixir_syntax('elixirAlias', '\.\(Bar\)\@=') 42 | expect(str).to include_elixir_syntax('elixirAlias', 'Bar') 43 | expect(str).to include_elixir_syntax('elixirAlias', '\.\(Baz\)\@=') 44 | expect(str).to include_elixir_syntax('elixirAlias', 'Baz') 45 | expect(str).to include_elixir_syntax('elixirOperator', '\.\(fun\)\@=') 46 | expect(str).to include_elixir_syntax('elixirId', 'fun') 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /indent/eelixir.vim: -------------------------------------------------------------------------------- 1 | if exists("b:did_indent") 2 | finish 3 | endif 4 | 5 | runtime! indent/elixir.vim 6 | unlet! b:did_indent 7 | setlocal indentexpr= 8 | 9 | let s:cpo_save = &cpo 10 | set cpo&vim 11 | 12 | if exists("b:eelixir_subtype") 13 | exe "runtime! indent/".b:eelixir_subtype.".vim" 14 | else 15 | runtime! indent/html.vim 16 | endif 17 | unlet! b:did_indent 18 | 19 | if &l:indentexpr == '' 20 | if &l:cindent 21 | let &l:indentexpr = 'cindent(v:lnum)' 22 | else 23 | let &l:indentexpr = 'indent(prevnonblank(v:lnum-1))' 24 | endif 25 | endif 26 | let b:eelixir_subtype_indentexpr = &l:indentexpr 27 | 28 | let b:did_indent = 1 29 | 30 | setlocal indentexpr=GetEelixirIndent() 31 | setlocal indentkeys=o,O,*,<>>,{,},0),0],o,O,!^F,=end,=else,=elsif,=catch,=after,=rescue 32 | 33 | " Only define the function once. 34 | if exists("*GetEelixirIndent") 35 | finish 36 | endif 37 | 38 | function! GetEelixirIndent(...) 39 | if a:0 && a:1 == '.' 40 | let v:lnum = line('.') 41 | elseif a:0 && a:1 =~ '^\d' 42 | let v:lnum = a:1 43 | endif 44 | let vcol = col('.') 45 | call cursor(v:lnum,1) 46 | let inelixir = searchpair('<%','','%>','W') 47 | call cursor(v:lnum,vcol) 48 | if inelixir && getline(v:lnum) !~ '^<%\|^\s*%>' 49 | let ind = GetElixirIndent() 50 | else 51 | exe "let ind = ".b:eelixir_subtype_indentexpr 52 | endif 53 | let lnum = prevnonblank(v:lnum-1) 54 | let line = getline(lnum) 55 | let cline = getline(v:lnum) 56 | if cline =~# '^\s*<%\s*\%(end\|else\|elsif\|catch\|after\|rescue\)\>.*%>' 57 | let ind -= &sw 58 | elseif line =~# '\S\s*<%\s*end\s*%>' 59 | let ind -= &sw 60 | endif 61 | if line =~# '<%[=%]\=\s*.*\(\\)\s*%>' || 62 | \ line =~# '<%\s*\%(else\|elsif\|catch\|after\|rescue\)\>.*%>' 63 | let ind += &sw 64 | endif 65 | if cline =~# '^\s*%>\s*$' 66 | let ind -= &sw 67 | endif 68 | return ind 69 | endfunction 70 | 71 | let &cpo = s:cpo_save 72 | unlet s:cpo_save 73 | -------------------------------------------------------------------------------- /ftplugin/elixir.vim: -------------------------------------------------------------------------------- 1 | if exists('b:did_ftplugin') 2 | finish 3 | endif 4 | let b:did_ftplugin = 1 5 | 6 | " Matchit support 7 | if exists('loaded_matchit') && !exists('b:match_words') 8 | let b:match_ignorecase = 0 9 | 10 | let b:match_words = '\:\@' . 11 | \ ':' . 12 | \ '\<\%(else\|elsif\|catch\|after\|rescue\)\:\@!\>' . 13 | \ ':' . 14 | \ '\:\@' . 15 | \ ',{:},\[:\],(:)' 16 | endif 17 | 18 | setlocal shiftwidth=2 softtabstop=2 expandtab iskeyword+=!,? 19 | setlocal comments=:# 20 | setlocal commentstring=#\ %s 21 | 22 | let &l:path = 23 | \ join([ 24 | \ 'lib/**', 25 | \ 'src/**', 26 | \ 'test/**', 27 | \ 'deps/**/lib/**', 28 | \ 'deps/**/src/**', 29 | \ &g:path 30 | \ ], ',') 31 | setlocal includeexpr=elixir#util#get_filename(v:fname) 32 | setlocal suffixesadd=.ex,.exs,.eex,.heex,.leex,.sface,.erl,.xrl,.yrl,.hrl 33 | 34 | let &l:define = 'def\(macro\|guard\|delegate\)\=p\=' 35 | 36 | silent! setlocal formatoptions-=t formatoptions+=croqlj 37 | 38 | let b:block_begin = '\<\(do$\|fn\>\)' 39 | let b:block_end = '\' 40 | 41 | nnoremap ]] ':silent keeppatterns /'.b:block_begin.'' 42 | nnoremap [[ ':silent keeppatterns ?'.b:block_begin.'' 43 | nnoremap ][ ':silent keeppatterns /'.b:block_end .'' 44 | nnoremap [] ':silent keeppatterns ?'.b:block_end .'' 45 | 46 | onoremap ]] ':silent keeppatterns /'.b:block_begin.'' 47 | onoremap [[ ':silent keeppatterns ?'.b:block_begin.'' 48 | onoremap ][ ':silent keeppatterns /'.b:block_end .'' 49 | onoremap [] ':silent keeppatterns ?'.b:block_end .'' 50 | 51 | let b:undo_ftplugin = 'setlocal sw< sts< et< isk< com< cms< path< inex< sua< def< fo<'. 52 | \ '| unlet! b:match_ignorecase b:match_words b:block_begin b:block_end' 53 | -------------------------------------------------------------------------------- /manual_install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | exit_whit_error_message() { 4 | printf '%s\n' "$1" >&2 5 | exit 1 6 | } 7 | 8 | show_help() { 9 | echo "The script to install the vim-elixir plugin" 10 | echo 11 | echo "Usage: ./manual_install.sh [OPTIONS]" 12 | echo 13 | echo "Options:" 14 | echo "-o, --output-dir string The name of the directory where plugin will be installed. By default the output directory name is 'vim-elixir'" 15 | echo " Example: ./manual_install.sh -o vim-elixir # The plugin will be installed in ~/.vim/pack/vim-elixir/start/vim-elixir directory" 16 | echo " ./manual_install.sh -o elixir-opts # The plugin will be installed in ~/.vim/pack/elixir-opts/start/elixir-opts directory" 17 | echo 18 | } 19 | 20 | # Initialize all the option variables. 21 | # This ensures we are not contaminated by variables from the environment. 22 | VIM_PLUGIN_NAME=vim-elixir 23 | 24 | while :; do 25 | case $1 in 26 | -h|-\?|--help) 27 | show_help 28 | exit 29 | ;; 30 | -o|--output-dir) 31 | if [ "$2" ]; then 32 | VIM_PLUGIN_NAME=$2 33 | shift 34 | else 35 | exit_whit_error_message 'ERROR: "--name" requires a non-empty option argument.' 36 | fi 37 | ;; 38 | --output-dir=?*) 39 | # Delete everything up to "=" and assign the remainder. 40 | VIM_PLUGIN_NAME=${1#*=} 41 | ;; 42 | --output-dir=) # Handle the case of an empty --name= 43 | exit_whit_error_message 'ERROR: "--name" requires a non-empty option argument.' 44 | ;; 45 | -?*) 46 | printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 47 | ;; 48 | *) 49 | # Default case: No more options, so break out of the loop. 50 | break 51 | esac 52 | 53 | shift 54 | done 55 | 56 | VIM_INSTALL_DIR=~/.vim/pack/$VIM_PLUGIN_NAME/start/$VIM_PLUGIN_NAME 57 | 58 | mkdir -p $VIM_INSTALL_DIR 59 | 60 | echo "Installing plugin in the ${VIM_INSTALL_DIR} directory" 61 | for DIR in autoload compiler ftdetect ftplugin indent syntax 62 | do 63 | cp -R $DIR $VIM_INSTALL_DIR 64 | done -------------------------------------------------------------------------------- /autoload/db/adapter/get_repos.exs: -------------------------------------------------------------------------------- 1 | defmodule LoadRepos do 2 | defp load_apps do 3 | :code.get_path() 4 | |> Enum.flat_map(fn app_dir -> 5 | Path.join(app_dir, "*.app") |> Path.wildcard() 6 | end) 7 | |> Enum.map(fn app_file -> 8 | app_file |> Path.basename() |> Path.rootname(".app") |> String.to_atom() 9 | end) 10 | |> Enum.map(&Application.load/1) 11 | end 12 | 13 | defp configs do 14 | for {app, _, _} <- Application.loaded_applications(), 15 | repos = Application.get_env(app, :ecto_repos), 16 | is_list(repos) and repos != [], 17 | repo <- repos, 18 | do: {repo, Map.new(repo.config())} 19 | end 20 | 21 | defp config_to_url(_, %{url: url}), do: url 22 | 23 | defp config_to_url(repo, config) do 24 | host = 25 | case Map.fetch(config, :socket_dir) do 26 | :error -> Map.fetch!(config, :hostname) 27 | {:ok, socket_dir} -> socket_dir 28 | end 29 | username = Map.get(config, :username) 30 | password = Map.get(config, :password) 31 | database = Map.get(config, :database) 32 | parameters = Map.get(config, :parameters, []) 33 | 34 | %URI{ 35 | scheme: adapter_to_string(repo.__adapter__), 36 | host: "", 37 | path: Path.join("/", database), 38 | query: encode_options([host: host, user: username, password: password] ++ parameters) 39 | } 40 | |> URI.to_string() 41 | end 42 | 43 | defp adapter_to_string(Ecto.Adapters.Postgres), do: "postgres" 44 | defp adapter_to_string(Ecto.Adapters.MySQL), do: "mysql" 45 | defp adapter_to_string(mod), do: raise("Unknown adapter #{inspect(mod)}") 46 | 47 | defp encode_options(opts) do 48 | cleaned = 49 | for {k, v} <- opts, not is_nil(v), do: {k, v} 50 | 51 | URI.encode_query(cleaned) 52 | end 53 | 54 | def main do 55 | load_apps() 56 | 57 | configs() 58 | |> Enum.map(fn {repo, config} -> 59 | [inspect(repo), ?\s, config_to_url(repo, config)] 60 | end) 61 | |> Enum.intersperse(?\n) 62 | |> IO.puts() 63 | end 64 | end 65 | 66 | LoadRepos.main() 67 | -------------------------------------------------------------------------------- /spec/syntax/embedded_elixir_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Embedded Elixir syntax' do 6 | it 'elixir' do 7 | expect('<%= if true do %>').to include_eelixir_syntax('elixirKeyword', 'if') 8 | expect('<%= if true do %>').to include_eelixir_syntax('elixirBoolean', 'true') 9 | end 10 | 11 | it 'expression' do 12 | expect('<%= if true do %>').to include_eelixir_syntax('eelixirExpression', 'if') 13 | expect('<% end %>').to include_eelixir_syntax('eelixirExpression', 'end') 14 | end 15 | 16 | it 'quote' do 17 | expect('<%% def f %>').to include_eelixir_syntax('eelixirQuote', 'def') 18 | end 19 | 20 | it 'comment' do 21 | expect('<%# foo bar baz %>').to include_eelixir_syntax('eelixirComment', 'foo') 22 | end 23 | 24 | it 'delimiters' do 25 | expect('<% end %>').to include_eelixir_syntax('eelixirDelimiter', '<%') 26 | expect('<% end %>').to include_eelixir_syntax('eelixirDelimiter', '%>') 27 | end 28 | end 29 | 30 | describe 'Embedded Live Elixir syntax' do 31 | it 'elixir' do 32 | expect('<%= if true do %>').to include_leelixir_syntax('elixirKeyword', 'if') 33 | expect('<%= if true do %>').to include_leelixir_syntax('elixirBoolean', 'true') 34 | end 35 | 36 | it 'expression' do 37 | expect('<%= if true do %>').to include_leelixir_syntax('eelixirExpression', 'if') 38 | expect('<% end %>').to include_leelixir_syntax('eelixirExpression', 'end') 39 | end 40 | 41 | it 'quote' do 42 | expect('<%% def f %>').to include_leelixir_syntax('eelixirQuote', 'def') 43 | end 44 | 45 | it 'comment' do 46 | expect('<%# foo bar baz %>').to include_leelixir_syntax('eelixirComment', 'foo') 47 | end 48 | 49 | it 'delimiters' do 50 | expect('<% end %>').to include_leelixir_syntax('eelixirDelimiter', '<%') 51 | expect('<% end %>').to include_leelixir_syntax('eelixirDelimiter', '%>') 52 | end 53 | end 54 | 55 | describe 'Embedded Surface syntax' do 56 | it 'elixir' do 57 | expect('{{ @foo }}').to include_surface_syntax('elixirVariable', 'foo') 58 | expect('{{ @foo }}').to include_surface_syntax('surfaceDelimiter', '{{') 59 | expect('{{ @foo }}').to include_surface_syntax('surfaceDelimiter', '}}') 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/indent/blocks_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting blocks' do 6 | i <<~EOF 7 | do 8 | something 9 | end 10 | EOF 11 | 12 | i <<~EOF 13 | defmodule Test do 14 | def lol do 15 | IO.inspect :end 16 | end 17 | end 18 | EOF 19 | 20 | i <<~EOF 21 | defmodule Hello do 22 | def name, do: IO.puts "bobmarley" 23 | # expect next line starting here 24 | 25 | def name(param) do 26 | param 27 | end 28 | end 29 | EOF 30 | 31 | i <<~EOF 32 | defmodule Hello do 33 | def name, do: IO.puts "bobmarley" 34 | 35 | def name(param) do 36 | param 37 | end 38 | end 39 | EOF 40 | 41 | i <<~EOF 42 | def f do 43 | if true, do: 42 44 | end 45 | EOF 46 | 47 | i <<~EOF 48 | def f do 49 | x = :do 50 | end 51 | EOF 52 | 53 | i <<~EOF 54 | defmodule Test do 55 | def test do 56 | one = 57 | user 58 | |> build_assoc(:videos) 59 | |> Video.changeset() 60 | 61 | other = 62 | user2 63 | |> build_assoc(:videos) 64 | |> Video.changeset() 65 | end 66 | end 67 | EOF 68 | 69 | i <<~EOF 70 | defmodule MyMod do 71 | def how_are_you do 72 | IO.puts "I'm filling bad :(" 73 | IO.puts "really bad" 74 | end 75 | end 76 | EOF 77 | 78 | i <<~EOF 79 | defmodule MyMod do 80 | def how_are_you do 81 | "function return" 82 | end 83 | end 84 | EOF 85 | 86 | i <<~EOF 87 | scope "/", API do 88 | pipe_through :api # Use the default browser stack 89 | 90 | get "/url", Controller, :index 91 | post "/url", Controller, :create 92 | end 93 | EOF 94 | 95 | i <<~EOF 96 | def hello do 97 | {:ok, _} = TaskRunner.TaskStore.start_link(name: @task_store) 98 | {:ok, _} = Workspace.start_link 99 | {:ok, pending_task_sup} = TaskRunner.PendingTaskSupervisor.start_link 100 | end 101 | EOF 102 | 103 | i <<~EOF 104 | def handle_info(:tick, state = %{policy_iteration: []}) do 105 | state = put_in(state[:policy_iteration], state.policy) 106 | {:noreply, state} 107 | end 108 | EOF 109 | end 110 | -------------------------------------------------------------------------------- /spec/syntax/variable_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Variable syntax' do 6 | it 'unused' do 7 | expect(<<~EOF).to include_elixir_syntax('elixirUnusedVariable', '_from') 8 | def handle_call(:pop, _from, [h|stack]) do 9 | { :reply, h, stack } 10 | end 11 | EOF 12 | end 13 | 14 | it 'unused in function body' do 15 | expect(<<~EOF).not_to include_elixir_syntax('elixirUnusedVariable', '_from') 16 | def handle_call(:pop) 17 | Hello._from 18 | end 19 | EOF 20 | end 21 | 22 | it 'unused, multiple lines' do 23 | expect(<<~EOF).to include_elixir_syntax('elixirUnusedVariable', '_from') 24 | def handle_call(:pop, 25 | _from, 26 | [h|stack]) do 27 | { :reply, h, stack } 28 | end 29 | EOF 30 | end 31 | 32 | it 'unused, single char' do 33 | expect(<<~EOF).to include_elixir_syntax('elixirUnusedVariable', '_') 34 | def call(:pop, _, [h|stack]) do 35 | { :reply, h, stack } 36 | end 37 | EOF 38 | end 39 | 40 | it 'unused in pattern_match' do 41 | str = <<~EOF 42 | def sign_in(conn, %{ 43 | "data" => %{ 44 | "type" => "doctor", 45 | "attributes" => %{ 46 | "institution_code" => institution_code, 47 | "password" => password, 48 | "email_or_phone" => email_or_phone}}}, _user, _claims) do 49 | :ok 50 | end 51 | EOF 52 | expect(str).to include_elixir_syntax('elixirUnusedVariable', '_user') 53 | expect(str).to include_elixir_syntax('elixirUnusedVariable', '_claims') 54 | end 55 | 56 | it 'unused, in anonymous function, inline' do 57 | expect(<<~EOF).to include_elixir_syntax('elixirUnusedVariable', '_unused') 58 | fun = fn _unused -> false end 59 | EOF 60 | end 61 | 62 | it 'unused, in anonymous function, multiple lines' do 63 | expect(<<~EOF).to include_elixir_syntax('elixirUnusedVariable', '_unused') 64 | fun = fn 65 | ([], _unused) -> true 66 | end 67 | EOF 68 | end 69 | 70 | it 'unused, in pattern matching' do 71 | expect(<<~EOF).to include_elixir_syntax('elixirUnusedVariable', '_unused') 72 | _unused = false 73 | EOF 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/syntax/atom_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Atom syntax' do 6 | KEYWORDS = %w( 7 | def 8 | defp 9 | defmodule 10 | defprotocol 11 | defimpl 12 | defrecord 13 | defrecordp 14 | defmacro 15 | defmacrop 16 | defdelegate 17 | defoverridable 18 | defexception 19 | defcallback 20 | defstruct 21 | ) 22 | 23 | it '`atom:` style keyword used as an atom' do 24 | KEYWORDS.each do |kw| 25 | expect(<<~EOF).to include_elixir_syntax('elixirAtom', kw), "expected #{kw} to be an elixirAtom" 26 | defmodule XmlElement do 27 | require Record 28 | import Record, only: [#{kw}: 2, extract: 2] 29 | end 30 | EOF 31 | end 32 | end 33 | 34 | it '`:atom =>` style keyword used as an atom' do 35 | KEYWORDS.each do |kw| 36 | expect(<<~EOF).to include_elixir_syntax('elixirAtom', kw), "expected #{kw} to be an elixirAtom" 37 | defmodule XmlElement do 38 | require Record 39 | import Record, only: [:#{kw} => 2, :extract => 2] 40 | end 41 | EOF 42 | end 43 | end 44 | 45 | it 'atoms as part of a comprehension' do 46 | s = 'for kvp <- map, do: &atomize_key/1, into: %{}' 47 | expect(s).to include_elixir_syntax('elixirAtom', 'do') 48 | expect(s).to include_elixir_syntax('elixirAtom', 'into') 49 | end 50 | 51 | it 'defoverridable' do 52 | expect(<<~EOF).to include_elixir_syntax('elixirAtom', 'init:') 53 | defmodule Test do 54 | defmacro __using__(_options) do 55 | quote do 56 | def init(args) do 57 | {:ok, args} 58 | end 59 | defoverridable init: 1 60 | end 61 | end 62 | end 63 | EOF 64 | expect(<<~EOF).to include_elixir_syntax('elixirAtom', 'init:') 65 | defmodule Test do 66 | defmacro __using__(_options) do 67 | quote do 68 | def init(args) do 69 | {:ok, args} 70 | end 71 | defoverridable [init: 1] 72 | end 73 | end 74 | end 75 | EOF 76 | end 77 | 78 | it '`Atom:` style atoms used in keyword list' do 79 | expect(<<~EOF).to include_elixir_syntax('elixirAtom', 'Protocols:') 80 | def project do 81 | [ 82 | docs: [ 83 | groups_for_modules: [ 84 | Protocols: [Enumerable], 85 | ] 86 | ] 87 | ] 88 | end 89 | EOF 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/syntax/struct_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Struct syntax' do 6 | it 'without defaults' do 7 | expect(<<~EOF).to include_elixir_syntax('elixirAtom', ':name') 8 | defstruct [:name, :age] 9 | EOF 10 | end 11 | 12 | it 'with defaults' do 13 | expect(<<~EOF).to include_elixir_syntax('elixirAtom', 'name:') 14 | defstruct name: "john", age: 27 15 | EOF 16 | end 17 | 18 | it 'structs' do 19 | str = %q(%MyStruct{name: "one"}) 20 | 21 | expect(str).to include_elixir_syntax('elixirStructDelimiter', '%') 22 | expect(str).to include_elixir_syntax('elixirStruct', '%') 23 | 24 | expect(str).to include_elixir_syntax('elixirAlias', 'MyStruct') 25 | expect(str).to include_elixir_syntax('elixirStruct', 'MyStruct') 26 | 27 | expect(str).to include_elixir_syntax('elixirStructDelimiter', '{') 28 | expect(str).to include_elixir_syntax('elixirStruct', '{') 29 | 30 | expect(str).to include_elixir_syntax('elixirAtom', 'name:') 31 | expect(str).to include_elixir_syntax('elixirStruct', 'name:') 32 | 33 | expect(str).to include_elixir_syntax('elixirStructDelimiter', '}') 34 | end 35 | 36 | it 'properly closes strings in structs' do 37 | str = <<~'EOF' 38 | %MyStruct{url: "http://127.0.0.1:#{port}"} # anchor 39 | # this should not be a string still 40 | EOF 41 | 42 | expect(str).to include_elixir_syntax('elixirStruct', '{url') 43 | 44 | expect(str).to include_elixir_syntax('elixirStringDelimiter', '"http') 45 | expect(str).to include_elixir_syntax('elixirStruct', '"http') 46 | 47 | expect(str).to include_elixir_syntax('elixirInterpolationDelimiter', '#{') 48 | expect(str).to include_elixir_syntax('elixirStruct', '#{') 49 | 50 | expect(str).to include_elixir_syntax('elixirInterpolationDelimiter', '}"}') 51 | expect(str).to include_elixir_syntax('elixirStruct', '}"}') 52 | expect(str).not_to include_elixir_syntax('elixirStructDelimiter', '}"}') 53 | 54 | expect(str).to include_elixir_syntax('elixirStringDelimiter', '"}') 55 | expect(str).to include_elixir_syntax('elixirStruct', '"}') 56 | 57 | expect(str).to include_elixir_syntax('elixirStringDelimiter', '"}') 58 | expect(str).to include_elixir_syntax('elixirStruct', '"}') 59 | 60 | expect(str).to include_elixir_syntax('elixirStructDelimiter', '} #') 61 | 62 | expect(str).to include_elixir_syntax('elixirComment', '# this should not be a string still') 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/indent/case_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting case statements' do 6 | i <<~EOF 7 | case some_function do 8 | :ok -> 9 | :ok 10 | { :error, :message } -> 11 | { :error, :message } 12 | end 13 | EOF 14 | 15 | i <<~EOF 16 | case Connection.open(rabbitmq) do 17 | {:ok, conn} -> 18 | Woody.info "CONNECTION_SUCCESSFUL" 19 | {:ok, chan} = Channel.open(conn) 20 | {:error, error} -> 21 | Woody.info "CONNECTION_FAILED" 22 | :timer.sleep(10000) 23 | end 24 | EOF 25 | 26 | i <<~EOF 27 | defmodule M do 28 | defp _fetch(result, key, deep_key) do 29 | case _fetch(result, key) do 30 | {:ok, val} -> 31 | case _fetch(val, deep_key) do 32 | :error -> {:error, :deep} 33 | res -> res 34 | end 35 | 36 | :error -> {:error, :shallow} 37 | end 38 | end 39 | EOF 40 | 41 | i <<~EOF 42 | case Connection.open(rabbitmq) do 43 | {:ok, conn} -> 44 | Woody.info "CONNECTION_SUCCESSFUL" 45 | {:ok, chan} = Channel.open(conn) 46 | {:error, error} -> 47 | Woody.info "CONNECTION_FAILED" 48 | :timer.sleep(10000) 49 | end 50 | EOF 51 | 52 | i <<~'EOF' 53 | decoded_msg = case JSON.decode(msg) do 54 | {:error, _} -> 55 | a = "a" 56 | b = "dasdas" 57 | ">#{a}<>#{b}<" 58 | {:ok, decoded} -> decoded 59 | end 60 | EOF 61 | 62 | i <<~EOF 63 | case Repo.insert(changeset) do 64 | {:ok, user} -> 65 | conn 66 | |> put_flash(:info, "%{user.name} created!") 67 | |> redirect(to: user_path(conn, :index)) 68 | {:error, changeset} -> 69 | render(conn, "new.html", changeset: changeset) 70 | end 71 | EOF 72 | 73 | i <<~EOF 74 | case st do 75 | sym -> 76 | code = if true do 77 | :ok 78 | else 79 | :error 80 | end 81 | Logger.info(code) 82 | st 83 | end 84 | EOF 85 | 86 | i <<~EOF 87 | case world do 88 | "apple" -> 89 | IO.puts "its an apple" 90 | 91 | IO.puts "no really, its an apple" 92 | "orange" -> 93 | IO.puts "its not an apple" 94 | IO.puts "believe it or not" 95 | end 96 | EOF 97 | 98 | i <<~EOF 99 | case o do 100 | a -> 101 | e(fn -> f end) 102 | end 103 | EOF 104 | 105 | i <<~EOF 106 | case pattern do 107 | :* -> :ok 108 | _ -> :error 109 | end 110 | EOF 111 | end 112 | -------------------------------------------------------------------------------- /spec/indent/pipeline_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting pipeline' do 6 | i <<~EOF 7 | "a,b,c,d" 8 | |> String.split(",") 9 | |> Enum.reverse 10 | EOF 11 | 12 | i <<~EOF 13 | [ h | t ] = "a,b,c,d" 14 | |> String.split(",") 15 | |> Enum.reverse 16 | EOF 17 | 18 | i <<~EOF 19 | def test do 20 | [ h | t ] = "a,b,c,d" 21 | |> String.split(",") 22 | |> Enum.reverse 23 | 24 | { :ok, h } 25 | end 26 | EOF 27 | 28 | i <<~EOF 29 | def test do 30 | my_post = Post 31 | |> where([p], p.id == 10) 32 | |> where([p], u.user_id == 1) 33 | |> select([p], p) 34 | end 35 | EOF 36 | 37 | i <<~EOF 38 | def test do 39 | "a,b,c,d" 40 | |> String.split(",") 41 | |> Enum.first 42 | |> case do 43 | "a" -> "A" 44 | _ -> "Z" 45 | end 46 | end 47 | EOF 48 | 49 | i <<~EOF 50 | defrecord RECORD, field_a: nil, field_b: nil 51 | 52 | rec = RECORD.new 53 | |> IO.inspect 54 | EOF 55 | 56 | i <<~EOF 57 | defmodule MyMod do 58 | def export_info(users) do 59 | {:ok, infos} = users 60 | |> Enum.map(fn (u) -> do_something(u) end) 61 | |> Enum.map(fn (u) -> 62 | do_even_more(u) 63 | end) 64 | |> finall_thing 65 | 66 | infos 67 | end 68 | end 69 | EOF 70 | 71 | i <<~EOF 72 | def build_command(input, output) do 73 | "embedded=here" 74 | |> 75 | end 76 | EOF 77 | 78 | i <<~EOF 79 | def build_command(input, output) do 80 | 'embedded=here' 81 | |> 82 | EOF 83 | 84 | i <<~EOF 85 | def build_command(input, output) do 86 | %{:hello => :world} 87 | |> 88 | end 89 | EOF 90 | 91 | %w(<= >= == != === !== =~).each do |op| 92 | i <<~EOF 93 | def build_command(input, output) do 94 | true #{op} false 95 | |> IO.inspect 96 | end 97 | EOF 98 | end 99 | 100 | i <<~EOF 101 | upcased_names = names 102 | |> Enum.map(fn name -> 103 | String.upcase(name) 104 | end) 105 | 106 | IO.inspect names 107 | EOF 108 | 109 | i <<~EOF 110 | upcased_names = names 111 | |> Enum.map(fn name -> 112 | String.upcase(name) end) 113 | 114 | IO.inspect names 115 | EOF 116 | 117 | i <<~EOF 118 | upcased_names = names 119 | |> Enum.map(fn name -> 120 | String.upcase(name) 121 | end) 122 | 123 | |> do_stuff 124 | EOF 125 | 126 | i <<~EOF 127 | def hello do 128 | do_something 129 | |> Pipe.to_me 130 | {:ok} 131 | end 132 | EOF 133 | 134 | i <<~EOF 135 | defmodule MyModule do 136 | def do_stuff do 137 | name = 138 | "Dr. Zaius" 139 | |> determine_name 140 | 141 | hello 142 | end 143 | end 144 | EOF 145 | end 146 | -------------------------------------------------------------------------------- /spec/indent/with_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'with' do 6 | i <<~EOF 7 | with {:ok, msg} <- Msgpax.unpack(payload) do 8 | {:ok, rebuild(msg)} 9 | else 10 | error -> error 11 | end 12 | EOF 13 | 14 | i <<~EOF 15 | with {:ok, width} <- Map.fetch(opts, :width), 16 | double_width = width * 2, 17 | {:ok, height} <- Map.fetch(opts, :height) 18 | do 19 | {:ok, double_width * height} 20 | end 21 | EOF 22 | 23 | i <<~EOF 24 | with {:ok, width} <- Map.fetch(opts, :width), 25 | double_width = width * 2, 26 | {:ok, height} <- Map.fetch(opts, :height), 27 | do: {:ok, double_width * height} 28 | EOF 29 | 30 | i <<~EOF 31 | with {:ok, width} <- Map.fetch(opts, :width), 32 | {:ok, height} <- Map.fetch(opts, :height) 33 | do 34 | {:ok, width * height} 35 | else 36 | :error -> 37 | {:error, :wrong_data} 38 | end 39 | EOF 40 | 41 | i <<~EOF 42 | with {:ok, width} <- Map.fetch(opts, :width), 43 | {:ok, height} <- Map.fetch(opts, :height), 44 | do: 45 | {:ok, 46 | width * height * height * height * height * height * height * height * height * height * 47 | height * height * height * height * height * height * height}, 48 | else: (:error -> {:error, :wrong_data}) 49 | EOF 50 | 51 | i <<~'EOF' 52 | # This file is responsible for configuring your application 53 | # and its dependencies with the aid of the Mix.Config module. 54 | use Mix.Config 55 | 56 | import_config "#{Mix.env}.exs" 57 | EOF 58 | 59 | i <<~'EOF' 60 | with {:ok, %File.Stat{size: size}} when size > 0 <- File.stat(first_frame_path) do 61 | File.rename(first_frame_path, output_path) 62 | {:ok, %Result{path: output_path}} 63 | else 64 | error -> 65 | {:error, error} 66 | end 67 | EOF 68 | 69 | i <<~'EOF' 70 | def resend_confirmation(username) when is_binary(username) do 71 | with user = %User{confirmed_at: nil} <- get_by(username: username) do 72 | {:ok, user} = 73 | user 74 | |> DB.add_confirm_token 75 | |> update_user() 76 | Log.info(%Log{user: user.id, message: "send new confirmation"}) 77 | send_welcome(user) 78 | {:ok, user} 79 | else 80 | nil -> 81 | {:error, "not found"} 82 | %User{email: email} -> 83 | Email.already_confirmed(email) 84 | {:error, "already confirmed"} 85 | end 86 | end 87 | EOF 88 | 89 | i <<~'EOF' 90 | def create_user(params) do 91 | profile = UserProfile.registration_changeset(%UserProfile{}, params) 92 | 93 | user_cs = 94 | %User{} 95 | |> User.registration_changeset(params) 96 | |> put_assoc(:user_profile, profile) 97 | 98 | with {:ok, user} <- Repo.insert(user_cs, returning: false) do 99 | Log.info(%Log{user: user.id, message: "user created"}) 100 | send_welcome(user) 101 | {:ok, user} 102 | end 103 | end 104 | EOF 105 | 106 | i <<~'EOF' 107 | def my_function do 108 | with :ok <- some_call, 109 | :ok <- another_call do 110 | 111 | end 112 | end 113 | EOF 114 | 115 | i <<~'EOF' 116 | with {:ok, foo} <- thing(1), 117 | {:ok, bar} <- thing(2) do 118 | foo + bar 119 | end 120 | EOF 121 | end 122 | -------------------------------------------------------------------------------- /syntax/eelixir.vim: -------------------------------------------------------------------------------- 1 | if exists("b:current_syntax") 2 | finish 3 | endif 4 | 5 | let s:cpo_save = &cpo 6 | set cpo&vim 7 | 8 | if !exists("main_syntax") 9 | let main_syntax = 'eelixir' 10 | endif 11 | 12 | if !exists("g:eelixir_default_subtype") 13 | let g:eelixir_default_subtype = "html" 14 | endif 15 | 16 | if !exists("b:eelixir_subtype") 17 | let s:lines = getline(1)."\n".getline(2)."\n".getline(3)."\n".getline(4)."\n".getline(5)."\n".getline("$") 18 | let b:eelixir_subtype = matchstr(s:lines,'eelixir_subtype=\zs\w\+') 19 | if b:eelixir_subtype == '' 20 | let b:eelixir_subtype = matchstr(&filetype,'^eex\.\zs\w\+') 21 | endif 22 | if b:eelixir_subtype == '' 23 | let b:eelixir_subtype = matchstr(&filetype,'^heex\.\zs\w\+') 24 | endif 25 | if b:eelixir_subtype == '' 26 | let b:eelixir_subtype = matchstr(&filetype,'^leex\.\zs\w\+') 27 | endif 28 | if b:eelixir_subtype == '' 29 | let b:eelixir_subtype = matchstr(&filetype,'^sface\.\zs\w\+') 30 | endif 31 | if b:eelixir_subtype == '' 32 | let b:eelixir_subtype = matchstr(substitute(expand("%:t"),'\c\%(\.eex\|\.heex\|\.leex\|\.sface\|\.eelixir\)\+$','',''),'\.\zs\w\+$') 33 | endif 34 | if b:eelixir_subtype == 'ex' 35 | let b:eelixir_subtype = 'elixir' 36 | elseif b:eelixir_subtype == 'exs' 37 | let b:eelixir_subtype = 'elixir' 38 | elseif b:eelixir_subtype == 'yml' 39 | let b:eelixir_subtype = 'yaml' 40 | elseif b:eelixir_subtype == 'js' 41 | let b:eelixir_subtype = 'javascript' 42 | elseif b:eelixir_subtype == 'txt' 43 | " Conventional; not a real file type 44 | let b:eelixir_subtype = 'text' 45 | elseif b:eelixir_subtype == '' 46 | let b:eelixir_subtype = g:eelixir_default_subtype 47 | endif 48 | endif 49 | 50 | if exists("b:eelixir_subtype") && b:eelixir_subtype != '' 51 | exe "runtime! syntax/".b:eelixir_subtype.".vim" 52 | unlet! b:current_syntax 53 | endif 54 | 55 | syn include @elixirTop syntax/elixir.vim 56 | 57 | syn cluster eelixirRegions contains=eelixirBlock,surfaceExpression,eelixirExpression,eelixirComment 58 | 59 | exe 'syn region eelixirExpression matchgroup=eelixirDelimiter start="<%" end="%\@" contains=@elixirTop containedin=ALLBUT,@eelixirRegions keepend' 60 | exe 'syn region eelixirExpression matchgroup=eelixirDelimiter start="<%=" end="%\@" contains=@elixirTop containedin=ALLBUT,@eelixirRegions keepend' 61 | exe 'syn region surfaceExpression matchgroup=surfaceDelimiter start="{{" end="}}" contains=@elixirTop containedin=ALLBUT,@eelixirRegions keepend' 62 | exe 'syn region surfaceExpression matchgroup=surfaceDelimiter start="{" end="}" contains=@elixirTop containedin=ALLBUT,@eelixirRegions keepend' 63 | exe 'syn region surfaceExpression matchgroup=surfaceDelimiter start="{" end="}" skip="#{[^}]*}" contains=@elixirTop containedin=htmlValue keepend' 64 | exe 'syn region eelixirQuote matchgroup=eelixirDelimiter start="<%%" end="%\@" contains=@elixirTop containedin=ALLBUT,@eelixirRegions keepend' 65 | exe 'syn region eelixirComment matchgroup=eelixirDelimiter start="<%#" end="%\@" contains=elixirTodo,@Spell containedin=ALLBUT,@eelixirRegions keepend' 66 | 67 | " Define the default highlighting. 68 | 69 | hi def link eelixirDelimiter PreProc 70 | hi def link surfaceDelimiter PreProc 71 | hi def link eelixirComment Comment 72 | 73 | let b:current_syntax = 'eelixir' 74 | 75 | if main_syntax == 'eelixir' 76 | unlet main_syntax 77 | endif 78 | 79 | let &cpo = s:cpo_save 80 | unlet s:cpo_save 81 | -------------------------------------------------------------------------------- /doc/elixir.txt: -------------------------------------------------------------------------------- 1 | *elixir.txt* Vim configuration files for Elixir http://elixir-lang.org/ 2 | 3 | Author: Plataformatec 4 | License: Apache License Version 2.0 5 | 6 | ============================================================================== 7 | CONTENTS *elixir-contents* 8 | 9 | INTRODUCTION |elixir-introduction| 10 | INTERFACE |elixir-interface| 11 | FUNCTIONS |elixir-functions| 12 | KEY MAPPINGS |elixir-key-mappings| 13 | OPTIONS |elixir-options| 14 | SETTINGS |elixir-settings| 15 | 16 | ============================================================================== 17 | INTRODUCTION *elixir-introduction* 18 | 19 | *elixir* provides Vim configuration files for Elixir http://elixir-lang.org/ 20 | 21 | * Syntax highlighting for Elixir and EEx files 22 | * Filetype detection for `.ex`, `.exs`, `.eex`, `.heex`, `.leex`, and `.sface` files 23 | * Automatic indentation 24 | * Integration between Ecto projects and |vim-dadbod| for running SQL queries 25 | on defined Ecto repositories 26 | 27 | 28 | Latest Version: 29 | https://github.com/elixir-editors/vim-elixir 30 | 31 | 32 | ============================================================================== 33 | INTERFACE *elixir-interface* 34 | 35 | ------------------------------------------------------------------------------ 36 | FUNCTIONS *elixir-functions* 37 | 38 | db#adapter#ecto#canonicalize({url}) *db#adapter#ecto#canonicalize()* 39 | TODO 40 | 41 | db#adapter#ecto#complete_opaque({url}) *db#adapter#ecto#complete_opaque()* 42 | TODO 43 | 44 | elixir#indent#indent({lnum}) *elixir#indent#indent()* 45 | TODO 46 | 47 | elixir#indent#searchpair_back_skip() *elixir#indent#searchpair_back_skip()* 48 | TODO 49 | 50 | *elixir#indent#handle_top_of_file()* 51 | elixir#indent#handle_top_of_file({context}) 52 | TODO 53 | 54 | *elixir#indent#handle_follow_prev_nb()* 55 | elixir#indent#handle_follow_prev_nb({context}) 56 | TODO 57 | 58 | *elixir#indent#handle_following_trailing_binary_operator()* 59 | elixir#indent#handle_following_trailing_binary_operator({context}) 60 | TODO 61 | 62 | *elixir#indent#handle_starts_with_pipe()* 63 | elixir#indent#handle_starts_with_pipe({context}) 64 | TODO 65 | 66 | *elixir#indent#handle_starts_with_end()* 67 | elixir#indent#handle_starts_with_end({context}) 68 | TODO 69 | 70 | *elixir#indent#handle_starts_with_binary_operator()* 71 | elixir#indent#handle_starts_with_binary_operator({context}) 72 | TODO 73 | 74 | *elixir#indent#handle_inside_block()* 75 | elixir#indent#handle_inside_block({context}) 76 | TODO 77 | 78 | *elixir#indent#handle_inside_generic_block()* 79 | elixir#indent#handle_inside_generic_block({context}) 80 | TODO 81 | 82 | elixir#util#get_filename({word}) *elixir#util#get_filename({word})* 83 | TODO 84 | 85 | 86 | ------------------------------------------------------------------------------ 87 | KEY MAPPINGS *elixir-key-mappings* 88 | 89 | TODO 90 | 91 | 92 | 93 | ============================================================================== 94 | SETTINGS *elixir-settings* 95 | 96 | *g:eelixir_default_subtype* 97 | TODO 98 | 99 | *g:elixir_indent_debug* 100 | TODO 101 | 102 | *g:elixir_indent_max_lookbehind* 103 | TODO 104 | 105 | *g:elixir_use_markdown_for_docs* 106 | TODO 107 | 108 | *g:path* 109 | TODO 110 | 111 | ============================================================================== 112 | vim:tw=78:fo=tcq2mM:ts=8:ft=help:norl 113 | -------------------------------------------------------------------------------- /spec/syntax/strings_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'String syntax' do 6 | describe 'binary' do 7 | it 'double quoted string' do 8 | expect('foo "test"').to include_elixir_syntax('elixirString', 'test') 9 | end 10 | 11 | it 'double quoted string with escaped quote' do 12 | expect('"this \"test is all one string"').to include_elixir_syntax('elixirString', 'test') 13 | end 14 | 15 | it 'charlist with escaped quote' do 16 | expect(<<-'EOF').to include_elixir_syntax('elixirCharList', 'test') 17 | 'this \'test is all one charlist' 18 | EOF 19 | end 20 | 21 | it 'interpolation in string' do 22 | expect('do_something "foo #{test}"').to include_elixir_syntax('elixirInterpolation', 'test') 23 | end 24 | end 25 | 26 | describe 'heredoc' do 27 | it 'heredoc must be string' do 28 | ex = <<~EOF 29 | def function do 30 | """ 31 | foo "test" 32 | """ 33 | end 34 | EOF 35 | expect(ex).to include_elixir_syntax('elixirString', 'foo') 36 | expect(ex).to include_elixir_syntax('elixirString', 'test') 37 | end 38 | 39 | it 'interpolation in string in heredoc' do 40 | expect(<<~'EOF').to include_elixir_syntax('elixirInterpolation', '#{') 41 | def function do 42 | """ 43 | foo "#{test}" 44 | """ 45 | end 46 | EOF 47 | end 48 | 49 | it 'interpolation in heredoc' do 50 | expect(<<~'EOF').to include_elixir_syntax('elixirInterpolation', '#{') 51 | def function do 52 | """ 53 | foo #{test} 54 | """ 55 | end 56 | EOF 57 | end 58 | 59 | it 'correctly terminates heredocs with no spaces at the start of the line' do 60 | expect(<<~'EOF'.gsub(/^\s+/, '')).to include_elixir_syntax('elixirAtom', ':bar') 61 | """ 62 | foo 63 | """ 64 | :bar 65 | EOF 66 | 67 | expect(<<~'EOF'.gsub(/^\s+/, '')).to include_elixir_syntax('elixirAtom', ':bar') 68 | ''' 69 | foo 70 | ''' 71 | :bar 72 | EOF 73 | end 74 | 75 | it 'interpolation with a tuple' do 76 | str = <<~'EOF' 77 | "Failed sending tasks #{inspect {:unexpected_status_code, s}}" 78 | EOF 79 | expect(str).not_to include_elixir_syntax('elixirInterpolationDelimiter', '}}"$') 80 | expect(str).to include_elixir_syntax('elixirInterpolationDelimiter', '}"$') 81 | end 82 | 83 | it 'interpolation with a tuple' do 84 | str = <<~'EOF' 85 | "Failed sending tasks #{inspect %{unexpected_status_code: s}}" 86 | EOF 87 | expect(str).not_to include_elixir_syntax('elixirInterpolationDelimiter', '}}"$') 88 | expect(str).to include_elixir_syntax('elixirInterpolationDelimiter', '}"$') 89 | end 90 | 91 | it 'interpolation with a struct' do 92 | str = <<~'EOF' 93 | "Failed sending tasks #{inspect %ResponseStruct{unexpected_status_code: s}}" 94 | EOF 95 | expect(str).not_to include_elixir_syntax('elixirInterpolationDelimiter', '}}"$') 96 | expect(str).to include_elixir_syntax('elixirInterpolationDelimiter', '}"$') 97 | end 98 | 99 | it 'strings with embedded braces' do 100 | str = <<~EOF 101 | x = [ 102 | {:text, "asd {"}, 103 | {:text, "qwe"}, 104 | ] 105 | EOF 106 | expect(str).to include_elixir_syntax('elixirString', '{"}') 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /spec/indent/lists_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting lists' do 6 | i <<~EOF 7 | def example do 8 | [ :foo, 9 | :bar, 10 | :baz ] 11 | end 12 | EOF 13 | 14 | i <<~EOF 15 | [ 16 | [ 17 | :foo 18 | ] 19 | ] 20 | EOF 21 | 22 | i <<~EOF 23 | def project do 24 | [ name: "mix", 25 | version: "0.1.0", 26 | deps: deps ] 27 | end 28 | EOF 29 | 30 | i <<~EOF 31 | def config do 32 | [ name: 33 | "John" ] 34 | end 35 | EOF 36 | 37 | i <<~EOF 38 | def test do 39 | [ { :cowboy, github: "extend/cowboy" }, 40 | { :dynamo, "0.1.0-dev", github: "elixir-lang/dynamo" }, 41 | { :ecto, github: "elixir-lang/ecto" }, 42 | { :pgsql, github: "semiocast/pgsql" } ] 43 | end 44 | EOF 45 | 46 | i <<~EOF 47 | def test do 48 | [ [:a, :b, :c], 49 | [:d, :e, :f] ] 50 | end 51 | EOF 52 | 53 | i <<~EOF 54 | def test do 55 | [ app: :first, 56 | version: "0.0.1", 57 | dynamos: [First.Dynamo], 58 | compilers: [:elixir, :dynamo, :ecto, :app], 59 | env: [prod: [compile_path: "ebin"]], 60 | compile_path: "tmp/first/ebin", 61 | deps: deps ] 62 | end 63 | EOF 64 | 65 | i <<~EOF 66 | def project do 67 | [ 68 | { :bar, path: "deps/umbrella/apps/bar" }, 69 | { :umbrella, path: "deps/umbrella" } 70 | ] 71 | end 72 | EOF 73 | 74 | i <<~EOF 75 | def test do 76 | a = [ 77 | %{ 78 | foo: 1, 79 | bar: 2 80 | } 81 | ] 82 | 83 | b = %{ 84 | [ 85 | :foo, 86 | :bar 87 | ] 88 | } 89 | 90 | [ 91 | a, 92 | b 93 | ] 94 | end 95 | EOF 96 | 97 | i <<~EOF 98 | def create(conn, %{ 99 | "grant_type" => "password", 100 | "username" => username, 101 | "password" => password 102 | }) do 103 | 1 104 | end 105 | EOF 106 | 107 | i <<~EOF 108 | def double(x) do 109 | add( 110 | x, 111 | y 112 | ) 113 | end 114 | EOF 115 | 116 | i <<~EOF 117 | def double(x) do 118 | add( 119 | x, 120 | y, 121 | w, 122 | z 123 | ) 124 | end 125 | EOF 126 | 127 | i <<~EOF 128 | def double(x) do 129 | result = add( 130 | x, 131 | z 132 | ) 133 | div(result, 2) 134 | end 135 | EOF 136 | 137 | i <<~EOF 138 | defmodule Module do 139 | @person1 { name: "name", 140 | age: 18, 141 | enabled?: true } 142 | @person2 { name: "other name", 143 | age: 21, 144 | enabled?: false } 145 | end 146 | EOF 147 | 148 | i <<~EOF 149 | def test_another_feature do 150 | assert json_response(conn, 200) == %{ 151 | "results" => [ 152 | %{ 153 | "id" => result.id, 154 | } 155 | ] 156 | } 157 | end 158 | EOF 159 | 160 | i <<~EOF 161 | defmodule Mod do 162 | def test do 163 | foo == %{ 164 | } 165 | 166 | assert json_response == %{ 167 | "id" => "identifier" 168 | } 169 | end 170 | end 171 | EOF 172 | 173 | i <<~EOF 174 | defmodule Mod do 175 | def fun do 176 | json_logger = Keyword.merge(Application.get_env(:logger, :json_logger, []), options) 177 | Application.put_env(:logger, :json_logger, json_logger) 178 | level = Keyword.get(json_logger, :level) 179 | 180 | %{level: level, output: :console} 181 | end 182 | end 183 | EOF 184 | 185 | i <<~EOF 186 | defmodule Mod do 187 | def fun do 188 | Enum.each(s.routing_keys, fn k -> Queue.bind(chan, s.queue, s.exchange, routing_key: k) end) 189 | Basic.consume(chan, s.queue, nil, no_ack: true) 190 | end 191 | end 192 | EOF 193 | 194 | i <<~EOF 195 | def init(_) do 196 | children = [ 197 | worker(QueueSet, [[name: @queue_set]]), 198 | worker(Producer, [[name: @producer]]), 199 | worker(ConsumerSupervisor, [[{@producer, max_demand: @max_executors}]]) 200 | ] 201 | 202 | supervise(children, strategy: :one_for_one) 203 | end 204 | EOF 205 | end 206 | -------------------------------------------------------------------------------- /ftplugin/eelixir.vim: -------------------------------------------------------------------------------- 1 | if exists("b:did_ftplugin") 2 | finish 3 | endif 4 | 5 | let s:save_cpo = &cpo 6 | set cpo-=C 7 | 8 | let s:undo_ftplugin = "" 9 | let s:browsefilter = "All Files (*.*)\t*.*\n" 10 | let s:match_words = "" 11 | 12 | if !exists("g:eelixir_default_subtype") 13 | let g:eelixir_default_subtype = "html" 14 | endif 15 | 16 | if !exists("b:eelixir_subtype") 17 | let s:lines = join(getline(1, 5) + [getline('$')], "\n") 18 | let b:eelixir_subtype = matchstr(s:lines,'eelixir_subtype=\zs\w\+') 19 | if b:eelixir_subtype == '' 20 | let b:eelixir_subtype = matchstr(&filetype,'^eex\.\zs\w\+') 21 | endif 22 | if b:eelixir_subtype == '' 23 | let b:eelixir_subtype = matchstr(&filetype,'^heex\.\zs\w\+') 24 | endif 25 | if b:eelixir_subtype == '' 26 | let b:eelixir_subtype = matchstr(&filetype,'^leex\.\zs\w\+') 27 | endif 28 | if b:eelixir_subtype == '' 29 | let b:eelixir_subtype = matchstr(&filetype,'^sface\.\zs\w\+') 30 | endif 31 | if b:eelixir_subtype == '' 32 | let b:eelixir_subtype = matchstr(substitute(expand("%:t"),'\c\%(\.eex\|\.heex\|\.leex\|\.sface\|\.eelixir\)\+$','',''),'\.\zs\w\+$') 33 | endif 34 | if b:eelixir_subtype == 'ex' 35 | let b:eelixir_subtype = 'elixir' 36 | elseif b:eelixir_subtype == 'exs' 37 | let b:eelixir_subtype = 'elixir' 38 | elseif b:eelixir_subtype == 'yml' 39 | let b:eelixir_subtype = 'yaml' 40 | elseif b:eelixir_subtype == 'js' 41 | let b:eelixir_subtype = 'javascript' 42 | elseif b:eelixir_subtype == 'txt' 43 | " Conventional; not a real file type 44 | let b:eelixir_subtype = 'text' 45 | elseif b:eelixir_subtype == '' 46 | let b:eelixir_subtype = g:eelixir_default_subtype 47 | endif 48 | endif 49 | 50 | if exists("b:eelixir_subtype") && b:eelixir_subtype != '' 51 | exe "runtime! ftplugin/".b:eelixir_subtype.".vim ftplugin/".b:eelixir_subtype."_*.vim ftplugin/".b:eelixir_subtype."/*.vim" 52 | else 53 | runtime! ftplugin/html.vim ftplugin/html_*.vim ftplugin/html/*.vim 54 | endif 55 | unlet! b:did_ftplugin 56 | 57 | " Override our defaults if these were set by an included ftplugin. 58 | if exists("b:undo_ftplugin") 59 | let s:undo_ftplugin = b:undo_ftplugin 60 | unlet b:undo_ftplugin 61 | endif 62 | if exists("b:browsefilter") 63 | let s:browsefilter = b:browsefilter 64 | unlet b:browsefilter 65 | endif 66 | if exists("b:match_words") 67 | let s:match_words = b:match_words 68 | unlet b:match_words 69 | endif 70 | 71 | runtime! ftplugin/elixir.vim ftplugin/elixir_*.vim ftplugin/elixir/*.vim 72 | let b:did_ftplugin = 1 73 | 74 | " Combine the new set of values with those previously included. 75 | if exists("b:undo_ftplugin") 76 | let s:undo_ftplugin = b:undo_ftplugin . " | " . s:undo_ftplugin 77 | endif 78 | if exists ("b:browsefilter") 79 | let s:browsefilter = substitute(b:browsefilter,'\cAll Files (\*\.\*)\t\*\.\*\n','','') . s:browsefilter 80 | endif 81 | if exists("b:match_words") 82 | let s:match_words = b:match_words . ',' . s:match_words 83 | endif 84 | 85 | " Load the combined list of match_words for matchit.vim 86 | if exists("loaded_matchit") 87 | let b:match_words = s:match_words 88 | endif 89 | 90 | if !exists('b:surround_45') 91 | " When using surround `-` (ASCII 45) would provide `<% selection %>` 92 | let b:surround_45 = "<% \r %>" 93 | endif 94 | if !exists('b:surround_61') 95 | " When using surround `=` (ASCII 61) would provide `<%= selection %>` 96 | let b:surround_61 = "<%= \r %>" 97 | endif 98 | if !exists('b:surround_35') 99 | " When using surround `#` (ASCII 35) would provide `<%# selection %>` 100 | let b:surround_35 = "<%# \r %>" 101 | endif 102 | if !exists('b:surround_123') 103 | " When using surround `{` (ASCII 123) would provide `{{ selection }}` 104 | let b:surround_123 = "{{ \r }}" 105 | endif 106 | if !exists('b:surround_5') 107 | " When using surround `` (ASCII 5 `ENQ`) would provide `<% selection %>\n<% end %>` 108 | let b:surround_5 = "<% \r %>\n<% end %>" 109 | endif 110 | 111 | setlocal comments=:<%# 112 | setlocal commentstring=<%#\ %s\ %> 113 | 114 | let b:undo_ftplugin = "setl cms< " . 115 | \ " | unlet! b:browsefilter b:match_words | " . s:undo_ftplugin 116 | 117 | let &cpo = s:save_cpo 118 | -------------------------------------------------------------------------------- /spec/syntax/exunit_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'ExUnit syntax' do 6 | it 'test macro' do 7 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitMacro', 'test') 8 | test 'that stuff works' do 9 | assert true 10 | end 11 | EOF 12 | end 13 | 14 | it 'describe macro' do 15 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitMacro', 'describe') 16 | describe 'some_function/1' do 17 | test 'that stuff works' do 18 | assert true 19 | end 20 | end 21 | EOF 22 | end 23 | 24 | it 'setup macro' do 25 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitMacro', 'setup') 26 | setup do 27 | IO.puts "hi mom" 28 | end 29 | 30 | test 'that stuff works' do 31 | assert true 32 | end 33 | EOF 34 | end 35 | 36 | it 'setup_all macro' do 37 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitMacro', 'setup_all') 38 | setup_all do 39 | IO.puts "hi mom" 40 | end 41 | 42 | test 'that stuff works' do 43 | assert true 44 | end 45 | EOF 46 | end 47 | 48 | it 'on_exit macro' do 49 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitMacro', 'on_exit') 50 | setup_all do 51 | IO.puts "hi mom" 52 | on_exit fn() -> 53 | do_something 54 | end 55 | end 56 | 57 | test 'that stuff works' do 58 | assert true 59 | end 60 | EOF 61 | end 62 | 63 | it 'assert' do 64 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitAssert', 'assert') 65 | test 'that stuff works' do 66 | assert true 67 | end 68 | EOF 69 | end 70 | 71 | it 'assert_in_delta' do 72 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitAssert', 'assert_in_delta') 73 | test 'that stuff works' do 74 | assert_in_delta true 75 | end 76 | EOF 77 | end 78 | 79 | it 'assert_raise' do 80 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitAssert', 'assert_raise') 81 | test 'that stuff works' do 82 | assert_raise true 83 | end 84 | EOF 85 | end 86 | 87 | it 'assert_receive' do 88 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitAssert', 'assert_receive') 89 | test 'that stuff works' do 90 | assert_receive true 91 | end 92 | EOF 93 | end 94 | 95 | it 'assert_received' do 96 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitAssert', 'assert_received') 97 | test 'that stuff works' do 98 | assert_received true 99 | end 100 | EOF 101 | end 102 | 103 | it 'catch_error' do 104 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitAssert', 'catch_error') 105 | test 'that stuff works' do 106 | catch_error true 107 | end 108 | EOF 109 | end 110 | 111 | it 'catch_exit' do 112 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitAssert', 'catch_exit') 113 | test 'that stuff works' do 114 | catch_exit true 115 | end 116 | EOF 117 | end 118 | 119 | it 'catch_throw' do 120 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitAssert', 'catch_throw') 121 | test 'that stuff works' do 122 | catch_throw true 123 | end 124 | EOF 125 | end 126 | 127 | it 'flunk' do 128 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitAssert', 'flunk') 129 | test 'that stuff works' do 130 | flunk true 131 | end 132 | EOF 133 | end 134 | 135 | it 'refute' do 136 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitAssert', 'refute') 137 | test 'that stuff works' do 138 | refute true 139 | end 140 | EOF 141 | end 142 | 143 | it 'refute_in_delta' do 144 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitAssert', 'refute_in_delta') 145 | test 'that stuff works' do 146 | refute_in_delta true 147 | end 148 | EOF 149 | end 150 | 151 | it 'refute_receive' do 152 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitAssert', 'refute_receive') 153 | test 'that stuff works' do 154 | refute_receive true 155 | end 156 | EOF 157 | end 158 | 159 | it 'refute_received' do 160 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitAssert', 'refute_received') 161 | test 'that stuff works' do 162 | refute_received true 163 | end 164 | EOF 165 | end 166 | 167 | it 'doctest' do 168 | expect(<<~EOF).to include_elixir_syntax('elixirExUnitMacro', 'doctest') 169 | module MyTest do 170 | doctest MyModule 171 | end 172 | EOF 173 | end 174 | end 175 | -------------------------------------------------------------------------------- /spec/syntax/sigil_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Sigil syntax' do 6 | it 'as function argument' do 7 | expect('def f(~s(")), do: true').to include_elixir_syntax('elixirSigilDelimiter', '\~s(') 8 | expect('def f(~s(")), do: true').to include_elixir_syntax('elixirSigil', '"') 9 | expect("def f(~s(')), do: true").to include_elixir_syntax('elixirSigil', "'") 10 | expect('def f(~s(")), do: true').not_to include_elixir_syntax('elixirSigilDelimiter', '"') 11 | end 12 | 13 | it 'as function argument multiline content' do 14 | ex = <<~'EOF' 15 | f( 16 | ~S""" 17 | foo 18 | """, 19 | bar 20 | ) 21 | EOF 22 | 23 | expect(ex).to include_elixir_syntax('elixirSigilDelimiter', 'S"""') 24 | expect(ex).to include_elixir_syntax('elixirSigil', 'foo') 25 | end 26 | 27 | describe 'upper case' do 28 | it 'string' do 29 | expect('~S(string)').to include_elixir_syntax('elixirSigilDelimiter', 'S') 30 | expect('~S(string)').to include_elixir_syntax('elixirSigil', 'string') 31 | end 32 | 33 | it 'character list' do 34 | expect('~C(charlist)').to include_elixir_syntax('elixirSigilDelimiter', 'C') 35 | expect('~C(charlist)').to include_elixir_syntax('elixirSigil', 'charlist') 36 | end 37 | 38 | it 'regular expression' do 39 | expect('~R(regex)').to include_elixir_syntax('elixirSigilDelimiter', 'R') 40 | expect('~R(regex)').to include_elixir_syntax('elixirSigil', 'regex') 41 | end 42 | 43 | it 'list of words' do 44 | expect('~W(list of words)').to include_elixir_syntax('elixirSigilDelimiter', 'W') 45 | expect('~W(list of words)').to include_elixir_syntax('elixirSigil', 'list') 46 | end 47 | 48 | it 'delimited with parenthesis' do 49 | expect('~S(foo bar)').to include_elixir_syntax('elixirSigilDelimiter', '(') 50 | expect('~S(foo bar)').to include_elixir_syntax('elixirSigilDelimiter', ')') 51 | end 52 | 53 | it 'delimited with braces' do 54 | expect('~S{foo bar}').to include_elixir_syntax('elixirSigilDelimiter', '{') 55 | expect('~S{foo bar}').to include_elixir_syntax('elixirSigilDelimiter', '}') 56 | end 57 | 58 | it 'delimited with brackets' do 59 | expect('~S[foo bar]').to include_elixir_syntax('elixirSigilDelimiter', '[') 60 | expect('~S[foo bar]').to include_elixir_syntax('elixirSigilDelimiter', ']') 61 | end 62 | 63 | it 'escapes double quotes unless only preceded by whitespace' do 64 | expect(<<~EOF).to include_elixir_syntax('elixirSigilDelimiter', %q(^\s*\zs""")) 65 | ~r""" 66 | foo """ 67 | """ 68 | EOF 69 | end 70 | 71 | it 'escapes single quotes unless only preceded by whitespace' do 72 | expect(<<~EOF).to include_elixir_syntax('elixirSigilDelimiter', %q(^\s*\zs''')) 73 | ~r''' 74 | foo ''' 75 | ''' 76 | EOF 77 | end 78 | 79 | it 'without escapes' do 80 | expect('~S(foo \n bar)').not_to include_elixir_syntax('elixirRegexEscape', '\\') 81 | end 82 | 83 | it 'without interpolation' do 84 | expect('~S(foo #{bar})').not_to include_elixir_syntax('elixirInterpolation', 'bar') 85 | end 86 | 87 | it 'without escaped parenthesis' do 88 | expect('~S(\( )').not_to include_elixir_syntax('elixirRegexEscapePunctuation', '( ') 89 | end 90 | 91 | it 'Live EEx' do 92 | expect('~L"""liveview template"""').to include_elixir_syntax('elixirSigilDelimiter', '"""') 93 | end 94 | 95 | it 'Surface EEx' do 96 | expect('~H"""surface template"""').to include_elixir_syntax('elixirSigilDelimiter', '"""') 97 | end 98 | 99 | it 'EEx' do 100 | expect('~E"""Phoenix.HTML template"""').to include_elixir_syntax('elixirSigilDelimiter', '"""') 101 | expect('~e"""Phoenix.HTML template"""').to include_elixir_syntax('elixirSigilDelimiter', '"""') 102 | end 103 | end 104 | 105 | describe 'lower case' do 106 | it 'string' do 107 | expect('~s(string)').to include_elixir_syntax('elixirSigilDelimiter', 's') 108 | expect('~s(string)').to include_elixir_syntax('elixirSigil', 'string') 109 | end 110 | 111 | it 'character list' do 112 | expect('~c(charlist)').to include_elixir_syntax('elixirSigilDelimiter', 'c') 113 | expect('~c(charlist)').to include_elixir_syntax('elixirSigil', 'charlist') 114 | end 115 | 116 | it 'regular expression' do 117 | expect('~r(regex)').to include_elixir_syntax('elixirSigilDelimiter', 'r') 118 | expect('~r(regex)').to include_elixir_syntax('elixirSigil', 'regex') 119 | end 120 | 121 | it 'list of words' do 122 | expect('~w(list of words)').to include_elixir_syntax('elixirSigilDelimiter', 'w') 123 | expect('~w(list of words)').to include_elixir_syntax('elixirSigil', 'list') 124 | end 125 | 126 | it 'with escapes' do 127 | expect('~s(foo \n bar)').to include_elixir_syntax('elixirRegexEscapePunctuation', '\\') 128 | end 129 | 130 | it 'with interpolation' do 131 | expect('~s(foo #{bar})').to include_elixir_syntax('elixirInterpolation', 'bar') 132 | end 133 | 134 | it 'with escaped parenthesis' do 135 | expect('~s(\( )').to include_elixir_syntax('elixirRegexEscapePunctuation', '( ') 136 | end 137 | 138 | it 'interpolation with slashes' do 139 | expect('~s/foo #{bar}/').to include_elixir_syntax('elixirInterpolation', 'bar') 140 | end 141 | 142 | it 'escapes with slashes' do 143 | expect('~s/foo \n bar/').to include_elixir_syntax('elixirRegexEscapePunctuation', '\\') 144 | end 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-elixir 2 | 3 | [![Build Status](https://travis-ci.org/elixir-editors/vim-elixir.svg?branch=master)](https://travis-ci.org/elixir-editors/vim-elixir) 4 | 5 | [Elixir](http://elixir-lang.org) support for vim 6 | 7 | ## Description 8 | 9 | Features: 10 | 11 | * Syntax highlighting for Elixir and EEx files 12 | * Filetype detection for `.ex`, `.exs`, `.eex`, `.heex`, `.leex`, and `.sface` files 13 | * Automatic indentation 14 | * Integration between Ecto projects and [vim-dadbod][] for running SQL queries 15 | on defined Ecto repositories 16 | 17 | ## Installation 18 | 19 | `vim-elixir` can be installed either with a plugin manager or by directly copying the files into your vim folders (location varies between platforms) 20 | 21 | ### Plugin Managers 22 | 23 | If you are using a plugin manager then add `vim-elixir` the way you would any other plugin: 24 | 25 | ```bash 26 | # Using vim 8 native package loading 27 | # http://vimhelp.appspot.com/repeat.txt.html#packages 28 | git clone https://github.com/elixir-editors/vim-elixir.git ~/.vim/pack/my-packages/start/vim-elixir 29 | 30 | # Using pathogen 31 | git clone https://github.com/elixir-editors/vim-elixir.git ~/.vim/bundle/vim-elixir 32 | ``` 33 | 34 | ```viml 35 | " Using vim-plug 36 | Plug 'elixir-editors/vim-elixir' 37 | 38 | " Using Vundle 39 | Plugin 'elixir-editors/vim-elixir' 40 | 41 | " Using NeoBundle 42 | NeoBundle 'elixir-editors/vim-elixir' 43 | ``` 44 | 45 | ### Manual Installation 46 | 47 | If you are not using a package manager then you can use the provided `manual_install.sh` script to copy the files into their respective homes. 48 | 49 | Run [./manual_install.sh](manual_install.sh) to copy the contents of each directory in the respective directories inside `~/.vim`. 50 | 51 | ## Configuration 52 | 53 | You must add the following to your `~/.vimrc`: 54 | 55 | ``` 56 | " Enable syntax highlighting 57 | syntax on 58 | 59 | " Enables filetype detection, loads ftplugin, and loads indent 60 | " (Not necessary on nvim and may not be necessary on vim 8.2+) 61 | filetype plugin indent on 62 | ``` 63 | 64 | ## Notes/Caveats 65 | 66 | ### `mix format` Integration 67 | 68 | We've decided not to include `mix format` integration into `vim-elixir`. 69 | If you'd like to set it up yourself, you have the following options: 70 | 71 | * For asynchronous execution of the formatter, have a look at [vim-mix-format](https://github.com/mhinz/vim-mix-format) 72 | * Add it as a `formatprg` (e.g. `setlocal formatprg=mix\ format\ -`) 73 | 74 | Why isn't this supported? We've run into two major issues with calling out to `mix format`. 75 | First `mix format` would not work unless your program compiled. 76 | Second `mix format` added an external process dependency to `vim-elixir`. 77 | 78 | If someone really wanted to try and add this then we might be able to model it after `vim-go`'s `go fmt` integration 79 | which I think could be acceptable to merge into master. 80 | 81 | ## Development 82 | 83 | ### Maintenance Help 84 | 85 | `vim-elixir` is looking for new maintainers. 86 | If you get a lot of value from it, know vimscript well, or eager to learn about it then feel free to get in touch with @jbodah (GH issue, elixir-lang Slack) 87 | 88 | ### Running the Tests 89 | 90 | The tests depend on having Ruby installed. 91 | They also depend on a GUI vim (gvim, mvim) with server support. 92 | If you do not have gvim or mvim in your PATH then you can create a `.gvim_path` file in the vim-elixir root directory which specifies the path to the GUI vim executable. 93 | 94 | To run the tests: `bundle exec parallel_rspec spec` 95 | 96 | ### Developing in Docker 97 | 98 | You can spawn a container with vim and your development configs using `bin/vim` or `bin/nvim` 99 | 100 | ### Debugging Indent 101 | 102 | ``` 103 | # Open vim in a container loading this plugin 104 | bin/vim myfile.ex 105 | 106 | # Debug statements should be configured to print automatically 107 | # Write/indent some code 108 | :messages 109 | 110 | # You should see output like the following: 111 | # ==> Indenting line 3 112 | # text = ' _ -> :wowo' 113 | # testing handler elixir#indent#handle_top_of_file 114 | # testing handler elixir#indent#handle_starts_with_string_continuation 115 | # testing handler elixir#indent#handle_following_trailing_binary_operator 116 | # testing handler elixir#indent#handle_starts_with_pipe 117 | # testing handler elixir#indent#handle_starts_with_binary_operator 118 | # testing handler elixir#indent#handle_inside_block 119 | # pattern matching relative to lnum 2 120 | # current line contains ->; assuming match definition 121 | # line 3: elixir#indent#handle_inside_block returned 4 122 | # 1 change; before #1 4 seconds ago 123 | # 124 | # This tells you which line is being inspected as well as which handlers are being run 125 | # and which branches are being exercised by those handlers 126 | ``` 127 | 128 | ### Feature Wishlist 129 | 130 | Here is a list of features that I think would be great additions to `vim-elixir`: 131 | 132 | * Regularly merging `vim-elixir` into `vim` and keeping the sync up-to-date 133 | * Fixing our build so it can run regularly on CI 134 | * Live view support 135 | * Testing .exs files and ensuring feature compatibility between .ex and .exs 136 | * Documentation (e.g. `:h vim-elixir`) 137 | * README docs for various .vimrc options/flags 138 | * Identifying and rewriting tests that conflict with `mix format` 139 | * Fixes for indentation rule edge cases (e.g. `with`, see GH issues for examples) 140 | * Simplifying syntax rules 141 | * Performance optimizations for syntax/indent rules (especially for determining if something is a string) 142 | 143 | [vim-dadbod]: https://github.com/tpope/vim-dadbod 144 | -------------------------------------------------------------------------------- /spec/indent/embedded_views_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Indenting embedded views' do 6 | i <<~EOF 7 | def render(assigns) do 8 | ~L""" 9 |
10 | Some content 11 |
12 | """ 13 | end 14 | EOF 15 | 16 | i <<~EOF 17 | def render(assigns) do 18 | ~H""" 19 |
20 |
21 | This is immediately nested 22 |
23 | 24 | There's a self-closing tag 25 |
26 |
27 |
28 | """ 29 | end 30 | EOF 31 | 32 | i <<~EOF 33 | def render(assigns) do 34 | ~L""" 35 |
36 | Some content 37 |
38 | """ 39 | end 40 | EOF 41 | 42 | i <<~EOF 43 | def render(assigns) do 44 | ~L""" 45 |
48 | Some content 49 |
50 | """ 51 | end 52 | EOF 53 | 54 | i <<~EOF 55 | def render(assigns) do 56 | ~L""" 57 |
58 |

Some paragraph

59 | """ 60 | end 61 | EOF 62 | 63 | i <<~EOF 64 | def render(assigns) do 65 | ~L""" 66 |
67 | it 68 |
69 | keeps 70 |
71 | nesting 72 |
73 |
74 |
75 | """ 76 | end 77 | EOF 78 | 79 | i <<~EOF 80 | def render(assgins) do 81 | ~L""" 82 |
83 | <%= for i <- iter do %> 84 |
<%= i %>
85 | <% end %> 86 |
87 | """ 88 | end 89 | EOF 90 | 91 | i <<~EOF 92 | def render(assigns) do 93 | ~L""" 94 | <%= live_component @socket, 95 | Component, 96 | id: "<%= @id %>", 97 | user: @user do 98 | %> 99 | 100 |
101 |
102 |

Some Header

103 |
104 |
105 |

Some Section

106 |

107 | I'm some text 108 |

109 |
110 |
111 | 112 | <% end %> 113 | """ 114 | end 115 | EOF 116 | 117 | i <<~EOF 118 | def render(assigns) do 119 | ~L""" 120 | <%= render_component, 121 | @socket, 122 | Component do %> 123 | 124 |

Multi-line opening eex tag that takes a block

125 | <% end %> 126 | """ 127 | end 128 | EOF 129 | 130 | i <<~EOF 131 | def render(assigns) do 132 | ~L""" 133 |
134 | <%= render_component, 135 | @socket, 136 | Component %> 137 |
138 | 139 | <%= render_component, 140 | @socket, 141 | Component %> 142 |

Multi-line single eex tag

143 | """ 144 | end 145 | EOF 146 | 147 | i <<~EOF 148 | def render(assigns) do 149 | ~H""" 150 | "bar" 159 | } 160 | }} 161 | /> 162 | """ 163 | end 164 | EOF 165 | 166 | i <<~EOF 167 | def render(assigns) do 168 | ~L""" 169 | <%= live_component @socket, 170 | Component, 171 | id: "<%= @id %>", 172 | team: @team do 173 | %> 174 | 175 |
176 |
177 |
178 | A deeply nested tree 179 |
180 | with trailing whitespace 181 | 182 |
183 |
184 |
185 |
186 | 187 |
191 | 192 | <%= for i <- iter do %> 193 |
<%= i %>
194 | <% end %> 195 | 196 |
203 | 204 |
    205 |
  • 206 | {{ item }} 207 |
  • 208 |
209 | 210 |
211 | Hi

hi

212 | I'm ok, ok? 213 |
214 | hi there! 215 |
216 |
217 |
218 |

hi

219 |
220 |
221 |
222 |
223 | 224 | 225 | 226 | 230 |
content
231 |
232 | 233 |
234 | 235 |
hi
236 | 237 |
238 |
239 | content 240 |
241 |
242 |
243 | content in new div after a self-closing div 244 |
245 |
246 | 247 |

251 | <%= @solo.eex_tag %> 252 | 255 | content 256 | 257 |

258 | 259 | <% end %> 260 | """ 261 | end 262 | EOF 263 | end 264 | -------------------------------------------------------------------------------- /spec/syntax/doc_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'documentation syntax' do 6 | describe 'string' do 7 | it 'doc in double quotes' do 8 | ex = '@doc "foo"' 9 | expect(ex).to include_elixir_syntax('elixirDocString', 'foo') 10 | expect(ex).to include_elixir_syntax('elixirDocStringDelimiter', '"') 11 | end 12 | 13 | it 'doc in sigil_S' do 14 | ex = '@doc ~S(foo)' 15 | expect(ex).to include_elixir_syntax('elixirDocString', 'foo') 16 | expect(ex).to include_elixir_syntax('elixirDocSigilDelimiter', 'S') 17 | end 18 | end 19 | 20 | describe 'heredoc' do 21 | it 'doc with multiline content' do 22 | ex = <<~'EOF' 23 | @callbackdoc """ 24 | foo 25 | """ 26 | EOF 27 | expect(ex).to include_elixir_syntax('elixirVariable', 'doc') 28 | expect(ex).to include_elixir_syntax('elixirDocString', 'foo') 29 | expect(ex).to include_elixir_syntax('elixirDocStringDelimiter', '"""') 30 | end 31 | 32 | it 'doc with sigil_S triple double-quoted multiline content' do 33 | ex = <<~'EOF' 34 | @doc ~S""" 35 | foo 36 | """ 37 | EOF 38 | expect(ex).to include_elixir_syntax('elixirVariable', 'doc') 39 | expect(ex).to include_elixir_syntax('elixirDocSigilDelimiter', 'S"""') 40 | expect(ex).to include_elixir_syntax('elixirDocString', 'foo') 41 | end 42 | 43 | it 'doc with sigil_S triple double-quoted multiline content with parentheses' do 44 | ex = <<~'EOF' 45 | @doc(~S""" 46 | foo 47 | """) 48 | EOF 49 | expect(ex).to include_elixir_syntax('elixirVariable', 'doc') 50 | expect(ex).to include_elixir_syntax('elixirDocSigilDelimiter', 'S"""') 51 | expect(ex).to include_elixir_syntax('elixirDocString', 'foo') 52 | end 53 | 54 | it 'doc with sigil_S triple single-quoted multiline content' do 55 | ex = <<~'EOF' 56 | @doc ~S''' 57 | foo 58 | ''' 59 | EOF 60 | expect(ex).to include_elixir_syntax('elixirVariable', 'doc') 61 | expect(ex).to include_elixir_syntax('elixirDocSigilDelimiter', "S'''") 62 | expect(ex).to include_elixir_syntax('elixirDocString', 'foo') 63 | end 64 | 65 | it 'doc with sigil_S triple single-quoted multiline content with parentheses' do 66 | ex = <<~'EOF' 67 | @doc(~S''' 68 | foo 69 | ''') 70 | EOF 71 | expect(ex).to include_elixir_syntax('elixirVariable', 'doc') 72 | expect(ex).to include_elixir_syntax('elixirDocSigilDelimiter', "S'''") 73 | expect(ex).to include_elixir_syntax('elixirDocString', 'foo') 74 | end 75 | 76 | it 'doc with triple single-quoted multiline content is not a doc string' do 77 | ex = <<~'EOF' 78 | @doc ''' 79 | foo 80 | ''' 81 | EOF 82 | expect(ex).not_to include_elixir_syntax('elixirDocString', 'foo') 83 | end 84 | 85 | it 'doc with multiline escaped' do 86 | ex = <<~'EOF' 87 | @doc """ 88 | foo 89 | ``` 90 | @xxx \""" 91 | bar 92 | \""" 93 | ``` 94 | baz 95 | """ 96 | EOF 97 | expect(ex).to include_elixir_syntax('elixirDocString', 'foo') 98 | expect(ex).to include_elixir_syntax('elixirDocString', 'bar') 99 | expect(ex).to include_elixir_syntax('elixirDocString', 'baz') 100 | end 101 | 102 | it 'doc skip interpolation' do 103 | ex = <<~'EOF' 104 | @doc """ 105 | foo #{bar} 106 | """ 107 | EOF 108 | expect(ex).to include_elixir_syntax('elixirDocString', 'foo') 109 | expect(ex).to include_elixir_syntax('elixirDocStringDelimiter', '"""') 110 | expect(ex).to include_elixir_syntax('elixirInterpolation', 'bar') 111 | end 112 | 113 | it 'doc with doctest' do 114 | ex = <<~'EOF' 115 | @doc """ 116 | doctest 117 | 118 | iex> Enum.map [1, 2, 3], fn(x) -> 119 | ...> x * 2 120 | ...> end 121 | [2, 4, 6] 122 | 123 | """ 124 | EOF 125 | expect(ex).to include_elixir_syntax('elixirDocString', 'doctest') 126 | expect(ex).to include_elixir_syntax('elixirDocTest', 'map') 127 | expect(ex).to include_elixir_syntax('elixirDocTest', 'x \* 2') 128 | expect(ex).to include_elixir_syntax('elixirDocTest', '2, 4, 6') 129 | end 130 | 131 | describe 'doctest without newline after' do 132 | it 'with heredoc' do 133 | ex = <<~'EOF' 134 | @doc """ 135 | doctest 136 | 137 | iex> 1 + 2 138 | 3 139 | """ 140 | def some_fun(x), do: x 141 | EOF 142 | expect(ex).to include_elixir_syntax('elixirDocString', 'doctest') 143 | expect(ex).to include_elixir_syntax('elixirDocTest', '1 + 2') 144 | expect(ex).to include_elixir_syntax('elixirDefine', 'def') 145 | end 146 | 147 | it 'with double quote' do 148 | ex = <<~'EOF' 149 | @doc " 150 | doctest 151 | 152 | iex> \"bob\" 153 | \"bob\" 154 | " 155 | def some_fun(x), do: x 156 | EOF 157 | expect(ex).to include_elixir_syntax('elixirDocString', 'doctest') 158 | expect(ex).to include_elixir_syntax('elixirDocTest', 'bob') 159 | expect(ex).to include_elixir_syntax('elixirDefine', 'def') 160 | end 161 | 162 | it 'with sigil_S' do 163 | ex = <<~'EOF' 164 | @doc ~S( 165 | doctest 166 | 167 | iex> to_string("bob"\) 168 | "bob" 169 | ) 170 | def some_fun(x), do: x 171 | EOF 172 | expect(ex).to include_elixir_syntax('elixirDocString', 'doctest') 173 | expect(ex).to include_elixir_syntax('elixirDocTest', 'bob') 174 | expect(ex).to include_elixir_syntax('elixirDefine', 'def') 175 | end 176 | 177 | it 'with sigil_s' do 178 | ex = <<~'EOF' 179 | @doc ~s( 180 | doctest 181 | 182 | iex> to_string("bob"\) 183 | "bob" 184 | ) 185 | def some_fun(x), do: x 186 | EOF 187 | expect(ex).to include_elixir_syntax('elixirDocString', 'doctest') 188 | expect(ex).to include_elixir_syntax('elixirDocTest', 'bob') 189 | expect(ex).to include_elixir_syntax('elixirDefine', 'def') 190 | end 191 | end 192 | 193 | it 'doc with inline code' do 194 | ex = <<~'EOF' 195 | @doc """ 196 | doctest with inline code `List.wrap([])` 197 | """ 198 | EOF 199 | expect(ex).to include_elixir_syntax('elixirDocString', 'doctest') 200 | expect(ex).to include_elixir_syntax('elixirDocString', 'wrap') 201 | end 202 | 203 | describe "use markdown for docs" do 204 | before(:each) { VIM.command("let g:elixir_use_markdown_for_docs = 1") } 205 | after(:each) { VIM.command("let g:elixir_use_markdown_for_docs = 0") } 206 | 207 | it 'doc with inline code' do 208 | ex = <<~'EOF' 209 | @doc """ 210 | doc with inline code `List.wrap([])` 211 | """ 212 | EOF 213 | expect(ex).to include_elixir_syntax('elixirDocString', 'inline') 214 | expect(ex).to include_elixir_syntax('markdownCode', 'wrap') 215 | end 216 | end 217 | end 218 | end 219 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/expectations' 2 | require 'tmpdir' 3 | require 'vimrunner' 4 | require 'vimrunner/rspec' 5 | 6 | GVIM_PATH_FILE = File.expand_path('../../.gvim_path', __FILE__) 7 | 8 | class Buffer 9 | FOLD_PLACEHOLDER = ''.freeze 10 | 11 | def initialize(vim, type) 12 | @file = ".fixture.#{type}" 13 | @vim = vim 14 | end 15 | 16 | def reindent(content) 17 | with_file content do 18 | min_indent = content.each_line.map { |line| line[/\s*/].size }.min 19 | cmd = "ggVG:s/\\s\\{0,#{min_indent}}//" # remove all indentation 20 | cmd += 'gg=G' # force vim to indent the file 21 | @vim.normal cmd 22 | end 23 | end 24 | 25 | def type(content) 26 | with_file do 27 | @vim.normal 'gg' 28 | 29 | lines = content.each_line 30 | count = lines.count 31 | @vim.type("i") 32 | lines.each_with_index do |line, index| 33 | @vim.type("#{line.strip}") 34 | @vim.type("") if index < count - 1 35 | end 36 | end 37 | end 38 | 39 | def syntax(content, pattern) 40 | with_file content 41 | 42 | # Using this function with a `pattern` that is not in `content` is pointless. 43 | # 44 | # @vim.search() silently fails if a pattern is not found and the cursor 45 | # won't move. So, if the current cursor position happens to sport the 46 | # expected syntax group already, this can lead to false positive tests. 47 | # 48 | # We work around this by using Vim's search() function, which returns 0 if 49 | # there is no match. 50 | if @vim.echo("search(#{pattern.inspect})") == '0' 51 | return [] 52 | end 53 | 54 | syngroups = @vim.echo <<~EOF 55 | map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")') 56 | EOF 57 | 58 | # From: "['elixirRecordDeclaration', 'elixirAtom']" 59 | # To: ["elixirRecordDeclaration", "elixirAtom"] 60 | syngroups.gsub!(/["'\[\]]/, '').split(', ') 61 | end 62 | 63 | def fold_and_replace(content, fold_on_line) 64 | with_file content do 65 | cmd = ":set foldmethod=syntax" 66 | cmd += "zO" 67 | cmd += "#{fold_on_line}G" 68 | cmd += "zc" 69 | cmd += "cc#{FOLD_PLACEHOLDER}" 70 | cmd += ":.s/\s*//" 71 | @vim.normal(cmd) 72 | end 73 | end 74 | 75 | private 76 | 77 | def with_file(content = nil) 78 | edit_file(content) 79 | 80 | yield if block_given? 81 | 82 | @vim.normal ":w" 83 | @vim.normal ":redraw" 84 | IO.read(@file) 85 | end 86 | 87 | def edit_file(content) 88 | File.write(@file, content) if content 89 | @vim.edit @file 90 | end 91 | end 92 | 93 | class Differ 94 | def self.diff(result, expected) 95 | instance.diff(result, expected) 96 | end 97 | 98 | def self.instance 99 | @instance ||= new 100 | end 101 | 102 | def initialize 103 | @differ = RSpec::Support::Differ.new( 104 | object_preparer: -> (object) do 105 | RSpec::Matchers::Composable.surface_descriptions_in(object) 106 | end, 107 | color: RSpec::Matchers.configuration.color? 108 | ) 109 | end 110 | 111 | def diff(result, expected) 112 | @differ.diff_as_string(result, expected) 113 | end 114 | end 115 | 116 | module ExBuffer 117 | def self.new 118 | Buffer.new(VIM, :ex) 119 | end 120 | end 121 | 122 | module EexBuffer 123 | def self.new 124 | Buffer.new(VIM, :eex) 125 | end 126 | end 127 | 128 | module HeexBuffer 129 | def self.new 130 | Buffer.new(VIM, :heex) 131 | end 132 | end 133 | 134 | module LeexBuffer 135 | def self.new 136 | Buffer.new(VIM, :leex) 137 | end 138 | end 139 | 140 | module SurfaceBuffer 141 | def self.new 142 | Buffer.new(VIM, :sface) 143 | end 144 | end 145 | 146 | RSpec::Matchers.define :be_typed_with_right_indent do |syntax| 147 | buffer = Buffer.new(VIM, syntax || :ex) 148 | 149 | match do |code| 150 | @typed = buffer.type(code) 151 | @typed == code 152 | end 153 | 154 | failure_message do |code| 155 | <<~EOM 156 | Expected 157 | 158 | #{@typed} 159 | to be indented as 160 | 161 | #{code} 162 | 163 | when typed 164 | EOM 165 | end 166 | end 167 | 168 | { 169 | be_elixir_indentation: :ex, 170 | be_eelixir_indentation: :eex, 171 | be_heelixir_indentation: :heex, 172 | be_leelixir_indentation: :leex, 173 | be_surface_indentation: :sface 174 | }.each do |matcher, type| 175 | RSpec::Matchers.define matcher do 176 | buffer = Buffer.new(VIM, type) 177 | 178 | match do |code| 179 | reindented = buffer.reindent(code) 180 | reindented == code 181 | end 182 | 183 | failure_message do |code| 184 | <<~EOM 185 | Expected 186 | 187 | #{buffer.reindent(code)} 188 | to be indented as 189 | 190 | #{code} 191 | 192 | when bulk indented 193 | EOM 194 | end 195 | end 196 | end 197 | 198 | { 199 | include_elixir_syntax: :ex, 200 | include_eelixir_syntax: :eex, 201 | include_heelixir_syntax: :heex, 202 | include_leelixir_syntax: :leex, 203 | include_surface_syntax: :sface 204 | }.each do |matcher, type| 205 | RSpec::Matchers.define matcher do |syntax, pattern| 206 | buffer = Buffer.new(VIM, type) 207 | 208 | match do |code| 209 | buffer.syntax(code, pattern).include? syntax.to_s 210 | end 211 | 212 | failure_message do |code| 213 | <<~EOF 214 | expected #{buffer.syntax(code, pattern)} 215 | to include syntax '#{syntax}' 216 | for pattern: /#{pattern}/ 217 | in: 218 | #{code} 219 | EOF 220 | end 221 | 222 | failure_message_when_negated do |code| 223 | <<~EOF 224 | expected #{buffer.syntax(code, pattern)} 225 | *NOT* to include syntax '#{syntax}' 226 | for pattern: /#{pattern}/ 227 | in: 228 | #{code} 229 | EOF 230 | end 231 | end 232 | end 233 | 234 | RSpec::Matchers.define :fold_lines do 235 | buffer = Buffer.new(VIM, :ex) 236 | 237 | match do |code| 238 | @code = code 239 | 240 | pattern = /# fold\s*$/ 241 | 242 | placeholder_set = false 243 | @expected = code.each_line.reduce([]) do |acc, line| 244 | if line =~ pattern 245 | if !placeholder_set 246 | placeholder_set = true 247 | acc << (Buffer::FOLD_PLACEHOLDER + "\n") 248 | end 249 | else 250 | acc << line 251 | end 252 | 253 | acc 254 | end.join 255 | 256 | fold_on_line = code.each_line.find_index { |l| l =~ pattern } + 1 257 | @actual = buffer.fold_and_replace(code, fold_on_line) 258 | 259 | @expected == @actual 260 | end 261 | 262 | failure_message do |code| 263 | <<~EOF 264 | Folded 265 | 266 | #{@code} 267 | and unexpectedly got 268 | 269 | #{@actual} 270 | EOF 271 | end 272 | end 273 | 274 | Vimrunner::RSpec.configure do |config| 275 | config.reuse_server = true 276 | 277 | config.start_vim do 278 | VIM = 279 | if File.exist?(GVIM_PATH_FILE) 280 | Vimrunner::Server.new(executable: File.read(GVIM_PATH_FILE).rstrip).start 281 | else 282 | Vimrunner.start_gvim 283 | end 284 | VIM.add_plugin(File.expand_path('..', __dir__)) 285 | cmd = ':filetype off' 286 | cmd += ':filetype plugin indent on' 287 | cmd += ':autocmd FileType * setlocal formatoptions-=c formatoptions-=r formatoptions-=o' # disable automatic comment continuation 288 | cmd += ":set ignorecase" # make sure we test ignorecase 289 | VIM.normal(cmd) 290 | VIM 291 | end 292 | end 293 | 294 | RSpec.configure do |config| 295 | config.order = :random 296 | 297 | # Run a single spec by adding the `focus: true` option 298 | config.filter_run_including focus: true 299 | config.run_all_when_everything_filtered = true 300 | end 301 | 302 | RSpec::Core::ExampleGroup.instance_eval do 303 | def i(str) 304 | gen_tests(:it, str) 305 | end 306 | 307 | def ip(str) 308 | gen_tests(:pending, str) 309 | end 310 | 311 | private 312 | 313 | def gen_tests(method, str) 314 | send method, "\n#{str}" do 315 | expect(str).to be_elixir_indentation 316 | end 317 | 318 | send method, "typed: \n#{str}" do 319 | expect(str).to be_typed_with_right_indent 320 | end 321 | end 322 | end 323 | -------------------------------------------------------------------------------- /spec/indent/basic_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Basic indenting' do 6 | i <<~EOF 7 | 8 | defmodule Hello do 9 | EOF 10 | 11 | i <<~EOF 12 | defmodule Hello do 13 | def some_func do 14 | EOF 15 | 16 | i <<~EOF 17 | defmodule Hello do 18 | def some_func do 19 | end 20 | EOF 21 | 22 | i <<~EOF 23 | defmodule Hello do 24 | def some_func do 25 | end 26 | end 27 | EOF 28 | 29 | i <<~EOF 30 | defmodule Hello.World do 31 | def some_func do 32 | IO.puts "hello world" 33 | end 34 | end 35 | EOF 36 | 37 | i <<~EOF 38 | defmodule Hello.World do 39 | def some_func do 40 | IO.puts "hello world" 41 | end 42 | def some_other_func do 43 | IO.puts "hello world" 44 | end 45 | end 46 | EOF 47 | 48 | i <<~EOF 49 | defmodule Hello.World do 50 | def some_func do 51 | IO.puts "hello world" 52 | end 53 | 54 | def some_other_func do 55 | IO.puts "hello world" 56 | end 57 | end 58 | EOF 59 | 60 | i <<~EOF 61 | defmodule Hello.World do 62 | def some_func do 63 | IO.puts "hello world" 64 | end 65 | 66 | def some_other_func do 67 | IO.puts "hello world" 68 | IO.puts "hello world" 69 | IO.puts "hello world" 70 | IO.puts "hello world" 71 | end 72 | end 73 | EOF 74 | 75 | i <<~EOF 76 | defmodule Hello.World do 77 | def some_func do 78 | IO.puts "hello world" 79 | end 80 | 81 | def some_other_func do 82 | if blah? do 83 | blah 84 | else 85 | not_blah 86 | end 87 | end 88 | end 89 | EOF 90 | 91 | i <<~EOF 92 | defmodule Hello.World do 93 | def some_func do 94 | IO.puts "hello world" 95 | end 96 | 97 | def some_other_func do 98 | if blah? do 99 | blah 100 | else 101 | not_blah 102 | end 103 | if blah? do 104 | blah 105 | else 106 | not_blah 107 | end 108 | end 109 | end 110 | EOF 111 | 112 | i <<~EOF 113 | defmodule Hello.World do 114 | def some_func do 115 | IO.puts "hello world" 116 | end 117 | 118 | def some_other_func do 119 | if blah? do 120 | blah 121 | if blah? do 122 | blah 123 | else 124 | not_blah 125 | end 126 | else 127 | not_blah 128 | end 129 | end 130 | end 131 | EOF 132 | 133 | i <<~EOF 134 | defmodule Hello.World do 135 | def some_func do 136 | cond do 137 | {:abc} -> false 138 | _ -> true 139 | end 140 | end 141 | end 142 | EOF 143 | 144 | i <<~EOF 145 | defmodule Hello.World do 146 | def some_func do 147 | cond do 148 | {:abc} -> false 149 | 150 | _ -> true 151 | end 152 | end 153 | end 154 | EOF 155 | 156 | i <<~EOF 157 | defmodule Hello.World do 158 | def some_func do 159 | cond do 160 | {:abc} -> 161 | say_hello 162 | say_goodbye 163 | 164 | _ -> 165 | say_hello 166 | say_goodbye 167 | end 168 | end 169 | end 170 | EOF 171 | 172 | i <<~EOF 173 | defmodule Hello.World do 174 | def some_func do 175 | cond do 176 | {:abc} -> 177 | cond do 178 | {:abc} -> 179 | say_hello 180 | say_goodbye 181 | _ -> 182 | say_hello 183 | say_goodbye 184 | end 185 | say_hello 186 | say_goodbye 187 | 188 | _ -> 189 | say_hello 190 | say_goodbye 191 | end 192 | end 193 | end 194 | EOF 195 | 196 | i <<~EOF 197 | defmodule Hello do 198 | def hello do 199 | case word do 200 | :one -> :two 201 | :high -> :low 202 | end 203 | end 204 | end 205 | EOF 206 | 207 | i <<~EOF 208 | defmodule Hello do 209 | def hello do 210 | case word do 211 | :one -> :two 212 | 213 | :high -> :low 214 | end 215 | end 216 | end 217 | EOF 218 | 219 | i <<~EOF 220 | defmodule Hello do 221 | def hello do 222 | case word do 223 | :one -> 224 | :two 225 | 226 | :high -> 227 | :low 228 | end 229 | end 230 | end 231 | EOF 232 | 233 | i <<~EOF 234 | defmodule Hello do 235 | def hello do 236 | case word do 237 | :one -> 238 | case word do 239 | :one -> 240 | :two 241 | 242 | :high -> 243 | :low 244 | end 245 | :two 246 | 247 | :high -> 248 | :low 249 | end 250 | end 251 | end 252 | EOF 253 | 254 | i <<~EOF 255 | defmodule Hello do 256 | defmacro hello do 257 | quote do 258 | blah 259 | end 260 | end 261 | end 262 | EOF 263 | 264 | i <<~EOF 265 | defmodule Hello do 266 | def hello do 267 | unless blah do 268 | blah 269 | end 270 | end 271 | end 272 | EOF 273 | 274 | i <<~EOF 275 | defmodule Hello do 276 | def hello do 277 | if stinky?, do: clean 278 | if smelly?, do: clean 279 | end 280 | end 281 | EOF 282 | 283 | i <<~EOF 284 | defmodule Hello do 285 | def hello do 286 | name = 287 | "one" 288 | street = 289 | "two" 290 | end 291 | end 292 | EOF 293 | 294 | %w(= == === != !== <= >= <> && || + - * / ~~~ ^^^ <<< >>> ||| &&&).each do |bin_op| 295 | i <<~EOF 296 | defmodule Hello do 297 | def hello do 298 | name #{bin_op} 299 | "one" 300 | street #{bin_op} 301 | "two" 302 | end 303 | end 304 | EOF 305 | 306 | i <<~EOF 307 | defmodule Hello do 308 | def hello do 309 | name #{bin_op} "one" 310 | street #{bin_op} "two" 311 | end 312 | end 313 | EOF 314 | end 315 | 316 | i <<~EOF 317 | defmodule Hi do 318 | def hi do 319 | fn hello -> 320 | :world 321 | end 322 | end 323 | end 324 | EOF 325 | 326 | i <<~EOF 327 | defmodule Hello do 328 | def hello do 329 | name = "one" 330 | street = "two" 331 | end 332 | end 333 | EOF 334 | 335 | i <<~EOF 336 | defmodule Hi do 337 | def hi do 338 | fn hello -> :world end 339 | fn hello -> :world end 340 | end 341 | end 342 | EOF 343 | 344 | i <<~EOF 345 | defmodule Hi do 346 | def hi do 347 | fn hello -> 348 | case hello do 349 | :one -> 350 | case word do 351 | :one -> 352 | :two 353 | 354 | :high -> 355 | :low 356 | end 357 | :two 358 | 359 | :high -> 360 | :low 361 | end 362 | end 363 | end 364 | end 365 | EOF 366 | 367 | i <<~EOF 368 | hello = 369 | "str" 370 | |> Pipe.do_stuff 371 | |> Pipe.do_stuff 372 | 373 | |> Pipe.do_stuff 374 | |> Pipe.do_stuff(fn -> 375 | more stuff 376 | end) 377 | 378 | |> Pipe.do_stuff 379 | EOF 380 | 381 | i <<~EOF 382 | defmodule Hi do 383 | defp hi do 384 | :hello 385 | end 386 | 387 | defp hi do 388 | :hello 389 | end 390 | end 391 | EOF 392 | 393 | i <<~EOF 394 | defmodule Hi do 395 | defp hi do 396 | [ 397 | :one, 398 | :two, 399 | fn -> 400 | :three 401 | end, 402 | :four 403 | ] 404 | end 405 | end 406 | EOF 407 | 408 | i <<~EOF 409 | defmodule Hi do 410 | defp hi do 411 | { 412 | :one, 413 | :two, 414 | fn -> 415 | :three 416 | end, 417 | :four 418 | } 419 | end 420 | end 421 | EOF 422 | 423 | i <<~EOF 424 | defmodule Hi do 425 | defp hi do 426 | %Struct{ 427 | :one, 428 | :two, 429 | fn -> 430 | :three 431 | end, 432 | :four 433 | } 434 | end 435 | end 436 | EOF 437 | 438 | i <<~EOF 439 | defmodule Hi do 440 | defp hi do 441 | %{ 442 | :one, 443 | :two, 444 | fn -> 445 | :three 446 | end, 447 | :four 448 | } 449 | end 450 | end 451 | EOF 452 | 453 | i <<~EOF 454 | defmodule Hi do 455 | defp hi do 456 | try do 457 | raise "boom" 458 | rescue 459 | e in errs -> 460 | IO.puts "one" 461 | 462 | _ -> 463 | IO.puts "one" 464 | end 465 | end 466 | end 467 | EOF 468 | 469 | i <<~EOF 470 | defmodule Hi do 471 | defp hi do 472 | try do 473 | raise "wtf" 474 | catch 475 | e -> 476 | IO.puts "one" 477 | 478 | _ -> 479 | IO.puts "one" 480 | end 481 | end 482 | end 483 | EOF 484 | 485 | i <<~EOF 486 | defmodule Hi do 487 | defp hi do 488 | receive do 489 | {:hello, world} -> 490 | :ok 491 | after 492 | 1000 -> 493 | IO.puts "one" 494 | 495 | 2000 -> 496 | IO.puts "one" 497 | end 498 | end 499 | end 500 | EOF 501 | 502 | i <<~EOF 503 | defmodule Hi do 504 | defp hi do 505 | receive do 506 | {:hello, world} -> 507 | :ok 508 | 509 | _ -> 510 | :err 511 | end 512 | end 513 | end 514 | EOF 515 | 516 | i <<~EOF 517 | defmodule Hi do 518 | defp hi do 519 | fn 520 | :ok -> 521 | IO.puts :ok 522 | _ -> 523 | IO.puts :err 524 | end 525 | end 526 | end 527 | EOF 528 | 529 | i <<~EOF 530 | defmodule Hi do 531 | defp hi do 532 | fn 533 | :ok -> IO.puts :ok 534 | _ -> IO.puts :err 535 | end 536 | end 537 | end 538 | EOF 539 | 540 | i <<~EOF 541 | fun2 = fn :foo -> 542 | :bar 543 | 'end' 544 | end 545 | 546 | EOF 547 | 548 | i <<~EOF 549 | fun2 = fn :foo -> 550 | :bar 551 | 'end' 552 | end 553 | EOF 554 | 555 | i <<~EOF 556 | fun3 = fn :foo -> 557 | :bar 558 | :send 559 | end 560 | EOF 561 | 562 | i <<~EOF 563 | defmodule Hi do 564 | def hello_world do 565 | "end" 566 | 'end' 567 | end 568 | EOF 569 | end 570 | -------------------------------------------------------------------------------- /autoload/elixir/indent.vim: -------------------------------------------------------------------------------- 1 | if !exists("g:elixir_indent_max_lookbehind") 2 | let g:elixir_indent_max_lookbehind = 30 3 | endif 4 | 5 | " Return the effective value of 'shiftwidth' 6 | function! s:sw() 7 | return &shiftwidth == 0 ? &tabstop : &shiftwidth 8 | endfunction 9 | 10 | function! elixir#indent#indent(lnum) 11 | let lnum = a:lnum 12 | let text = getline(lnum) 13 | let prev_nb_lnum = prevnonblank(lnum-1) 14 | let prev_nb_text = getline(prev_nb_lnum) 15 | 16 | call s:debug("==> Indenting line " . lnum) 17 | call s:debug("text = '" . text . "'") 18 | 19 | let [_, curs_lnum, curs_col, _] = getpos('.') 20 | call cursor(lnum, 0) 21 | 22 | let handlers = [ 23 | \'inside_embedded_view', 24 | \'top_of_file', 25 | \'starts_with_string_continuation', 26 | \'following_trailing_binary_operator', 27 | \'starts_with_pipe', 28 | \'starts_with_binary_operator', 29 | \'inside_block', 30 | \'starts_with_end', 31 | \'inside_generic_block', 32 | \'follow_prev_nb' 33 | \] 34 | for handler in handlers 35 | call s:debug('testing handler elixir#indent#handle_'.handler) 36 | let context = {'lnum': lnum, 'text': text, 'first_nb_char_idx': match(text, '\w'), 'prev_nb_lnum': prev_nb_lnum, 'prev_nb_text': prev_nb_text} 37 | let indent = function('elixir#indent#handle_'.handler)(context) 38 | if indent == -2 39 | " Keep indent the same 40 | call s:debug('line '.lnum.': elixir#indent#handle_'.handler.' returned -2; returning indent of -1') 41 | call cursor(curs_lnum, curs_col) 42 | return -1 43 | elseif indent != -1 44 | call s:debug('line '.lnum.': elixir#indent#handle_'.handler.' returned '.indent) 45 | call cursor(curs_lnum, curs_col) 46 | return indent 47 | endif 48 | endfor 49 | 50 | call s:debug("defaulting") 51 | call cursor(curs_lnum, curs_col) 52 | return 0 53 | endfunction 54 | 55 | function! s:debug(str) 56 | if exists("g:elixir_indent_debug") && g:elixir_indent_debug 57 | echom a:str 58 | endif 59 | endfunction 60 | 61 | function! s:starts_with(context, expr) 62 | return s:_starts_with(a:context.text, a:expr, a:context.lnum) 63 | endfunction 64 | 65 | function! s:prev_starts_with(context, expr) 66 | return s:_starts_with(a:context.prev_nb_text, a:expr, a:context.prev_nb_lnum) 67 | endfunction 68 | 69 | function! s:in_embedded_view() 70 | let groups = map(synstack(line('.'), col('.')), "synIDattr(v:val, 'name')") 71 | for group in ['elixirPhoenixESigil', 'elixirLiveViewSigil', 'elixirSurfaceSigil', 'elixirHeexSigil'] 72 | if index(groups, group) >= 0 73 | return 1 74 | endif 75 | endfor 76 | 77 | return 0 78 | endfunction 79 | 80 | " Returns 0 or 1 based on whether or not the text starts with the given 81 | " expression and is not a string or comment 82 | function! s:_starts_with(text, expr, lnum) 83 | let pos = match(a:text, '^\s*'.a:expr) 84 | if pos == -1 85 | return 0 86 | else 87 | " NOTE: @jbodah 2017-02-24: pos is the index of the match which is 88 | " zero-indexed. Add one to make it the column number 89 | if s:is_string_or_comment(a:lnum, pos + 1) 90 | return 0 91 | else 92 | return 1 93 | end 94 | end 95 | endfunction 96 | 97 | function! s:prev_ends_with(context, expr) 98 | return s:_ends_with(a:context.prev_nb_text, a:expr, a:context.prev_nb_lnum) 99 | endfunction 100 | 101 | " Returns 0 or 1 based on whether or not the text ends with the given 102 | " expression and is not a string or comment 103 | function! s:_ends_with(text, expr, lnum) 104 | let pos = match(a:text, a:expr.'\s*$') 105 | if pos == -1 106 | return 0 107 | else 108 | if s:is_string_or_comment(a:lnum, pos) 109 | return 0 110 | else 111 | return 1 112 | end 113 | end 114 | endfunction 115 | 116 | " Returns 0 or 1 based on whether or not the given line number and column 117 | " number pair is a string or comment 118 | function! s:is_string_or_comment(line, col) 119 | return s:syntax_name(a:line, a:col) =~ '\%(String\|Comment\|CharList\)' 120 | endfunction 121 | 122 | function! s:syntax_name(line, col) 123 | return synIDattr(synID(a:line, a:col, 1), "name") 124 | endfunction 125 | 126 | " Skip expression for searchpair. Returns 0 or 1 based on whether the value 127 | " under the cursor is a string or comment 128 | function! elixir#indent#searchpair_back_skip() 129 | " NOTE: @jbodah 2017-02-27: for some reason this function gets called with 130 | " and index that doesn't exist in the line sometimes. Detect and account for 131 | " that situation 132 | let curr_col = col('.') 133 | if getline('.')[curr_col-1] == '' 134 | let curr_col = curr_col-1 135 | endif 136 | return s:is_string_or_comment(line('.'), curr_col) 137 | endfunction 138 | 139 | " DRY up regex for keywords that 1) makes sure we only look at complete words 140 | " and 2) ignores atoms 141 | function! s:keyword(expr) 142 | return ':\@:\@!' 143 | endfunction 144 | 145 | " Start at the end of text and search backwards looking for a match. Also peek 146 | " ahead if we get a match to make sure we get a complete match. This means 147 | " that the result should be the position of the start of the right-most match 148 | function! s:find_last_pos(lnum, text, match) 149 | let last = len(a:text) - 1 150 | let c = last 151 | 152 | while c >= 0 153 | let substr = strpart(a:text, c, last) 154 | let peek = strpart(a:text, c - 1, last) 155 | let ss_match = match(substr, a:match) 156 | if ss_match != -1 157 | let peek_match = match(peek, a:match) 158 | if peek_match == ss_match + 1 159 | let syng = synIDattr(synID(a:lnum, c + ss_match, 1), 'name') 160 | if syng !~ '\%(String\|Comment\|CharList\)' 161 | return c + ss_match 162 | end 163 | end 164 | end 165 | let c -= 1 166 | endwhile 167 | 168 | return -1 169 | endfunction 170 | 171 | function! elixir#indent#handle_inside_embedded_view(context) 172 | if !s:in_embedded_view() 173 | return -1 174 | endif 175 | 176 | " Multi-line Surface data delimiters 177 | let pair_lnum = searchpair('{{', '', '}}', 'bW', "line('.') == ".a:context.lnum." || s:is_string_or_comment(line('.'), col('.'))", max([0, a:context.lnum - g:elixir_indent_max_lookbehind])) 178 | if pair_lnum 179 | if a:context.text =~ '}}$' 180 | return indent(pair_lnum) 181 | elseif a:context.text =~ '}}*>$' 182 | return -1 183 | elseif s:prev_ends_with(a:context, '[\|%{') 184 | return indent(a:context.prev_nb_lnum) + s:sw() 185 | elseif a:context.prev_nb_text =~ ',$' 186 | return indent(a:context.prev_nb_lnum) 187 | else 188 | return indent(pair_lnum) + s:sw() 189 | endif 190 | endif 191 | 192 | " Multi-line opening tag -- >, />, or %> are on a different line that their opening < 193 | let pair_lnum = searchpair('^\s\+<.*[^>]$', '', '^[^<]*[/%}]\?>$', 'bW', "line('.') == ".a:context.lnum." || s:is_string_or_comment(line('.'), col('.'))", max([0, a:context.lnum - g:elixir_indent_max_lookbehind])) 194 | if pair_lnum 195 | if a:context.text =~ '^\s\+\%\(>\|\/>\|%>\|}}>\)$' 196 | call s:debug("current line is a lone >, />, or %>") 197 | return indent(pair_lnum) 198 | elseif a:context.text =~ '\%\(>\|\/>\|%>\|}}>\)$' 199 | call s:debug("current line ends in >, />, or %>") 200 | if s:prev_ends_with(a:context, ',') 201 | return indent(a:context.prev_nb_lnum) 202 | else 203 | return -1 204 | endif 205 | else 206 | call s:debug("in the body of a multi-line opening tag") 207 | return indent(pair_lnum) + s:sw() 208 | endif 209 | endif 210 | 211 | " Special cases 212 | if s:prev_ends_with(a:context, '^[^<]*do\s%>') 213 | call s:debug("prev line closes a multi-line do block") 214 | return indent(a:context.prev_nb_lnum) 215 | elseif a:context.prev_nb_text =~ 'do\s*%>$' 216 | call s:debug("prev line opens a do block") 217 | return indent(a:context.prev_nb_lnum) + s:sw() 218 | elseif a:context.text =~ '^\s\+<\/[a-zA-Z0-9\.\-_]\+>\|<% end %>' 219 | call s:debug("a single closing tag") 220 | if a:context.prev_nb_text =~ '^\s\+<[^%\/]*[^/]>.*<\/[a-zA-Z0-9\.\-_]\+>$' 221 | call s:debug("opening and closing tags are on the same line") 222 | return indent(a:context.prev_nb_lnum) - s:sw() 223 | elseif a:context.prev_nb_text =~ '^\s\+<[^%\/]*[^/]>\|\s\+>' 224 | call s:debug("prev line is opening html tag or single >") 225 | return indent(a:context.prev_nb_lnum) 226 | elseif s:prev_ends_with(a:context, '^[^<]*\%\(do\s\)\@') 227 | call s:debug("prev line closes a multi-line eex tag") 228 | return indent(a:context.prev_nb_lnum) - 2 * s:sw() 229 | else 230 | return indent(a:context.prev_nb_lnum) - s:sw() 231 | endif 232 | elseif a:context.text =~ '^\s*<%\s*\%(end\|else\|catch\|rescue\)\>.*%>' 233 | call s:debug("eex middle or closing eex tag") 234 | return indent(a:context.prev_nb_lnum) - s:sw() 235 | elseif a:context.prev_nb_text =~ '\s*<\/\|<% end %>$' 236 | call s:debug("prev is closing tag") 237 | return indent(a:context.prev_nb_lnum) 238 | elseif a:context.prev_nb_text =~ '^\s\+<[^%\/]*[^/]>.*<\/[a-zA-Z0-9\.\-_]\+>$' 239 | call s:debug("opening and closing tags are on the same line") 240 | return indent(a:context.prev_nb_lnum) 241 | elseif s:prev_ends_with(a:context, '\s\+\/>') 242 | call s:debug("prev ends with a single \>") 243 | return indent(a:context.prev_nb_lnum) 244 | elseif s:prev_ends_with(a:context, '^[^<]*\/>') 245 | call s:debug("prev line is closing a multi-line self-closing tag") 246 | return indent(a:context.prev_nb_lnum) - s:sw() 247 | elseif s:prev_ends_with(a:context, '^\s\+<.*\/>') 248 | call s:debug("prev line is closing self-closing tag") 249 | return indent(a:context.prev_nb_lnum) 250 | elseif a:context.prev_nb_text =~ '^\s\+%\?>$' 251 | call s:debug("prev line is a single > or %>") 252 | return indent(a:context.prev_nb_lnum) + s:sw() 253 | endif 254 | 255 | " Simple HTML (ie, opening tag is not split across lines) 256 | let pair_lnum = searchpair('^\s\+<[^%\/].*[^\/>]>$', '', '^\s\+<\/\w\+>$', 'bW', "line('.') == ".a:context.lnum." || s:is_string_or_comment(line('.'), col('.'))", max([0, a:context.lnum - g:elixir_indent_max_lookbehind])) 257 | if pair_lnum 258 | call s:debug("simple HTML") 259 | if a:context.text =~ '^\s\+<\/\w\+>$' 260 | return indent(pair_lnum) 261 | else 262 | return indent(pair_lnum) + s:sw() 263 | endif 264 | endif 265 | 266 | return -1 267 | endfunction 268 | 269 | function! elixir#indent#handle_top_of_file(context) 270 | if a:context.prev_nb_lnum == 0 271 | return 0 272 | else 273 | return -1 274 | end 275 | endfunction 276 | 277 | function! elixir#indent#handle_starts_with_string_continuation(context) 278 | if s:syntax_name(a:context.lnum, a:context.first_nb_char_idx) =~ '\(String\|Comment\|CharList\)$' 279 | return -2 280 | else 281 | return -1 282 | end 283 | endfunction 284 | 285 | function! elixir#indent#handle_follow_prev_nb(context) 286 | return s:get_base_indent(a:context.prev_nb_lnum, a:context.prev_nb_text) 287 | endfunction 288 | 289 | " Given the line at `lnum`, returns the indent of the line that acts as the 'base indent' 290 | " for this line. In particular it traverses backwards up things like pipelines 291 | " to find the beginning of the expression 292 | function! s:get_base_indent(lnum, text) 293 | let prev_nb_lnum = prevnonblank(a:lnum - 1) 294 | let prev_nb_text = getline(prev_nb_lnum) 295 | 296 | let binary_operator = '\%(=\|<>\|>>>\|<=\|||\|+\|\~\~\~\|-\|&&\|<<<\|/\|\^\^\^\|\*\)' 297 | let data_structure_close = '\%(\]\|}\|)\)' 298 | let pipe = '|>' 299 | 300 | if s:_starts_with(a:text, binary_operator, a:lnum) 301 | return s:get_base_indent(prev_nb_lnum, prev_nb_text) 302 | elseif s:_starts_with(a:text, pipe, a:lnum) 303 | return s:get_base_indent(prev_nb_lnum, prev_nb_text) 304 | elseif s:_ends_with(prev_nb_text, binary_operator, prev_nb_lnum) 305 | return s:get_base_indent(prev_nb_lnum, prev_nb_text) 306 | elseif s:_ends_with(a:text, data_structure_close, a:lnum) 307 | let data_structure_open = '\%(\[\|{\|(\)' 308 | let close_match_idx = match(a:text, data_structure_close . '\s*$') 309 | call cursor(a:lnum, close_match_idx + 1) 310 | let [open_match_lnum, open_match_col] = searchpairpos(data_structure_open, '', data_structure_close, 'bnW') 311 | let open_match_text = getline(open_match_lnum) 312 | return s:get_base_indent(open_match_lnum, open_match_text) 313 | else 314 | return indent(a:lnum) 315 | endif 316 | endfunction 317 | 318 | function! elixir#indent#handle_following_trailing_binary_operator(context) 319 | let binary_operator = '\%(=\|<>\|>>>\|<=\|||\|+\|\~\~\~\|-\|&&\|<<<\|/\|\^\^\^\|\*\)' 320 | 321 | if s:prev_ends_with(a:context, binary_operator) 322 | return indent(a:context.prev_nb_lnum) + s:sw() 323 | else 324 | return -1 325 | endif 326 | endfunction 327 | 328 | function! elixir#indent#handle_starts_with_pipe(context) 329 | if s:starts_with(a:context, '|>') 330 | let match_operator = '\%(!\|=\|<\|>\)\@\|\~\)\@!' 331 | let pos = s:find_last_pos(a:context.prev_nb_lnum, a:context.prev_nb_text, match_operator) 332 | if pos == -1 333 | return indent(a:context.prev_nb_lnum) 334 | else 335 | let next_word_pos = match(strpart(a:context.prev_nb_text, pos+1, len(a:context.prev_nb_text)-1), '\S') 336 | if next_word_pos == -1 337 | return indent(a:context.prev_nb_lnum) + s:sw() 338 | else 339 | return pos + 1 + next_word_pos 340 | end 341 | end 342 | else 343 | return -1 344 | endif 345 | endfunction 346 | 347 | function! elixir#indent#handle_starts_with_end(context) 348 | if s:starts_with(a:context, s:keyword('end')) 349 | let pair_lnum = searchpair(s:keyword('do\|fn'), '', s:keyword('end').'\zs', 'bnW', "line('.') == " . line('.') . " || elixir#indent#searchpair_back_skip()") 350 | return indent(pair_lnum) 351 | else 352 | return -1 353 | endif 354 | endfunction 355 | 356 | function! elixir#indent#handle_starts_with_binary_operator(context) 357 | let binary_operator = '\%(=\|<>\|>>>\|<=\|||\|+\|\~\~\~\|-\|&&\|<<<\|/\|\^\^\^\|\*\)' 358 | 359 | if s:starts_with(a:context, binary_operator) 360 | let match_operator = '\%(!\|=\|<\|>\)\@\|\~\)\@!' 361 | let pos = s:find_last_pos(a:context.prev_nb_lnum, a:context.prev_nb_text, match_operator) 362 | if pos == -1 363 | return indent(a:context.prev_nb_lnum) 364 | else 365 | let next_word_pos = match(strpart(a:context.prev_nb_text, pos+1, len(a:context.prev_nb_text)-1), '\S') 366 | if next_word_pos == -1 367 | return indent(a:context.prev_nb_lnum) + s:sw() 368 | else 369 | return pos + 1 + next_word_pos 370 | end 371 | end 372 | else 373 | return -1 374 | endif 375 | endfunction 376 | 377 | " To handle nested structures properly we need to find the innermost 378 | " nested structure. For example, we might be in a function in a map in a 379 | " function, etc... so we need to first figure out what the innermost structure 380 | " is then forward execution to the proper handler 381 | function! elixir#indent#handle_inside_block(context) 382 | let start_pattern = '\C\%(\\|\\|\\|\\|\\|\\|\\|\\|{\|\[\|(\):\@!' 383 | let end_pattern = '\C\%(\\|\]\|}\|)\)' 384 | " hack - handle do: better 385 | let block_info = searchpairpos(start_pattern, '', end_pattern, 'bnW', "line('.') == " . line('.') . " || elixir#indent#searchpair_back_skip() || getline(line('.')) =~ 'do:'", max([0, a:context.lnum - g:elixir_indent_max_lookbehind])) 386 | let block_start_lnum = block_info[0] 387 | call s:debug("block_start_lnum=" . block_start_lnum) 388 | let block_start_col = block_info[1] 389 | if block_start_lnum != 0 || block_start_col != 0 390 | let block_text = getline(block_start_lnum) 391 | let block_start_char = block_text[block_start_col - 1] 392 | call s:debug("block_start_char=" . block_start_char) 393 | 394 | let never_match = '' 395 | let config = { 396 | \'f': {'aligned_clauses': s:keyword('end'), 'pattern_match_clauses': never_match}, 397 | \'q': {'aligned_clauses': s:keyword('end'), 'pattern_match_clauses': never_match}, 398 | \'c': {'aligned_clauses': s:keyword('end'), 'pattern_match_clauses': never_match}, 399 | \'t': {'aligned_clauses': s:keyword('end\|catch\|rescue\|after\|else'), 'pattern_match_clauses': s:keyword('catch\|rescue\|else')}, 400 | \'r': {'aligned_clauses': s:keyword('end\|after'), 'pattern_match_clauses': s:keyword('after')}, 401 | \'i': {'aligned_clauses': s:keyword('end\|else'), 'pattern_match_clauses': never_match}, 402 | \'[': {'aligned_clauses': ']', 'pattern_match_clauses': never_match}, 403 | \'{': {'aligned_clauses': '}', 'pattern_match_clauses': never_match}, 404 | \'(': {'aligned_clauses': ')', 'pattern_match_clauses': never_match} 405 | \} 406 | 407 | " if `with` clause... 408 | if block_start_char == 'w' 409 | call s:debug("testing s:handle_with") 410 | return s:handle_with(block_start_lnum, block_start_col, a:context) 411 | else 412 | let block_config = config[block_start_char] 413 | " if aligned clause (closing tag/`else` clause/etc...) then indent this 414 | " at the same level as the block open tag (e.g. `if`/`case`/etc...) 415 | if s:starts_with(a:context, block_config.aligned_clauses) 416 | call s:debug("clause") 417 | return indent(block_start_lnum) 418 | else 419 | if block_config.pattern_match_clauses == never_match 420 | let relative_lnum = block_start_lnum 421 | else 422 | let clause_lnum = searchpair(block_config.pattern_match_clauses, '', '*', 'bnW', "line('.') == " . line('.') . " || elixir#indent#searchpair_back_skip()", block_start_lnum) 423 | call s:debug("clause_lum=" . clause_lnum) 424 | let relative_lnum = max([clause_lnum, block_start_lnum]) 425 | end 426 | call s:debug("pattern matching relative to lnum " . relative_lnum) 427 | return s:do_handle_pattern_match_block(relative_lnum, a:context) 428 | endif 429 | end 430 | else 431 | return -1 432 | end 433 | endfunction 434 | 435 | function! s:handle_with(start_lnum, start_col, context) 436 | let block_info = searchpairpos('\C\%(\\|\\|\\)', '', s:keyword('end'), 'bnW', "line('.') == " . line('.') . " || elixir#indent#searchpair_back_skip()") 437 | let block_start_lnum = block_info[0] 438 | let block_start_col = block_info[1] 439 | 440 | let block_start_text = getline(block_start_lnum) 441 | let block_start_char = block_start_text[block_start_col - 1] 442 | 443 | if s:starts_with(a:context, s:keyword('do\|else\|end')) 444 | return indent(a:start_lnum) 445 | elseif block_start_char == 'w' || s:starts_with(a:context, '\C\(do\|else\):') 446 | return indent(a:start_lnum) + 5 447 | elseif s:_starts_with(block_start_text, '\C\(do\|else\):', a:start_lnum) 448 | return indent(block_start_lnum) + s:sw() 449 | else 450 | return s:do_handle_pattern_match_block(a:start_lnum, a:context) 451 | end 452 | endfunction 453 | 454 | function! s:do_handle_pattern_match_block(relative_line, context) 455 | let relative_indent = indent(a:relative_line) 456 | " hack! 457 | if a:context.text =~ '\(fn.*\)\@' 458 | call s:debug("current line contains ->; assuming match definition") 459 | return relative_indent + s:sw() 460 | elseif search('\(fn.*\)\@', 'bnW', a:relative_line) != 0 461 | call s:debug("a previous line contains ->; assuming match handler") 462 | return relative_indent + 2 * s:sw() 463 | else 464 | call s:debug("couldn't find any previous ->; assuming body text") 465 | return relative_indent + s:sw() 466 | end 467 | endfunction 468 | 469 | function! elixir#indent#handle_inside_generic_block(context) 470 | let pair_lnum = searchpair(s:keyword('do\|fn'), '', s:keyword('end'), 'bW', "line('.') == ".a:context.lnum." || s:is_string_or_comment(line('.'), col('.'))", max([0, a:context.lnum - g:elixir_indent_max_lookbehind])) 471 | if pair_lnum 472 | " TODO: @jbodah 2017-03-29: this should probably be the case in *all* 473 | " blocks 474 | if s:prev_ends_with(a:context, ',') 475 | return indent(pair_lnum) + 2 * s:sw() 476 | else 477 | return indent(pair_lnum) + s:sw() 478 | endif 479 | else 480 | return -1 481 | endif 482 | endfunction 483 | -------------------------------------------------------------------------------- /syntax/elixir.vim: -------------------------------------------------------------------------------- 1 | if !exists("main_syntax") 2 | if exists("b:current_syntax") 3 | finish 4 | endif 5 | let main_syntax = "elixir" 6 | endif 7 | 8 | let s:cpo_save = &cpo 9 | set cpo&vim 10 | 11 | syn cluster elixirNotTop contains=@elixirRegexSpecial,@elixirStringContained,@elixirDeclaration,elixirTodo,elixirArguments,elixirBlockDefinition,elixirUnusedVariable,elixirStructDelimiter,elixirListDelimiter 12 | syn cluster elixirRegexSpecial contains=elixirRegexEscape,elixirRegexCharClass,elixirRegexQuantifier,elixirRegexEscapePunctuation 13 | syn cluster elixirStringContained contains=elixirInterpolation,elixirRegexEscape,elixirRegexCharClass 14 | syn cluster elixirDeclaration contains=elixirFunctionDeclaration,elixirPrivateFunctionDeclaration,elixirModuleDeclaration,elixirProtocolDeclaration,elixirImplDeclaration,elixirRecordDeclaration,elixirPrivateRecordDeclaration,elixirMacroDeclaration,elixirPrivateMacroDeclaration,elixirDelegateDeclaration,elixirOverridableDeclaration,elixirExceptionDeclaration,elixirCallbackDeclaration,elixirStructDeclaration 15 | 16 | syn match elixirComment '#.*' contains=elixirTodo,@Spell 17 | syn keyword elixirTodo FIXME NOTE TODO OPTIMIZE XXX HACK contained 18 | 19 | syn match elixirId '\<[_a-zA-Z]\w*[!?]\?\>' contains=elixirUnusedVariable 20 | 21 | syn match elixirKeyword '\(\.\)\@:\@!' 22 | 23 | syn keyword elixirInclude import require alias use 24 | 25 | syn keyword elixirSelf self 26 | 27 | syn match elixirUnusedVariable contained '\%(\.\)\@\%((\)\@!' 28 | 29 | syn match elixirOperator '\v\.@' 30 | syn match elixirOperator '!==\|!=\|!' 31 | syn match elixirOperator '=\~\|===\|==\|=' 32 | syn match elixirOperator '<<<\|<<\|<=\|<-\|<' 33 | syn match elixirOperator '>>>\|>>\|>=\|>' 34 | syn match elixirOperator '->\|--\|-' 35 | syn match elixirOperator '++\|+' 36 | syn match elixirOperator '&&&\|&&\|&' 37 | syn match elixirOperator '|||\|||\||>\||' 38 | syn match elixirOperator '\.\.\|\.' 39 | syn match elixirOperator "\^\^\^\|\^" 40 | syn match elixirOperator '\\\\\|::\|\*\|/\|\~\~\~\|@' 41 | syn match elixirOperator '\~>\|\~>>\|<\~\|<<\~\|<\~>' 42 | 43 | syn match elixirAlias '\([a-z]\)\@=]\@!\)\?\|<>\|===\?\|>=\?\|<=\?\)' 46 | syn match elixirAtom '\(:\)\@\|&&\?\|%\(()\|\[\]\|{}\)\|++\?\|--\?\|||\?\|!\|//\|[%&`/|]\)' 47 | syn match elixirAtom "\%([a-zA-Z_]\w*[?!]\?\):\(:\)\@!" 48 | 49 | syn keyword elixirBoolean true false nil 50 | 51 | syn match elixirVariable '@[a-z]\w*' 52 | syn match elixirVariable '&\d\+' 53 | 54 | syn keyword elixirPseudoVariable __FILE__ __DIR__ __MODULE__ __ENV__ __CALLER__ __STACKTRACE__ 55 | 56 | syn match elixirNumber '\<-\?\d\(_\?\d\)*\(\.[^[:space:][:digit:]]\@!\(_\?\d\)*\)\?\([eE][-+]\?\d\(_\?\d\)*\)\?\>' 57 | syn match elixirNumber '\<-\?0[xX][0-9A-Fa-f]\+\>' 58 | syn match elixirNumber '\<-\?0[oO][0-7]\+\>' 59 | syn match elixirNumber '\<-\?0[bB][01]\+\>' 60 | 61 | syn match elixirRegexEscape "\\\\\|\\[aAbBcdDefGhHnrsStvVwW]\|\\\d\{3}\|\\x[0-9a-fA-F]\{2}" contained 62 | syn match elixirRegexEscapePunctuation "?\|\\.\|*\|\\\[\|\\\]\|+\|\\^\|\\\$\|\\|\|\\(\|\\)\|\\{\|\\}" contained 63 | syn match elixirRegexQuantifier "[*?+][?+]\=" contained display 64 | syn match elixirRegexQuantifier "{\d\+\%(,\d*\)\=}?\=" contained display 65 | syn match elixirRegexCharClass "\[:\(alnum\|alpha\|ascii\|blank\|cntrl\|digit\|graph\|lower\|print\|punct\|space\|upper\|word\|xdigit\):\]" contained display 66 | 67 | syn region elixirRegex matchgroup=elixirRegexDelimiter start="%r/" end="/[uiomxfr]*" skip="\\\\" contains=@elixirRegexSpecial 68 | 69 | syn region elixirTuple matchgroup=elixirTupleDelimiter start="\(\w\|#\)\@\|0[0-7]{0,2}[0-7]\@!\>\|[^x0MC]\)\|(\\[MC]-)+\w\|[^\s\\]\)" 87 | 88 | syn region elixirBlock matchgroup=elixirBlockDefinition start="\:\@!" end="\" contains=ALLBUT,@elixirNotTop fold 89 | syn region elixirAnonymousFunction matchgroup=elixirBlockDefinition start="\" end="\" contains=ALLBUT,@elixirNotTop fold 90 | 91 | syn region elixirArguments start="(" end=")" contained contains=elixirOperator,elixirAtom,elixirMap,elixirStruct,elixirTuple,elixirPseudoVariable,elixirAlias,elixirBoolean,elixirVariable,elixirUnusedVariable,elixirNumber,elixirDocString,elixirAtomInterpolated,elixirRegex,elixirString,elixirStringDelimiter,elixirRegexDelimiter,elixirInterpolationDelimiter,elixirSigil,elixirAnonymousFunction,elixirComment,elixirCharList,elixirCharListDelimiter 92 | 93 | syn match elixirDelimEscape "\\[(<{\[)>}\]/\"'|]" transparent display contained contains=NONE 94 | 95 | syn region elixirSigil matchgroup=elixirSigilDelimiter start="\~\u\+\z(/\|\"\|'\||\)" end="\z1" skip="\\\\\|\\\z1" contains=elixirDelimEscape fold 96 | syn region elixirSigil matchgroup=elixirSigilDelimiter start="\~\u\+{" end="}" skip="\\\\\|\\}" contains=elixirDelimEscape fold 97 | syn region elixirSigil matchgroup=elixirSigilDelimiter start="\~\u\+<" end=">" skip="\\\\\|\\>" contains=elixirDelimEscape fold 98 | syn region elixirSigil matchgroup=elixirSigilDelimiter start="\~\u\+\[" end="\]" skip="\\\\\|\\\]" contains=elixirDelimEscape fold 99 | syn region elixirSigil matchgroup=elixirSigilDelimiter start="\~\u\+(" end=")" skip="\\\\\|\\)" contains=elixirDelimEscape fold 100 | 101 | syn region elixirSigil matchgroup=elixirSigilDelimiter start="\~\l\z(/\|\"\|'\||\)" end="\z1" skip="\\\\\|\\\z1" fold 102 | syn region elixirSigil matchgroup=elixirSigilDelimiter start="\~\l{" end="}" skip="\\\\\|\\}" contains=@elixirStringContained,elixirRegexEscapePunctuation fold 103 | syn region elixirSigil matchgroup=elixirSigilDelimiter start="\~\l<" end=">" skip="\\\\\|\\>" contains=@elixirStringContained,elixirRegexEscapePunctuation fold 104 | syn region elixirSigil matchgroup=elixirSigilDelimiter start="\~\l\[" end="\]" skip="\\\\\|\\\]" contains=@elixirStringContained,elixirRegexEscapePunctuation fold 105 | syn region elixirSigil matchgroup=elixirSigilDelimiter start="\~\l(" end=")" skip="\\\\\|\\)" contains=@elixirStringContained,elixirRegexEscapePunctuation fold 106 | syn region elixirSigil matchgroup=elixirSigilDelimiter start="\~\l\/" end="\/" skip="\\\\\|\\\/" contains=@elixirStringContained,elixirRegexEscapePunctuation fold 107 | 108 | " Sigils surrounded with heredoc 109 | syn region elixirSigil matchgroup=elixirSigilDelimiter start=+\~\u\+\z("""\)+ end=+^\s*\z1+ skip=+\\"+ fold 110 | syn region elixirSigil matchgroup=elixirSigilDelimiter start=+\~\u\z('''\)+ end=+^\s*\z1+ skip=+\\'+ fold 111 | syn region elixirSigil matchgroup=elixirSigilDelimiter start=+\~\l\z("""\)+ end=+^\s*\z1+ skip=+\\"+ fold 112 | syn region elixirSigil matchgroup=elixirSigilDelimiter start=+\~\l\z('''\)+ end=+^\s*\z1+ skip=+\\'+ fold 113 | 114 | " LiveView-specific sigils for embedded templates 115 | syntax include @HTML syntax/html.vim 116 | unlet b:current_syntax 117 | syntax region elixirHeexSigil matchgroup=elixirSigilDelimiter keepend start=+\~H\z("""\)+ end=+^\s*\z1+ skip=+\\"+ contains=@HTML fold 118 | syntax region elixirSurfaceSigil matchgroup=elixirSigilDelimiter keepend start=+\~F\z("""\)+ end=+^\s*\z1+ skip=+\\"+ contains=@HTML fold 119 | syntax region elixirLiveViewSigil matchgroup=elixirSigilDelimiter keepend start=+\~L\z("""\)+ end=+^\s*\z1+ skip=+\\"+ contains=@HTML fold 120 | syntax region elixirPhoenixESigil matchgroup=elixirSigilDelimiter keepend start=+\~E\z("""\)+ end=+^\s*\z1+ skip=+\\"+ contains=@HTML fold 121 | syntax region elixirPhoenixeSigil matchgroup=elixirSigilDelimiter keepend start=+\~e\z("""\)+ end=+^\s*\z1+ skip=+\\"+ contains=@HTML fold 122 | 123 | syn cluster elixirTemplateSigils contains=elixirLiveViewSigil,elixirHeexSigil,elixirSurfaceSigil,elixirPhoenixESigil,elixirPhoenixeSigil 124 | 125 | syn region heexComponent matchgroup=eelixirDelimiter start="<\.[a-z_]\+" end="%\@" contains=ALLBUT,@elixirNotTop keepend 126 | syn region eelixirExpression matchgroup=eelixirDelimiter start="<%" end="%\@" contains=ALLBUT,@elixirNotTop containedin=@elixirTemplateSigils keepend 127 | syn region eelixirExpression matchgroup=eelixirDelimiter start="<%=" end="%\@" contains=ALLBUT,@elixirNotTop containedin=@elixirTemplateSigils keepend 128 | syn region eelixirQuote matchgroup=eelixirDelimiter start="<%%" end="%\@" contains=ALLBUT,@elixirNotTop containedin=@elixirTemplateSigils keepend 129 | syn region heexComment matchgroup=eelixirDelimiter start="<%!--" end="%\@