" ColorschemeDegrade: Degrade gvim colorschemes to be suitable for a terminal " Maintainer: Matthew Wozniski (mjw@drexel.edu) " Date: Sun, 21 Oct 2007 21:04:33 -0400 " Version: 0.2 " History: TODO(History Link) " Installation: Drop this script into ~/.vim/plugin. " Whenever you change colorschemes using the :colorscheme command, this script " will be executed. If you're running in 256 color terminal or an 88 color " terminal, as reported by the command ":echo &t_Co" it will take the colors " that the scheme specified for use in the gui and use an approximation " algorithm to try to gracefully degrade them to the closest color available. " If you are running in a gui or if t_Co is reported as less than 88 colors, " no changes are made. " Abort if running in vi-compatible mode or the user doesn't want or need us. if &cp || has("gui_running") || ! has("gui") || exists('g:colorschemedegrade_loaded') if &cp && &verbose echomsg "Not loading ColorschemeDegrade in compatible mode." endif if has('gui_running') && &verbose echomsg "Not loading ColorschemeDegrade in gui mode." endif if ! has('gui') && &verbose echomsg "Unfortunately, ColorschemeDegrade needs gui support. Not loading." endif finish endif " A local copy of rgb.txt must be included, since I can't count on it being in " a standard location. But, we won't load it unless we need it. let s:rgb = {} let g:colorschemedegrade_loaded = 1 let s:savecpo = &cpo set cpo&vim " Script-local variables {{{1 " Script-local variables defining the rgb vals on a 256-color cube {{{2 " Every possible 256-color cube color is made up of 3 rgb values, all out of " this table. let s:vals_greys_256 = [ 0, 8, 18, 28, 38, \ 48, 58, 68, 78, 88, \ 95, 98, 108, 118, 128, \ 135, 138, 148, 158, 168, \ 175, 178, 188, 198, 208, \ 215, 218, 228, 238, 255 ] " Many of those colors can only be used for a grey (r == g == b). This subset " can be mix-and-matched. let s:vals_color_256 = [ 0, 95, 135, 175, 215, 255 ] " This table holds the midpoints between each of the possible grey values, as " well as one extra element higher than all, to be used in the approximation " algorithm. let s:mids_greys_256 = [ 4, 13, 23, 33, 43, \ 53, 63, 73, 83, 91, \ 96, 103, 113, 123, 131, \ 136, 143, 153, 163, 171, \ 176, 183, 193, 203, 211, \ 216, 223, 233, 246, 256 ] " This table is the same, only for the non-grey midpoints. let s:mids_color_256 = [ 48, 115, 155, 195, 235, 256 ] " Script-local variables defining the rgb vals on a 88-color cube {{{2 " Every possible 88-color cube color is made up of 3 rgb values, all out of " this table. let s:vals_greys_88 = [ 0, 46, 92, 115, 139, 162, \ 185, 205, 208, 231, 255 ] " Many of those colors can only be used for a grey (r == g == b). This subset " can be mix-and-matched. let s:vals_color_88 = [ 0, 139, 205, 255 ] " This table holds the midpoints between each of the possible grey values, as " well as one extra element higher than all, to be used in the approximation " algorithm. let s:mids_greys_88 = [ 23, 69, 103, 127, 150, 173, \ 195, 206, 219, 243, 256 ] " This table is the same, only for the non-grey midpoints. let s:mids_color_88 = [ 69, 172, 230, 256 ] " Function definitions {{{1 " Given 3 hex strings rr, gg, bb, return the closest color cube number. function! s:FindClosestCode(h1,h2,h3) let d1 = str2nr(a:h1, 16) let d2 = str2nr(a:h2, 16) let d3 = str2nr(a:h3, 16) let r = s:FindClosest(d1, s:vals_greys_{&t_Co}, s:mids_greys_{&t_Co}) let g = s:FindClosest(d2, s:vals_greys_{&t_Co}, s:mids_greys_{&t_Co}) let b = s:FindClosest(d3, s:vals_greys_{&t_Co}, s:mids_greys_{&t_Co}) if(r == g && g == b) return s:GreyComponentTo{&t_Co}Cube(r) else let r = s:FindClosest(d1, s:vals_color_{&t_Co}, s:mids_color_{&t_Co}) let g = s:FindClosest(d2, s:vals_color_{&t_Co}, s:mids_color_{&t_Co}) let b = s:FindClosest(d3, s:vals_color_{&t_Co}, s:mids_color_{&t_Co}) return s:RGBComponentsTo{&t_Co}Cube(r, g, b) endif endfunction " Given a number, an array of elements, and an array of midpts, find the index " of the least midpt that the number is strictly less than, and return the " corresponding element. function! s:FindClosest(num, elems, midpts) for i in range(len(a:elems)) if ( a:num < a:midpts[i] ) return a:elems[i] endif endfor endfunction " Expects a decimal value 'x' between 0 and 255, inclusive " Returns a 256-color colorcube number for the color at RGB=x,x,x function! s:GreyComponentTo256Cube(num) if(a:num % 10 == 8) return 232 + (a:num - 8) / 10 else " Not in the greyscale ramp, so we can use our normal processing return s:RGBComponentsTo256Cube(a:num, a:num, a:num) endif endfunction " Expects a decimal value 'x' between 0 and 255, inclusive " Returns an 88-color colorcube number for the color at RGB=x,x,x function! s:GreyComponentTo88Cube(num) if a:num == 46 return 80 elseif a:num == 92 return 81 elseif a:num == 115 return 82 elseif a:num == 139 return 83 elseif a:num == 162 return 84 elseif a:num == 185 return 85 elseif a:num == 208 return 86 elseif a:num == 231 return 87 else " Not in the greyscale ramp, so we can use our normal processing return s:RGBComponentsTo88Cube(a:num, a:num, a:num) endif endfunction " Expects 3 decimal values 'r', 'g', and 'b', each between 0 and 255 inclusive. " Returns a 256-color colorcube number for the color at RGB=r,g,b. " Will not use the greyscale ramp. function! s:RGBComponentsTo256Cube(r,g,b) let rc = index(s:vals_color_256, a:r) let gc = index(s:vals_color_256, a:g) let bc = index(s:vals_color_256, a:b) return (rc * 36 + gc * 6 + bc + 16) endfunction " Expects 3 decimal values 'r', 'g', and 'b', each between 0 and 255 inclusive. " Returns a 88-color colorcube number for the color at RGB=r,g,b. " Will not use the greyscale ramp. function! s:RGBComponentsTo88Cube(r,g,b) let rc = index(s:vals_color_88, a:r) let gc = index(s:vals_color_88, a:g) let bc = index(s:vals_color_88, a:b) return (rc * 16 + gc * 4 + bc + 16) endfunction " Check if the provided value is found in "g:colorschemedegrade_ignore", which " may either be a list or a comma or space separated string. If the variable " "g:colorschemedegrade_ignore" is not present, the default is "bold italic" function! s:ignoring(attr) if !exists("g:colorschemedegrade_ignore") let ignore = [ 'bold', 'italic' ] elseif type(g:colorschemedegrade_ignore) == type("") let ignore = split(g:colorschemedegrade_ignore, '[ ,]') elseif type(g:colorschemedegrade_ignore) == type([]) let ignore = g:colorschemedegrade_ignore else return 0 endif return index(ignore, a:attr) != -1 endfunction " Sets some settings, calls s:ColorschemeDegradeImpl to handle actually " degrading the colorscheme, then restores the settings. This wrapper " should make sure that we don't accidentally recurse, and that settings are " restored properly even if something throws. function! s:ColorschemeDegrade() if g:colors_name =~ ".*-rgb" return endif let saveei = &ei set ei+=ColorScheme if exists("g:colors_name") let colors_name = g:colors_name unlet g:colors_name endif let savelz = &lz set lz let rv = -1 try let rv = s:ColorschemeDegradeImpl() catch let ex = v:exception endtry let &lz = savelz if exists("colors_name") let g:colors_name = colors_name endif let &ei = saveei if exists("ex") echoerr 'ColorschemeDegrade failed: ' . substitute(ex, '.\{-}:', '', '') endif return rv endfunction " For every highlight group, sets the cterm values to the best approximation " of the gui values possible given the value of &t_Co. function! s:ColorschemeDegradeImpl() if has('gui_running') || (&t_Co != 256 && &t_Co != 88) return endif let g:highlights = "" redir => g:highlights " Normal must be set 1st for ctermfg=bg, etc, and resetting it doesn't hurt silent highlight Normal silent highlight redir END let hilines = split(g:highlights, '\n') " hilines[0] is Normal. If that doesn't use gui colors, we should probably " just give up. That way we don't muck up an already 256/88 color scheme. if hilines[0] !~ 'gui[fb]g' if &verbose echomsg "Not degrading colorscheme; doesn't set Normal group gui colors" endif return endif call filter(hilines, 'v:val !~ "links to" && v:val !~ "cleared"') let i = 0 let end = len(hilines) while i < end let line = hilines[i] let i += 1 while i < end && hilines[i] !~ '\' let line .= hilines[i] let i += 1 endwhile let line = substitute(line, '\', '', '') let line = substitute(line, '\8[35];' \ && exists('g:colorschemedegrade_changecube') \ && g:colorschemedegrade_changecube if !exists("s:lastcubepos") let s:lastcubepos="15" let s:colors = {} endif let tisave = &t_ti let tesave = &t_te set t_ti= t_te= if has_key(s:colors, tolower(val)) let nr = s:colors[tolower(val)] else let s:lastcubepos = s:lastcubepos + 1 if s:lastcubepos >= &t_Co let s:lastcubepos = 16 endif let nr = s:lastcubepos let s:colors[tolower(val)] = nr if $STY == "" exe 'sil !echo -n -e "\\033]4;' . nr . ';\' . val . '\\a"' else exe 'sil !echo -n -e "\\033P\\033]4;' . nr . ';\' . val . '\\a\\033\\\\"' endif endif let &t_ti = tisave let &t_te = tesave exe 'hi' higrp var.'='.nr else let r = val[1] . val[2] let g = val[3] . val[4] let b = val[5] . val[6] exe 'hi ' . higrp . ' ' . var . '=' . s:FindClosestCode(r, g, b) endif endif endfor endwhile if exists("s:lastcubepos") unlet s:lastcubepos endif endfunction augroup ColorSchemeDegrade au! au ColorScheme * call s:ColorschemeDegrade() augroup END autocmd TermResponse * if exists("g:colors_name") \ | exe "colorscheme" g:colors_name \ | call s:ColorschemeDegrade() \ | endif if exists("g:colors_name") " Don't do anything unless :colorscheme has already been called call s:ColorschemeDegrade() endif let &cpo = s:savecpo unlet s:savecpo " vim:set sw=2 sts=2 fdm=marker: