1" This script tests a color scheme for some errors and lists potential errors.
2" Load the scheme and source this script, like this:
3"    :edit colors/desert.vim | :so colors/tools/check_colors.vim
4
5let s:save_cpo= &cpo
6set cpo&vim
7
8func! Test_check_colors()
9  let l:savedview = winsaveview()
10  call cursor(1,1)
11  let err = {}
12
13  " 1) Check g:colors_name is existing
14  if !search('\<\%(g:\)\?colors_name\>', 'cnW')
15    let err['colors_name'] = 'g:colors_name not set'
16  else
17    let err['colors_name'] = 'OK'
18  endif
19
20  " 2) Check for some well-defined highlighting groups
21  let hi_groups = [
22        \ 'ColorColumn',
23        \ 'Comment',
24        \ 'Conceal',
25        \ 'Constant',
26        \ 'Cursor',
27        \ 'CursorColumn',
28        \ 'CursorLine',
29        \ 'CursorLineNr',
30        \ 'DiffAdd',
31        \ 'DiffChange',
32        \ 'DiffDelete',
33        \ 'DiffText',
34        \ 'Directory',
35        \ 'EndOfBuffer',
36        \ 'Error',
37        \ 'ErrorMsg',
38        \ 'FoldColumn',
39        \ 'Folded',
40        \ 'Identifier',
41        \ 'Ignore',
42        \ 'IncSearch',
43        \ 'LineNr',
44        \ 'MatchParen',
45        \ 'ModeMsg',
46        \ 'MoreMsg',
47        \ 'NonText',
48        \ 'Normal',
49        \ 'Pmenu',
50        \ 'PmenuSbar',
51        \ 'PmenuSel',
52        \ 'PmenuThumb',
53        \ 'PreProc',
54        \ 'Question',
55        \ 'QuickFixLine',
56        \ 'Search',
57        \ 'SignColumn',
58        \ 'Special',
59        \ 'SpecialKey',
60        \ 'SpellBad',
61        \ 'SpellCap',
62        \ 'SpellLocal',
63        \ 'SpellRare',
64        \ 'Statement',
65        \ 'StatusLine',
66        \ 'StatusLineNC',
67        \ 'StatusLineTerm',
68        \ 'StatusLineTermNC',
69        \ 'TabLine',
70        \ 'TabLineFill',
71        \ 'TabLineSel',
72        \ 'Title',
73        \ 'Todo',
74        \ 'ToolbarButton',
75        \ 'ToolbarLine',
76        \ 'Type',
77        \ 'Underlined',
78        \ 'VertSplit',
79        \ 'Visual',
80        \ 'VisualNOS',
81        \ 'WarningMsg',
82        \ 'WildMenu',
83        \ ]
84  let groups = {}
85  for group in hi_groups
86    if search('\c@suppress\s\+\<' .. group .. '\>', 'cnW')
87      " skip check, if the script contains a line like
88      " @suppress Visual:
89      continue
90    endif
91    if search('hi\%[ghlight]!\= \+link \+' .. group, 'cnW') " Linked group
92      continue
93    endif
94    if !search('hi\%[ghlight] \+\<' .. group .. '\>', 'cnW')
95      let groups[group] = 'No highlight definition for ' .. group
96      continue
97    endif
98    if !search('hi\%[ghlight] \+\<' .. group .. '\>.*[bf]g=', 'cnW')
99      let groups[group] = 'Missing foreground or background color for ' .. group
100      continue
101    endif
102    if search('hi\%[ghlight] \+\<' .. group .. '\>.*guibg=', 'cnW') &&
103        \ !search('hi\%[ghlight] \+\<' .. group .. '\>.*ctermbg=', 'cnW')
104	\ && group != 'Cursor'
105      let groups[group] = 'Missing bg terminal color for ' .. group
106      continue
107    endif
108    if !search('hi\%[ghlight] \+\<' .. group .. '\>.*guifg=', 'cnW')
109	  \ && group !~ '^Diff'
110      let groups[group] = 'Missing guifg definition for ' .. group
111      continue
112    endif
113    if !search('hi\%[ghlight] \+\<' .. group .. '\>.*ctermfg=', 'cnW')
114	  \ && group !~ '^Diff'
115	  \ && group != 'Cursor'
116      let groups[group] = 'Missing ctermfg definition for ' .. group
117      continue
118    endif
119    " do not check for background colors, they could be intentionally left out
120    call cursor(1,1)
121  endfor
122  let err['highlight'] = groups
123
124  " 3) Check, that it does not set background highlighting
125  " Doesn't ':hi Normal ctermfg=253 ctermfg=233' also set the background sometimes?
126  let bg_set = '\(set\?\|setl\(ocal\)\?\) .*\(background\|bg\)=\(dark\|light\)'
127  let bg_let = 'let \%([&]\%([lg]:\)\?\)\%(background\|bg\)\s*=\s*\([''"]\?\)\w\+\1'
128  let bg_pat = '\%(' .. bg_set .. '\|' .. bg_let .. '\)'
129  let line = search(bg_pat, 'cnW')
130  if search(bg_pat, 'cnW')
131    exe line
132    if search('hi \U\w\+\s\+\S', 'cbnW')
133      let err['background'] = 'Should not set background option after :hi statement'
134    endif
135  else
136    let err['background'] = 'OK'
137  endif
138  call cursor(1,1)
139
140  " 4) Check, that t_Co is checked
141  let pat = '[&]t_Co\s*[<>=]=\?\s*\d\+'
142  if !search(pat, 'ncW')
143    let err['t_Co'] = 'Does not check terminal for capable colors'
144  endif
145
146  " 5) Initializes correctly, e.g. should have a section like
147  " hi clear
148  " if exists("syntax_on")
149  " syntax reset
150  " endif
151  let pat = 'hi\%[ghlight]\s*clear\n\s*if\s*exists(\([''"]\)syntax_on\1)\n\s*syn\%[tax]\s*reset\n\s*endif'
152  if !search(pat, 'cnW')
153    let err['init'] = 'No initialization'
154  endif
155
156  " 6) Does not use :syn on
157  if search('syn\%[tax]\s\+on', 'cnW')
158    let err['background'] = 'Should not issue :syn on'
159  endif
160
161  " 7) Does not define filetype specific groups like vimCommand, htmlTag,
162  let hi_groups = filter(getcompletion('', 'filetype'), { _,v -> v !~# '\%[no]syn\%(color\|load\|tax\)' })
163  let ft_groups = []
164  " let group = '\%('.join(hi_groups, '\|').'\)' " More efficient than a for loop, but less informative
165  for group in hi_groups
166    let pat = '\Chi\%[ghlight]!\= *\%[link] \+\zs' .. group .. '\w\+\>\ze \+.' " Skips `hi clear`
167    if search(pat, 'cW')
168      call add(ft_groups, matchstr(getline('.'), pat))
169    endif
170    call cursor(1,1)
171  endfor
172  if !empty(ft_groups)
173    let err['filetype'] = get(err, 'filetype', 'Should not define: ') . join(uniq(sort(ft_groups)))
174  endif
175
176  " 8) Were debugPC and debugBreakpoint defined?
177  for group in ['debugPC', 'debugBreakpoint']
178    let pat = '\Chi\%[ghlight]!\= *\%[link] \+\zs' .. group .. '\>'
179    if search(pat, 'cnW')
180      let line = search(pat, 'cW')
181      let err['filetype'] = get(err, 'filetype', 'Should not define: ') . matchstr(getline('.'), pat). ' '
182    endif
183    call cursor(1,1)
184  endfor
185
186  " 9) Normal should be defined first, not use reverse, fg or bg
187  call cursor(1,1)
188  let pat = 'hi\%[ghlight] \+\%(link\|clear\)\@!\w\+\>'
189  call search(pat, 'cW') " Look for the first hi def, skipping `hi link` and `hi clear`
190  if getline('.') !~# '\m\<Normal\>'
191    let err['highlight']['Normal'] = 'Should be defined first'
192  elseif getline('.') =~# '\m\%(=\%(fg\|bg\)\)'
193    let err['highlight']['Normal'] = "Should not use 'fg' or 'bg'"
194  elseif getline('.') =~# '\m=\%(inv\|rev\)erse'
195    let err['highlight']['Normal'] = 'Should not use reverse mode'
196  endif
197
198  call winrestview(l:savedview)
199  let g:err = err
200
201  " print Result
202  call Result(err)
203endfu
204
205fu! Result(err)
206  let do_groups = 0
207  echohl Title|echomsg "---------------"|echohl Normal
208  for key in sort(keys(a:err))
209    if key is# 'highlight'
210      let do_groups = !empty(a:err[key])
211      continue
212    else
213      if a:err[key] !~ 'OK'
214        echohl Title
215      endif
216      echomsg printf("%15s: %s", key, a:err[key])
217      echohl Normal
218    endif
219  endfor
220  echohl Title|echomsg "---------------"|echohl Normal
221  if do_groups
222    echohl Title | echomsg "Groups" | echohl Normal
223    for v1 in sort(keys(a:err['highlight']))
224      echomsg printf("%25s: %s", v1, a:err['highlight'][v1])
225    endfor
226  endif
227endfu
228
229call Test_check_colors()
230
231let &cpo = s:save_cpo
232unlet s:save_cpo
233