├── screenshots └── video-01.gif ├── ftplugin ├── concourse.vim └── concourse │ └── tagbar.vim ├── ftdetect └── concourse.vim ├── README.md ├── LICENSE ├── autoload └── concourse.vim ├── plugin └── concourse.vim └── syntax └── concourse.vim /screenshots/video-01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luan/vim-concourse/HEAD/screenshots/video-01.gif -------------------------------------------------------------------------------- /ftplugin/concourse.vim: -------------------------------------------------------------------------------- 1 | " Vim indent file 2 | " Language: Concourse Flavored YAML 3 | " Author: Luan Santos 4 | " URL: https://github.com/luan/vim-concourse 5 | 6 | setlocal autoindent sw=2 ts=2 expandtab foldmethod=syntax 7 | setlocal indentexpr= 8 | setlocal norelativenumber nocursorline 9 | setlocal commentstring=#\ %s 10 | 11 | if globpath(&rtp, 'plugin/rainbow.vim') != "" 12 | silent! RainbowToggleOff 13 | endif 14 | 15 | if !exists("g:concourse_flytags_bin") 16 | let g:concourse_flytags_bin = "flytags" 17 | endif 18 | 19 | " vim:set sw=2: 20 | -------------------------------------------------------------------------------- /ftdetect/concourse.vim: -------------------------------------------------------------------------------- 1 | " Determine if normal YAML or Concourse YAML 2 | " Language: Concourse Flavored YAML 3 | " Author: Luan Santos 4 | " URL: https://github.com/luan/vim-concourse 5 | 6 | autocmd BufNewFile,BufRead *.yml,*.yaml call s:SelectConcourse() 7 | 8 | fun! s:SelectConcourse() 9 | " Bail out if 'filetype' is already set to "concourse". 10 | if index(split(&ft, '\.'), 'concourse') != -1 11 | return 12 | endif 13 | 14 | let lines = join(getline('1', '$'), "\n") 15 | 16 | let grep = 'grep -E' 17 | if executable('ag') 18 | let grep = 'ag' 19 | endif 20 | 21 | let fp = expand(":p") 22 | let concourseRegex = '^(resources|jobs):' 23 | let concourseKeyCount = system('cat ' . fp . ' | ' . grep . ' "' . concourseRegex . '" | wc -l') 24 | 25 | if concourseKeyCount =~# '2' 26 | execute 'set filetype=concourse' 27 | else 28 | endif 29 | endfun 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vim Concourse 2 | 3 | Syntax detection and highlighting for [concourse](http://concourse.ci) pipelines. 4 | 5 | ## Features 6 | 7 | * Custom highlighting aware of concourse terminology 8 | * Folding 9 | * Auto detecting concourse files 10 | * [Tagbar](https://raw.githubusercontent.com/luan/flytags/master/screenshots/screenshot-01.png) integration 11 | * Automatic [ctags](http://ctags.sourceforge.net/) generation for use with `` on jobs and resources 12 | 13 | ## Settings 14 | 15 | By default vim-concourse will generate ctags for your concourse pipelines using [flytags](http://github.com/luan/flytags) on save. 16 | You can disable this functionality with: 17 | ```vim 18 | let g:concourse_tags_autosave = 0 19 | ``` 20 | 21 | ## In Action 22 | 23 | ![vim-concourse video](https://raw.githubusercontent.com/luan/vim-concourse/master/screenshots/video-01.gif) 24 | 25 | ## Credits 26 | 27 | * [vim-go](https://github.com/fatih/vim-go), where from a lot of the plugin code was based 28 | * [concourse](http://concourse.ci), the concourse ci system 29 | 30 | ## License 31 | 32 | The BSD 3-Clause License - see [LICENSE](LICENSE) for more details. 33 | 34 | Uses the vim-go LICENSE and copyrights since a lot of the code was re-used. 35 | 36 | -------------------------------------------------------------------------------- /ftplugin/concourse/tagbar.vim: -------------------------------------------------------------------------------- 1 | " Check if tagbar is installed under plugins or is directly under rtp 2 | " this covers pathogen + Vundle/Bundle 3 | " 4 | " Also make sure the ctags command exists 5 | " 6 | if !executable('ctags') 7 | finish 8 | elseif globpath(&rtp, 'plugin/tagbar.vim') == "" 9 | finish 10 | endif 11 | 12 | function! s:SetTagbar() 13 | let bin_path = concourse#CheckBinPath(g:concourse_flytags_bin) 14 | if empty(bin_path) 15 | return 16 | endif 17 | 18 | if !exists("g:tagbar_type_concourse") 19 | let g:tagbar_type_concourse = { 20 | \ 'ctagstype' : 'concourse', 21 | \ 'kinds' : [ 22 | \ 'p:primitives', 23 | \ 't:resource_types', 24 | \ 'g:groups', 25 | \ 'r:resources', 26 | \ 'i:inputs', 27 | \ 'k:tasks', 28 | \ 'o:outputs', 29 | \ 'j:jobs', 30 | \ ], 31 | \ 'sro' : '.', 32 | \ 'kind2scope' : { 33 | \ 'p' : 'ptype', 34 | \ 'j' : 'stype' 35 | \ }, 36 | \ 'scope2kind' : { 37 | \ 'ptype' : 'p', 38 | \ 'stype' : 'j' 39 | \ }, 40 | \ 'ctagsbin' : expand(bin_path), 41 | \ 'ctagsargs' : '-sort -silent' 42 | \ } 43 | endif 44 | endfunction 45 | 46 | call s:SetTagbar() 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Luan Santos 2 | Copyright (c) 2015, Fatih Arslan 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of vim-go nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | -------------------------------------------------------------------------------- /autoload/concourse.vim: -------------------------------------------------------------------------------- 1 | " IsWin returns 1 if current OS is Windows or 0 otherwise 2 | function! concourse#IsWin() 3 | let win = ['win16', 'win32', 'win64', 'win95'] 4 | for w in win 5 | if (has(w)) 6 | return 1 7 | endif 8 | endfor 9 | 10 | return 0 11 | endfunction 12 | 13 | " PathSep returns the appropriate OS specific path separator. 14 | function! concourse#PathSep() 15 | if concourse#IsWin() 16 | return '\' 17 | endif 18 | return '/' 19 | endfunction 20 | 21 | " Default returns the default GOPATH. If there is a single GOPATH it returns 22 | " it. For multiple GOPATHS separated with a the OS specific separator, only 23 | " the first one is returned 24 | function! concourse#DefaultPath() 25 | let go_paths = split($GOPATH, concourse#PathListSep()) 26 | 27 | if len(go_paths) == 1 28 | return $GOPATH 29 | endif 30 | 31 | return go_paths[0] 32 | endfunction 33 | 34 | " PathListSep returns the appropriate OS specific path list separator. 35 | function! concourse#PathListSep() 36 | if concourse#IsWin() 37 | return ";" 38 | endif 39 | return ":" 40 | endfunction 41 | 42 | " BinPath returns the binary path of installed tools. 43 | function! concourse#BinPath() 44 | let bin_path = "" 45 | 46 | " check if our global custom path is set, if not check if $GOBIN is set so 47 | " we can use it, otherwise use $GOPATH + '/bin' 48 | if exists("g:go_bin_path") 49 | let bin_path = g:go_bin_path 50 | elseif $GOBIN != "" 51 | let bin_path = $GOBIN 52 | elseif $GOPATH != "" 53 | let bin_path = expand(concourse#DefaultPath() . "/bin/") 54 | else 55 | " could not find anything 56 | endif 57 | 58 | return bin_path 59 | endfunction 60 | 61 | " CheckBinPath checks whether the given binary exists or not and returns the 62 | " path of the binary. It returns an empty string doesn't exists. 63 | function! concourse#CheckBinPath(binpath) 64 | " remove whitespaces if user applied something like 'goimports ' 65 | let binpath = substitute(a:binpath, '^\s*\(.\{-}\)\s*$', '\1', '') 66 | 67 | " if it's in PATH just return it 68 | if executable(binpath) 69 | return binpath 70 | endif 71 | 72 | " just get the basename 73 | let basename = fnamemodify(binpath, ":t") 74 | 75 | " check if we have an appropriate bin_path 76 | let concourse_bin_path = concourse#BinPath() 77 | if empty(concourse_bin_path) 78 | echo "vim-concourse: could not find '" . basename . "'. Run :ConcourseInstallBinaries to fix it." 79 | return "" 80 | endif 81 | 82 | " append our GOBIN and GOPATH paths and be sure they can be found there... 83 | " let us search in our GOBIN and GOPATH paths 84 | let old_path = $PATH 85 | let $PATH = $PATH . concourse#PathListSep() . concourse_bin_path 86 | 87 | if !executable(basename) 88 | echo "vim-concourse: could not find '" . basename . "'. Run :ConcourseInstallBinaries to fix it." 89 | " restore back! 90 | let $PATH = old_path 91 | return "" 92 | endif 93 | 94 | let $PATH = old_path 95 | 96 | return concourse_bin_path . concourse#PathSep() . basename 97 | endfunction 98 | -------------------------------------------------------------------------------- /plugin/concourse.vim: -------------------------------------------------------------------------------- 1 | " install necessary Concourse tools 2 | if exists("g:concourse_loaded_install") 3 | finish 4 | endif 5 | let g:concourse_loaded_install = 1 6 | 7 | 8 | " these packages are used by vim-concourse and can be automatically installed if 9 | " needed by the user with GoInstallBinaries 10 | let s:packages = [ 11 | \ "github.com/luan/flytags", 12 | \ ] 13 | 14 | " These commands are available on any filetypes 15 | command! ConcourseInstallBinaries call s:ConcourseInstallBinaries(-1) 16 | command! ConcourseUpdateBinaries call s:ConcourseInstallBinaries(1) 17 | 18 | " ConcourseInstallBinaries downloads and install all necessary binaries stated 19 | " in the packages variable. It uses by default $GOBIN or $GOPATH/bin as the 20 | " binary target install directory. ConcourseInstallBinaries doesn't install 21 | " binaries if they exist, to update current binaries pass 1 to the argument. 22 | function! s:ConcourseInstallBinaries(updateBinaries) 23 | if $GOPATH == "" 24 | echohl Error 25 | echomsg "vim-concourse: $GOPATH is not set" 26 | echohl None 27 | return 28 | endif 29 | 30 | let err = s:CheckBinaries() 31 | if err != 0 32 | return 33 | endif 34 | 35 | let concourse_bin_path = concourse#BinPath() 36 | 37 | " change $GOBIN so go get can automatically install to it 38 | let $GOBIN = concourse_bin_path 39 | 40 | " old_path is used to restore users own path 41 | let old_path = $PATH 42 | 43 | " vim's executable path is looking in PATH so add our go_bin path to it 44 | let $PATH = $PATH . concourse#PathListSep() . concourse_bin_path 45 | 46 | " when shellslash is set on MS-* systems, shellescape puts single quotes 47 | " around the output string. cmd on Windows does not handle single quotes 48 | " correctly. Unsetting shellslash forces shellescape to use double quotes 49 | " instead. 50 | let resetshellslash = 0 51 | if has('win32') && &shellslash 52 | let resetshellslash = 1 53 | set noshellslash 54 | endif 55 | 56 | let cmd = "go get -u -v " 57 | 58 | let s:go_version = matchstr(system("go version"), '\d.\d.\d') 59 | 60 | " https://github.com/golang/go/issues/10791 61 | if s:go_version > "1.4.0" && s:go_version < "1.5.0" 62 | let cmd .= "-f " 63 | endif 64 | 65 | for pkg in s:packages 66 | let basename = fnamemodify(pkg, ":t") 67 | let binname = "go_" . basename . "_bin" 68 | 69 | let bin = basename 70 | if exists("g:{binname}") 71 | let bin = g:{binname} 72 | endif 73 | 74 | if !executable(bin) || a:updateBinaries == 1 75 | if a:updateBinaries == 1 76 | echo "vim-concourse: Updating ". basename .". Reinstalling ". pkg . " to folder " . concourse_bin_path 77 | else 78 | echo "vim-concourse: ". basename ." not found. Installing ". pkg . " to folder " . concourse_bin_path 79 | endif 80 | 81 | 82 | let out = system(cmd . shellescape(pkg)) 83 | if v:shell_error 84 | echo "Error installing ". pkg . ": " . out 85 | endif 86 | endif 87 | endfor 88 | 89 | " restore back! 90 | let $PATH = old_path 91 | if resetshellslash 92 | set shellslash 93 | endif 94 | endfunction 95 | 96 | " CheckBinaries checks if the necessary binaries to install the Go tool 97 | " commands are available. 98 | function! s:CheckBinaries() 99 | if !executable('go') 100 | echohl Error | echomsg "vim-concourse: go executable not found." | echohl None 101 | return -1 102 | endif 103 | 104 | if !executable('git') 105 | echohl Error | echomsg "vim-concourse: git executable not found." | echohl None 106 | return -1 107 | endif 108 | endfunction 109 | 110 | " FlyTags generates ctags for the current buffer 111 | function! s:FlyTags() 112 | if &filetype != "concourse" 113 | return 114 | endif 115 | 116 | let bin_path = concourse#CheckBinPath(g:concourse_flytags_bin) 117 | if empty(bin_path) 118 | return 119 | endif 120 | call system(expand(bin_path) . " -f " . &tags . " " . expand("%:p")) 121 | endfunction 122 | 123 | " Autocommands 124 | " ============================================================================ 125 | " 126 | augroup vim-concourse 127 | autocmd! 128 | 129 | " run gometalinter on save 130 | if get(g:, "concourse_tags_autosave", 1) 131 | autocmd FileType concourse 132 | \ let b:tagspath = tempname() | 133 | \ exec 'setlocal tags='.b:tagspath | 134 | \ call s:FlyTags() 135 | autocmd BufWritePost *.yml call s:FlyTags() 136 | endif 137 | augroup END 138 | 139 | 140 | " vim:ts=4:sw=4:et 141 | -------------------------------------------------------------------------------- /syntax/concourse.vim: -------------------------------------------------------------------------------- 1 | " Vim syntaxtax/macro file 2 | " Language: Concourse Flavored YAML 3 | " Author: Luan Santos 4 | " URL: https://github.com/luan/vim-concourse 5 | 6 | if version < 600 7 | syntaxtax clear 8 | endif 9 | 10 | syntax sync fromstart 11 | 12 | syntax match yamlBlock "[\[\]\{\}\|\>]" 13 | syntax match yamlOperator "[?^+-]\|=>" 14 | syntax match yamlDelimiter /\v(^[^:]*)@<=:/ 15 | syntax match yamlDelimiter /\v^\s*- / 16 | 17 | syntax match yamlNumber /\v[{, ][_0-9]+((\.[_0-9]*)(e[+-][_0-9]+)?)?\ze($|[, \t])/ 18 | syntax match yamlNumber /\v([+\-]?\.inf|\.NaN)/ 19 | 20 | syntax region yamlComment start="\v(^| )\#" end="$" 21 | syntax match yamlIndicator "#YAML:\S\+" 22 | 23 | syntax region yamlString start="'" end="'" skip="\\'" 24 | syntax region yamlString start='"' end='"' skip='\\"' contains=yamlEscape 25 | syntax match yamlEscape +\\[abfnrtv'"\\]+ contained 26 | syntax match yamlEscape "\\\o\o\=\o\=" contained 27 | syntax match yamlEscape "\\x\x\+" contained 28 | 29 | syntax region concourseInterpolation matchgroup=PreCondit start="{{" end="}}" 30 | 31 | syntax match yamlType "!\S\+" 32 | 33 | syntax keyword yamlConstant NULL Null null NONE None none NIL Nil nil 34 | syntax keyword yamlConstant TRUE True true YES Yes yes ON On on 35 | syntax keyword yamlConstant FALSE False false NO No no OFF Off off 36 | syntax match yamlConstant /\v( |\{ ?)@<=\~\ze( ?\}|, |$)/ 37 | 38 | syntax match yamlKey /\v[0-9A-Za-z_-]+\ze:( |$)/ 39 | syntax match yamlAnchor /\v(: )@<=\&\S+/ 40 | syntax match yamlAlias /\v(: )@<=\*\S+/ 41 | 42 | hi link yamlConstant Keyword 43 | hi link yamlNumber Keyword 44 | hi link yamlIndicator PreCondit 45 | hi link yamlAnchor Function 46 | hi link yamlAlias Function 47 | hi link yamlKey Identifier 48 | hi link yamlType Type 49 | 50 | hi link yamlComment Comment 51 | hi link yamlBlock Operator 52 | hi link yamlOperator Operator 53 | hi link yamlDelimiter Delimiter 54 | hi link yamlString String 55 | hi link yamlEscape Special 56 | 57 | let primitives = ['groups', 'jobs', 'resources', 'resource_types'] 58 | 59 | let resourceOptions = ['name', 'type', 'source', 'check_every'] 60 | let resourceTypeOptions = ['name', 'type', 'source'] 61 | let jobOptions = ['name', 'serial', 'serial_groups', 'max_in_flight', 'public', 'disable_manual_trigger', 'plan'] 62 | let groupOptions = ['name', 'jobs', 'resources'] 63 | let concourseOptions = resourceOptions + resourceTypeOptions + jobOptions + groupOptions 64 | 65 | let stepTypes = ['get', 'put', 'task'] 66 | let stepGroups = ['aggregate', 'timeout', 'do', 'on_success', 'on_failure', 'ensure', 'try'] 67 | let stepOptions = ['tags', 'attempts', 'resource', 'trigger', 'passed', 68 | \ 'file', 'privileged', 'input_mapping', 'output_mapping', 'version'] 69 | let stepOptionRegions = ['get_params', 'params', 'config'] 70 | 71 | let stepKeys = stepTypes + stepGroups + stepOptions + stepOptionRegions 72 | 73 | let primitivesRegex = join(primitives, "|") 74 | 75 | syntax region concoursePrimitiveGroup matchgroup=concourseRootKey start=/\v^groups\ze:/ skip=/\v^([- ].*)?$/ excludenl end=/^/ contains=yamlDelimiter,concoursePrimitive fold keepend 76 | syntax region concoursePrimitiveGroup matchgroup=concourseRootKey start=/\v^jobs\ze:/ skip=/\v^([- ].*)?$/ excludenl end=/^/ contains=yamlDelimiter,concoursePrimitive fold keepend 77 | syntax region concoursePrimitiveGroup matchgroup=concourseRootKey start=/\v^resources\ze:/ skip=/\v^([- ].*)?$/ excludenl end=/^/ contains=yamlDelimiter,concoursePrimitive fold keepend 78 | syntax region concoursePrimitiveGroup matchgroup=concourseRootKey start=/\v^resource_types\ze:/ skip=/\v^([- ].*)?$/ excludenl end=/^/ contains=yamlDelimiter,concoursePrimitive fold keepend 79 | 80 | if has('nvim') 81 | syntax match concourseName /\v(name: )@<=.*/ contained 82 | endif 83 | 84 | syntax region concoursePrimitive start=/\v^\z(\s*)-/ skip=/\v\\./ excludenl end=/\v\ze\n?^\z1-/ 85 | \ contained 86 | \ contains=ALL 87 | \ fold transparent 88 | 89 | for concourseOption in concourseOptions 90 | execute 'syntax match concourseOptions /\v\s*-?\s*' . concourseOption . '\ze:/ contained' 91 | endfor 92 | 93 | 94 | let stepsRegex = join(stepTypes + stepGroups, "|") 95 | execute 'syntax region concourseStep start=/\v^\z(\s*)- (' . stepsRegex . ')\ze:/ skip=/\v^\z1 .*$/ excludenl end=/^/ contained contains=ALL fold transparent' 96 | 97 | syntax region concoursePlan matchgroup=concourseKeywords start=/\v^\z(\s*)plan\ze:/ skip=/\v^\z1[-].*$/ excludenl end=/^/ 98 | \ contained 99 | \ contains=concourseOptionRegion,concourseStep,yamlConstant,yamlIndicator,yamlAnchor,yamlAlias,yamlKey,yamlType,yamlComment,yamlBlock,yamlOperator,yamlDelimiter,yamlString,yamlEscape 100 | \ fold transparent 101 | 102 | for key in stepKeys 103 | execute 'syntax match concourseSteps /\v\s*-?\s*' . key . '\ze:/ contained' 104 | endfor 105 | 106 | let stepsOptionRegionsRegex = join(stepOptionRegions, "|") 107 | execute 'syntax region concourseOptionRegion matchgroup=concourseSteps start=/\v^\z(\s*)(' . stepsOptionRegionsRegex . ')\ze:/ skip=/\v^\z1 .*$/ end=/^/ contained contains=TOP fold transparent' 108 | execute 'syntax region concourseOptionRegion matchgroup=concourseSteps start=/\v^\z(\s*)(' . stepsOptionRegionsRegex . ')\ze: \{/ excludenl end=/\v\ze}/ contained contains=TOP fold transparent' 109 | 110 | " Setupt the hilighting links 111 | 112 | hi link concourseKeywords Function 113 | hi link concourseOptions Function 114 | hi link concourseName Type 115 | hi link concourseSteps Type 116 | hi link concourseRootKey Keyword 117 | hi link concourseInterpolation String 118 | hi link concoursePlan Function 119 | --------------------------------------------------------------------------------