Completed beta release of CSApprox.vim

This commit is contained in:
Matt Wozniski 2008-09-15 07:30:35 -04:00
parent 48ce53f0d4
commit fa556ebcd8
2 changed files with 1019 additions and 0 deletions

View File

@ -0,0 +1,356 @@
*CSApprox.txt* Bringing GVim colorschemes to the terminal!
*csapprox* *csapprox.vim*
_____ ____ ___ ~
/ ___// __// _ | ___ ___ ____ ___ __ __ ~
/ /__ _\ \ / __ | / _ \ / _ \ / __// _ \ \ \ / ~
\___//___//_/ |_|/ .__// .__//_/ \___//_\_\ ~
/_/ /_/ ~
For Vim version 7.0 or newer
Last changed 12 Sep 2008
By Matt Wozniski
mjw@drexel.edu
Reference Manual~
*csapprox-toc*
1. Description |csapprox-intro|
2. Requirements |csapprox-requirements|
3. Configuration |csapprox-configure|
4. Rationale/Design |csapprox-design|
5. Known Bugs and Limitations |csapprox-limitations|
6. Appendix - Terminals and Palettes |csapprox-terminal-list|
7. Changelog |csapprox-changelog|
8. Contact Info |csapprox-author|
The functionality mentioned here is a plugin, see |add-plugin|.
You can avoid loading this plugin by setting the "CSApprox_loaded" global
variable in your |vimrc| file: >
:let g:CSApprox_loaded = 1
==============================================================================
1. Description *csapprox-intro*
There is a wealth of colorschemes available for Vim. Unfortunately, since
traditional terminal emulators have only supported 2, 8 or 16 colors,
colorscheme authors have tended to avoid writing colorschemes for terminal
Vim, sticking instead to GVim. Even now that nearly every popular terminal
supports either 88 or 256 colors, few colorschemes are written to support
them. This may be because the terminal color codes are just numbers from 0 to
87 or 255 with no semantic meaning, or because the same number doesn't yield
the same color in all terminals, or simply because the colorscheme author
doesn't use the terminal and doesn't want to take the time to support
terminals.
Whatever the reason, this leaves users of many modern terminal emulators in
the awkward position of having a terminal emulator that supports many colors,
but having very few colorschemes that were written to utilize those colors.
This is where CSApprox comes in. It attempts to fill this void by making GVim
colorschemes transparently backwards compatible with terminal Vim in a high
color terminal. Basically, whenever a colorscheme sets some colors for the
GUI, this script runs and tries to figure out the closest color available in
the terminal's color palette to the color the colorscheme author wanted.
Unfortunately, this does not work well all the time, and it has some
limitations (see |csapprox-limitations|). Most of the time, however, this
gives a very close approximation to the GVim colors without requiring any
changes to the colorscheme, or any user interaction.
==============================================================================
2. Requirements *csapprox-requirements*
For CSApprox to work, there are 2 major requirements that must be met.
a) GUI support *csapprox-gui-support* *csapprox-+gui*
The "vim" binary must be built with GUI support (see |csapprox-limitations|
for an explanation). Unfortunately, several Linux distributions only include
GUI support in their "gvim" binary, and not in their "vim" binary. You can
check if GUI support is available by doing: >
:echo has('gui')
If you are in an environment where the "vim" binary does not include GUI
support, but where a "gvim" binary is available, you can probably launch Vim
with GUI support anyway using the |-v| flag in the shell: >
gvim -v
If that does not work and no package with GUI support is available, you will
need to compile Vim yourself and ensure that GUI support is included. If this
is inconvenient for you, make sure that the Vim maintainer for your
distribution knows it.
b) Properly configured terminal *csapprox-terminal*
As said above, many modern terminals support 88 or 256 colors, but most of
these default to setting $TERM to something generic (usually "xterm"). Since
Vim uses the value of the "colors" attribute for the current $TERM in terminfo
to figure out the number of colors used internally as 't_Co', this plugin will
either need for 't_Co' to be set to 88 or 256 in |vimrc|, or for $TERM to be
set to something that implies high color support. Possible choices include
"xterm-256color" for 256 color support and "rxvt-unicode" for 88 color
support.
Also, there are three different 256 color cube palettes available and CSApprox
has no way to tell which you're using unless $TERM is set to something that is
specific to the terminal, like "konsole" or "Eterm". Because of this, the
most sane behavior is assuming the user is using the most popular palette,
which is used by all but Konsole and Eterm, whenever $TERM is set to something
generic like 'xterm' or 'screen'. You can provide a different default,
however - see |csapprox-configuration|.
*csapprox-terminal-example*
To turn on high color support without fixing $TERM, you can put something like
this into your |vimrc|: >
if &term == 'xterm' && $HOSTNAME == 'my-machine'
" On my machine, I use Konsole with 256 color support
set t_Co=256
let g:CSApprox_konsole = 1
endif
==============================================================================
3. Configuration *csapprox-configure*
There are several global variables that can be set to configure the behavior
of CSApprox. They are listed roughly based on the likelihood that the end
user might want to know about them.
g:CSApprox_loaded *g:CSApprox_loaded*
If set in your |vimrc|, CSApprox is not loaded.
g:CSApprox_verbose_level *g:CSApprox_verbose_level*
While running, CSApprox sets 'verbose' to this value (default 1). This
allows CSApprox to default to warning whenever something might be wrong,
but allows the user to disable this behavior if he wants. The most
important messages will be shown at verbosity level 1, some less important
ones will be shown at verbosity level 2. Internal errors and
configuration errors are shown regardless of verbosity level.
g:CSApprox_eterm *g:CSApprox_eterm*
If set, CSApprox will use the Eterm palette when 'term' is "xterm" or
"screen*". Otherwise, the xterm palette would be used.
g:CSApprox_konsole *g:CSApprox_konsole*
If set, CSApprox will use the Konsole palette when 'term' is "xterm" or
"screen*". Otherwise, the xterm palette would be used.
g:CSApprox_attr_map *g:CSApprox_attr_map*
Since some attributes (like 'guisp') can't be used in a terminal, and
others (like 'italic') are often very ugly in terminals, a generic way to
map between a requested attribute and another attribute is included. This
variable should be set to a Dictionary, where the keys are strings
representing the attributes the author wanted set, and the values are the
strings that the user wants set instead. If a value is '', it means the
attribute should just be ignored. The default is to replace 'italic' with
'underline', and to use 'fg' instead of 'sp': >
let g:CSApprox_attr_map = { 'italic' : 'underline', 'sp' : 'fg' }
<
Your author prefers disabling bold and italic entirely, so uses this: >
let g:CSApprox_attr_map = { 'bold' : '', 'italic' : '', 'sp' : 'fg' }
<
Note: You can only map an attribute representing a color to another
attribute representing a color; likewise with boolean attributes.
After all, sp -> bold and italic -> fg would be nonsensical.
g:CSApprox_extra_rgb_txt_dirs *g:CSApprox_extra_rgb_txt_dirs*
When the colorscheme author uses a color by name, CSApprox needs to figure
out what #rrggbb value it stands for. It does this by parsing rgb.txt,
but first needs to locate it. It has a default search path included, but
should it fail to find rgb.txt, this variable can be set to a List of
other directories that ought to be searched. Default search path: >
[ /usr/local/share/X11, /usr/share/X11, /etc/X11, /usr/local/lib/X11,
/usr/lib/X11, /usr/local/X11R6/lib/X11, /usr/X11R6/lib/X11]
g:CSApprox_approximator_function *g:CSApprox_approximator_function*
If the default approximation function doesn't work well enough, the user
(or another author wishing to extend this plugin) can write another
approximation function. This function should take three numbers,
representing r, g, and b in decimal, and return the index on the color
cube that best matches those colors. Assigning a |Funcref| to this
variable will override the default approximator with the one the Funcref
references.
g:CSApprox_redirfallback *g:CSApprox_redirfallback*
As of Vim 7.2.018, there is a bug in the Vim function synIDattr() that
stops it from correctly reporting information for the 'guisp' attribute.
CSApprox includes a workaround for this problem, as well as a test that
ought to disable this workaround once synIDattr() is working properly.
If this test should happen to give improper results somehow, the user can
force the behavior with this variable. When set to 1, the workaround will
always be used, and when set to 0, synIDattr() is blindly used.
==============================================================================
4. Rationale/Design *csapprox-design*
Ideally, the aim is for CSApprox to be completely transparent to the user.
This is why the approach I take is entirely different from the GuiColorScheme
script, which will break on any but the simplest colorschemes. Unfortunately,
given the difficulty of determining exactly which terminal emulator the user
is running, and what features it supports, and which color palette it's using,
perfect transparency is difficult. So, to this end, I've attempted to default
to settings that make it unlikely that this script ever makes things worse
(this is why I chose not to override t_Co to 256 myself), and I've attempted
to make it easy to override my choice of defaults when necessary (through
g:CSApprox_approximator_function, g:CSApprox_konsole, g:CSApprox_eterm,
g:CSApprox_attr_map, etc). If any of my choices seem to be causing extra work
with no real advantages, though, I'd like to hear about it. Feel free to
email me with any improvements upon my design you might want to suggest.
==============================================================================
5. Known Bugs and Limitations *csapprox-limitations*
GUI support is required.
There is nothing I can do about this given my chosen design. CSApprox works
by being notified every time a colorscheme sets some GUI colors, then
approximating those colors to similar terminal colors. Unfortunately, when
Vim is not built with GUI support, it doesn't bother to store the GUI
colors, so querying for them fails. This leaves me completely unable to
tell what the colorscheme was trying to do. See |csapprox-+gui| for some
potential work arounds if your distribution doesn't provide a Vim with +gui.
User intervention is sometimes required for information about the terminal.
This is really an insurmountable problem. Unfortunately, most terminal
emulators default to setting $TERM to 'xterm', even when they're not really
compatible with an xterm. $TERM is really the only reliable way to
find anything at all out about the terminal you're running in, so there's no
way to know if the terminal supports 88 or 256 colors without either the
terminal telling me (using $TERM) or the user telling me (using t_Co).
Similarly, unless $TERM is set to something that implies a certain color
palette ought to be used, there's no way for me to know, so I'm forced to
default to the most common, xterm's palette, and allow the user to override
my choice with |g:CSApprox_konsole| or |g:CSApprox_eterm|. An example of
configuring Vim to work around a terminal where $TERM is set to something
generic without configuring the terminal properly is shown at
|csapprox-terminal-example|.
Some colorschemes could fail if they try to be too smart.
A colorscheme could decide to only set colors for the mode Vim is running
in. If a scheme only sets GUI colors when the GUI is running, instead of
using the usual approach of setting all colors and letting Vim choose which
to use, my approach falls apart. My method for figuring out what the scheme
author wants the scheme to look like absolutely depends upon him setting the
GUI colors in all modes. Fortunately, the few colorschemes that do this
seem to be, by and large, intended for 256 color terminals already, meaning
that skipping them is the proper behavior.
It's slow.
For me, it takes Vim's startup time from 0.15 seconds to 0.45 seconds. This
is probably still acceptable, but it is definitely worth trying to cut down
on this time in future versions.
==============================================================================
6. Appendix - Terminals and Palettes *csapprox-terminal-list*
What follows is a list of terminals known to have and known not to have high
color support. This list is certainly incomplete; feel free to contact me
with more to add to either list.
*csapprox-terminals-good*
------------------------------- Good Terminals -------------------------------
The most recent versions of each of these terminals can be compiled with
either 88 or 256 color support.
*csapprox-xterm*
xterm:
256 color palette
Colors composed of: [ 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF ]
Greys composed of: [ 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E,
0x58, 0x62, 0x6C, 0x76, 0x80, 0x8A, 0x94, 0x9E,
0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE ]
*csapprox-urxvt*
rxvt-unicode (urxvt):
88 colors by default (but a patch is available to use xterm's palette)
Colors composed of: [ 0x00, 0x8B, 0xCD, 0xFF ]
Greys composed of: [ 0x2E, 0x5C, 0x73, 0x8B, 0xA2, 0xB9, 0xD0, 0xE7 ]
*csapprox-pterm* *csapprox-putty*
PuTTY (pterm; putty.exe):
256 colors; same palette as xterm
*csapprox-mrxvt*
Mrxvt (mrxvt):
256 colors; same palette as xterm
*csapprox-gnome-terminal*
GNOME Terminal (gnome-terminal):
256 colors; same palette as xterm
*csapprox-roxterm*
ROXTerm (roxterm):
256 colors; same palette as xterm
*csapprox-xfce4-terminal*
Terminal (xfce4-terminal):
256 colors; same palette as xterm
*csapprox-iterm.app*
iTerm (iTerm.app):
256 colors; same palette as xterm
*csapprox-konsole*
Konsole (konsole):
256 color palette
Colors composed of: [ 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF ]
Same greyscales as xterm
You should set the g:CSApprox_konsole variable unless $TERM begins with
'konsole', case insensitive
*csapprox-eterm*
eterm (Eterm):
256 color palette
Colors composed of: [ 0x00, 0x2A, 0x55, 0x7F, 0xAA, 0xD4 ]
Same greyscales as xterm
You should set the g:CSApprox_eterm variable unless $TERM begins with
'eterm', case insensitive
*csapprox-screen*
GNU Screen (screen):
256 color support. Internally, uses the xterm palette, but this is only
relevant when running screen inside a terminal with fewer than 256 colors,
in which case screen will attempt to map between its own 256 color cube
and the colors supported by the real terminal to the best of its ability.
*csapprox-terminals-bad*
-------------------------------- Bad Terminals -------------------------------
This is a list of terminals known *_not_* to have high color support:
*csapprox-terminal.app*
Terminal.app (as of OS X 10.5.2)
*csapprox-aterm*
aterm (as of version 1.00.01)
*csapprox-xiterm*
xiterm (as of version 0.5)
*csapprox-wterm*
wterm (as of version 6.2.9)
*csapprox-mlterm*
mlterm (as of version 2.9.4)
*csapprox-kterm*
kterm (as of version 6.2.0)
==============================================================================
7. Changelog *csapprox-changelog*
0.90 14 Sep 2008 Initial beta release
==============================================================================
8. Contact Info *csapprox-author*
Your author, a Vim nerd with some free time, was sick of seeing terminals
always get the short end of the stick. He'd like to be notified of any
problems you find - after all, he took the time to write all this lovely
documentation, and this plugin, which took more time than you could possibly
imagine to get working transparently for every colorscheme he could get his
hands on. You can contact him with any problems or praises at mjw@drexel.edu
==============================================================================
vim:tw=78:fo=tcq2:isk=!-~,^*,^\|,^\":ts=8:ft=help:norl:

View File

@ -0,0 +1,663 @@
" CSApprox: Approximate gvim colorschemes to suitable terminal colors
" Maintainer: Matthew Wozniski (mjw@drexel.edu)
" Date: Sun, 14 Sep 2008 12:43:33 -0400
" Version: 0.90
" History: :help csapprox-changelog
" 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 ":set 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. Also, no changes will be made if the colorscheme seems
" to have been high color already.
" {>1} Basic plugin setup
" {>2} Check preconditions
" Quit if the user doesn't want or need us or is missing the gui feature. We
" need +gui to be able to check the gui color settings; vim doesn't bother to
" store them if it is not built with +gui.
if has("gui_running") || ! has("gui") || exists('g:CSApprox_loaded')
" XXX This depends upon knowing the default for g:CSApprox_verbose_level
if has('gui_running')
\ && exists("g:CSApprox_verbose_level") && g:CSApprox_verbose_level > 1
echomsg "Not loading CSApprox in gui mode."
elseif ! has('gui')
\ && (!exists("g:CSApprox_verbose_level") || g:CSApprox_verbose_level)
echomsg "CSApprox needs gui support - not loading."
endif
finish
endif
" {1} Mark us as loaded, and disable all compatibility options for now.
let g:CSApprox_loaded = 1
let s:savecpo = &cpo
set cpo&vim
" {>1} Built-in approximation algorithm
" {>2} Cube definitions
let s:xterm_colors = [ 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF ]
let s:eterm_colors = [ 0x00, 0x2A, 0x55, 0x7F, 0xAA, 0xD4 ]
let s:konsole_colors = [ 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF ]
let s:xterm_greys = [ 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A,
\ 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76,
\ 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2,
\ 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE ]
let s:urxvt_colors = [ 0x00, 0x8B, 0xCD, 0xFF ]
let s:urxvt_greys = [ 0x2E, 0x5C, 0x73, 0x8B,
\ 0xA2, 0xB9, 0xD0, 0xE7 ]
" {>2} Integer comparator
" Used to sort the complete list of possible colors
function! s:IntCompare(i1, i2)
return a:i1 == a:i2 ? 0 : a:i1 > a:i2 ? 1 : -1
endfunc
" {>2} Approximator
" Takes 3 decimal values for r, g, and b, and returns the closest cube number.
" Uses &term to determine which cube should be used, though if &term is set to
" "xterm" the variables g:CSApprox_eterm and g:CSApprox_konsole can be used to
" change the default palette.
"
" This approximator considers closeness based upon the individiual components.
" For each of r, g, and b, it finds the closest cube component available on
" the cube. If the three closest matches can combine to form a valid color,
" this color is used, otherwise we repeat the search with the greys removed,
" meaning that the three new matches must make a valid color when combined.
function! s:ApproximatePerComponent(r,g,b)
let greys = (&t_Co == 88 ? s:urxvt_greys : s:xterm_greys)
if &t_Co == 88
let colors = s:urxvt_colors
elseif ((&term ==# 'xterm' || &term =~# '^screen')
\ && exists('g:CSApprox_konsole'))
\ || &term =~? '^konsole'
let colors = s:konsole_colors
elseif ((&term ==# 'xterm' || &term =~# '^screen')
\ && exists('g:CSApprox_eterm'))
\ || &term =~? '^eterm'
let colors = s:eterm_colors
else
let colors = s:xterm_colors
endif
let greyscolors = sort(greys + colors, "s:IntCompare")
let r = s:NearestElemInList(a:r, greyscolors)
let g = s:NearestElemInList(a:g, greyscolors)
let b = s:NearestElemInList(a:b, greyscolors)
let len = len(colors)
if (r == g && g == b && index(greys, r) > 0)
return 16 + len * len * len + index(greys, r)
else
let r = s:NearestElemInList(a:r, colors)
let g = s:NearestElemInList(a:g, colors)
let b = s:NearestElemInList(a:b, colors)
return index(colors, r) * len * len
\ + index(colors, g) * len
\ + index(colors, b)
\ + 16
endif
endfunction
" {>2} Color comparator
" Finds the nearest element to the given element in the given list
function! s:NearestElemInList(elem, list)
let len = len(a:list)
for i in range(len)
if (i == len - 1) || (a:elem <= (a:list[i] + a:list[i+1]) / 2)
return a:list[i]
endif
endfor
endfunction
" {>1} Collect info for the set highlights
" {>2} Determine if synIDattr is usable
" As of 7.2.018, synIDattr() can't be used to check 'guisp', and no official
" patch has been released despite my suggesting one. So, in an attempt to be
" forward compatible, I've included a test to see if synIDattr() works
" properly. If synIDattr() works properly, we'll use it to check the 'guisp'
" attribute, otherwise we'll fall back onto using :redir and checking the
" output of :highlight. This test can be overridden by setting the global
" variable g:CSApprox_redirfallback to 1 (to force use of :redir) or to 0 (to
" force use of synIDattr()).
function! s:NeedRedirFallback()
if !exists("g:CSApprox_redirfallback")
hi CSApproxTest guisp=Red gui=standout
if synIDattr(hlID('CSApproxTest'), 'sp', 'gui') == '1'
" We requested the 'sp' attribute, but vim thought we wanted 'standout'
" So, reporting of the guisp attribute is broken. Fall back on :redir
let g:CSApprox_redirfallback=1
else
" Reporting guisp works, use synIDattr
let g:CSApprox_redirfallback=0
endif
endif
return g:CSApprox_redirfallback
endfunction
" {>2} Collect and store the highlights
" Get a dictionary containing information for every highlight group not merely
" linked to another group. Return value is a dictionary, with highlight group
" numbers for keys and values that are dictionaries with four keys each,
" 'name', 'term', 'cterm', and 'gui'. 'name' holds the group name, and each
" of the others holds highlight information for that particular mode.
function! s:Highlights()
let rv = {}
let i = 1
while 1
if synIDtrans(i) == 0
break
endif
if !has_key(rv, synIDtrans(i))
let group = {}
let group.name = synIDattr(synIDtrans(i), "name")
for where in [ "term", "cterm", "gui" ]
let group[where] = {}
for attr in [ "fg", "bg", "sp", "bold", "italic",
\ "reverse", "underline", "undercurl" ]
let group[where][attr] = synIDattr(synIDtrans(i), attr, where)
endfor
if s:NeedRedirFallback()
redir => temp
exe 'sil hi ' . group.name
redir END
let temp = matchstr(temp, where.'sp=\zs.*')
if len(temp) == 0 || temp[0] =~ '\s'
let temp = ""
else
" Make sure we can handle guisp='dark red'
let temp = substitute(temp, '[\x00].*', '', '')
let temp = substitute(temp, '\s*\(c\=term\|gui\).*', '', '')
let temp = substitute(temp, '\s*$', '', '')
endif
let group[where]["sp"] = temp
endif
endfor
let rv[synIDtrans(i)] = group
endif
let i += 1
endwhile
return rv
endfunction
" {>1} Handle color names
" Place to store rgb.txt name to color mappings - lazy loaded if needed
let s:rgb = {}
" {>2} Builtin gui color names
" gui_x11.c and gui_gtk_x11.c have some default colors names that are searched
" if a color is not in rgb.txt. We'll pretend they're in rgb.txt with these
" values, and overwrite them with a different value if we find them...
let s:rgb_defaults = { "lightred" : "#FFBBBB",
\ "lightgreen" : "#88FF88",
\ "lightmagenta" : "#FFBBFF",
\ "darkcyan" : "#008888",
\ "darkblue" : "#0000BB",
\ "darkred" : "#BB0000",
\ "darkmagenta" : "#BB00BB",
\ "darkgrey" : "#BBBBBB",
\ "darkyellow" : "#BBBB00",
\ "gray10" : "#1A1A1A",
\ "grey10" : "#1A1A1A",
\ "gray20" : "#333333",
\ "grey20" : "#333333",
\ "gray30" : "#4D4D4D",
\ "grey30" : "#4D4D4D",
\ "gray40" : "#666666",
\ "grey40" : "#666666",
\ "gray50" : "#7F7F7F",
\ "grey50" : "#7F7F7F",
\ "gray60" : "#999999",
\ "grey60" : "#999999",
\ "gray70" : "#B3B3B3",
\ "grey70" : "#B3B3B3",
\ "gray80" : "#CCCCCC",
\ "grey80" : "#CCCCCC",
\ "gray90" : "#E5E5E5",
\ "grey90" : "#E5E5E5" }
" {>2} Find and parse rgb.txt
" Search for an rgb.txt in a set of default directories. If the user wishes
" to override the default search path, he can specify a list of other
" directories to search first in g:CSApprox_extra_rgb_txt_dirs. When rgb.txt
" has been located, and verified to be good (by having enough non-blank
" non-comment correctly formatted lines), the parsed information is stored to
" the dictionary s:rgb - the keys are color names (in lowercase), the values
" are strings representing color values (as '#rrggbb').
function! s:UpdateRgbHash()
" Pattern for ignored lines - all blanks, or blanks then !
let ignorepat = '^\s*\%(!.*\)\=$'
" fmt is (blanks?)(red)(blanks)(green)(blanks)(blue)(blanks)(name)
let parsepat = '^\s*\(\d\+\)\s\+\(\d\+\)\s\+\(\d\+\)\s\+\(.*\)$'
let user = []
if exists("g:CSApprox_extra_rgb_txt_dirs")
if type(g:CSApprox_extra_rgb_txt_dirs) == type([])
let user = g:CSApprox_extra_rgb_txt_dirs
else
let user = [ g:CSApprox_extra_rgb_txt_dirs ]
endif
endif
for dir in user + [ '/usr/local/share/X11',
\ '/usr/share/X11',
\ '/etc/X11',
\ '/usr/local/lib/X11',
\ '/usr/lib/X11',
\ '/usr/local/X11R6/lib/X11',
\ '/usr/X11R6/lib/X11' ]
let s:rgb = copy(s:rgb_defaults)
sil! let lines = readfile(dir . '/rgb.txt')
for line in lines
if line =~ ignorepat
continue " Line is blank, entirely spaces, or a comment
endif
let v = matchlist(line, parsepat)
if len(v) > 0
let s:rgb[tolower(v[4])] = printf("%02x%02x%02x", v[1], v[2], v[3])
endif
endfor
if len(s:rgb) > 50
return 0 " Long enough, must have been valid
endif
endfor
let s:rgb = {}
throw "Failed to find a valid rgb.txt!"
endfunction
" {>1} Derive and set cterm attributes
" {>2} Attribute overrides
" Allow the user to override a specified attribute with another attribute.
" For example, the default is to map 'italic' to 'underline' (since many
" terminals cannot display italic text, and gvim itself will replace italics
" with underlines where italicizing is impossible), and to replace 'sp' with
" 'fg' (since terminals can't use one color for the underline and another for
" the foreground, we color the entire word). This default can of course be
" overridden by the user, by setting g:CSApprox_attr_map. This map must be
" a dictionary of string keys, representing the same attributes that synIDattr
" can look up, to string values, representing the attribute mapped to or an
" empty string to disable the given attribute entirely.
function! s:attr_map(attr)
let attr = tolower(a:attr)
if attr == 'inverse'
let attr = 'reverse'
endif
let valid_attrs = [ 'bg', 'fg', 'sp', 'bold', 'italic',
\ 'reverse', 'underline', 'undercurl' ]
if index(valid_attrs, attr) == -1
throw "Looking up invalid attribute '" . attr . "'"
endif
if !exists("g:CSApprox_attr_map") || type(g:CSApprox_attr_map) != type({})
let g:CSApprox_attr_map = { 'italic' : 'underline', 'sp' : 'fg' }
endif
let rv = get(g:CSApprox_attr_map, attr, attr)
if index(valid_attrs, rv) == -1 && rv != ''
" The user mapped 'attr' to something invalid
throw "Bad attr map: '" . attr . "' to unknown attribute '" . rv . "'"
endif
let colorattrs = [ 'fg', 'bg', 'sp' ]
if rv != '' && !!(index(colorattrs, attr)+1) != !!(index(colorattrs, rv)+1)
" The attribute the user mapped to was valid, but of a different type.
throw "Bad attr map: Can't map color attr to boolean (".attr."->".rv.")"
endif
if rv == 'inverse'
let rv = 'reverse' " Internally always use 'reverse' instead of 'inverse'
elseif rv == 'sp'
" Terminals can't handle the guisp attribute; disable it if it was left on
let rv = ''
endif
return rv
endfunction
" {>2} Map gui settings to cterm settings
" Given information about a highlight group, replace the cterm settings with
" the mapped gui settings, applying any attribute overrides along the way. In
" particular, this gives special treatment to the 'reverse' attribute and the
" 'guisp' attribute. In particular, if the 'reverse' attribute is set for
" gvim, we unset it for the terminal and instead set ctermfg to match guibg
" and vice versa, since terminals can consider a 'reverse' flag to mean using
" default-bg-on-default-fg instead of current-bg-on-current-fg. We also
" ensure that the 'sp' attribute is never set for cterm, since no terminal can
" handle that particular highlight. If the user wants to display the guisp
" color, he should map it to either 'fg' or 'bg' using g:CSApprox_attr_map.
function! s:FixupCtermInfo(hl)
let hl = a:hl
" Find attributes to be set in the terminal
for attr in [ "bold", "italic", "reverse", "underline", "undercurl" ]
let hl.cterm[attr] = ''
if hl.gui[attr] == 1
if s:attr_map(attr) != ''
let hl.cterm[ s:attr_map(attr) ] = 1
endif
endif
endfor
for color in [ "bg", "fg" ]
let eff_color = color
if hl.cterm['reverse']
let eff_color = (color == 'bg' ? 'fg' : 'bg')
endif
let hl.cterm[color] = get(hl.gui, s:attr_map(eff_color), '')
endfor
if hl.gui['sp'] != '' && s:attr_map('sp') != ''
let hl.cterm[s:attr_map('sp')] = hl.gui['sp']
endif
if hl.cterm['reverse'] && hl.cterm.bg == ''
let hl.cterm.bg = 'fg'
endif
if hl.cterm['reverse'] && hl.cterm.fg == ''
let hl.cterm.fg = 'bg'
endif
if hl.cterm['reverse']
let hl.cterm.reverse = ''
endif
endfunction
" {>2} Set cterm colors for a highlight group
" Given the information for a single highlight group (ie, the value of
" one of the items in s:Highlights()), uses s:FixupCtermInfo to parse the
" structure and normalize it for use on a cterm, then handles matching the
" gvim colors to the closest cterm colors by calling the approximator
" specified with g:CSApprox_approximator_function and sets the colors and
" attributes appropriately to match the gui.
function! s:SetCtermFromGui(hl)
let hl = a:hl
call s:FixupCtermInfo(hl)
" Set up the default approximator function, if needed
if !exists("g:CSApprox_approximator_function")
let g:CSApprox_approximator_function=function("s:ApproximatePerComponent")
endif
" Clear existing highlights
exe 'hi ' . hl.name . ' cterm=NONE ctermbg=NONE ctermfg=NONE'
for which in [ 'bg', 'fg' ]
let val = hl.cterm[which]
" Skip unset colors
if val == -1 || val == ""
continue
endif
" Try translating anything but 'fg', 'bg', #rrggbb, and rrggbb from an
" rgb.txt color to a #rrggbb color
if val !~? '^[fb]g$' && val !~ '^#\=\x\{6}$'
if empty(s:rgb)
call s:UpdateRgbHash()
endif
try
let val = s:rgb[tolower(val)]
catch /^/
if &verbose
echomsg "CSApprox: Colorscheme uses unknown color \"" . val . "\""
endif
continue
endtry
endif
if val =~? '^[fb]g$'
exe 'hi ' . hl.name . ' cterm' . which . '=' . val
let hl.cterm[which] = val
elseif val =~ '^#\=\x\{6}$'
let val = substitute(val, '^#', '', '')
let r = str2nr(val[0] . val[1], 16)
let g = str2nr(val[2] . val[3], 16)
let b = str2nr(val[4] . val[5], 16)
let hl.cterm[which] = g:CSApprox_approximator_function(r, g, b)
exe 'hi ' . hl.name . ' cterm' . which . '=' . hl.cterm[which]
else
throw "Internal error handling color: " . val
endif
endfor
" Finally, set the attributes
let attributes = []
for attribute in [ 'bold', 'italic', 'underline', 'undercurl' ]
if hl.cterm[attribute] == 1
let attributes += [ attribute ]
endif
endfor
if !empty(attributes)
exe 'hi ' . hl.name . ' cterm=' . join(attributes, ',')
endif
endfunction
" {>1} Top-level control
" {>2} Variable storing highlights between runs
" This allows us to remember what the highlights looked like when we last ran,
" and compare against it the next time we're called. Using this means that we
" can avoid work when we'd just be duplicating our own work if we tried to
" match the gui colors to cterm colors again. More subtly, it also allows us
" to support composite colorschemes that start with a :colorscheme to load an
" existing colorscheme, and then add or modify highlights that the sourced
" scheme provides. Since we get called twice by such a scheme, things would
" fall apart without info saved between runs - the first call would set some
" high colors, and the second would bail because some high colors are set; it
" would think that the scheme was already 256 color even though it wasn't.
let s:highlights = {}
" {>2} Builtin cterm color names above 15
" Vim defines some color name to high color mappings internally (see
" syntax.c:do_highlight). Since we don't want to overwrite a colorscheme that
" was actually written for a high color terminal with our choices, but have no
" way to tell if a colorscheme was written for a high color terminal, we fall
" back on guessing. If any highlight group has a cterm color set to 16 or
" higher, and it wasn't set by this script, we assume that the user has used
" a high color colorscheme - unless that color is one of the below, which vim
" can set internally when a color is requested by name.
let s:presets_88 = []
let s:presets_88 += [32] " Brown
let s:presets_88 += [72] " DarkYellow
let s:presets_88 += [84] " Gray
let s:presets_88 += [84] " Grey
let s:presets_88 += [82] " DarkGray
let s:presets_88 += [82] " DarkGrey
let s:presets_88 += [43] " LightBlue
let s:presets_88 += [61] " LightGreen
let s:presets_88 += [63] " LightCyan
let s:presets_88 += [74] " LightRed
let s:presets_88 += [75] " LightMagenta
let s:presets_88 += [78] " LightYellow
let s:presets_256 = []
let s:presets_256 += [130] " Brown
let s:presets_256 += [130] " DarkYellow
let s:presets_256 += [248] " Gray
let s:presets_256 += [248] " Grey
let s:presets_256 += [242] " DarkGray
let s:presets_256 += [242] " DarkGrey
let s:presets_256 += [ 81] " LightBlue
let s:presets_256 += [121] " LightGreen
let s:presets_256 += [159] " LightCyan
let s:presets_256 += [224] " LightRed
let s:presets_256 += [225] " LightMagenta
let s:presets_256 += [229] " LightYellow
" {>2} Highlight comparator
" Comparator that sorts numbers matching the highlight id of the 'Normal'
" group before anything else; all others stay in random order. This allows us
" to ensure that the Normal group is the first group we set. If it weren't,
" we could get E419 or E420 if a later color used guibg=bg or the likes.
function! s:SortNormalFirst(num1, num2)
if a:num1 == hlID('Normal') && a:num1 != a:num2
return -1
elseif a:num2 == hlID('Normal') && a:num1 != a:num2
return 1
else
return 0
endif
endfunction
" {>2} Main function
" Wrapper around the actual implementation to make it easier to ensure that
" all temporary settings are restored by the time we return, whether or not
" something was thrown. Additionally, sets the 'verbose' option to
" g:CSApprox_verbose_level (default 1) for the duration of the main function.
" This allows us to default to a message whenever any error, even
" a recoverable one, occurs, meaning the user quickly finds out when
" something's wrong, but makes it very easy for the user to make us silent.
function! s:CSApprox()
try
let savelz = &lz
let savevbs = &vbs
set lz
" colors_name must be unset and reset, or vim will helpfully reload the
" colorscheme when we set the background for the Normal group.
" See the help entries ':hi-normal-cterm' and 'g:colors_name'
if exists("g:colors_name")
let colors_name = g:colors_name
unlet g:colors_name
endif
" Set up our verbosity level, if needed.
" Default to 1, so the user can know if something's wrong.
if !exists("g:CSApprox_verbose_level")
let g:CSApprox_verbose_level = 1
endif
sil! let &verbose=g:CSApprox_verbose_level
call s:CSApproxImpl()
finally
if exists("colors_name")
let g:colors_name = colors_name
endif
let &lz = savelz
let &vbs = savevbs
endtry
endfunction
" {>2} CSApprox implementation
" Verifies that the user has not started the gui, and that vim recognizes his
" terminal as having enough colors for us to go on, then gathers the existing
" highlights, removes the ones that match what were set on the last run
" through, and sets the cterm colors to match the gui colors for all modified
" highlights.
function! s:CSApproxImpl()
" Return if not running in an 88/256 color terminal
if has('gui_running') || (&t_Co != 256 && &t_Co != 88)
if &verbose && &t_Co != 256 && &t_Co != 88
echomsg "CSApprox skipped; terminal only has" &t_Co "colors, not 88/256"
endif
return
endif
" Get the current highlight colors
let highlights = s:Highlights()
" If the Normal group is cleared, set it to gvim's default, black on white
" Though this would be a really weird thing for a scheme to do... *shrug*
if highlights[hlID('Normal')].gui.bg == ''
let highlights[hlID('Normal')].gui.bg = 'white'
endif
if highlights[hlID('Normal')].gui.fg == ''
let highlights[hlID('Normal')].gui.fg = 'black'
endif
" Create a list of colors that have changed since the last iteration
let modified = []
for hlid in keys(highlights)
if !has_key(s:highlights, hlid)
\ || s:highlights[hlid].cterm != highlights[hlid].cterm
\ || s:highlights[hlid].gui != highlights[hlid].gui
let modified += [hlid]
endif
endfor
" Make sure that the script is not already 256 color by checking to make
" sure that no modified groups are set to a value above 256, unless the
" color they're set to can be set internally by vim (gotten by scraping
" color_numbers_{88,256} in syntax.c:do_highlight)
for hlid in modified
let val = highlights[hlid]
if ( val.cterm.bg > 15
\ && index(s:presets_{&t_Co}, str2nr(val.cterm.bg)) < 0
\ && val.cterm.bg !=
\ get(get(get(s:highlights, hlid, {}), 'cterm', {}), 'bg', ''))
\ || ( val.cterm.fg > 15
\ && index(s:presets_{&t_Co}, str2nr(val.cterm.fg)) < 0
\ && val.cterm.fg !=
\ get(get(get(s:highlights, hlid, {}), 'cterm', {}), 'fg', ''))
" The value is set above 15, and wasn't set by us or vim.
if &verbose >= 2
echomsg 'CSApprox: Exiting - high color found for' val.name
endif
return
endif
endfor
" Then, set all the modified colors to approximate the gui colors.
call sort(modified, "s:SortNormalFirst")
" And finally set each modified color's cterm attributes to match gui
for hlid in modified
call s:SetCtermFromGui(highlights[hlid])
endfor
" And store the new highlights for use in the next iteration
let s:highlights = s:Highlights()
endfunction
" {>1} Hooks
" {>2} Autocmds
" Set up an autogroup to hook us on the completion of any :colorscheme command
augroup CSApprox
au!
au ColorScheme * call s:CSApprox()
augroup END
" {>2} Execute
" The last thing to do when sourced is to run and actually fix up the colors.
call s:CSApprox()
" {>1} Restore compatibility options
let &cpo = s:savecpo
unlet s:savecpo
" {0} vim:sw=2:sts=2:et:fdm=expr:fde=substitute(matchstr(getline(v\:lnum),'^\\s*"\\s*{\\zs.\\{-}\\ze}'),'^$','=','')