diff options
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | autoload/crystal_lang.vim | 332 | ||||
| -rw-r--r-- | autoload/vital.vim | 16 | ||||
| -rw-r--r-- | autoload/vital/_crystal.vim | 313 | ||||
| -rw-r--r-- | autoload/vital/_crystal/ColorEcho.vim | 182 | ||||
| -rw-r--r-- | autoload/vital/_crystal/Data/List.vim | 446 | ||||
| -rw-r--r-- | autoload/vital/_crystal/Data/String.vim | 572 | ||||
| -rw-r--r-- | autoload/vital/_crystal/Prelude.vim | 389 | ||||
| -rw-r--r-- | autoload/vital/_crystal/Process.vim | 185 | ||||
| -rw-r--r-- | autoload/vital/_crystal/Web/JSON.vim | 112 | ||||
| -rwxr-xr-x | build | 1 | ||||
| -rw-r--r-- | ftdetect/polyglot.vim | 6 | ||||
| -rw-r--r-- | ftplugin/crystal.vim | 60 | ||||
| -rw-r--r-- | indent/crystal.vim | 639 | ||||
| -rw-r--r-- | syntax/crystal.vim | 393 | 
15 files changed, 3647 insertions, 0 deletions
| @@ -34,6 +34,7 @@ Optionally download one of the [releases](https://github.com/sheerun/vim-polyglo  - [clojure](https://github.com/guns/vim-clojure-static) (syntax, indent, autoload, ftplugin, ftdetect)  - [coffee-script](https://github.com/kchmck/vim-coffee-script) (syntax, indent, compiler, autoload, ftplugin, ftdetect)  - [cryptol](https://github.com/victoredwardocallaghan/cryptol.vim) (syntax, compiler, ftplugin, ftdetect) +- [crystal](https://github.com/rhysd/vim-crystal) (syntax, indent, autoload, ftplugin, ftdetect)  - [cql](https://github.com/elubow/cql-vim) (syntax, ftdetect)  - [css](https://github.com/JulesWang/css.vim) (syntax)  - [cucumber](https://github.com/tpope/vim-cucumber) (syntax, indent, compiler, ftplugin, ftdetect) diff --git a/autoload/crystal_lang.vim b/autoload/crystal_lang.vim new file mode 100644 index 00000000..8b1252d2 --- /dev/null +++ b/autoload/crystal_lang.vim @@ -0,0 +1,332 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1 +   +let s:save_cpo = &cpo +set cpo&vim + +let s:V = vital#of('crystal') +let s:P = s:V.import('Process') +let s:J = s:V.import('Web.JSON') +let s:C = s:V.import('ColorEcho') + +function! s:echo_error(msg, ...) abort +    echohl ErrorMsg +    if a:0 == 0 +        echomsg a:msg +    else +        echomsg call('printf', [a:msg] + a:000) +    endif +    echohl None +endfunction + +function! s:run_cmd(cmd) abort +    if !executable(g:crystal_compiler_command) +        throw "vim-crystal: Error: '" . g:crystal_compiler_command . "' command is not found." +    endif +    return s:P.system(a:cmd) +endfunction + +function! s:find_root_by_spec(d) abort +    let dir = finddir('spec', a:d . ';') +    if dir ==# '' +        return '' +    endif + +    " Note: ':h:h' for {root}/spec/ -> {root}/spec -> {root} +    return fnamemodify(dir, ':p:h:h') +endfunction + +function! crystal_lang#entrypoint_for(file_path) abort +    let parent_dir = fnamemodify(a:file_path, ':p:h') +    let root_dir = s:find_root_by_spec(parent_dir) +    if root_dir ==# '' +        " No spec diretory found. No need to make temporary file +        return a:file_path +    endif + +    let temp_name = root_dir . '/__vim-crystal-temporary-entrypoint-' . fnamemodify(a:file_path, ':t') +    let contents = [ +                \   'require "spec"', +                \   'require "./spec/**"', +                \   printf('require "./%s"', fnamemodify(a:file_path, ':p')[strlen(root_dir)+1 : ]) +                \ ] + +    let result = writefile(contents, temp_name) +    if result == -1 +        " Note: When writefile() failed +        return a:file_path +    endif + +    return temp_name +endfunction + +function! crystal_lang#tool(name, file, pos, option_str) abort +    let entrypoint = crystal_lang#entrypoint_for(a:file) +    let cmd = printf( +                \   '%s tool %s --no-color %s --cursor %s:%d:%d %s', +                \   g:crystal_compiler_command, +                \   a:name, +                \   a:option_str, +                \   a:file, +                \   a:pos[1], +                \   a:pos[2], +                \   entrypoint +                \ ) + +    try +        let output = s:run_cmd(cmd) +        return {'failed': s:P.get_last_status(), 'output': output} +    finally +        " Note: +        " If the entry point is temporary file, delete it finally. +        if a:file !=# entrypoint +            call delete(entrypoint) +        endif +    endtry +endfunction + +" `pos` is assumed a returned value from getpos() +function! crystal_lang#impl(file, pos, option_str) abort +    return crystal_lang#tool('implementations', a:file, a:pos, a:option_str) +endfunction + +function! s:jump_to_impl(impl) abort +    execute 'edit' a:impl.filename +    call cursor(a:impl.line, a:impl.column) +endfunction + +function! crystal_lang#jump_to_definition(file, pos) abort +    echo 'analyzing definitions under cursor...' + +    let cmd_result = crystal_lang#impl(a:file, a:pos, '--format json') +    if cmd_result.failed +        return s:echo_error(cmd_result.output) +    endif + +    let impl = s:J.decode(cmd_result.output) +    if impl.status !=# 'ok' +        return s:echo_error(impl.message) +    endif + +    if len(impl.implementations) == 1 +        call s:jump_to_impl(impl.implementations[0]) +        return +    endif + +    let message = "Multiple definitions detected.  Choose a number\n\n" +    for idx in range(len(impl.implementations)) +        let i = impl.implementations[idx] +        let message .= printf("[%d] %s:%d:%d\n", idx, i.filename, i.line, i.column) +    endfor +    let message .= "\n" +    let idx = str2nr(input(message, "\n> ")) +    call s:jump_to_impl(impl.implementations[idx]) +endfunction + +function! crystal_lang#context(file, pos, option_str) abort +    return crystal_lang#tool('context', a:file, a:pos, a:option_str) +endfunction + +function! crystal_lang#type_hierarchy(file, option_str) abort +    let cmd = printf( +                \   '%s tool hierarchy --no-color %s %s', +                \   g:crystal_compiler_command, +                \   a:option_str, +                \   a:file +                \ ) + +    return s:run_cmd(cmd) +endfunction + +function! s:find_completion_start() abort +    let c = col('.') +    if c <= 1 +        return -1 +    endif + +    let line = getline('.')[:c-2] +    return match(line, '\w\+$') +endfunction + +function! crystal_lang#complete(findstart, base) abort +    if a:findstart +        echom 'find start' +        return s:find_completion_start() +    endif + +    let cmd_result = crystal_lang#context(expand('%'), getpos('.'), '--format json') +    if cmd_result.failed +        return +    endif + +    let contexts = s:J.decode(cmd_result.output) +    if contexts.status !=# 'ok' +        return +    endif + +    let candidates = [] + +    for c in contexts.contexts +        for [name, desc] in items(c) +            let candidates += [{ +                        \   'word': name, +                        \   'menu': ': ' . desc . ' [var]', +                        \ }] +        endfor +    endfor + +    return candidates +endfunction + +function! crystal_lang#get_spec_switched_path(absolute_path) abort +    let base = fnamemodify(a:absolute_path, ':t:r') + +    " TODO: Make cleverer +    if base =~# '_spec$' +        let parent = fnamemodify(substitute(a:absolute_path, '/spec/', '/src/', ''), ':h') +        return parent . '/' . matchstr(base, '.\+\ze_spec$') . '.cr' +    else +        let parent = fnamemodify(substitute(a:absolute_path, '/src/', '/spec/', ''), ':h') +        return parent . '/' . base . '_spec.cr' +    endif +endfunction + +function! crystal_lang#switch_spec_file(...) abort +    let path = a:0 == 0 ? expand('%:p') : fnamemodify(a:1, ':p') +    if path !~# '.cr$' +        return s:echo_error('Not crystal source file: ' . path) +    endif + +    execute 'edit!' crystal_lang#get_spec_switched_path(path) +endfunction + +function! s:run_spec(root, path, ...) abort +    " Note: +    " `crystal spec` can't understand absolute path. +    let cmd = printf( +            \   '%s spec %s%s', +            \   g:crystal_compiler_command, +            \   a:path, +            \   a:0 == 0 ? '' : (':' . a:1) +            \ ) + +    let saved_cwd = getcwd() +    let cd = haslocaldir() ? 'lcd' : 'cd' +    try +        execute cd a:root +        call s:C.echo(s:run_cmd(cmd)) +    finally +        execute cd saved_cwd +    endtry +endfunction + +function! crystal_lang#run_all_spec(...) abort +    let path = a:0 == 0 ? expand('%:p:h') : a:1 +    let root_path = s:find_root_by_spec(path) +    if root_path ==# '' +        return s:echo_error("'spec' directory is not found") +    endif +    call s:run_spec(root_path, 'spec') +endfunction + +function! crystal_lang#run_current_spec(...) abort +    " /foo/bar/src/poyo.cr +    let path = a:0 == 0 ? expand('%:p') : fnamemodify(a:1, ':p') +    if path !~# '.cr$' +        return s:echo_error('Not crystal source file: ' . path) +    endif + +    " /foo/bar/src +    let source_dir = fnamemodify(path, ':h') + +    " /foo/bar +    let root_dir = s:find_root_by_spec(source_dir) +    if root_dir ==# '' +        return s:echo_error("'spec' directory is not found") +    endif + +    " src +    let rel_path = source_dir[strlen(root_dir)+1 : ] + +    if path =~# '_spec.cr$' +        call s:run_spec(root_dir, path[strlen(root_dir)+1 : ], line('.')) +    else +        let spec_path = substitute(rel_path, '^src', 'spec', '') . '/' . fnamemodify(path, ':t:r') . '_spec.cr' +        if !filereadable(root_dir . '/' . spec_path) +            return s:echo_error('Error: Could not find a spec source corresponding to ' . path) +        endif +        call s:run_spec(root_dir, spec_path) +    endif +endfunction + +function! crystal_lang#format_string(code, ...) abort +    let cmd = printf( +            \   '%s tool format --no-color %s -', +            \   g:crystal_compiler_command, +            \   get(a:, 1, '') +            \ ) +    let output = s:P.system(cmd, a:code) +    if s:P.get_last_status() +        throw 'vim-crystal: Error on formatting: ' . output +    endif +    return output +endfunction + +function! s:get_saved_states() abort +    let result = {} +    let fname = bufname('%') +    let current_winnr = winnr() +    for i in range(1, winnr('$')) +        let bufnr = winbufnr(i) +        if bufnr == -1 +            continue +        endif +        if bufname(bufnr) ==# fname +            execute i 'wincmd w' +            let result[i] = { +                \     'pos': getpos('.'), +                \     'screen': winsaveview() +                \ } +        endif +    endfor +    execute current_winnr 'wincmd w' +    return result +endfunction + +function! crystal_lang#format(option_str) abort +    if !executable(g:crystal_compiler_command) +        " Finish command silently +        return +    endif + +    let formatted = crystal_lang#format_string(join(getline(1, '$'), "\n"), a:option_str) +    let formatted = substitute(formatted, '\n$', '', '') + +    let sel_save = &l:selection +    let ve_save = &virtualedit +    let &l:selection = 'inclusive' +    let &virtualedit = '' +    let [save_g_reg, save_g_regtype] = [getreg('g'), getregtype('g')] +    let windows_save = s:get_saved_states() + +    try +        call setreg('g', formatted, 'v') +        silent normal! ggvG$"gp +    finally +        call setreg('g', save_g_reg, save_g_regtype) +        let &l:selection = sel_save +        let &virtualedit = ve_save +        let winnr = winnr() +        for winnr in keys(windows_save) +            let w = windows_save[winnr] +            execute winnr 'wincmd w' +            call setpos('.', w.pos) +            call winrestview(w.screen) +        endfor +        execute winnr 'wincmd w' +    endtry +endfunction + +let &cpo = s:save_cpo +unlet s:save_cpo + +endif diff --git a/autoload/vital.vim b/autoload/vital.vim new file mode 100644 index 00000000..98fe948f --- /dev/null +++ b/autoload/vital.vim @@ -0,0 +1,16 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1 +   +function! vital#of(name) abort +  let files = globpath(&runtimepath, 'autoload/vital/' . a:name . '.vital') +  let file = split(files, "\n") +  if empty(file) +    throw 'vital: version file not found: ' . a:name +  endif +  let ver = readfile(file[0], 'b') +  if empty(ver) +    throw 'vital: invalid version file: ' . a:name +  endif +  return vital#_{substitute(ver[0], '\W', '', 'g')}#new() +endfunction + +endif diff --git a/autoload/vital/_crystal.vim b/autoload/vital/_crystal.vim new file mode 100644 index 00000000..f5ff37f3 --- /dev/null +++ b/autoload/vital/_crystal.vim @@ -0,0 +1,313 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1 +   +let s:self_version = expand('<sfile>:t:r') +let s:self_file = expand('<sfile>') + +" Note: The extra argument to globpath() was added in Patch 7.2.051. +let s:globpath_third_arg = v:version > 702 || v:version == 702 && has('patch51') + +let s:loaded = {} +let s:cache_module_path = {} +let s:cache_sid = {} + +let s:_vital_files_cache_runtimepath = '' +let s:_vital_files_cache = [] +let s:_unify_path_cache = {} + +function! s:import(name, ...) abort +  let target = {} +  let functions = [] +  for a in a:000 +    if type(a) == type({}) +      let target = a +    elseif type(a) == type([]) +      let functions = a +    endif +    unlet a +  endfor +  let module = s:_import(a:name) +  if empty(functions) +    call extend(target, module, 'keep') +  else +    for f in functions +      if has_key(module, f) && !has_key(target, f) +        let target[f] = module[f] +      endif +    endfor +  endif +  return target +endfunction + +function! s:load(...) dict abort +  for arg in a:000 +    let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg] +    let target = split(join(as, ''), '\W\+') +    let dict = self +    let dict_type = type({}) +    while !empty(target) +      let ns = remove(target, 0) +      if !has_key(dict, ns) +        let dict[ns] = {} +      endif +      if type(dict[ns]) == dict_type +        let dict = dict[ns] +      else +        unlet dict +        break +      endif +    endwhile + +    if exists('dict') +      call extend(dict, s:_import(name)) +    endif +    unlet arg +  endfor +  return self +endfunction + +function! s:unload() abort +  let s:loaded = {} +  let s:cache_sid = {} +  let s:cache_module_path = {} +endfunction + +function! s:exists(name) abort +  return s:_get_module_path(a:name) !=# '' +endfunction + +function! s:search(pattern) abort +  let paths = s:_vital_files(a:pattern) +  let modules = sort(map(paths, 's:_file2module(v:val)')) +  return s:_uniq(modules) +endfunction + +function! s:expand_modules(entry, all) abort +  if type(a:entry) == type([]) +    let candidates = s:_concat(map(copy(a:entry), 's:search(v:val)')) +    if empty(candidates) +      throw printf('vital: Any of module %s is not found', string(a:entry)) +    endif +    if eval(join(map(copy(candidates), 'has_key(a:all, v:val)'), '+')) +      let modules = [] +    else +      let modules = [candidates[0]] +    endif +  else +    let modules = s:search(a:entry) +    if empty(modules) +      throw printf('vital: Module %s is not found', a:entry) +    endif +  endif +  call filter(modules, '!has_key(a:all, v:val)') +  for module in modules +    let a:all[module] = 1 +  endfor +  return modules +endfunction + +function! s:_import(name) abort +  if type(a:name) == type(0) +    return s:_build_module(a:name) +  endif +  let path = s:_get_module_path(a:name) +  if path ==# '' +    throw 'vital: module not found: ' . a:name +  endif +  let sid = s:_get_sid_by_script(path) +  if !sid +    try +      execute 'source' fnameescape(path) +    catch /^Vim\%((\a\+)\)\?:E484/ +      throw 'vital: module not found: ' . a:name +    catch /^Vim\%((\a\+)\)\?:E127/ +      " Ignore. +    endtry + +    let sid = s:_get_sid_by_script(path) +  endif +  return s:_build_module(sid) +endfunction + +function! s:_get_module_path(name) abort +  let key = a:name . '_' +  if has_key(s:cache_module_path, key) +    return s:cache_module_path[key] +  endif +  if s:_is_absolute_path(a:name) && filereadable(a:name) +    return a:name +  endif +  if a:name ==# '' +    let paths = [s:self_file] +  elseif a:name =~# '\v^\u\w*%(\.\u\w*)*$' +    let paths = s:_vital_files(a:name) +  else +    throw 'vital: Invalid module name: ' . a:name +  endif + +  call filter(paths, 'filereadable(expand(v:val, 1))') +  let path = get(paths, 0, '') +  let s:cache_module_path[key] = path +  return path +endfunction + +function! s:_get_sid_by_script(path) abort +  if has_key(s:cache_sid, a:path) +    return s:cache_sid[a:path] +  endif + +  let path = s:_unify_path(a:path) +  for line in filter(split(s:_redir('scriptnames'), "\n"), +  \                  'stridx(v:val, s:self_version) > 0') +    let list = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$') +    if !empty(list) && s:_unify_path(list[2]) ==# path +      let s:cache_sid[a:path] = list[1] - 0 +      return s:cache_sid[a:path] +    endif +  endfor +  return 0 +endfunction + +function! s:_file2module(file) abort +  let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?') +  let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$') +  return join(split(tail, '[\\/]\+'), '.') +endfunction + +if filereadable(expand('<sfile>:r') . '.VIM') +  " resolve() is slow, so we cache results. +  " Note: On windows, vim can't expand path names from 8.3 formats. +  " So if getting full path via <sfile> and $HOME was set as 8.3 format, +  " vital load duplicated scripts. Below's :~ avoid this issue. +  function! s:_unify_path(path) abort +    if has_key(s:_unify_path_cache, a:path) +      return s:_unify_path_cache[a:path] +    endif +    let value = tolower(fnamemodify(resolve(fnamemodify( +    \                   a:path, ':p')), ':~:gs?[\\/]?/?')) +    let s:_unify_path_cache[a:path] = value +    return value +  endfunction +else +  function! s:_unify_path(path) abort +    return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?')) +  endfunction +endif + +if s:globpath_third_arg +  function! s:_runtime_files(path) abort +    return split(globpath(&runtimepath, a:path, 1), "\n") +  endfunction +else +  function! s:_runtime_files(path) abort +    return split(globpath(&runtimepath, a:path), "\n") +  endfunction +endif + +function! s:_vital_files(pattern) abort +  if s:_vital_files_cache_runtimepath !=# &runtimepath +    let path = printf('autoload/vital/%s/**/*.vim', s:self_version) +    let s:_vital_files_cache = s:_runtime_files(path) +    let mod = ':p:gs?[\\/]\+?/?' +    call map(s:_vital_files_cache, 'fnamemodify(v:val, mod)') +    let s:_vital_files_cache_runtimepath = &runtimepath +  endif +  let target = substitute(a:pattern, '\.', '/', 'g') +  let target = substitute(target, '\*', '[^/]*', 'g') +  let regexp = printf('autoload/vital/%s/%s.vim', s:self_version, target) +  return filter(copy(s:_vital_files_cache), 'v:val =~# regexp') +endfunction + +" Copy from System.Filepath +if has('win16') || has('win32') || has('win64') +  function! s:_is_absolute_path(path) abort +    return a:path =~? '^[a-z]:[/\\]' +  endfunction +else +  function! s:_is_absolute_path(path) abort +    return a:path[0] ==# '/' +  endfunction +endif + +function! s:_build_module(sid) abort +  if has_key(s:loaded, a:sid) +    return copy(s:loaded[a:sid]) +  endif +  let functions = s:_get_functions(a:sid) + +  let prefix = '<SNR>' . a:sid . '_' +  let module = {} +  for func in functions +    let module[func] = function(prefix . func) +  endfor +  if has_key(module, '_vital_created') +    call module._vital_created(module) +  endif +  let export_module = filter(copy(module), 'v:key =~# "^\\a"') +  let s:loaded[a:sid] = get(g:, 'vital_debug', 0) ? module : export_module +  if has_key(module, '_vital_loaded') +    let V = vital#{s:self_version}#new() +    call module._vital_loaded(V) +  endif +  return copy(s:loaded[a:sid]) +endfunction + +if exists('+regexpengine') +  function! s:_get_functions(sid) abort +    let funcs = s:_redir(printf("function /\\%%#=2^\<SNR>%d_", a:sid)) +    let map_pat = '<SNR>' . a:sid . '_\zs\w\+' +    return map(split(funcs, "\n"), 'matchstr(v:val, map_pat)') +  endfunction +else +  function! s:_get_functions(sid) abort +    let prefix = '<SNR>' . a:sid . '_' +    let funcs = s:_redir('function') +    let filter_pat = '^\s*function ' . prefix +    let map_pat = prefix . '\zs\w\+' +    return map(filter(split(funcs, "\n"), +    \          'stridx(v:val, prefix) > 0 && v:val =~# filter_pat'), +    \          'matchstr(v:val, map_pat)') +  endfunction +endif + +if exists('*uniq') +  function! s:_uniq(list) abort +    return uniq(a:list) +  endfunction +else +  function! s:_uniq(list) abort +    let i = len(a:list) - 1 +    while 0 < i +      if a:list[i] ==# a:list[i - 1] +        call remove(a:list, i) +        let i -= 2 +      else +        let i -= 1 +      endif +    endwhile +    return a:list +  endfunction +endif + +function! s:_concat(lists) abort +  let result_list = [] +  for list in a:lists +    let result_list += list +  endfor +  return result_list +endfunction + +function! s:_redir(cmd) abort +  let [save_verbose, save_verbosefile] = [&verbose, &verbosefile] +  set verbose=0 verbosefile= +  redir => res +    silent! execute a:cmd +  redir END +  let [&verbose, &verbosefile] = [save_verbose, save_verbosefile] +  return res +endfunction + +function! vital#{s:self_version}#new() abort +  return s:_import('') +endfunction + +endif diff --git a/autoload/vital/_crystal/ColorEcho.vim b/autoload/vital/_crystal/ColorEcho.vim new file mode 100644 index 00000000..f2eae658 --- /dev/null +++ b/autoload/vital/_crystal/ColorEcho.vim @@ -0,0 +1,182 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1 +   +scriptencoding utf-8 +let s:save_cpo = &cpo +set cpo&vim + +function! s:_is_available() abort +    if has('gui_running') +        return 1 +    endif + +    if has('win32') || has('win64') +        return 0 +    endif + +    return exists('&t_Co') && (&t_Co == 8 || &t_Co == 256) +endfunction + +function! s:is_available() abort +    if exists('s:is_available_cache') +        return s:is_available_cache +    endif +    let s:is_available_cache = s:_is_available() +    return s:is_available_cache +endfunction + +function! s:_define_ansi_highlights() abort +    hi ansiNone cterm=NONE gui=NONE + +    hi ansiBlackBg ctermbg=black guibg=black cterm=none gui=none +    hi ansiRedBg ctermbg=red guibg=red cterm=none gui=none +    hi ansiGreenBg ctermbg=green guibg=green cterm=none gui=none +    hi ansiYellowBg ctermbg=yellow guibg=yellow cterm=none gui=none +    hi ansiBlueBg ctermbg=blue guibg=blue cterm=none gui=none +    hi ansiMagentaBg ctermbg=magenta guibg=magenta cterm=none gui=none +    hi ansiCyanBg ctermbg=cyan guibg=cyan cterm=none gui=none +    hi ansiWhiteBg ctermbg=white guibg=white cterm=none gui=none +    hi ansiGrayBg ctermbg=gray guibg=gray cterm=none gui=none + +    hi ansiBlackFg ctermfg=black guifg=black cterm=none gui=none +    hi ansiRedFg ctermfg=red guifg=red cterm=none gui=none +    hi ansiGreenFg ctermfg=green guifg=green cterm=none gui=none +    hi ansiYellowFg ctermfg=yellow guifg=yellow cterm=none gui=none +    hi ansiBlueFg ctermfg=blue guifg=blue cterm=none gui=none +    hi ansiMagentaFg ctermfg=magenta guifg=magenta cterm=none gui=none +    hi ansiCyanFg ctermfg=cyan guifg=cyan cterm=none gui=none +    hi ansiWhiteFg ctermfg=white guifg=white cterm=none gui=none +    hi ansiGrayFg ctermfg=gray guifg=gray cterm=none gui=none + +    hi ansiBoldBlackFg ctermfg=black guifg=black cterm=none gui=none cterm=bold gui=bold +    hi ansiBoldRedFg ctermfg=red guifg=red cterm=none gui=none cterm=bold gui=bold +    hi ansiBoldGreenFg ctermfg=green guifg=green cterm=none gui=none cterm=bold gui=bold +    hi ansiBoldYellowFg ctermfg=yellow guifg=yellow cterm=none gui=none cterm=bold gui=bold +    hi ansiBoldBlueFg ctermfg=blue guifg=blue cterm=none gui=none cterm=bold gui=bold +    hi ansiBoldMagentaFg ctermfg=magenta guifg=magenta cterm=none gui=none cterm=bold gui=bold +    hi ansiBoldCyanFg ctermfg=cyan guifg=cyan cterm=none gui=none cterm=bold gui=bold +    hi ansiBoldWhiteFg ctermfg=white guifg=white cterm=none gui=none cterm=bold gui=bold +    hi ansiBoldGrayFg ctermfg=gray guifg=gray cterm=none gui=none cterm=bold gui=bold + +    hi ansiUnderlineBlackFg ctermfg=black guifg=black cterm=none gui=none cterm=underline gui=underline +    hi ansiUnderlineRedFg ctermfg=red guifg=red cterm=none gui=none cterm=underline gui=underline +    hi ansiUnderlineGreenFg ctermfg=green guifg=green cterm=none gui=none cterm=underline gui=underline +    hi ansiUnderlineYellowFg ctermfg=yellow guifg=yellow cterm=none gui=none cterm=underline gui=underline +    hi ansiUnderlineBlueFg ctermfg=blue guifg=blue cterm=none gui=none cterm=underline gui=underline +    hi ansiUnderlineMagentaFg ctermfg=magenta guifg=magenta cterm=none gui=none cterm=underline gui=underline +    hi ansiUnderlineCyanFg ctermfg=cyan guifg=cyan cterm=none gui=none cterm=underline gui=underline +    hi ansiUnderlineWhiteFg ctermfg=white guifg=white cterm=none gui=none cterm=underline gui=underline +    hi ansiUnderlineGrayFg ctermfg=gray guifg=gray cterm=none gui=none cterm=underline gui=underline + +endfunction + +let s:echorizer = { +        \   'value': '', +        \   'attr': '', +        \ } + +function s:echorizer.eat() abort +    let matched = match(self.value, '\e\[\d*;\=m') +    if matched == -1 +        return {} +    endif + +    let matched_end = matchend(self.value, '\e\[\d*;\=m') + +    let token = { +        \   'body': matched == 0 ? '' : self.value[ : matched-1], +        \   'code': matchstr(self.value[matched : matched_end-1], '\d\+') +        \ } + +    let self.value = self.value[matched_end : ] + +    return token +endfunction + +let s:COLORS = { +    \   0: "None", +    \   30: "BlackFg", +    \   31: "RedFg", +    \   32: "GreenFg", +    \   33: "YellowFg", +    \   34: "BlueFg", +    \   35: "MagentaFg", +    \   36: "CyanFg", +    \   37: "WhiteFg", +    \   40: "BlackBg", +    \   41: "RedBg", +    \   42: "GreenBg", +    \   43: "YellowBg", +    \   44: "BlueBg", +    \   45: "MagentaBg", +    \   46: "CyanBg", +    \   47: "WhiteBg", +    \   90: "GrayFg", +    \ } + +function s:echorizer.echo_ansi(code) abort +    if !has_key(s:COLORS, a:code) +        return +    endif + +    execute 'echohl' 'ansi' . self.attr . s:COLORS[a:code] + +    if a:code == 0 +        let self.attr = '' +    endif +endfunction + +function s:echorizer.echo() abort +    echo + +    while 1 +        let token = self.eat() +        if token == {} +            break +        endif + +        if token.body !=# '' +            echon token.body +        endif + +        " TODO: Now only one attribute can be specified +        if token.code == 1 +            let self.attr = 'Bold' +        elseif token.code == 4 +            let self.attr = 'Underline' +        elseif token.code ==# '' +            call self.echo_ansi(0) +        else +            call self.echo_ansi(token.code) +        endif +    endwhile + +    echon self.value +    echohl None +    let self.value = '' +endfunction + +function! s:get_echorizer(str) abort +    let e = deepcopy(s:echorizer) +    let e.value = a:str +    return e +endfunction + +function! s:echo(str) abort +    if !s:is_available() +        echo substitute(a:str, '\e[.*m', '', 'g') +        return +    endif + +    if !exists('g:__vital_color_echo_already_highlighted') +        call s:_define_ansi_highlights() +        let g:__vital_color_echo_already_highlighted = 1 +    endif + +    let echorizer = s:get_echorizer(a:str) +    call echorizer.echo() +endfunction + +let &cpo = s:save_cpo +unlet s:save_cpo + +endif diff --git a/autoload/vital/_crystal/Data/List.vim b/autoload/vital/_crystal/Data/List.vim new file mode 100644 index 00000000..55703b3d --- /dev/null +++ b/autoload/vital/_crystal/Data/List.vim @@ -0,0 +1,446 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1 +   +" Utilities for list. + +let s:save_cpo = &cpo +set cpo&vim + +function! s:pop(list) abort +  return remove(a:list, -1) +endfunction + +function! s:push(list, val) abort +  call add(a:list, a:val) +  return a:list +endfunction + +function! s:shift(list) abort +  return remove(a:list, 0) +endfunction + +function! s:unshift(list, val) abort +  return insert(a:list, a:val) +endfunction + +function! s:cons(x, xs) abort +  return [a:x] + a:xs +endfunction + +function! s:conj(xs, x) abort +  return a:xs + [a:x] +endfunction + +" Removes duplicates from a list. +function! s:uniq(list) abort +  return s:uniq_by(a:list, 'v:val') +endfunction + +" Removes duplicates from a list. +function! s:uniq_by(list, f) abort +  let list = map(copy(a:list), printf('[v:val, %s]', a:f)) +  let i = 0 +  let seen = {} +  while i < len(list) +    let key = string(list[i][1]) +    if has_key(seen, key) +      call remove(list, i) +    else +      let seen[key] = 1 +      let i += 1 +    endif +  endwhile +  return map(list, 'v:val[0]') +endfunction + +function! s:clear(list) abort +  if !empty(a:list) +    unlet! a:list[0 : len(a:list) - 1] +  endif +  return a:list +endfunction + +" Concatenates a list of lists. +" XXX: Should we verify the input? +function! s:concat(list) abort +  let memo = [] +  for Value in a:list +    let memo += Value +  endfor +  return memo +endfunction + +" Take each elements from lists to a new list. +function! s:flatten(list, ...) abort +  let limit = a:0 > 0 ? a:1 : -1 +  let memo = [] +  if limit == 0 +    return a:list +  endif +  let limit -= 1 +  for Value in a:list +    let memo += +          \ type(Value) == type([]) ? +          \   s:flatten(Value, limit) : +          \   [Value] +    unlet! Value +  endfor +  return memo +endfunction + +" Sorts a list with expression to compare each two values. +" a:a and a:b can be used in {expr}. +function! s:sort(list, expr) abort +  if type(a:expr) == type(function('function')) +    return sort(a:list, a:expr) +  endif +  let s:expr = a:expr +  return sort(a:list, 's:_compare') +endfunction + +function! s:_compare(a, b) abort +  return eval(s:expr) +endfunction + +" Sorts a list using a set of keys generated by mapping the values in the list +" through the given expr. +" v:val is used in {expr} +function! s:sort_by(list, expr) abort +  let pairs = map(a:list, printf('[v:val, %s]', a:expr)) +  return map(s:sort(pairs, +  \      'a:a[1] ==# a:b[1] ? 0 : a:a[1] ># a:b[1] ? 1 : -1'), 'v:val[0]') +endfunction + +" Returns a maximum value in {list} through given {expr}. +" Returns 0 if {list} is empty. +" v:val is used in {expr} +function! s:max_by(list, expr) abort +  if empty(a:list) +    return 0 +  endif +  let list = map(copy(a:list), a:expr) +  return a:list[index(list, max(list))] +endfunction + +" Returns a minimum value in {list} through given {expr}. +" Returns 0 if {list} is empty. +" v:val is used in {expr} +" FIXME: -0x80000000 == 0x80000000 +function! s:min_by(list, expr) abort +  return s:max_by(a:list, '-(' . a:expr . ')') +endfunction + +" Returns List of character sequence between [a:from, a:to] +" e.g.: s:char_range('a', 'c') returns ['a', 'b', 'c'] +function! s:char_range(from, to) abort +  return map( +  \   range(char2nr(a:from), char2nr(a:to)), +  \   'nr2char(v:val)' +  \) +endfunction + +" Returns true if a:list has a:value. +" Returns false otherwise. +function! s:has(list, value) abort +  return index(a:list, a:value) isnot -1 +endfunction + +" Returns true if a:list[a:index] exists. +" Returns false otherwise. +" NOTE: Returns false when a:index is negative number. +function! s:has_index(list, index) abort +  " Return true when negative index? +  " let index = a:index >= 0 ? a:index : len(a:list) + a:index +  return 0 <= a:index && a:index < len(a:list) +endfunction + +" similar to Haskell's Data.List.span +function! s:span(f, xs) abort +  let border = len(a:xs) +  for i in range(len(a:xs)) +    if !eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g')) +      let border = i +      break +    endif +  endfor +  return border == 0 ? [[], copy(a:xs)] : [a:xs[: border - 1], a:xs[border :]] +endfunction + +" similar to Haskell's Data.List.break +function! s:break(f, xs) abort +  return s:span(printf('!(%s)', a:f), a:xs) +endfunction + +" similar to Haskell's Data.List.takeWhile +function! s:take_while(f, xs) abort +  return s:span(a:f, a:xs)[0] +endfunction + +" similar to Haskell's Data.List.partition +function! s:partition(f, xs) abort +  return [filter(copy(a:xs), a:f), filter(copy(a:xs), '!(' . a:f . ')')] +endfunction + +" similar to Haskell's Prelude.all +function! s:all(f, xs) abort +  return !s:any(printf('!(%s)', a:f), a:xs) +endfunction + +" similar to Haskell's Prelude.any +function! s:any(f, xs) abort +  return !empty(filter(map(copy(a:xs), a:f), 'v:val')) +endfunction + +" similar to Haskell's Prelude.and +function! s:and(xs) abort +  return s:all('v:val', a:xs) +endfunction + +" similar to Haskell's Prelude.or +function! s:or(xs) abort +  return s:any('v:val', a:xs) +endfunction + +function! s:map_accum(expr, xs, init) abort +  let memo = [] +  let init = a:init +  for x in a:xs +    let expr = substitute(a:expr, 'v:memo', init, 'g') +    let expr = substitute(expr, 'v:val', x, 'g') +    let [tmp, init] = eval(expr) +    call add(memo, tmp) +  endfor +  return memo +endfunction + +" similar to Haskell's Prelude.foldl +function! s:foldl(f, init, xs) abort +  let memo = a:init +  for x in a:xs +    let expr = substitute(a:f, 'v:val', string(x), 'g') +    let expr = substitute(expr, 'v:memo', string(memo), 'g') +    unlet memo +    let memo = eval(expr) +  endfor +  return memo +endfunction + +" similar to Haskell's Prelude.foldl1 +function! s:foldl1(f, xs) abort +  if len(a:xs) == 0 +    throw 'vital: Data.List: foldl1' +  endif +  return s:foldl(a:f, a:xs[0], a:xs[1:]) +endfunction + +" similar to Haskell's Prelude.foldr +function! s:foldr(f, init, xs) abort +  return s:foldl(a:f, a:init, reverse(copy(a:xs))) +endfunction + +" similar to Haskell's Prelude.fold11 +function! s:foldr1(f, xs) abort +  if len(a:xs) == 0 +    throw 'vital: Data.List: foldr1' +  endif +  return s:foldr(a:f, a:xs[-1], a:xs[0:-2]) +endfunction + +" similar to python's zip() +function! s:zip(...) abort +  return map(range(min(map(copy(a:000), 'len(v:val)'))), "map(copy(a:000), 'v:val['.v:val.']')") +endfunction + +" similar to zip(), but goes until the longer one. +function! s:zip_fill(xs, ys, filler) abort +  if empty(a:xs) && empty(a:ys) +    return [] +  elseif empty(a:ys) +    return s:cons([a:xs[0], a:filler], s:zip_fill(a:xs[1 :], [], a:filler)) +  elseif empty(a:xs) +    return s:cons([a:filler, a:ys[0]], s:zip_fill([], a:ys[1 :], a:filler)) +  else +    return s:cons([a:xs[0], a:ys[0]], s:zip_fill(a:xs[1 :], a:ys[1: ], a:filler)) +  endif +endfunction + +" Inspired by Ruby's with_index method. +function! s:with_index(list, ...) abort +  let base = a:0 > 0 ? a:1 : 0 +  return map(copy(a:list), '[v:val, v:key + base]') +endfunction + +" similar to Ruby's detect or Haskell's find. +function! s:find(list, default, f) abort +  for x in a:list +    if eval(substitute(a:f, 'v:val', string(x), 'g')) +      return x +    endif +  endfor +  return a:default +endfunction + +" Returns the index of the first element which satisfies the given expr. +function! s:find_index(xs, f, ...) abort +  let len = len(a:xs) +  let start = a:0 > 0 ? (a:1 < 0 ? len + a:1 : a:1) : 0 +  let default = a:0 > 1 ? a:2 : -1 +  if start >=# len || start < 0 +    return default +  endif +  for i in range(start, len - 1) +    if eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g')) +      return i +    endif +  endfor +  return default +endfunction + +" Returns the index of the last element which satisfies the given expr. +function! s:find_last_index(xs, f, ...) abort +  let len = len(a:xs) +  let start = a:0 > 0 ? (a:1 < 0 ? len + a:1 : a:1) : len - 1 +  let default = a:0 > 1 ? a:2 : -1 +  if start >=# len || start < 0 +    return default +  endif +  for i in range(start, 0, -1) +    if eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g')) +      return i +    endif +  endfor +  return default +endfunction + +" Similar to find_index but returns the list of indices satisfying the given expr. +function! s:find_indices(xs, f, ...) abort +  let len = len(a:xs) +  let start = a:0 > 0 ? (a:1 < 0 ? len + a:1 : a:1) : 0 +  let result = [] +  if start >=# len || start < 0 +    return result +  endif +  for i in range(start, len - 1) +    if eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g')) +      call add(result, i) +    endif +  endfor +  return result +endfunction + +" Return non-zero if a:list1 and a:list2 have any common item(s). +" Return zero otherwise. +function! s:has_common_items(list1, list2) abort +  return !empty(filter(copy(a:list1), 'index(a:list2, v:val) isnot -1')) +endfunction + +function! s:intersect(list1, list2) abort +  let items = [] +  " for funcref +  for X in a:list1 +    if index(a:list2, X) != -1 && index(items, X) == -1 +      let items += [X] +    endif +  endfor +  return items +endfunction + +" similar to Ruby's group_by. +function! s:group_by(xs, f) abort +  let result = {} +  let list = map(copy(a:xs), printf('[v:val, %s]', a:f)) +  for x in list +    let Val = x[0] +    let key = type(x[1]) !=# type('') ? string(x[1]) : x[1] +    if has_key(result, key) +      call add(result[key], Val) +    else +      let result[key] = [Val] +    endif +    unlet Val +  endfor +  return result +endfunction + +function! s:_default_compare(a, b) abort +  return a:a <# a:b ? -1 : a:a ># a:b ? 1 : 0 +endfunction + +function! s:binary_search(list, value, ...) abort +  let Predicate = a:0 >= 1 ? a:1 : 's:_default_compare' +  let dic = a:0 >= 2 ? a:2 : {} +  let start = 0 +  let end = len(a:list) - 1 + +  while 1 +    if start > end +      return -1 +    endif + +    let middle = (start + end) / 2 + +    let compared = call(Predicate, [a:value, a:list[middle]], dic) + +    if compared < 0 +      let end = middle - 1 +    elseif compared > 0 +      let start = middle + 1 +    else +      return middle +    endif +  endwhile +endfunction + +function! s:product(lists) abort +  let result = [[]] +  for pool in a:lists +    let tmp = [] +    for x in result +      let tmp += map(copy(pool), 'x + [v:val]') +    endfor +    let result = tmp +  endfor +  return result +endfunction + +function! s:permutations(list, ...) abort +  if a:0 > 1 +    throw 'vital: Data.List: too many arguments' +  endif +  let r = a:0 == 1 ? a:1 : len(a:list) +  if r > len(a:list) +    return [] +  elseif r < 0 +    throw 'vital: Data.List: {r} must be non-negative integer' +  endif +  let n = len(a:list) +  let result = [] +  for indices in s:product(map(range(r), 'range(n)')) +    if len(s:uniq(indices)) == r +      call add(result, map(indices, 'a:list[v:val]')) +    endif +  endfor +  return result +endfunction + +function! s:combinations(list, r) abort +  if a:r > len(a:list) +    return [] +  elseif a:r < 0 +    throw 'vital: Data:List: {r} must be non-negative integer' +  endif +  let n = len(a:list) +  let result = [] +  for indices in s:permutations(range(n), a:r) +    if s:sort(copy(indices), 'a:a - a:b') == indices +      call add(result, map(indices, 'a:list[v:val]')) +    endif +  endfor +  return result +endfunction + +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim:set et ts=2 sts=2 sw=2 tw=0: + +endif diff --git a/autoload/vital/_crystal/Data/String.vim b/autoload/vital/_crystal/Data/String.vim new file mode 100644 index 00000000..c2e2382c --- /dev/null +++ b/autoload/vital/_crystal/Data/String.vim @@ -0,0 +1,572 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1 +   +" Utilities for string. + +let s:save_cpo = &cpo +set cpo&vim + +function! s:_vital_loaded(V) abort +  let s:V = a:V +  let s:P = s:V.import('Prelude') +  let s:L = s:V.import('Data.List') +endfunction + +function! s:_vital_depends() abort +  return ['Prelude', 'Data.List'] +endfunction + +" Substitute a:from => a:to by string. +" To substitute by pattern, use substitute() instead. +function! s:replace(str, from, to) abort +  return s:_replace(a:str, a:from, a:to, 'g') +endfunction + +" Substitute a:from => a:to only once. +" cf. s:replace() +function! s:replace_first(str, from, to) abort +  return s:_replace(a:str, a:from, a:to, '') +endfunction + +" implement of replace() and replace_first() +function! s:_replace(str, from, to, flags) abort +  return substitute(a:str, '\V'.escape(a:from, '\'), escape(a:to, '\'), a:flags) +endfunction + +function! s:scan(str, pattern) abort +  let list = [] +  call substitute(a:str, a:pattern, '\=add(list, submatch(0)) == [] ? "" : ""', 'g') +  return list +endfunction + +function! s:reverse(str) abort +  return join(reverse(split(a:str, '.\zs')), '') +endfunction + +function! s:starts_with(str, prefix) abort +  return stridx(a:str, a:prefix) == 0 +endfunction + +function! s:ends_with(str, suffix) abort +  let idx = strridx(a:str, a:suffix) +  return 0 <= idx && idx + len(a:suffix) == len(a:str) +endfunction + +function! s:common_head(strs) abort +  if empty(a:strs) +    return '' +  endif +  let len = len(a:strs) +  if len == 1 +    return a:strs[0] +  endif +  let strs = len == 2 ? a:strs : sort(copy(a:strs)) +  let pat = substitute(strs[0], '.', '\="[" . escape(submatch(0), "^\\") . "]"', 'g') +  return pat == '' ? '' : matchstr(strs[-1], '\C^\%[' . pat . ']') +endfunction + +" Split to two elements of List. ([left, right]) +" e.g.: s:split3('neocomplcache', 'compl') returns ['neo', 'compl', 'cache'] +function! s:split_leftright(expr, pattern) abort +  let [left, _, right] = s:split3(a:expr, a:pattern) +  return [left, right] +endfunction + +function! s:split3(expr, pattern) abort +  let ERROR = ['', '', ''] +  if a:expr ==# '' || a:pattern ==# '' +    return ERROR +  endif +  let begin = match(a:expr, a:pattern) +  if begin is -1 +    return ERROR +  endif +  let end   = matchend(a:expr, a:pattern) +  let left  = begin <=# 0 ? '' : a:expr[: begin - 1] +  let right = a:expr[end :] +  return [left, a:expr[begin : end-1], right] +endfunction + +" Slices into strings determines the number of substrings. +" e.g.: s:nsplit("neo compl cache", 2, '\s') returns ['neo', 'compl cache'] +function! s:nsplit(expr, n, ...) abort +  let pattern = get(a:000, 0, '\s') +  let keepempty = get(a:000, 1, 1) +  let ret = [] +  let expr = a:expr +  if a:n <= 1 +    return [expr] +  endif +  while 1 +    let pos = match(expr, pattern) +    if pos == -1 +      if expr !~ pattern || keepempty +        call add(ret, expr) +      endif +      break +    elseif pos >= 0 +      let left = pos > 0 ? expr[:pos-1] : '' +      if pos > 0 || keepempty +        call add(ret, left) +      endif +      let ml = len(matchstr(expr, pattern)) +      if pos == 0 && ml == 0 +        let pos = 1 +      endif +      let expr = expr[pos+ml :] +    endif +    if len(expr) == 0 +      break +    endif +    if len(ret) == a:n - 1 +      call add(ret, expr) +      break +    endif +  endwhile +  return ret +endfunction + +" Returns the number of character in a:str. +" NOTE: This returns proper value +" even if a:str contains multibyte character(s). +" s:strchars(str) {{{ +if exists('*strchars') +  function! s:strchars(str) abort +    return strchars(a:str) +  endfunction +else +  function! s:strchars(str) abort +    return strlen(substitute(copy(a:str), '.', 'x', 'g')) +  endfunction +endif "}}} + +" Returns the bool of contains any multibyte character in s:str +function! s:contains_multibyte(str) abort "{{{ +  return strlen(a:str) != s:strchars(a:str) +endfunction "}}} + +" Remove last character from a:str. +" NOTE: This returns proper value +" even if a:str contains multibyte character(s). +function! s:chop(str) abort "{{{ +  return substitute(a:str, '.$', '', '') +endfunction "}}} + +" Remove last \r,\n,\r\n from a:str. +function! s:chomp(str) abort "{{{ +  return substitute(a:str, '\%(\r\n\|[\r\n]\)$', '', '') +endfunction "}}} + +" wrap() and its internal functions +" * _split_by_wcswidth_once() +" * _split_by_wcswidth() +" * _concat() +" * wrap() +" +" NOTE _concat() is just a copy of Data.List.concat(). +" FIXME don't repeat yourself +function! s:_split_by_wcswidth_once(body, x) abort +  let fst = s:strwidthpart(a:body, a:x) +  let snd = s:strwidthpart_reverse(a:body, s:wcswidth(a:body) - s:wcswidth(fst)) +  return [fst, snd] +endfunction + +function! s:_split_by_wcswidth(body, x) abort +  let memo = [] +  let body = a:body +  while s:wcswidth(body) > a:x +    let [tmp, body] = s:_split_by_wcswidth_once(body, a:x) +    call add(memo, tmp) +  endwhile +  call add(memo, body) +  return memo +endfunction + +function! s:trim(str) abort +  return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$') +endfunction + +function! s:trim_start(str) abort +  return matchstr(a:str,'^\s*\zs.\{-}$') +endfunction + +function! s:trim_end(str) abort +  return matchstr(a:str,'^.\{-}\ze\s*$') +endfunction + +function! s:wrap(str,...) abort +  let _columns = a:0 > 0 ? a:1 : &columns +  return s:L.concat( +        \ map(split(a:str, '\r\n\|[\r\n]'), 's:_split_by_wcswidth(v:val, _columns - 1)')) +endfunction + +function! s:nr2byte(nr) abort +  if a:nr < 0x80 +    return nr2char(a:nr) +  elseif a:nr < 0x800 +    return nr2char(a:nr/64+192).nr2char(a:nr%64+128) +  else +    return nr2char(a:nr/4096%16+224).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128) +  endif +endfunction + +function! s:nr2enc_char(charcode) abort +  if &encoding == 'utf-8' +    return nr2char(a:charcode) +  endif +  let char = s:nr2byte(a:charcode) +  if strlen(char) > 1 +    let char = strtrans(iconv(char, 'utf-8', &encoding)) +  endif +  return char +endfunction + +function! s:nr2hex(nr) abort +  let n = a:nr +  let r = "" +  while n +    let r = '0123456789ABCDEF'[n % 16] . r +    let n = n / 16 +  endwhile +  return r +endfunction + +" If a ==# b, returns -1. +" If a !=# b, returns first index of different character. +function! s:diffidx(a, b) abort +  return a:a ==# a:b ? -1 : strlen(s:common_head([a:a, a:b])) +endfunction + +function! s:substitute_last(expr, pat, sub) abort +  return substitute(a:expr, printf('.*\zs%s', a:pat), a:sub, '') +endfunction + +function! s:dstring(expr) abort +  let x = substitute(string(a:expr), "^'\\|'$", '', 'g') +  let x = substitute(x, "''", "'", 'g') +  return printf('"%s"', escape(x, '"')) +endfunction + +function! s:lines(str) abort +  return split(a:str, '\r\?\n') +endfunction + +function! s:_pad_with_char(str, left, right, char) abort +  return repeat(a:char, a:left). a:str. repeat(a:char, a:right) +endfunction + +function! s:pad_left(str, width, ...) abort +  let char = get(a:, 1, ' ') +  if strdisplaywidth(char) != 1 +    throw "vital: Data.String: Can't use non-half-width characters for padding." +  endif +  let left = max([0, a:width - strdisplaywidth(a:str)]) +  return s:_pad_with_char(a:str, left, 0, char) +endfunction + +function! s:pad_right(str, width, ...) abort +  let char = get(a:, 1, ' ') +  if strdisplaywidth(char) != 1 +    throw "vital: Data.String: Can't use non-half-width characters for padding." +  endif +  let right = max([0, a:width - strdisplaywidth(a:str)]) +  return s:_pad_with_char(a:str, 0, right, char) +endfunction + +function! s:pad_both_sides(str, width, ...) abort +  let char = get(a:, 1, ' ') +  if strdisplaywidth(char) != 1 +    throw "vital: Data.String: Can't use non-half-width characters for padding." +  endif +  let space = max([0, a:width - strdisplaywidth(a:str)]) +  let left = space / 2 +  let right = space - left +  return s:_pad_with_char(a:str, left, right, char) +endfunction + +function! s:pad_between_letters(str, width, ...) abort +  let char = get(a:, 1, ' ') +  if strdisplaywidth(char) != 1 +    throw "vital: Data.String: Can't use non-half-width characters for padding." +  endif +  let letters = split(a:str, '\zs') +  let each_width = a:width / len(letters) +  let str = join(map(letters, 's:pad_both_sides(v:val, each_width, char)'), '') +  if a:width - strdisplaywidth(str) > 0 +    return char. s:pad_both_sides(str, a:width - 1, char) +  endif +  return str +endfunction + +function! s:justify_equal_spacing(str, width, ...) abort +  let char = get(a:, 1, ' ') +  if strdisplaywidth(char) != 1 +    throw "vital: Data.String: Can't use non-half-width characters for padding." +  endif +  let letters = split(a:str, '\zs') +  let first_letter = letters[0] +  " {width w/o the first letter} / {length w/o the first letter} +  let each_width = (a:width - strdisplaywidth(first_letter)) / (len(letters) - 1) +  let remainder = (a:width - strdisplaywidth(first_letter)) % (len(letters) - 1) +  return first_letter. join(s:L.concat([ +\     map(letters[1:remainder], 's:pad_left(v:val, each_width + 1, char)'), +\     map(letters[remainder + 1:], 's:pad_left(v:val, each_width, char)') +\   ]), '') +endfunction + +function! s:levenshtein_distance(str1, str2) abort +  let letters1 = split(a:str1, '\zs') +  let letters2 = split(a:str2, '\zs') +  let length1 = len(letters1) +  let length2 = len(letters2) +  let distances = map(range(1, length1 + 1), 'map(range(1, length2 + 1), "0")') + +  for i1 in range(0, length1) +    let distances[i1][0] = i1 +  endfor +  for i2 in range(0, length2) +    let distances[0][i2] = i2 +  endfor + +  for i1 in range(1, length1) +    for i2 in range(1, length2) +      let cost = (letters1[i1 - 1] ==# letters2[i2 - 1]) ? 0 : 1 + +      let distances[i1][i2] = min([ +      \ distances[i1 - 1][i2    ] + 1, +      \ distances[i1    ][i2 - 1] + 1, +      \ distances[i1 - 1][i2 - 1] + cost, +      \]) +    endfor +  endfor + +  return distances[length1][length2] +endfunction + +function! s:padding_by_displaywidth(expr, width, float) abort +  let padding_char = ' ' +  let n = a:width - strdisplaywidth(a:expr) +  if n <= 0 +    let n = 0 +  endif +  if a:float < 0 +    return a:expr . repeat(padding_char, n) +  elseif 0 < a:float +    return repeat(padding_char, n) . a:expr +  else +    if n % 2 is 0 +      return repeat(padding_char, n / 2) . a:expr . repeat(padding_char, n / 2) +    else +      return repeat(padding_char, (n - 1) / 2) . a:expr . repeat(padding_char, (n - 1) / 2) . padding_char +    endif +  endif +endfunction + +function! s:split_by_displaywidth(expr, width, float, is_wrap) abort +  if a:width is 0 +    return [''] +  endif + +  let lines = [] + +  let cs = split(a:expr, '\zs') +  let cs_index = 0 + +  let text = '' +  while cs_index < len(cs) +    if cs[cs_index] is "\n" +      let text = s:padding_by_displaywidth(text, a:width, a:float) +      let lines += [text] +      let text = '' +    else +      let w = strdisplaywidth(text . cs[cs_index]) + +      if w < a:width +        let text .= cs[cs_index] +      elseif a:width < w +        let text = s:padding_by_displaywidth(text, a:width, a:float) +      else +        let text .= cs[cs_index] +      endif + +      if a:width <= w +        let lines += [text] +        let text = '' +        if a:is_wrap +          if a:width < w +            if a:width < strdisplaywidth(cs[cs_index]) +              while get(cs, cs_index, "\n") isnot "\n" +                let cs_index += 1 +              endwhile +              continue +            else +              let text = cs[cs_index] +            endif +          endif +        else +          while get(cs, cs_index, "\n") isnot "\n" +            let cs_index += 1 +          endwhile +          continue +        endif +      endif + +    endif +    let cs_index += 1 +  endwhile + +  if !empty(text) +    let lines += [ s:padding_by_displaywidth(text, a:width, a:float) ] +  endif + +  return lines +endfunction + +function! s:hash(str) abort +  if exists('*sha256') +    return sha256(a:str) +  else +    " This gives up sha256ing but just adds up char with index. +    let sum = 0 +    for i in range(len(a:str)) +      let sum += char2nr(a:str[i]) * (i + 1) +    endfor + +    return printf('%x', sum) +  endif +endfunction + +function! s:truncate(str, width) abort +  " Original function is from mattn. +  " http://github.com/mattn/googlereader-vim/tree/master + +  if a:str =~# '^[\x00-\x7f]*$' +    return len(a:str) < a:width ? +          \ printf('%-'.a:width.'s', a:str) : strpart(a:str, 0, a:width) +  endif + +  let ret = a:str +  let width = s:wcswidth(a:str) +  if width > a:width +    let ret = s:strwidthpart(ret, a:width) +    let width = s:wcswidth(ret) +  endif + +  if width < a:width +    let ret .= repeat(' ', a:width - width) +  endif + +  return ret +endfunction + +function! s:truncate_skipping(str, max, footer_width, separator) abort +  let width = s:wcswidth(a:str) +  if width <= a:max +    let ret = a:str +  else +    let header_width = a:max - s:wcswidth(a:separator) - a:footer_width +    let ret = s:strwidthpart(a:str, header_width) . a:separator +          \ . s:strwidthpart_reverse(a:str, a:footer_width) +  endif +  return s:truncate(ret, a:max) +endfunction + +function! s:strwidthpart(str, width) abort +  if a:width <= 0 +    return '' +  endif +  let strarr = split(a:str, '\zs') +  let width = s:wcswidth(a:str) +  let index = len(strarr) +  let diff = (index + 1) / 2 +  let rightindex = index - 1 +  while width > a:width +    let index = max([rightindex - diff + 1, 0]) +    let partwidth = s:wcswidth(join(strarr[(index):(rightindex)], '')) +    if width - partwidth >= a:width || diff <= 1 +      let width -= partwidth +      let rightindex = index - 1 +    endif +    if diff > 1 +      let diff = diff / 2 +    endif +  endwhile +  return index ? join(strarr[:index - 1], '') : '' +endfunction + +function! s:strwidthpart_reverse(str, width) abort +  if a:width <= 0 +    return '' +  endif +  let strarr = split(a:str, '\zs') +  let width = s:wcswidth(a:str) +  let strlen = len(strarr) +  let diff = (strlen + 1) / 2 +  let leftindex = 0 +  let index = -1 +  while width > a:width +    let index = min([leftindex + diff, strlen]) - 1 +    let partwidth = s:wcswidth(join(strarr[(leftindex):(index)], '')) +    if width - partwidth >= a:width || diff <= 1 +      let width -= partwidth +      let leftindex = index + 1 +    endif +    if diff > 1 +      let diff = diff / 2 +    endif +  endwhile +  return index < strlen ? join(strarr[(index + 1):], '') : '' +endfunction + +if v:version >= 703 +  " Use builtin function. +  function! s:wcswidth(str) abort +    return strwidth(a:str) +  endfunction +else +  function! s:wcswidth(str) abort +    if a:str =~# '^[\x00-\x7f]*$' +      return strlen(a:str) +    endif +    let mx_first = '^\(.\)' +    let str = a:str +    let width = 0 +    while 1 +      let ucs = char2nr(substitute(str, mx_first, '\1', '')) +      if ucs == 0 +        break +      endif +      let width += s:_wcwidth(ucs) +      let str = substitute(str, mx_first, '', '') +    endwhile +    return width +  endfunction + +  " UTF-8 only. +  function! s:_wcwidth(ucs) abort +    let ucs = a:ucs +    if (ucs >= 0x1100 +          \  && (ucs <= 0x115f +          \  || ucs == 0x2329 +          \  || ucs == 0x232a +          \  || (ucs >= 0x2e80 && ucs <= 0xa4cf +          \      && ucs != 0x303f) +          \  || (ucs >= 0xac00 && ucs <= 0xd7a3) +          \  || (ucs >= 0xf900 && ucs <= 0xfaff) +          \  || (ucs >= 0xfe30 && ucs <= 0xfe6f) +          \  || (ucs >= 0xff00 && ucs <= 0xff60) +          \  || (ucs >= 0xffe0 && ucs <= 0xffe6) +          \  || (ucs >= 0x20000 && ucs <= 0x2fffd) +          \  || (ucs >= 0x30000 && ucs <= 0x3fffd) +          \  )) +      return 2 +    endif +    return 1 +  endfunction +endif + +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim:set et ts=2 sts=2 sw=2 tw=0: + +endif diff --git a/autoload/vital/_crystal/Prelude.vim b/autoload/vital/_crystal/Prelude.vim new file mode 100644 index 00000000..be31f980 --- /dev/null +++ b/autoload/vital/_crystal/Prelude.vim @@ -0,0 +1,389 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1 +   +let s:save_cpo = &cpo +set cpo&vim + +if v:version ># 703 || +\  (v:version is 703 && has('patch465')) +  function! s:glob(expr) abort +    return glob(a:expr, 1, 1) +  endfunction +else +  function! s:glob(expr) abort +    let R = glob(a:expr, 1) +    return split(R, '\n') +  endfunction +endif + +function! s:globpath(path, expr) abort +  let R = globpath(a:path, a:expr, 1) +  return split(R, '\n') +endfunction + +" Wrapper functions for type(). +let [ +\   s:__TYPE_NUMBER, +\   s:__TYPE_STRING, +\   s:__TYPE_FUNCREF, +\   s:__TYPE_LIST, +\   s:__TYPE_DICT, +\   s:__TYPE_FLOAT] = [ +      \   type(3), +      \   type(""), +      \   type(function('tr')), +      \   type([]), +      \   type({}), +      \   has('float') ? type(str2float('0')) : -1] +" __TYPE_FLOAT = -1 when -float +" This doesn't match to anything. + +" Number or Float +function! s:is_numeric(Value) abort +  let _ = type(a:Value) +  return _ ==# s:__TYPE_NUMBER +  \   || _ ==# s:__TYPE_FLOAT +endfunction + +" Number +function! s:is_number(Value) abort +  return type(a:Value) ==# s:__TYPE_NUMBER +endfunction + +" Float +function! s:is_float(Value) abort +  return type(a:Value) ==# s:__TYPE_FLOAT +endfunction +" String +function! s:is_string(Value) abort +  return type(a:Value) ==# s:__TYPE_STRING +endfunction +" Funcref +function! s:is_funcref(Value) abort +  return type(a:Value) ==# s:__TYPE_FUNCREF +endfunction +" List +function! s:is_list(Value) abort +  return type(a:Value) ==# s:__TYPE_LIST +endfunction +" Dictionary +function! s:is_dict(Value) abort +  return type(a:Value) ==# s:__TYPE_DICT +endfunction + +function! s:truncate_skipping(str, max, footer_width, separator) abort +  call s:_warn_deprecated("truncate_skipping", "Data.String.truncate_skipping") + +  let width = s:wcswidth(a:str) +  if width <= a:max +    let ret = a:str +  else +    let header_width = a:max - s:wcswidth(a:separator) - a:footer_width +    let ret = s:strwidthpart(a:str, header_width) . a:separator +          \ . s:strwidthpart_reverse(a:str, a:footer_width) +  endif + +  return s:truncate(ret, a:max) +endfunction + +function! s:truncate(str, width) abort +  " Original function is from mattn. +  " http://github.com/mattn/googlereader-vim/tree/master + +  call s:_warn_deprecated("truncate", "Data.String.truncate") + +  if a:str =~# '^[\x00-\x7f]*$' +    return len(a:str) < a:width ? +          \ printf('%-'.a:width.'s', a:str) : strpart(a:str, 0, a:width) +  endif + +  let ret = a:str +  let width = s:wcswidth(a:str) +  if width > a:width +    let ret = s:strwidthpart(ret, a:width) +    let width = s:wcswidth(ret) +  endif + +  if width < a:width +    let ret .= repeat(' ', a:width - width) +  endif + +  return ret +endfunction + +function! s:strwidthpart(str, width) abort +  call s:_warn_deprecated("strwidthpart", "Data.String.strwidthpart") + +  if a:width <= 0 +    return '' +  endif +  let ret = a:str +  let width = s:wcswidth(a:str) +  while width > a:width +    let char = matchstr(ret, '.$') +    let ret = ret[: -1 - len(char)] +    let width -= s:wcswidth(char) +  endwhile + +  return ret +endfunction +function! s:strwidthpart_reverse(str, width) abort +  call s:_warn_deprecated("strwidthpart_reverse", "Data.String.strwidthpart_reverse") + +  if a:width <= 0 +    return '' +  endif +  let ret = a:str +  let width = s:wcswidth(a:str) +  while width > a:width +    let char = matchstr(ret, '^.') +    let ret = ret[len(char) :] +    let width -= s:wcswidth(char) +  endwhile + +  return ret +endfunction + +if v:version >= 703 +  " Use builtin function. +  function! s:wcswidth(str) abort +    call s:_warn_deprecated("wcswidth", "Data.String.wcswidth") +    return strwidth(a:str) +  endfunction +else +  function! s:wcswidth(str) abort +    call s:_warn_deprecated("wcswidth", "Data.String.wcswidth") + +    if a:str =~# '^[\x00-\x7f]*$' +      return strlen(a:str) +    end + +    let mx_first = '^\(.\)' +    let str = a:str +    let width = 0 +    while 1 +      let ucs = char2nr(substitute(str, mx_first, '\1', '')) +      if ucs == 0 +        break +      endif +      let width += s:_wcwidth(ucs) +      let str = substitute(str, mx_first, '', '') +    endwhile +    return width +  endfunction + +  " UTF-8 only. +  function! s:_wcwidth(ucs) abort +    let ucs = a:ucs +    if (ucs >= 0x1100 +          \  && (ucs <= 0x115f +          \  || ucs == 0x2329 +          \  || ucs == 0x232a +          \  || (ucs >= 0x2e80 && ucs <= 0xa4cf +          \      && ucs != 0x303f) +          \  || (ucs >= 0xac00 && ucs <= 0xd7a3) +          \  || (ucs >= 0xf900 && ucs <= 0xfaff) +          \  || (ucs >= 0xfe30 && ucs <= 0xfe6f) +          \  || (ucs >= 0xff00 && ucs <= 0xff60) +          \  || (ucs >= 0xffe0 && ucs <= 0xffe6) +          \  || (ucs >= 0x20000 && ucs <= 0x2fffd) +          \  || (ucs >= 0x30000 && ucs <= 0x3fffd) +          \  )) +      return 2 +    endif +    return 1 +  endfunction +endif + +let s:is_windows = has('win16') || has('win32') || has('win64') || has('win95') +let s:is_cygwin = has('win32unix') +let s:is_mac = !s:is_windows && !s:is_cygwin +      \ && (has('mac') || has('macunix') || has('gui_macvim') || +      \   (!isdirectory('/proc') && executable('sw_vers'))) +let s:is_unix = has('unix') + +function! s:is_windows() abort +  return s:is_windows +endfunction + +function! s:is_cygwin() abort +  return s:is_cygwin +endfunction + +function! s:is_mac() abort +  return s:is_mac +endfunction + +function! s:is_unix() abort +  return s:is_unix +endfunction + +function! s:_warn_deprecated(name, alternative) abort +  try +    echohl Error +    echomsg "Prelude." . a:name . " is deprecated!  Please use " . a:alternative . " instead." +  finally +    echohl None +  endtry +endfunction + +function! s:smart_execute_command(action, word) abort +  execute a:action . ' ' . (a:word == '' ? '' : '`=a:word`') +endfunction + +function! s:escape_file_searching(buffer_name) abort +  return escape(a:buffer_name, '*[]?{}, ') +endfunction + +function! s:escape_pattern(str) abort +  return escape(a:str, '~"\.^$[]*') +endfunction + +function! s:getchar(...) abort +  let c = call('getchar', a:000) +  return type(c) == type(0) ? nr2char(c) : c +endfunction + +function! s:getchar_safe(...) abort +  let c = s:input_helper('getchar', a:000) +  return type(c) == type("") ? c : nr2char(c) +endfunction + +function! s:input_safe(...) abort +  return s:input_helper('input', a:000) +endfunction + +function! s:input_helper(funcname, args) abort +  let success = 0 +  if inputsave() !=# success +    throw 'vital: Prelude: inputsave() failed' +  endif +  try +    return call(a:funcname, a:args) +  finally +    if inputrestore() !=# success +      throw 'vital: Prelude: inputrestore() failed' +    endif +  endtry +endfunction + +function! s:set_default(var, val) abort +  if !exists(a:var) || type({a:var}) != type(a:val) +    let {a:var} = a:val +  endif +endfunction + +function! s:substitute_path_separator(path) abort +  return s:is_windows ? substitute(a:path, '\\', '/', 'g') : a:path +endfunction + +function! s:path2directory(path) abort +  return s:substitute_path_separator(isdirectory(a:path) ? a:path : fnamemodify(a:path, ':p:h')) +endfunction + +function! s:_path2project_directory_git(path) abort +  let parent = a:path + +  while 1 +    let path = parent . '/.git' +    if isdirectory(path) || filereadable(path) +      return parent +    endif +    let next = fnamemodify(parent, ':h') +    if next == parent +      return '' +    endif +    let parent = next +  endwhile +endfunction + +function! s:_path2project_directory_svn(path) abort +  let search_directory = a:path +  let directory = '' + +  let find_directory = s:escape_file_searching(search_directory) +  let d = finddir('.svn', find_directory . ';') +  if d == '' +    return '' +  endif + +  let directory = fnamemodify(d, ':p:h:h') + +  " Search parent directories. +  let parent_directory = s:path2directory( +        \ fnamemodify(directory, ':h')) + +  if parent_directory != '' +    let d = finddir('.svn', parent_directory . ';') +    if d != '' +      let directory = s:_path2project_directory_svn(parent_directory) +    endif +  endif +  return directory +endfunction + +function! s:_path2project_directory_others(vcs, path) abort +  let vcs = a:vcs +  let search_directory = a:path + +  let find_directory = s:escape_file_searching(search_directory) +  let d = finddir(vcs, find_directory . ';') +  if d == '' +    return '' +  endif +  return fnamemodify(d, ':p:h:h') +endfunction + +function! s:path2project_directory(path, ...) abort +  let is_allow_empty = get(a:000, 0, 0) +  let search_directory = s:path2directory(a:path) +  let directory = '' + +  " Search VCS directory. +  for vcs in ['.git', '.bzr', '.hg', '.svn'] +    if vcs ==# '.git' +      let directory = s:_path2project_directory_git(search_directory) +    elseif vcs ==# '.svn' +      let directory = s:_path2project_directory_svn(search_directory) +    else +      let directory = s:_path2project_directory_others(vcs, search_directory) +    endif +    if directory != '' +      break +    endif +  endfor + +  " Search project file. +  if directory == '' +    for d in ['build.xml', 'prj.el', '.project', 'pom.xml', 'package.json', +          \ 'Makefile', 'configure', 'Rakefile', 'NAnt.build', +          \ 'P4CONFIG', 'tags', 'gtags'] +      let d = findfile(d, s:escape_file_searching(search_directory) . ';') +      if d != '' +        let directory = fnamemodify(d, ':p:h') +        break +      endif +    endfor +  endif + +  if directory == '' +    " Search /src/ directory. +    let base = s:substitute_path_separator(search_directory) +    if base =~# '/src/' +      let directory = base[: strridx(base, '/src/') + 3] +    endif +  endif + +  if directory == '' && !is_allow_empty +    " Use original path. +    let directory = search_directory +  endif + +  return s:substitute_path_separator(directory) +endfunction + +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim:set et ts=2 sts=2 sw=2 tw=0: + +endif diff --git a/autoload/vital/_crystal/Process.vim b/autoload/vital/_crystal/Process.vim new file mode 100644 index 00000000..c2deb9e5 --- /dev/null +++ b/autoload/vital/_crystal/Process.vim @@ -0,0 +1,185 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1 +   +" TODO: move all comments to doc file. +" +" +" FIXME: This module name should be Vital.System ? +" But the name has been already taken. + +let s:save_cpo = &cpo +set cpo&vim + + +" FIXME: Unfortunately, can't use s:_vital_loaded() for this purpose. +" Because these variables are used when this script file is loaded. +let s:is_windows = has('win16') || has('win32') || has('win64') || has('win95') +let s:is_unix = has('unix') +" As of 7.4.122, the system()'s 1st argument is converted internally by Vim. +" Note that Patch 7.4.122 does not convert system()'s 2nd argument and +" return-value. We must convert them manually. +let s:need_trans = v:version < 704 || (v:version == 704 && !has('patch122')) + +let s:TYPE_DICT = type({}) +let s:TYPE_LIST = type([]) +let s:TYPE_STRING = type("") + + +" Execute program in the background from Vim. +" Return an empty string always. +" +" If a:expr is a List, shellescape() each argument. +" If a:expr is a String, the arguments are passed as-is. +" +" Windows: +" Using :!start , execute program without via cmd.exe. +" Spawning 'expr' with 'noshellslash' +" keep special characters from unwanted expansion. +" (see :help shellescape()) +" +" Unix: +" using :! , execute program in the background by shell. +function! s:spawn(expr, ...) abort +  let shellslash = 0 +  if s:is_windows +    let shellslash = &l:shellslash +    setlocal noshellslash +  endif +  try +    if type(a:expr) is s:TYPE_LIST +      let special = 1 +      let cmdline = join(map(a:expr, 'shellescape(v:val, special)'), ' ') +    elseif type(a:expr) is s:TYPE_STRING +      let cmdline = a:expr +      if a:0 && a:1 +        " for :! command +        let cmdline = substitute(cmdline, '\([!%#]\|<[^<>]\+>\)', '\\\1', 'g') +      endif +    else +      throw 'Process.spawn(): invalid argument (value type:'.type(a:expr).')' +    endif +    if s:is_windows +      silent execute '!start' cmdline +    else +      silent execute '!' cmdline '&' +    endif +  finally +    if s:is_windows +      let &l:shellslash = shellslash +    endif +  endtry +  return '' +endfunction + +" iconv() wrapper for safety. +function! s:iconv(expr, from, to) abort +  if a:from == '' || a:to == '' || a:from ==? a:to +    return a:expr +  endif +  let result = iconv(a:expr, a:from, a:to) +  return result != '' ? result : a:expr +endfunction + +" Check vimproc. +function! s:has_vimproc() abort +  if !exists('s:exists_vimproc') +    try +      call vimproc#version() +      let s:exists_vimproc = 1 +    catch +      let s:exists_vimproc = 0 +    endtry +  endif +  return s:exists_vimproc +endfunction + +" * {command} [, {input} [, {timeout}]] +" * {command} [, {dict}] +"   {dict} = { +"     use_vimproc: bool, +"     input: string, +"     timeout: bool, +"     background: bool, +"   } +function! s:system(str, ...) abort +  " Process optional arguments at first +  " because use_vimproc is required later +  " for a:str argument. +  let input = '' +  let use_vimproc = s:has_vimproc() +  let background = 0 +  let args = [] +  if a:0 ==# 1 +    " {command} [, {dict}] +    " a:1 = {dict} +    if type(a:1) is s:TYPE_DICT +      if has_key(a:1, 'use_vimproc') +        let use_vimproc = a:1.use_vimproc +      endif +      if has_key(a:1, 'input') +        let args += [s:iconv(a:1.input, &encoding, 'char')] +      endif +      if use_vimproc && has_key(a:1, 'timeout') +        " ignores timeout unless you have vimproc. +        let args += [a:1.timeout] +      endif +      if has_key(a:1, 'background') +        let background = a:1.background +      endif +    elseif type(a:1) is s:TYPE_STRING +      let args += [s:iconv(a:1, &encoding, 'char')] +    else +      throw 'Process.system(): invalid argument (value type:'.type(a:1).')' +    endif +  elseif a:0 >= 2 +    " {command} [, {input} [, {timeout}]] +    " a:000 = [{input} [, {timeout}]] +    let [input; rest] = a:000 +    let input   = s:iconv(input, &encoding, 'char') +    let args += [input] + rest +  endif + +  " Process a:str argument. +  if type(a:str) is s:TYPE_LIST +    let expr = use_vimproc ? '"''" . v:val . "''"' : 's:shellescape(v:val)' +    let command = join(map(copy(a:str), expr), ' ') +  elseif type(a:str) is s:TYPE_STRING +    let command = a:str +  else +    throw 'Process.system(): invalid argument (value type:'.type(a:str).')' +  endif +  if s:need_trans +    let command = s:iconv(command, &encoding, 'char') +  endif +  let args = [command] + args +  if background && (use_vimproc || !s:is_windows) +    let args[0] = args[0] . ' &' +  endif + +  let funcname = use_vimproc ? 'vimproc#system' : 'system' +  let output = call(funcname, args) +  let output = s:iconv(output, 'char', &encoding) +  return output +endfunction + +function! s:get_last_status() abort +  return s:has_vimproc() ? +        \ vimproc#get_last_status() : v:shell_error +endfunction + +if s:is_windows +  function! s:shellescape(command) abort +    return substitute(a:command, '[&()[\]{}^=;!''+,`~]', '^\0', 'g') +  endfunction +else +  function! s:shellescape(...) abort +    return call('shellescape', a:000) +  endfunction +endif + + +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim:set et ts=2 sts=2 sw=2 tw=0: + +endif diff --git a/autoload/vital/_crystal/Web/JSON.vim b/autoload/vital/_crystal/Web/JSON.vim new file mode 100644 index 00000000..0e42ee0d --- /dev/null +++ b/autoload/vital/_crystal/Web/JSON.vim @@ -0,0 +1,112 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1 +   +let s:save_cpo = &cpo +set cpo&vim + +function! s:_true() abort +  return 1 +endfunction + +function! s:_false() abort +  return 0 +endfunction + +function! s:_null() abort +  return 0 +endfunction + +let s:const = {} +let s:const.true = function('s:_true') +let s:const.false = function('s:_false') +let s:const.null = function('s:_null') + +function! s:_resolve(val, prefix) abort +  let t = type(a:val) +  if t == type('') +    let m = matchlist(a:val, '^' . a:prefix . '\(null\|true\|false\)$') +    if !empty(m) +      return s:const[m[1]] +    endif +  elseif t == type([]) || t == type({}) +    return map(a:val, 's:_resolve(v:val, a:prefix)') +  endif +  return a:val +endfunction + + +function! s:_vital_created(module) abort +  " define constant variables +  call extend(a:module, s:const) +endfunction + +function! s:_vital_loaded(V) abort +  let s:V = a:V +  let s:string = s:V.import('Data.String') +endfunction + +function! s:_vital_depends() abort +  return ['Data.String'] +endfunction + +" @vimlint(EVL102, 1, l:null) +" @vimlint(EVL102, 1, l:true) +" @vimlint(EVL102, 1, l:false) +function! s:decode(json, ...) abort +  let settings = extend({ +        \ 'use_token': 0, +        \}, get(a:000, 0, {})) +  let json = iconv(a:json, "utf-8", &encoding) +  let json = join(split(json, "\n"), '') +  let json = substitute(json, '\\u34;', '\\"', 'g') +  let json = substitute(json, '\\u\(\x\x\x\x\)', '\=s:string.nr2enc_char("0x".submatch(1))', 'g') +  if settings.use_token +    let prefix = '__Web.JSON__' +    while stridx(json, prefix) != -1 +      let prefix .= '_' +    endwhile +    let [null,true,false] = map(['null','true','false'], 'prefix . v:val') +    sandbox return s:_resolve(eval(json), prefix) +  else +    let [null,true,false] = [s:const.null(),s:const.true(),s:const.false()] +    sandbox return eval(json) +  endif +endfunction +" @vimlint(EVL102, 0, l:null) +" @vimlint(EVL102, 0, l:true) +" @vimlint(EVL102, 0, l:false) + +function! s:encode(val) abort +  if type(a:val) == 0 +    return a:val +  elseif type(a:val) == 1 +    let json = '"' . escape(a:val, '\"') . '"' +    let json = substitute(json, "\r", '\\r', 'g') +    let json = substitute(json, "\n", '\\n', 'g') +    let json = substitute(json, "\t", '\\t', 'g') +    return iconv(json, &encoding, "utf-8") +  elseif type(a:val) == 2 +    if s:const.true == a:val +      return 'true' +    elseif s:const.false == a:val +      return 'false' +    elseif s:const.null == a:val +      return 'null' +    else +      " backward compatibility +      return string(a:val) +    endif +  elseif type(a:val) == 3 +    return '[' . join(map(copy(a:val), 's:encode(v:val)'), ',') . ']' +  elseif type(a:val) == 4 +    return '{' . join(map(keys(a:val), 's:encode(v:val).":".s:encode(a:val[v:val])'), ',') . '}' +  else +    return string(a:val) +  endif +endfunction + +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim:set et ts=2 sts=2 sw=2 tw=0: + +endif @@ -105,6 +105,7 @@ PACKS="    clojure:guns/vim-clojure-static    coffee-script:kchmck/vim-coffee-script    cryptol:victoredwardocallaghan/cryptol.vim +  crystal:rhysd/vim-crystal    cql:elubow/cql-vim    css:JulesWang/css.vim    cucumber:tpope/vim-cucumber diff --git a/ftdetect/polyglot.vim b/ftdetect/polyglot.vim index da43539c..edb46195 100644 --- a/ftdetect/polyglot.vim +++ b/ftdetect/polyglot.vim @@ -60,6 +60,12 @@ au! BufRead,BufNewFile *.cyl set filetype=cryptol  au! BufRead,BufNewFile *.lcry set filetype=cryptol  au! BufRead,BufNewFile *.lcyl set filetype=cryptol  endif +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1 +   +autocmd BufNewFile,BufReadPost *.cr setlocal filetype=crystal +autocmd BufNewFile,BufReadPost Projectfile setlocal filetype=crystal +autocmd BufNewFile,BufReadPost *.ecr setlocal filetype=eruby +endif  if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'cucumber') == -1  autocmd BufNewFile,BufReadPost *.feature,*.story set filetype=cucumber diff --git a/ftplugin/crystal.vim b/ftplugin/crystal.vim new file mode 100644 index 00000000..f990a8da --- /dev/null +++ b/ftplugin/crystal.vim @@ -0,0 +1,60 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1 +   +if exists('b:did_ftplugin') +  finish +endif +let b:did_ftplugin = 1 + +let s:save_cpo = &cpo +set cpo&vim + +if exists('loaded_matchit') && !exists('b:match_words') +  let b:match_ignorecase = 0 + +  let b:match_words = +        \ '\<\%(if\|unless\|case\|while\|until\|for\|do\|class\|module\|struct\|lib\|macro\|ifdef\|def\|fun\|begin\)\>=\@!' . +        \ ':' . +        \ '\<\%(else\|elsif\|ensure\|when\|rescue\|break\|redo\|next\|retry\)\>' . +        \ ':' . +        \ '\<end\>' . +        \ ',{:},\[:\],(:)' + +  let b:match_skip = +        \ "synIDattr(synID(line('.'),col('.'),0),'name') =~ '" . +        \ "\\<crystal\\%(String\\|StringDelimiter\\|ASCIICode\\|Escape\\|" . +        \ "Interpolation\\|NoInterpolation\\|Comment\\|Documentation\\|" . +        \ "ConditionalModifier\\|RepeatModifier\\|OptionalDo\\|" . +        \ "Function\\|BlockArgument\\|KeywordAsMethod\\|ClassVariable\\|" . +        \ "InstanceVariable\\|GlobalVariable\\|Symbol\\)\\>'" +endif + +setlocal comments=:# +setlocal commentstring=#\ %s +setlocal suffixesadd=.cr + +" Set format for quickfix window +setlocal errorformat= +  \%ESyntax\ error\ in\ line\ %l:\ %m, +  \%ESyntax\ error\ in\ %f:%l:\ %m, +  \%EError\ in\ %f:%l:\ %m, +  \%C%p^, +  \%-C%.%# + +if get(g:, 'crystal_define_mappings', 1) +  nmap <buffer>gd <Plug>(crystal-jump-to-definition) +  nmap <buffer>gc <Plug>(crystal-show-context) +  nmap <buffer>gss <Plug>(crystal-spec-switch) +  nmap <buffer>gsa <Plug>(crystal-spec-run-all) +  nmap <buffer>gsc <Plug>(crystal-spec-run-current) +endif + +if &l:ofu ==# '' +  setlocal omnifunc=crystal_lang#complete +endif + +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim: nowrap sw=2 sts=2 ts=8: + +endif diff --git a/indent/crystal.vim b/indent/crystal.vim new file mode 100644 index 00000000..fad2822c --- /dev/null +++ b/indent/crystal.vim @@ -0,0 +1,639 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1 +   +" Only load this indent file when no other was loaded. +if exists('b:did_indent') +  finish +endif +let b:did_indent = 1 + +if !exists('g:crystal_indent_access_modifier_style') +  " Possible values: "normal", "indent", "outdent" +  let g:crystal_indent_access_modifier_style = 'normal' +endif + +setlocal nosmartindent + +" Now, set up our indentation expression and keys that trigger it. +setlocal indentexpr=GetCrystalIndent(v:lnum) +setlocal indentkeys=0{,0},0),0],!^F,o,O,e,:,. +setlocal indentkeys+==end,=else,=elsif,=when,=ensure,=rescue,==begin,==end +setlocal indentkeys+==private,=protected,=public + +" Only define the function once. +if exists('*GetCrystalIndent') +  finish +endif + +let s:cpo_save = &cpo +set cpo&vim + +" 1. Variables {{{1 +" ============ + +" Regex of syntax group names that are or delimit strings/symbols or are comments. +let s:syng_strcom = '\<crystal\%(Regexp\|RegexpDelimiter\|RegexpEscape' . +      \ '\|Symbol\|String\|StringDelimiter\|StringEscape\|ASCIICode' . +      \ '\|Interpolation\|InterpolationDelimiter\|NoInterpolation\|Comment\|Documentation\)\>' + +" Regex of syntax group names that are strings. +let s:syng_string = +      \ '\<crystal\%(String\|Interpolation\|NoInterpolation\|StringEscape\)\>' + +" Regex of syntax group names that are strings or documentation. +let s:syng_stringdoc = +      \'\<crystal\%(String\|Interpolation\|NoInterpolation\|StringEscape\|Documentation\)\>' + +" Expression used to check whether we should skip a match with searchpair(). +let s:skip_expr = +      \ "synIDattr(synID(line('.'),col('.'),1),'name') =~ '".s:syng_strcom."'" + +" Regex used for words that, at the start of a line, add a level of indent. +let s:crystal_indent_keywords = +      \ '^\s*\zs\<\%(module\|\%(abstract\)\=\s*\%(class\|struct\)\|enum\|if\|for\|macro' . +      \ '\|while\|until\|else\|elsif\|case\|when\|unless\|begin\|ensure\|rescue\|lib' . +      \ '\|\%(protected\|private\)\=\s*def\):\@!\>' . +      \ '\|\%([=,*/%+-]\|<<\|>>\|:\s\)\s*\zs' . +      \ '\<\%(if\|for\|while\|until\|case\|unless\|begin\):\@!\>' . +      \ '\|{%\s*\<\%(if\|for\|while\|until\|lib\|case\|unless\|begin\|else\|elsif\|when\)' + +" Regex used for words that, at the start of a line, remove a level of indent. +let s:crystal_deindent_keywords = +      \ '^\s*\zs\<\%(ensure\|else\|rescue\|elsif\|when\|end\):\@!\>' . +      \ '\|{%\s*\<\%(ensure\|else\|rescue\|elsif\|when\|end\)\>' + +" Regex that defines the start-match for the 'end' keyword. +" TODO: the do here should be restricted somewhat (only at end of line)? +let s:end_start_regex = +      \ '{%\s*\<\%(if\|for\|while\|until\|unless\|begin\|lib\)\>\|' . +      \ '\C\%(^\s*\|[=,*/%+\-|;{]\|<<\|>>\|:\s\)\s*\zs' . +      \ '\<\%(module\|\%(abstract\)\=\s*\%(class\|struct\)\|enum\|macro\|if\|for\|while\|until\|case\|unless\|begin\|lib' . +      \ '\|\%(protected\|private\)\=\s*def\):\@!\>' . +      \ '\|\%(^\|[^.:@$]\)\@<=\<do:\@!\>' + +" Regex that defines the middle-match for the 'end' keyword. +let s:end_middle_regex = +      \ '{%\s*\<\%(ensure\|else\|when\|elsif\)\>\s*%}\|' . +      \ '\<\%(ensure\|else\|\%(\%(^\|;\)\s*\)\@<=\<rescue:\@!\>\|when\|elsif\):\@!\>' + +" Regex that defines the end-match for the 'end' keyword. +let s:end_end_regex = '\%(^\|[^.:@$]\)\@<=\<end:\@!\>\|{%\s*\<\%(end\)\>' + +" Expression used for searchpair() call for finding match for 'end' keyword. +let s:end_skip_expr = s:skip_expr . +      \ ' || (expand("<cword>") == "do"' . +      \ ' && getline(".") =~ "^\\s*\\<\\(while\\|until\\|for\\):\\@!\\>")' + +" Regex that defines continuation lines, not including (, {, or [. +let s:non_bracket_continuation_regex = '\%([\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$' + +" Regex that defines continuation lines. +let s:continuation_regex = +      \ '\%(%\@<![({[\\.,:*/%+]\|\<and\|\<or\|\%(<%\)\@<![=-]\|\W[|&?]\|||\|&&\)\s*\%(#.*\)\=$' + +" Regex that defines continuable keywords +let s:continuable_regex = +      \ '\C\%(^\s*\|[=,*/%+\-|;{]\|<<\|>>\|:\s\)\s*\zs' . +      \ '\<\%(if\|for\|while\|until\|unless\):\@!\>' + +" Regex that defines bracket continuations +let s:bracket_continuation_regex = '%\@<!\%([({[]\)\s*\%(#.*\)\=$' + +" Regex that defines end of bracket continuation followed by another continuation +let s:bracket_switch_continuation_regex = '^\([^(]\+\zs).\+\)\+'.s:continuation_regex + +" Regex that defines the first part of a splat pattern +let s:splat_regex = '[[,(]\s*\*\s*\%(#.*\)\=$' + +" Regex that defines blocks. +" +" Note that there's a slight problem with this regex and s:continuation_regex. +" Code like this will be matched by both: +" +"   method_call do |(a, b)| +" +" The reason is that the pipe matches a hanging "|" operator. +" +let s:block_regex = +      \ '\%(\<do:\@!\>\|%\@<!{\)\s*\%(|\s*(*\s*\%([*@&]\=\h\w*,\=\s*\)\%(,\s*(*\s*[*@&]\=\h\w*\s*)*\s*\)*|\)\=\s*\%(#.*\)\=$' + +let s:block_continuation_regex = '^\s*[^])}\t ].*'.s:block_regex + +" Regex that describes a leading operator (only a method call's dot for now) +let s:leading_operator_regex = '^\s*[.]' + +" Regex that describes all indent access modifiers +let s:access_modifier_regex = '\C^\s*\%(public\|protected\|private\)\s*\%(#.*\)\=$' + +" 2. Auxiliary Functions {{{1 +" ====================== + +" Check if the character at lnum:col is inside a string, comment, or is ascii. +function s:IsInStringOrComment(lnum, col) +  return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_strcom +endfunction + +" Check if the character at lnum:col is inside a string. +function s:IsInString(lnum, col) +  return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_string +endfunction + +" Check if the character at lnum:col is inside a string or documentation. +function s:IsInStringOrDocumentation(lnum, col) +  return synIDattr(synID(a:lnum, a:col, 1), 'name') =~ s:syng_stringdoc +endfunction + +" Check if the character at lnum:col is inside a string delimiter +function s:IsInStringDelimiter(lnum, col) +  return synIDattr(synID(a:lnum, a:col, 1), 'name') ==# 'crystalStringDelimiter' +endfunction + +" Find line above 'lnum' that isn't empty, in a comment, or in a string. +function s:PrevNonBlankNonString(lnum) +  let in_block = 0 +  let lnum = prevnonblank(a:lnum) +  while lnum > 0 +    " Go in and out of blocks comments as necessary. +    " If the line isn't empty (with opt. comment) or in a string, end search. +    let line = getline(lnum) +    if line =~# '^=begin' +      if in_block +        let in_block = 0 +      else +        break +      endif +    elseif !in_block && line =~# '^=end' +      let in_block = 1 +    elseif !in_block && line !~# '^\s*#.*$' && !(s:IsInStringOrComment(lnum, 1) +          \ && s:IsInStringOrComment(lnum, strlen(line))) +      break +    endif +    let lnum = prevnonblank(lnum - 1) +  endwhile +  return lnum +endfunction + +" Find line above 'lnum' that started the continuation 'lnum' may be part of. +function s:GetMSL(lnum) +  " Start on the line we're at and use its indent. +  let msl = a:lnum +  let msl_body = getline(msl) +  let lnum = s:PrevNonBlankNonString(a:lnum - 1) +  while lnum > 0 +    " If we have a continuation line, or we're in a string, use line as MSL. +    " Otherwise, terminate search as we have found our MSL already. +    let line = getline(lnum) + +    if s:Match(msl, s:leading_operator_regex) +      " If the current line starts with a leading operator, keep its indent +      " and keep looking for an MSL. +      let msl = lnum +    elseif s:Match(lnum, s:splat_regex) +      " If the above line looks like the "*" of a splat, use the current one's +      " indentation. +      " +      " Example: +      "   Hash[* +      "     method_call do +      "       something +      " +      return msl +    elseif s:Match(lnum, s:non_bracket_continuation_regex) && +          \ s:Match(msl, s:non_bracket_continuation_regex) +      " If the current line is a non-bracket continuation and so is the +      " previous one, keep its indent and continue looking for an MSL. +      " +      " Example: +      "   method_call one, +      "     two, +      "     three +      " +      let msl = lnum +    elseif s:Match(lnum, s:non_bracket_continuation_regex) && +          \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex)) +      " If the current line is a bracket continuation or a block-starter, but +      " the previous is a non-bracket one, respect the previous' indentation, +      " and stop here. +      " +      " Example: +      "   method_call one, +      "     two { +      "     three +      " +      return lnum +    elseif s:Match(lnum, s:bracket_continuation_regex) && +          \ (s:Match(msl, s:bracket_continuation_regex) || s:Match(msl, s:block_continuation_regex)) +      " If both lines are bracket continuations (the current may also be a +      " block-starter), use the current one's and stop here +      " +      " Example: +      "   method_call( +      "     other_method_call( +      "       foo +      return msl +    elseif s:Match(lnum, s:block_regex) && +          \ !s:Match(msl, s:continuation_regex) && +          \ !s:Match(msl, s:block_continuation_regex) +      " If the previous line is a block-starter and the current one is +      " mostly ordinary, use the current one as the MSL. +      " +      " Example: +      "   method_call do +      "     something +      "     something_else +      return msl +    else +      let col = match(line, s:continuation_regex) + 1 +      if (col > 0 && !s:IsInStringOrComment(lnum, col)) +            \ || s:IsInString(lnum, strlen(line)) +        let msl = lnum +      else +        break +      endif +    endif + +    let msl_body = getline(msl) +    let lnum = s:PrevNonBlankNonString(lnum - 1) +  endwhile +  return msl +endfunction + +" Check if line 'lnum' has more opening brackets than closing ones. +function s:ExtraBrackets(lnum) +  let opening = {'parentheses': [], 'braces': [], 'brackets': []} +  let closing = {'parentheses': [], 'braces': [], 'brackets': []} + +  let line = getline(a:lnum) +  let pos  = match(line, '[][(){}]', 0) + +  " Save any encountered opening brackets, and remove them once a matching +  " closing one has been found. If a closing bracket shows up that doesn't +  " close anything, save it for later. +  while pos != -1 +    if !s:IsInStringOrComment(a:lnum, pos + 1) +      if line[pos] ==# '(' +        call add(opening.parentheses, {'type': '(', 'pos': pos}) +      elseif line[pos] ==# ')' +        if empty(opening.parentheses) +          call add(closing.parentheses, {'type': ')', 'pos': pos}) +        else +          let opening.parentheses = opening.parentheses[0:-2] +        endif +      elseif line[pos] ==# '{' +        call add(opening.braces, {'type': '{', 'pos': pos}) +      elseif line[pos] ==# '}' +        if empty(opening.braces) +          call add(closing.braces, {'type': '}', 'pos': pos}) +        else +          let opening.braces = opening.braces[0:-2] +        endif +      elseif line[pos] ==# '[' +        call add(opening.brackets, {'type': '[', 'pos': pos}) +      elseif line[pos] ==# ']' +        if empty(opening.brackets) +          call add(closing.brackets, {'type': ']', 'pos': pos}) +        else +          let opening.brackets = opening.brackets[0:-2] +        endif +      endif +    endif + +    let pos = match(line, '[][(){}]', pos + 1) +  endwhile + +  " Find the rightmost brackets, since they're the ones that are important in +  " both opening and closing cases +  let rightmost_opening = {'type': '(', 'pos': -1} +  let rightmost_closing = {'type': ')', 'pos': -1} + +  for opening in opening.parentheses + opening.braces + opening.brackets +    if opening.pos > rightmost_opening.pos +      let rightmost_opening = opening +    endif +  endfor + +  for closing in closing.parentheses + closing.braces + closing.brackets +    if closing.pos > rightmost_closing.pos +      let rightmost_closing = closing +    endif +  endfor + +  return [rightmost_opening, rightmost_closing] +endfunction + +function s:Match(lnum, regex) +  let line   = getline(a:lnum) +  let offset = match(line, '\C'.a:regex) +  let col    = offset + 1 + +  while offset > -1 && s:IsInStringOrComment(a:lnum, col) +    let offset = match(line, '\C'.a:regex, offset + 1) +    let col = offset + 1 +  endwhile + +  if offset > -1 +    return col +  else +    return 0 +  endif +endfunction + +" Locates the containing class/module's definition line, ignoring nested classes +" along the way. +" +function! s:FindContainingClass() +  let saved_position = getpos('.') + +  while searchpair(s:end_start_regex, s:end_middle_regex, s:end_end_regex, 'bW', +        \ s:end_skip_expr) > 0 +    if expand('<cword>') =~# '\<class\|module\>' +      let found_lnum = line('.') +      call setpos('.', saved_position) +      return found_lnum +    endif +  endwhile + +  call setpos('.', saved_position) +  return 0 +endfunction + +" 3. GetCrystalIndent Function {{{1 +" ========================= + +function GetCrystalIndent(...) +  " 3.1. Setup {{{2 +  " ---------- + +  " The value of a single shift-width +  if exists('*shiftwidth') +    let sw = shiftwidth() +  else +    let sw = &sw +  endif + +  " For the current line, use the first argument if given, else v:lnum +  let clnum = a:0 ? a:1 : v:lnum + +  " Set up variables for restoring position in file.  Could use clnum here. +  let vcol = col('.') + +  " 3.2. Work on the current line {{{2 +  " ----------------------------- + +  " Get the current line. +  let line = getline(clnum) +  let ind = -1 + +  " If this line is an access modifier keyword, align according to the closest +  " class declaration. +  if g:crystal_indent_access_modifier_style ==? 'indent' +    if s:Match(clnum, s:access_modifier_regex) +      let class_line = s:FindContainingClass() +      if class_line > 0 +        return indent(class_line) + sw +      endif +    endif +  elseif g:crystal_indent_access_modifier_style ==? 'outdent' +    if s:Match(clnum, s:access_modifier_regex) +      let class_line = s:FindContainingClass() +      if class_line > 0 +        return indent(class_line) +      endif +    endif +  endif + +  " If we got a closing bracket on an empty line, find its match and indent +  " according to it.  For parentheses we indent to its column - 1, for the +  " others we indent to the containing line's MSL's level.  Return -1 if fail. +  let col = matchend(line, '^\s*[]})]') +  if col > 0 && !s:IsInStringOrComment(clnum, col) +    call cursor(clnum, col) +    let bs = strpart('(){}[]', stridx(')}]', line[col - 1]) * 2, 2) +    if searchpair(escape(bs[0], '\['), '', bs[1], 'bW', s:skip_expr) > 0 +      if line[col-1] ==# ')' && col('.') != col('$') - 1 +        let ind = virtcol('.') - 1 +      else +        let ind = indent(s:GetMSL(line('.'))) +      endif +    endif +    return ind +  endif + +  " If we have a =begin or =end set indent to first column. +  if match(line, '^\s*\%(=begin\|=end\)$') != -1 +    return 0 +  endif + +  " If we have a deindenting keyword, find its match and indent to its level. +  " TODO: this is messy +  if s:Match(clnum, s:crystal_deindent_keywords) +    call cursor(clnum, 1) +    if searchpair(s:end_start_regex, s:end_middle_regex, s:end_end_regex, 'bW', +          \ s:end_skip_expr) > 0 +      let msl  = s:GetMSL(line('.')) +      let line = getline(line('.')) + +      if strpart(line, 0, col('.') - 1) =~# '=\s*$' && +            \ strpart(line, col('.') - 1, 2) !~# 'do' +        " assignment to case/begin/etc, on the same line, hanging indent +        let ind = virtcol('.') - 1 +      elseif getline(msl) =~# '=\s*\(#.*\)\=$' +        " in the case of assignment to the msl, align to the starting line, +        " not to the msl +        let ind = indent(line('.')) +      else +        " align to the msl +        let ind = indent(msl) +      endif +    endif +    return ind +  endif + +  " If we are in a multi-line string or line-comment, don't do anything to it. +  if s:IsInStringOrDocumentation(clnum, matchend(line, '^\s*') + 1) +    return indent('.') +  endif + +  " If we are at the closing delimiter of a "<<" heredoc-style string, set the +  " indent to 0. +  if line =~# '^\k\+\s*$' +        \ && s:IsInStringDelimiter(clnum, 1) +        \ && search('\V<<'.line, 'nbW') > 0 +    return 0 +  endif + +  " If the current line starts with a leading operator, add a level of indent. +  if s:Match(clnum, s:leading_operator_regex) +    return indent(s:GetMSL(clnum)) + sw +  endif + +  " 3.3. Work on the previous line. {{{2 +  " ------------------------------- + +  " Find a non-blank, non-multi-line string line above the current line. +  let lnum = s:PrevNonBlankNonString(clnum - 1) + +  " If the line is empty and inside a string, use the previous line. +  if line =~# '^\s*$' && lnum != prevnonblank(clnum - 1) +    return indent(prevnonblank(clnum)) +  endif + +  " At the start of the file use zero indent. +  if lnum == 0 +    return 0 +  endif + +  " Set up variables for the previous line. +  let line = getline(lnum) +  let ind = indent(lnum) + +  if s:Match(lnum, s:continuable_regex) && s:Match(lnum, s:continuation_regex) +    return indent(s:GetMSL(lnum)) + sw + sw +  endif + +  " If the previous line ended with a block opening, add a level of indent. +  if s:Match(lnum, s:block_regex) +    let msl = s:GetMSL(lnum) + +    if getline(msl) =~# '=\s*\(#.*\)\=$' +      " in the case of assignment to the msl, align to the starting line, +      " not to the msl +      let ind = indent(lnum) + sw +    else +      let ind = indent(msl) + sw +    endif +    return ind +  endif + +  " If the previous line started with a leading operator, use its MSL's level +  " of indent +  if s:Match(lnum, s:leading_operator_regex) +    return indent(s:GetMSL(lnum)) +  endif + +  " If the previous line ended with the "*" of a splat, add a level of indent +  if line =~ s:splat_regex +    return indent(lnum) + sw +  endif + +  " If the previous line contained unclosed opening brackets and we are still +  " in them, find the rightmost one and add indent depending on the bracket +  " type. +  " +  " If it contained hanging closing brackets, find the rightmost one, find its +  " match and indent according to that. +  if line =~# '[[({]' || line =~# '[])}]\s*\%(#.*\)\=$' +    let [opening, closing] = s:ExtraBrackets(lnum) + +    if opening.pos != -1 +      if opening.type ==# '(' && searchpair('(', '', ')', 'bW', s:skip_expr) > 0 +        if col('.') + 1 == col('$') +          return ind + sw +        else +          return virtcol('.') +        endif +      else +        let nonspace = matchend(line, '\S', opening.pos + 1) - 1 +        return nonspace > 0 ? nonspace : ind + sw +      endif +    elseif closing.pos != -1 +      call cursor(lnum, closing.pos + 1) +      normal! % + +      if s:Match(line('.'), s:crystal_indent_keywords) +        return indent('.') + sw +      else +        return indent('.') +      endif +    else +      call cursor(clnum, vcol) +    end +  endif + +  " If the previous line ended with an "end", match that "end"s beginning's +  " indent. +  let col = s:Match(lnum, '\%(^\|[^.:@$]\)\<end\>\s*\%(#.*\)\=$') +  if col > 0 +    call cursor(lnum, col) +    if searchpair(s:end_start_regex, '', s:end_end_regex, 'bW', +          \ s:end_skip_expr) > 0 +      let n = line('.') +      let ind = indent('.') +      let msl = s:GetMSL(n) +      if msl != n +        let ind = indent(msl) +      end +      return ind +    endif +  end + +  let col = s:Match(lnum, s:crystal_indent_keywords) +  if col > 0 +    call cursor(lnum, col) +    let ind = virtcol('.') - 1 + sw +    " TODO: make this better (we need to count them) (or, if a searchpair +    " fails, we know that something is lacking an end and thus we indent a +    " level +    if s:Match(lnum, s:end_end_regex) +      let ind = indent('.') +    endif +    return ind +  endif + +  " 3.4. Work on the MSL line. {{{2 +  " -------------------------- + +  " Set up variables to use and search for MSL to the previous line. +  let p_lnum = lnum +  let lnum = s:GetMSL(lnum) + +  " If the previous line wasn't a MSL. +  if p_lnum != lnum +    " If previous line ends bracket and begins non-bracket continuation decrease indent by 1. +    if s:Match(p_lnum, s:bracket_switch_continuation_regex) +      return ind - 1 +    " If previous line is a continuation return its indent. +    " TODO: the || s:IsInString() thing worries me a bit. +    elseif s:Match(p_lnum, s:non_bracket_continuation_regex) || s:IsInString(p_lnum,strlen(line)) +      return ind +    endif +  endif + +  " Set up more variables, now that we know we wasn't continuation bound. +  let line = getline(lnum) +  let msl_ind = indent(lnum) + +  " If the MSL line had an indenting keyword in it, add a level of indent. +  " TODO: this does not take into account contrived things such as +  " module Foo; class Bar; end +  if s:Match(lnum, s:crystal_indent_keywords) +    let ind = msl_ind + sw +    if s:Match(lnum, s:end_end_regex) +      let ind = ind - sw +    endif +    return ind +  endif + +  " If the previous line ended with [*+/.,-=], but wasn't a block ending or a +  " closing bracket, indent one extra level. +  if s:Match(lnum, s:non_bracket_continuation_regex) && !s:Match(lnum, '^\s*\([\])}]\|end\)') +    if lnum == p_lnum +      let ind = msl_ind + sw +    else +      let ind = msl_ind +    endif +    return ind +  endif + +  " }}}2 + +  return ind +endfunction + +" }}}1 + +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim:set sw=2 sts=2 ts=8 et: + +endif diff --git a/syntax/crystal.vim b/syntax/crystal.vim new file mode 100644 index 00000000..c361ad70 --- /dev/null +++ b/syntax/crystal.vim @@ -0,0 +1,393 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'crystal') == -1 +   +" Language: Crystal +" Based on Ruby syntax highlight +" which is made by Mirko Nasato and Doug Kearns +" --------------------------------------------- + +if exists('b:current_syntax') +  finish +endif + +syn cluster crystalNotTop contains=@crystalExtendedStringSpecial,@crystalRegexpSpecial,@crystalDeclaration,crystalConditional,crystalExceptional,crystalMethodExceptional,crystalTodo,crystalLinkAttr + +if exists('crystal_space_errors') +  if !exists('crystal_no_trail_space_error') +    syn match crystalSpaceError display excludenl "\s\+$" +  endif +  if !exists('crystal_no_tab_space_error') +    syn match crystalSpaceError display " \+\t"me=e-1 +  endif +endif + +" Operators +if exists('crystal_operators') +  syn match  crystalOperator "[~!^&|*/%+-]\|\%(class\s*\)\@<!<<\|<=>\|<=\|\%(<\|\<class\s\+\u\w*\s*\)\@<!<[^<]\@=\|===\|==\|=\~\|>>\|>=\|=\@<!>\|\*\*\|\.\.\.\|\.\.\|::" +  syn match  crystalOperator "->\|-=\|/=\|\*\*=\|\*=\|&&=\|&=\|&&\|||=\||=\|||\|%=\|+=\|!\~\|!=" +  syn region crystalBracketOperator matchgroup=crystalOperator start="\%(\w[?!]\=\|[]})]\)\@<=\[\s*" end="\s*]" contains=ALLBUT,@crystalNotTop +endif + +" Expression Substitution and Backslash Notation +syn match crystalStringEscape "\\\\\|\\[abefnrstv]\|\\\o\{1,3}\|\\x\x\{1,2}"						    contained display +syn match crystalStringEscape "\%(\\M-\\C-\|\\C-\\M-\|\\M-\\c\|\\c\\M-\|\\c\|\\C-\|\\M-\)\%(\\\o\{1,3}\|\\x\x\{1,2}\|\\\=\S\)" contained display + +syn region crystalInterpolation	      matchgroup=crystalInterpolationDelimiter start="#{" end="}" contained contains=ALLBUT,@crystalNotTop +syn match  crystalInterpolation	      "#\%(\$\|@@\=\)\w\+"    display contained contains=crystalInterpolationDelimiter,crystalInstanceVariable,crystalClassVariable,crystalGlobalVariable,crystalPredefinedVariable +syn match  crystalInterpolationDelimiter "#\ze\%(\$\|@@\=\)\w\+" display contained +syn match  crystalInterpolation	      "#\$\%(-\w\|\W\)"       display contained contains=crystalInterpolationDelimiter,crystalPredefinedVariable,crystalInvalidVariable +syn match  crystalInterpolationDelimiter "#\ze\$\%(-\w\|\W\)"    display contained +syn region crystalNoInterpolation	      start="\\#{" end="}"            contained +syn match  crystalNoInterpolation	      "\\#{"		      display contained +syn match  crystalNoInterpolation	      "\\#\%(\$\|@@\=\)\w\+"  display contained +syn match  crystalNoInterpolation	      "\\#\$\W"		      display contained + +syn match crystalDelimEscape	"\\[(<{\[)>}\]]" transparent display contained contains=NONE + +syn region crystalNestedParentheses    start="("  skip="\\\\\|\\)"  matchgroup=crystalString end=")"	transparent contained +syn region crystalNestedCurlyBraces    start="{"  skip="\\\\\|\\}"  matchgroup=crystalString end="}"	transparent contained +syn region crystalNestedAngleBrackets  start="<"  skip="\\\\\|\\>"  matchgroup=crystalString end=">"	transparent contained +syn region crystalNestedSquareBrackets start="\[" skip="\\\\\|\\\]" matchgroup=crystalString end="\]"	transparent contained + +" These are mostly Oniguruma ready +syn region crystalRegexpComment	matchgroup=crystalRegexpSpecial   start="(?#"								  skip="\\)"  end=")"  contained +syn region crystalRegexpParens	matchgroup=crystalRegexpSpecial   start="(\(?:\|?<\=[=!]\|?>\|?<[a-z_]\w*>\|?[imx]*-[imx]*:\=\|\%(?#\)\@!\)" skip="\\)"  end=")"  contained transparent contains=@crystalRegexpSpecial +syn region crystalRegexpBrackets	matchgroup=crystalRegexpCharClass start="\[\^\="								  skip="\\\]" end="\]" contained transparent contains=crystalStringEscape,crystalRegexpEscape,crystalRegexpCharClass oneline +syn match  crystalRegexpCharClass	"\\[DdHhSsWw]"	       contained display +syn match  crystalRegexpCharClass	"\[:\^\=\%(alnum\|alpha\|ascii\|blank\|cntrl\|digit\|graph\|lower\|print\|punct\|space\|upper\|xdigit\):\]" contained +syn match  crystalRegexpEscape	"\\[].*?+^$|\\/(){}[]" contained +syn match  crystalRegexpQuantifier	"[*?+][?+]\="	       contained display +syn match  crystalRegexpQuantifier	"{\d\+\%(,\d*\)\=}?\=" contained display +syn match  crystalRegexpAnchor	"[$^]\|\\[ABbGZz]"     contained display +syn match  crystalRegexpDot	"\."		       contained display +syn match  crystalRegexpSpecial	"|"		       contained display +syn match  crystalRegexpSpecial	"\\[1-9]\d\=\d\@!"     contained display +syn match  crystalRegexpSpecial	"\\k<\%([a-z_]\w*\|-\=\d\+\)\%([+-]\d\+\)\=>" contained display +syn match  crystalRegexpSpecial	"\\k'\%([a-z_]\w*\|-\=\d\+\)\%([+-]\d\+\)\='" contained display +syn match  crystalRegexpSpecial	"\\g<\%([a-z_]\w*\|-\=\d\+\)>" contained display +syn match  crystalRegexpSpecial	"\\g'\%([a-z_]\w*\|-\=\d\+\)'" contained display + +syn cluster crystalStringSpecial	      contains=crystalInterpolation,crystalNoInterpolation,crystalStringEscape +syn cluster crystalExtendedStringSpecial contains=@crystalStringSpecial,crystalNestedParentheses,crystalNestedCurlyBraces,crystalNestedAngleBrackets,crystalNestedSquareBrackets +syn cluster crystalRegexpSpecial	      contains=crystalInterpolation,crystalNoInterpolation,crystalStringEscape,crystalRegexpSpecial,crystalRegexpEscape,crystalRegexpBrackets,crystalRegexpCharClass,crystalRegexpDot,crystalRegexpQuantifier,crystalRegexpAnchor,crystalRegexpParens,crystalRegexpComment + +" Numbers and ASCII Codes +syn match crystalASCIICode	"\%(\w\|[]})\"'/]\)\@<!\%(?\%(\\M-\\C-\|\\C-\\M-\|\\M-\\c\|\\c\\M-\|\\c\|\\C-\|\\M-\)\=\%(\\\o\{1,3}\|\\x\x\{1,2}\|\\\=\S\)\)" +syn match crystalInteger	"\%(\%(\w\|[]})\"']\s*\)\@<!-\)\=\<0[xX]\x\+\%(_\x\+\)*\%(_*[ufi]\%(32\|64\)\)\=\>"								display +syn match crystalInteger	"\%(\%(\w\|[]})\"']\s*\)\@<!-\)\=\<\%(0[dD]\)\=\%(0\|[1-9]\d*\%(_\d\+\)*\)\%(_\x\+\)*\%(_*[ufi]\%(32\|64\)\)\=\>"						display +syn match crystalInteger	"\%(\%(\w\|[]})\"']\s*\)\@<!-\)\=\<0[oO]\=\o\+\%(_\o\+\)*\%(_\x\+\)*\%(_*[ufi]\%(32\|64\)\)\=\>"								display +syn match crystalInteger	"\%(\%(\w\|[]})\"']\s*\)\@<!-\)\=\<0[bB][01]\+\%(_[01]\+\)*\%(_\x\+\)*\%(_*[ufi]\%(32\|64\)\)\=\>"								display +syn match crystalFloat	"\%(\%(\w\|[]})\"']\s*\)\@<!-\)\=\<\%(0\|[1-9]\d*\%(_\d\+\)*\)\.\d\+\%(_\d\+\)*\%(_*f\%(32\|64\)\)\=\>"					display +syn match crystalFloat	"\%(\%(\w\|[]})\"']\s*\)\@<!-\)\=\<\%(0\|[1-9]\d*\%(_\d\+\)*\)\%(\.\d\+\%(_\d\+\)*\)\=\%([eE][-+]\=\d\+\%(_\d\+\)*\)\%(_*f\%(32\|64\)\)\=\>"	display + +" Identifiers +syn match crystalLocalVariableOrMethod "\<[_[:lower:]][_[:alnum:]]*[?!=]\=" contains=NONE display transparent +syn match crystalBlockArgument	    "&[_[:lower:]][_[:alnum:]]"		 contains=NONE display transparent + +syn match  crystalConstant		"\%(\%([.@$]\@<!\.\)\@<!\<\|::\)\_s*\zs\u\w*\%(\>\|::\)\@=" +syn match  crystalClassVariable	"@@\%(\h\|%\|[^\x00-\x7F]\)\%(\w\|%\|[^\x00-\x7F]\)*" display +syn match  crystalInstanceVariable "@\%(\h\|%\|[^\x00-\x7F]\)\%(\w\|%\|[^\x00-\x7F]\)*"  display +syn match  crystalGlobalVariable	"$\%(\%(\h\|%\|[^\x00-\x7F]\)\%(\w\|%\|[^\x00-\x7F]\)*\|-.\)" +syn match  crystalFreshVariable		"\%(\h\|[^\x00-\x7F]\)\@<!%\%(\h\|[^\x00-\x7F]\)\%(\w\|%\|[^\x00-\x7F]\)*" display +syn match  crystalSymbol		"[]})\"':]\@<!:\%(\^\|\~\|<<\|<=>\|<=\|<\|===\|[=!]=\|[=!]\~\|!\|>>\|>=\|>\||\|-@\|-\|/\|\[][=?]\|\[]\|\*\*\|\*\|&\|%\|+@\|+\|`\)" +syn match  crystalSymbol		"[]})\"':]\@<!:\$\%(-.\|[`~<=>_,;:!?/.'"@$*\&+0]\)" +syn match  crystalSymbol		"[]})\"':]\@<!:\%(\$\|@@\=\)\=\%(\h\|[^\x00-\x7F]\)\%(\w\|[^\x00-\x7F]\)*" +syn match  crystalSymbol		"[]})\"':]\@<!:\%(\h\|%\|[^\x00-\x7F]\)\%(\w\|%\|[^\x00-\x7F]\)*\%([?!=]>\@!\)\=" +syn match  crystalSymbol		"\%([{(,]\_s*\)\@<=\l\w*[!?]\=::\@!"he=e-1 +syn match  crystalSymbol		"[]})\"':]\@<!\%(\h\|[^\x00-\x7F]\)\%(\w\|[^\x00-\x7F]\)*[!?]\=:\s\@="he=e-1 +syn match  crystalSymbol		"\%([{(,]\_s*\)\@<=[[:space:],{]\l\w*[!?]\=::\@!"hs=s+1,he=e-1 +syn match  crystalSymbol		"[[:space:],{]\%(\h\|[^\x00-\x7F]\)\%(\w\|[^\x00-\x7F]\)*[!?]\=:\s\@="hs=s+1,he=e-1 +syn region crystalSymbol		start="[]})\"':]\@<!:\"" end="\"" skip="\\\\\|\\\"" contains=@crystalStringSpecial fold + +syn match  crystalBlockParameter	  "\%(\h\|%\|[^\x00-\x7F]\)\%(\w\|%\|[^\x00-\x7F]\)*" contained +syn region crystalBlockParameterList start="\%(\%(\<do\>\|{\)\s*\)\@<=|" end="|" oneline display contains=crystalBlockParameter + +syn match crystalInvalidVariable	 "$[^ %A-Za-z_-]" +syn match crystalPredefinedVariable #$[!$&"'*+,./0:;<=>?@\`~]# +syn match crystalPredefinedVariable "$\d\+"										   display +syn match crystalPredefinedVariable "$_\>"											   display +syn match crystalPredefinedVariable "$-[0FIKadilpvw]\>"									   display +syn match crystalPredefinedVariable "$\%(deferr\|defout\|stderr\|stdin\|stdout\)\>"					   display +syn match crystalPredefinedVariable "$\%(DEBUG\|FILENAME\|KCODE\|LOADED_FEATURES\|LOAD_PATH\|PROGRAM_NAME\|SAFE\|VERBOSE\)\>" display +syn match crystalPredefinedConstant "\%(\%(\.\@<!\.\)\@<!\|::\)\_s*\zs\%(MatchingData\|ARGF\|ARGV\|ENV\)\>\%(\s*(\)\@!" +syn match crystalPredefinedConstant "\%(\%(\.\@<!\.\)\@<!\|::\)\_s*\zs\%(DATA\|FALSE\|NIL\)\>\%(\s*(\)\@!" +syn match crystalPredefinedConstant "\%(\%(\.\@<!\.\)\@<!\|::\)\_s*\zs\%(STDERR\|STDIN\|STDOUT\|TOPLEVEL_BINDING\|TRUE\)\>\%(\s*(\)\@!" +syn match crystalPredefinedConstant "\%(\%(\.\@<!\.\)\@<!\|::\)\_s*\zs\%(crystal_\%(VERSION\|RELEASE_DATE\|PLATFORM\|PATCHLEVEL\|REVISION\|DESCRIPTION\|COPYRIGHT\|ENGINE\)\)\>\%(\s*(\)\@!" + +" Normal Regular Expression +syn region crystalRegexp matchgroup=crystalRegexpDelimiter start="\%(\%(^\|\<\%(and\|or\|while\|until\|unless\|if\|elsif\|ifdef\|when\|not\|then\|else\)\|[;\~=!|&(,[<>?:*+-]\)\s*\)\@<=/" end="/[iomxneus]*" skip="\\\\\|\\/" contains=@crystalRegexpSpecial fold +syn region crystalRegexp matchgroup=crystalRegexpDelimiter start="\%(\h\k*\s\+\)\@<=/[ \t=]\@!" end="/[iomxneus]*" skip="\\\\\|\\/" contains=@crystalRegexpSpecial fold + +" Generalized Regular Expression +syn region crystalRegexp matchgroup=crystalRegexpDelimiter start="%r\z([~`!@#$%^&*_\-+=|\:;"',.? /]\)" end="\z1[iomxneus]*" skip="\\\\\|\\\z1" contains=@crystalRegexpSpecial fold +syn region crystalRegexp matchgroup=crystalRegexpDelimiter start="%r{"				 end="}[iomxneus]*"   skip="\\\\\|\\}"	 contains=@crystalRegexpSpecial fold +syn region crystalRegexp matchgroup=crystalRegexpDelimiter start="%r<"				 end=">[iomxneus]*"   skip="\\\\\|\\>"	 contains=@crystalRegexpSpecial,crystalNestedAngleBrackets,crystalDelimEscape fold +syn region crystalRegexp matchgroup=crystalRegexpDelimiter start="%r\["				 end="\][iomxneus]*"  skip="\\\\\|\\\]"	 contains=@crystalRegexpSpecial fold +syn region crystalRegexp matchgroup=crystalRegexpDelimiter start="%r("				 end=")[iomxneus]*"   skip="\\\\\|\\)"	 contains=@crystalRegexpSpecial fold + +" Normal String and Shell Command Output +syn region crystalString matchgroup=crystalStringDelimiter start="\"" end="\"" skip="\\\\\|\\\"" contains=@crystalStringSpecial,@Spell fold +syn region crystalString matchgroup=crystalStringDelimiter start="`"	end="`"  skip="\\\\\|\\`"  contains=@crystalStringSpecial fold + +" Character +syn match crystalCharLiteral "'\%([^\\]\|\\[abefnrstv'\\]\|\\\o\{1,3}\|\\x\x\{1,2}\|\\u\x\{4}\)'" contained display + +" Generalized Single Quoted String, Symbol and Array of Strings +syn region crystalString matchgroup=crystalStringDelimiter start="%[qwi]\z([~`!@#$%^&*_\-+=|\:;"',.?/]\)" end="\z1" skip="\\\\\|\\\z1" fold +syn region crystalString matchgroup=crystalStringDelimiter start="%[qwi]{"				   end="}"   skip="\\\\\|\\}"	fold contains=crystalNestedCurlyBraces,crystalDelimEscape +syn region crystalString matchgroup=crystalStringDelimiter start="%[qwi]<"				   end=">"   skip="\\\\\|\\>"	fold contains=crystalNestedAngleBrackets,crystalDelimEscape +syn region crystalString matchgroup=crystalStringDelimiter start="%[qwi]\["				   end="\]"  skip="\\\\\|\\\]"	fold contains=crystalNestedSquareBrackets,crystalDelimEscape +syn region crystalString matchgroup=crystalStringDelimiter start="%[qwi]("				   end=")"   skip="\\\\\|\\)"	fold contains=crystalNestedParentheses,crystalDelimEscape +syn region crystalString matchgroup=crystalStringDelimiter start="%q "				   end=" "   skip="\\\\\|\\)"	fold +syn region crystalSymbol matchgroup=crystalSymbolDelimiter start="%s\z([~`!@#$%^&*_\-+=|\:;"',.? /]\)"   end="\z1" skip="\\\\\|\\\z1" fold +syn region crystalSymbol matchgroup=crystalSymbolDelimiter start="%s{"				   end="}"   skip="\\\\\|\\}"	fold contains=crystalNestedCurlyBraces,crystalDelimEscape +syn region crystalSymbol matchgroup=crystalSymbolDelimiter start="%s<"				   end=">"   skip="\\\\\|\\>"	fold contains=crystalNestedAngleBrackets,crystalDelimEscape +syn region crystalSymbol matchgroup=crystalSymbolDelimiter start="%s\["				   end="\]"  skip="\\\\\|\\\]"	fold contains=crystalNestedSquareBrackets,crystalDelimEscape +syn region crystalSymbol matchgroup=crystalSymbolDelimiter start="%s("				   end=")"   skip="\\\\\|\\)"	fold contains=crystalNestedParentheses,crystalDelimEscape + +" Generalized Double Quoted String and Array of Strings and Shell Command Output +" Note: %= is not matched here as the beginning of a double quoted string +syn region crystalString matchgroup=crystalStringDelimiter start="%\z([~`!@#$%^&*_\-+|\:;"',.?/]\)"	    end="\z1" skip="\\\\\|\\\z1" contains=@crystalStringSpecial fold +syn region crystalString matchgroup=crystalStringDelimiter start="%[QWIx]\z([~`!@#$%^&*_\-+=|\:;"',.?/]\)" end="\z1" skip="\\\\\|\\\z1" contains=@crystalStringSpecial fold +syn region crystalString matchgroup=crystalStringDelimiter start="%[QWIx]\={"				    end="}"   skip="\\\\\|\\}"	 contains=@crystalStringSpecial,crystalNestedCurlyBraces,crystalDelimEscape    fold +syn region crystalString matchgroup=crystalStringDelimiter start="%[QWIx]\=<"				    end=">"   skip="\\\\\|\\>"	 contains=@crystalStringSpecial,crystalNestedAngleBrackets,crystalDelimEscape  fold +syn region crystalString matchgroup=crystalStringDelimiter start="%[QWIx]\=\["				    end="\]"  skip="\\\\\|\\\]"	 contains=@crystalStringSpecial,crystalNestedSquareBrackets,crystalDelimEscape fold +syn region crystalString matchgroup=crystalStringDelimiter start="%[QWIx]\=("				    end=")"   skip="\\\\\|\\)"	 contains=@crystalStringSpecial,crystalNestedParentheses,crystalDelimEscape    fold +syn region crystalString matchgroup=crystalStringDelimiter start="%[Qx] "				    end=" "   skip="\\\\\|\\)"   contains=@crystalStringSpecial fold + +" Here Document +syn region crystalHeredocStart matchgroup=crystalStringDelimiter start=+\%(\%(class\s*\|\%([]})"'.]\|::\)\)\_s*\|\w\)\@<!<<-\=\zs\%(\%(\h\|[^\x00-\x7F]\)\%(\w\|[^\x00-\x7F]\)*\)+	 end=+$+ oneline contains=ALLBUT,@crystalNotTop +syn region crystalHeredocStart matchgroup=crystalStringDelimiter start=+\%(\%(class\s*\|\%([]})"'.]\|::\)\)\_s*\|\w\)\@<!<<-\=\zs"\%([^"]*\)"+ end=+$+ oneline contains=ALLBUT,@crystalNotTop +syn region crystalHeredocStart matchgroup=crystalStringDelimiter start=+\%(\%(class\s*\|\%([]})"'.]\|::\)\)\_s*\|\w\)\@<!<<-\=\zs'\%([^']*\)'+ end=+$+ oneline contains=ALLBUT,@crystalNotTop +syn region crystalHeredocStart matchgroup=crystalStringDelimiter start=+\%(\%(class\s*\|\%([]})"'.]\|::\)\)\_s*\|\w\)\@<!<<-\=\zs`\%([^`]*\)`+ end=+$+ oneline contains=ALLBUT,@crystalNotTop + +syn region crystalString start=+\%(\%(class\|::\)\_s*\|\%([]})"'.]\)\s\|\w\)\@<!<<\z(\%(\h\|[^\x00-\x7F]\)\%(\w\|[^\x00-\x7F]\)*\)\ze\%(.*<<-\=['`"]\=\h\)\@!+hs=s+2	matchgroup=crystalStringDelimiter end=+^\z1$+ contains=crystalHeredocStart,crystalHeredoc,@crystalStringSpecial fold keepend +syn region crystalString start=+\%(\%(class\|::\)\_s*\|\%([]})"'.]\)\s\|\w\)\@<!<<"\z([^"]*\)"\ze\%(.*<<-\=['`"]\=\h\)\@!+hs=s+2	matchgroup=crystalStringDelimiter end=+^\z1$+ contains=crystalHeredocStart,crystalHeredoc,@crystalStringSpecial fold keepend +syn region crystalString start=+\%(\%(class\|::\)\_s*\|\%([]})"'.]\)\s\|\w\)\@<!<<'\z([^']*\)'\ze\%(.*<<-\=['`"]\=\h\)\@!+hs=s+2	matchgroup=crystalStringDelimiter end=+^\z1$+ contains=crystalHeredocStart,crystalHeredoc			fold keepend +syn region crystalString start=+\%(\%(class\|::\)\_s*\|\%([]})"'.]\)\s\|\w\)\@<!<<`\z([^`]*\)`\ze\%(.*<<-\=['`"]\=\h\)\@!+hs=s+2	matchgroup=crystalStringDelimiter end=+^\z1$+ contains=crystalHeredocStart,crystalHeredoc,@crystalStringSpecial fold keepend + +syn region crystalString start=+\%(\%(class\|::\)\_s*\|\%([]}).]\)\s\|\w\)\@<!<<-\z(\%(\h\|[^\x00-\x7F]\)\%(\w\|[^\x00-\x7F]\)*\)\ze\%(.*<<-\=['`"]\=\h\)\@!+hs=s+3    matchgroup=crystalStringDelimiter end=+^\s*\zs\z1$+ contains=crystalHeredocStart,@crystalStringSpecial fold keepend +syn region crystalString start=+\%(\%(class\|::\)\_s*\|\%([]}).]\)\s\|\w\)\@<!<<-"\z([^"]*\)"\ze\%(.*<<-\=['`"]\=\h\)\@!+hs=s+3  matchgroup=crystalStringDelimiter end=+^\s*\zs\z1$+ contains=crystalHeredocStart,@crystalStringSpecial fold keepend +syn region crystalString start=+\%(\%(class\|::\)\_s*\|\%([]}).]\)\s\|\w\)\@<!<<-'\z([^']*\)'\ze\%(.*<<-\=['`"]\=\h\)\@!+hs=s+3  matchgroup=crystalStringDelimiter end=+^\s*\zs\z1$+ contains=crystalHeredocStart		     fold keepend +syn region crystalString start=+\%(\%(class\|::\)\_s*\|\%([]}).]\)\s\|\w\)\@<!<<-`\z([^`]*\)`\ze\%(.*<<-\=['`"]\=\h\)\@!+hs=s+3  matchgroup=crystalStringDelimiter end=+^\s*\zs\z1$+ contains=crystalHeredocStart,@crystalStringSpecial fold keepend + +if exists('main_syntax') && g:main_syntax ==# 'ecrystal' +  let b:crystal_no_expensive = 1 +end + +syn match  crystalAliasDeclaration    "[^[:space:];#.()]\+" contained contains=crystalSymbol,crystalGlobalVariable,crystalPredefinedVariable nextgroup=crystalAliasDeclaration2 skipwhite +syn match  crystalAliasDeclaration2   "[^[:space:];#.()]\+" contained contains=crystalSymbol,crystalGlobalVariable,crystalPredefinedVariable +syn match  crystalMethodDeclaration   "[^[:space:];#(]\+"	 contained contains=crystalConstant,crystalBoolean,crystalPseudoVariable,crystalInstanceVariable,crystalClassVariable,crystalGlobalVariable +syn match  crystalFunctionDeclaration "[^[:space:];#=]\+"	 contained contains=crystalConstant +syn match  crystalTypeDeclaration     "[^[:space:];#=]\+"	 contained contains=crystalConstant +syn match  crystalClassDeclaration    "[^[:space:];#<]\+"	 contained contains=crystalConstant,crystalOperator +syn match  crystalModuleDeclaration   "[^[:space:];#<]\+"	 contained contains=crystalConstant,crystalOperator +syn match  crystalStructDeclaration   "[^[:space:];#<]\+"	 contained contains=crystalConstant,crystalOperator +syn match  crystalLibDeclaration      "[^[:space:];#<]\+"	 contained contains=crystalConstant,crystalOperator +syn match  crystalMacroDeclaration    "[^[:space:];#<\"]\+"	 contained contains=crystalConstant,crystalOperator +syn match  crystalEnumDeclaration     "[^[:space:];#<\"]\+"	 contained contains=crystalConstant +syn match  crystalFunction "\<[_[:alpha:]][_[:alnum:]]*[?!=]\=[[:alnum:]_.:?!=]\@!" contained containedin=crystalMethodDeclaration,crystalFunctionDeclaration +syn match  crystalFunction "\%(\s\|^\)\@<=[_[:alpha:]][_[:alnum:]]*[?!=]\=\%(\s\|$\)\@=" contained containedin=crystalAliasDeclaration,crystalAliasDeclaration2 +syn match  crystalFunction "\%([[:space:].]\|^\)\@<=\%(\[\][=?]\=\|\*\*\|[+-]@\=\|[*/%|&^~]\|<<\|>>\|[<>]=\=\|<=>\|===\|[=!]=\|[=!]\~\|!\|`\)\%([[:space:];#(]\|$\)\@=" contained containedin=crystalAliasDeclaration,crystalAliasDeclaration2,crystalMethodDeclaration,crystalFunctionDeclaration + +syn cluster crystalDeclaration contains=crystalAliasDeclaration,crystalAliasDeclaration2,crystalMethodDeclaration,crystalFunctionDeclaration,crystalModuleDeclaration,crystalClassDeclaration,crystalStructDeclaration,crystalLibDeclaration,crystalMacroDeclaration,crystalFunction,crystalBlockParameter,crystalTypeDeclaration,crystalEnumDeclaration + +" Keywords +" Note: the following keywords have already been defined: +" begin case class def do end for if module unless until while +syn match   crystalControl	       "\<\%(break\|in\|next\|rescue\|return\)\>[?!]\@!" +syn match   crystalOperator       "\<defined?" display +syn match   crystalKeyword	       "\<\%(super\|previous_def\|yield\|as\|of\|with\)\>[?!]\@!" +syn match   crystalBoolean	       "\<\%(true\|false\)\>[?!]\@!" +syn match   crystalPseudoVariable "\<\%(nil\|self\|__DIR__\|__FILE__\|__LINE__\)\>[?!]\@!" " TODO: reorganise + +" Expensive Mode - match 'end' with the appropriate opening keyword for syntax +" based folding and special highlighting of module/class/method definitions +if !exists('b:crystal_no_expensive') && !exists('crystal_no_expensive') +  syn match crystalDefine "\<alias\>"  nextgroup=crystalAliasDeclaration  skipwhite skipnl +  syn match crystalDefine "\<def\>"    nextgroup=crystalMethodDeclaration skipwhite skipnl +  syn match crystalDefine "\<fun\>"    nextgroup=crystalFunctionDeclaration skipwhite skipnl +  syn match crystalDefine "\<undef\>"  nextgroup=crystalFunction	     skipwhite skipnl +  syn match crystalDefine "\<\%(type\|alias\)\>\%(\s*\h\w*\s*=\)\@=" nextgroup=crystalTypeDeclaration skipwhite skipnl +  syn match crystalClass  "\<class\>"  nextgroup=crystalClassDeclaration  skipwhite skipnl +  syn match crystalModule "\<module\>" nextgroup=crystalModuleDeclaration skipwhite skipnl +  syn match crystalStruct "\<struct\>" nextgroup=crystalStructDeclaration skipwhite skipnl +  syn match crystalLib	  "\<lib\>" nextgroup=crystalLibDeclaration skipwhite skipnl +  syn match crystalMacro  "\<macro\>" nextgroup=crystalMacroDeclaration skipwhite skipnl +  syn match crystalEnum	  "\<enum\>" nextgroup=crystalEnumDeclaration skipwhite skipnl + +  syn region crystalMethodBlock start="\<\%(def\|macro\)\>"	matchgroup=crystalDefine end="\%(\<\%(def\|macro\)\_s\+\)\@<!\<end\>" contains=ALLBUT,@crystalNotTop fold +  syn region crystalBlock	     start="\<class\>"	matchgroup=crystalClass  end="\<end\>"		       contains=ALLBUT,@crystalNotTop fold +  syn region crystalBlock	     start="\<module\>" matchgroup=crystalModule end="\<end\>"		       contains=ALLBUT,@crystalNotTop fold +  syn region crystalBlock	     start="\<struct\>" matchgroup=crystalStruct end="\<end\>"		       contains=ALLBUT,@crystalNotTop fold +  syn region crystalBlock	     start="\<lib\>" matchgroup=crystalLib    end="\<end\>"		       contains=ALLBUT,@crystalNotTop fold +  syn region crystalBlock	     start="\<enum\>" matchgroup=crystalEnum    end="\<end\>"		       contains=ALLBUT,@crystalNotTop fold + +  " modifiers +  syn match crystalConditionalModifier "\<\%(if\|unless\|ifdef\)\>"    display +  syn match crystalRepeatModifier	     "\<\%(while\|until\)\>" display + +  syn region crystalDoBlock      matchgroup=crystalControl start="\<do\>" end="\<end\>"                 contains=ALLBUT,@crystalNotTop fold +  " curly bracket block or hash literal +  syn region crystalCurlyBlock	matchgroup=crystalCurlyBlockDelimiter  start="{" end="}"				contains=ALLBUT,@crystalNotTop fold +  syn region crystalArrayLiteral	matchgroup=crystalArrayDelimiter	    start="\%(\w\|[\]})]\)\@<!\[" end="]"	contains=ALLBUT,@crystalNotTop fold + +  " statements without 'do' +  syn region crystalBlockExpression       matchgroup=crystalControl	  start="\<begin\>" end="\<end\>" contains=ALLBUT,@crystalNotTop fold +  syn region crystalCaseExpression	       matchgroup=crystalConditional start="\<case\>"  end="\<end\>" contains=ALLBUT,@crystalNotTop fold +  syn region crystalConditionalExpression matchgroup=crystalConditional start="\%(\%(^\|\.\.\.\=\|[{:,;([<>~\*/%&^|+=-]\|\%(\<[_[:lower:]][_[:alnum:]]*\)\@<![?!]\)\s*\)\@<=\%(if\|ifdef\|unless\)\>" end="\%(\%(\%(\.\@<!\.\)\|::\)\s*\)\@<!\<end\>" contains=ALLBUT,@crystalNotTop fold + +  syn match crystalConditional "\<\%(then\|else\|when\)\>[?!]\@!"	contained containedin=crystalCaseExpression +  syn match crystalConditional "\<\%(then\|else\|elsif\)\>[?!]\@!" contained containedin=crystalConditionalExpression + +  syn match crystalExceptional	  "\<\%(\%(\%(;\|^\)\s*\)\@<=rescue\|else\|ensure\)\>[?!]\@!" contained containedin=crystalBlockExpression +  syn match crystalMethodExceptional "\<\%(\%(\%(;\|^\)\s*\)\@<=rescue\|else\|ensure\)\>[?!]\@!" contained containedin=crystalMethodBlock + +  " statements with optional 'do' +  syn region crystalOptionalDoLine   matchgroup=crystalRepeat start="\<for\>[?!]\@!" start="\%(\%(^\|\.\.\.\=\|[{:,;([<>~\*/%&^|+-]\|\%(\<[_[:lower:]][_[:alnum:]]*\)\@<![!=?]\)\s*\)\@<=\<\%(until\|while\)\>" matchgroup=crystalOptionalDo end="\%(\<do\>\)" end="\ze\%(;\|$\)" oneline contains=ALLBUT,@crystalNotTop +  syn region crystalRepeatExpression start="\<for\>[?!]\@!" start="\%(\%(^\|\.\.\.\=\|[{:,;([<>~\*/%&^|+-]\|\%(\<[_[:lower:]][_[:alnum:]]*\)\@<![!=?]\)\s*\)\@<=\<\%(until\|while\)\>" matchgroup=crystalRepeat end="\<end\>" contains=ALLBUT,@crystalNotTop nextgroup=crystalOptionalDoLine fold + +  if !exists('crystal_minlines') +    let crystal_minlines = 500 +  endif +  exec 'syn sync minlines=' . crystal_minlines + +else +  syn match crystalControl "\<def\>[?!]\@!"    nextgroup=crystalMethodDeclaration skipwhite skipnl +  syn match crystalControl "\<fun\>[?!]\@!"    nextgroup=crystalFunctionDeclaration skipwhite skipnl +  syn match crystalControl "\<class\>[?!]\@!"  nextgroup=crystalClassDeclaration  skipwhite skipnl +  syn match crystalControl "\<module\>[?!]\@!" nextgroup=crystalModuleDeclaration skipwhite skipnl +  syn match crystalControl "\<struct\>[?!]\@!" nextgroup=crystalStructDeclaration skipwhite skipnl +  syn match crystalControl "\<lib\>[?!]\@!"    nextgroup=crystalLibDeclaration skipwhite skipnl +  syn match crystalControl "\<macro\>[?!]\@!"    nextgroup=crystalMacroDeclaration skipwhite skipnl +  syn match crystalControl "\<enum\>[?!]\@!"    nextgroup=crystalEnumDeclaration skipwhite skipnl +  syn match crystalControl "\<\%(case\|begin\|do\|for\|if\|ifdef\|unless\|while\|until\|else\|elsif\|ensure\|then\|when\|end\)\>[?!]\@!" +  syn match crystalKeyword "\<\%(alias\|undef\)\>[?!]\@!" +endif + +" Link attribute +syn region crystalLinkAttrRegion start="@\[" nextgroup=crystalLinkAttrRegionInner end="]" contains=crystalLinkAttr,crystalLinkAttrRegionInner transparent display oneline +syn region crystalLinkAttrRegionInner start="\%(@\[\)\@<=" end="]\@=" contained contains=ALLBUT,@crystalNotTop transparent display oneline +syn match crystalLinkAttr "@\[" contained containedin=crystalLinkAttrRegion display +syn match crystalLinkAttr "]" contained containedin=crystalLinkAttrRegion display + +" Special Methods +if !exists('crystal_no_special_methods') +  syn keyword crystalAccess    protected private +  " attr is a common variable name +  syn keyword crystalAttribute getter setter property abstract +  syn match   crystalControl   "\<\%(abort\|at_exit\|exit\|fork\|loop\)\>[?!]\@!" display +  syn keyword crystalException raise +  " false positive with 'include?' +  syn match   crystalInclude   "\<include\>[?!]\@!" display +  syn keyword crystalInclude   extend require +  syn keyword crystalKeyword   caller typeof pointerof sizeof instance_sizeof +  syn match   crystalRecord    "\<record\>[?!]\@!" display +endif + +" Macro +syn region crystalMacroRegion start="{%" end="%}" contains=ALLBUT,@crystalNotTop transparent display oneline +syn region crystalMacroRegion start="{{" end="}}" contains=ALLBUT,@crystalNotTop transparent display oneline +syn match crystalMacro "\%({%\|%}\|{{\|}}\)" nextgroup=crystalMacroRegion skipwhite display + +" Comments and Documentation +syn match   crystalSharpBang "\%^#!.*" display +syn keyword crystalTodo	  FIXME NOTE TODO OPTIMIZE XXX todo contained +syn match   crystalComment   "#.*" contains=crystalSharpBang,crystalSpaceError,crystalTodo,@Spell +if !exists('crystal_no_comment_fold') +  syn region crystalMultilineComment start="\%(\%(^\s*#.*\n\)\@<!\%(^\s*#.*\n\)\)\%(\(^\s*#.*\n\)\{1,}\)\@=" end="\%(^\s*#.*\n\)\@<=\%(^\s*#.*\n\)\%(^\s*#\)\@!" contains=crystalComment transparent fold keepend +endif + +" Note: this is a hack to prevent 'keywords' being highlighted as such when called as methods with an explicit receiver +syn match crystalKeywordAsMethod "\%(\%(\.\@<!\.\)\|::\)\_s*\%(alias\|begin\|break\|case\|class\|def\|defined\|do\|else\)\>"		  transparent contains=NONE +syn match crystalKeywordAsMethod "\%(\%(\.\@<!\.\)\|::\)\_s*\%(elsif\|end\|ensure\|false\|for\|if\|ifdef\|in\|module\|next\|nil\)\>"		  transparent contains=NONE +syn match crystalKeywordAsMethod "\%(\%(\.\@<!\.\)\|::\)\_s*\%(rescue\|return\|self\|super\|previous_def\|then\|true\)\>"		  transparent contains=NONE +syn match crystalKeywordAsMethod "\%(\%(\.\@<!\.\)\|::\)\_s*\%(undef\|unless\|until\|when\|while\|yield\|with\|__FILE__\|__LINE__\)\>" transparent contains=NONE + +syn match crystalKeywordAsMethod "\<\%(alias\|begin\|case\|class\|def\|do\|end\)[?!]" transparent contains=NONE +syn match crystalKeywordAsMethod "\<\%(if\|ifdef\|module\|undef\|unless\|until\|while\)[?!]" transparent contains=NONE + +syn match crystalKeywordAsMethod "\%(\%(\.\@<!\.\)\|::\)\_s*\%(abort\|at_exit\|caller\|exit\)\>"		transparent contains=NONE +syn match crystalKeywordAsMethod "\%(\%(\.\@<!\.\)\|::\)\_s*\%(extend\|fork\|include\)\>"			transparent contains=NONE +syn match crystalKeywordAsMethod "\%(\%(\.\@<!\.\)\|::\)\_s*\%(loop\|private\|protected\)\>"		transparent contains=NONE +syn match crystalKeywordAsMethod "\%(\%(\.\@<!\.\)\|::\)\_s*\%(require\|raise\)\>"	transparent contains=NONE +syn match crystalKeywordAsMethod "\%(\%(\.\@<!\.\)\|::\)\_s*\%(typeof\|pointerof\|sizeof\|instance_sizeof\|\)\>"	transparent contains=NONE + +hi def link crystalClass			crystalDefine +hi def link crystalModule			crystalDefine +hi def link crystalStruct			crystalDefine +hi def link crystalLib			crystalDefine +hi def link crystalMacro			crystalDefine +hi def link crystalEnum			crystalDefine +hi def link crystalMethodExceptional	crystalDefine +hi def link crystalDefine			Define +hi def link crystalFunction		Function +hi def link crystalConditional		Conditional +hi def link crystalConditionalModifier	crystalConditional +hi def link crystalExceptional		crystalConditional +hi def link crystalRepeat			Repeat +hi def link crystalRepeatModifier		crystalRepeat +hi def link crystalOptionalDo		crystalRepeat +hi def link crystalControl			Statement +hi def link crystalInclude			Include +hi def link crystalRecord			Statement +hi def link crystalInteger			Number +hi def link crystalASCIICode		Character +hi def link crystalFloat			Float +hi def link crystalBoolean			Boolean +hi def link crystalException		Exception +if !exists('crystal_no_identifiers') +  hi def link crystalIdentifier		Identifier +else +  hi def link crystalIdentifier		NONE +endif +hi def link crystalClassVariable		crystalIdentifier +hi def link crystalConstant		Type +hi def link crystalGlobalVariable		crystalIdentifier +hi def link crystalBlockParameter		crystalIdentifier +hi def link crystalInstanceVariable	crystalIdentifier +hi def link crystalFreshVariable	crystalIdentifier +hi def link crystalPredefinedIdentifier	crystalIdentifier +hi def link crystalPredefinedConstant	crystalPredefinedIdentifier +hi def link crystalPredefinedVariable	crystalPredefinedIdentifier +hi def link crystalSymbol			Constant +hi def link crystalKeyword			Keyword +hi def link crystalOperator		Operator +hi def link crystalAccess			Statement +hi def link crystalAttribute		Statement +hi def link crystalPseudoVariable		Constant +hi def link crystalCharLiteral		Character + +hi def link crystalComment			Comment +hi def link crystalTodo			Todo + +hi def link crystalStringEscape		Special +hi def link crystalInterpolationDelimiter	Delimiter +hi def link crystalNoInterpolation		crystalString +hi def link crystalSharpBang		PreProc +hi def link crystalRegexpDelimiter		crystalStringDelimiter +hi def link crystalSymbolDelimiter		crystalStringDelimiter +hi def link crystalStringDelimiter		Delimiter +hi def link crystalHeredoc			crystalString +hi def link crystalString			String +hi def link crystalRegexpEscape		crystalRegexpSpecial +hi def link crystalRegexpQuantifier	crystalRegexpSpecial +hi def link crystalRegexpAnchor		crystalRegexpSpecial +hi def link crystalRegexpDot		crystalRegexpCharClass +hi def link crystalRegexpCharClass		crystalRegexpSpecial +hi def link crystalRegexpSpecial		Special +hi def link crystalRegexpComment		Comment +hi def link crystalRegexp			crystalString + +hi def link crystalLinkAttr		PreProc + +hi def link crystalMacro		PreProc + +hi def link crystalInvalidVariable		Error +hi def link crystalError			Error +hi def link crystalSpaceError		crystalError + +let b:current_syntax = 'crystal' + +" vim: nowrap sw=2 sts=2 ts=8 noet: + +endif | 
