Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Version 1.00: Initial upload

  • Loading branch information...
commit 9f658abe4abf45dc38be76a1b42ab8e45cbf2df1 0 parents
Matt Wozniski authored committed
Showing with 1,126 additions and 0 deletions.
  1. +54 −0 README
  2. +365 −0 doc/CSApprox.txt
  3. +707 −0 plugin/CSApprox.vim
54 README
@@ -0,0 +1,54 @@
+This is a mirror of http://www.vim.org/scripts/script.php?script_id=2390
+
+CSApprox.vim
+
+============
+DESCRIPTION
+============
+
+It's hard to find colorschemes for terminal Vim. Most colorschemes are
+written to only support GVim, and don't work at all in terminal Vim.
+
+This plugin makes GVim-only colorschemes Just Work in terminal Vim, as long
+as the terminal supports 88 or 256 colors - and most do these days. This
+usually requires no user interaction (but see the help for what to do if
+things don't Just Work). After getting this plugin happily installed, any
+time you use :colorscheme it will do its magic and make the colorscheme Just
+Work.
+
+Whenever you change colorschemes using the :colorscheme command this script
+will be executed. 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 in your terminal. If you are running in
+a GUI or if your terminal doesn't support 88 or 256 colors, no changes are
+made. Also, no changes will be made if the colorscheme seems to have been
+high color already.
+
+If for some reason this transparent method isn't suitable to you (for instance
+if your environment can't be configured to meet the |csapprox-requirements|,
+or you need to work in Vim 6), another option is also available: using the
+|:CSApproxSnapshot| command to create a new GUI/88-/256-color terminal
+colorscheme. To use this command, a user would generally start GVim, choose a
+colorscheme that sets up the desired colors, and then use |:CSApproxSnapshot|
+to create a new colorscheme based on those colors that works in high color
+terminals. This method is more flexible than the transparent mode and works
+in more places, but also requires more user intervention, and makes it harder
+to deal with colorschemes being updated and such.
+
+======
+NOTES
+======
+
+Ideally, this plugin should require absolutely no configuration, but you may
+need some tweaking to make sure vim realizes that your terminal supports more
+than 16 colors. Also, konsole and Eterm users will want to make sure that
+this plugin realizes that the terminal does not use colors that are exactly
+xterm-compatible; they will want to skim through the help articles
+|csapprox-palettes| and |csapprox-configuration| for a better end result.
+
+==============
+SCREENSHOTS
+==============
+
+Some quick side-by-side screenshots can be found at
+http://www.cs.drexel.edu/~mjw452/CSApprox/
365 doc/CSApprox.txt
@@ -0,0 +1,365 @@
+*CSApprox.txt* Bringing GVim colorschemes to the terminal!
+
+ *csapprox* *csapprox.vim*
+
+ _____ ____ ___ ~
+ / ___// __// _ | ___ ___ ____ ___ __ __ ~
+ / /__ _\ \ / __ | / _ \ / _ \ / __// _ \ \ \ / ~
+ \___//___//_/ |_|/ .__// .__//_/ \___//_\_\ ~
+ /_/ /_/ ~
+ For Vim version 7.0 or newer
+ Last changed 05 Oct 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 that prints 0, the first thing to try would be searching for a larger vim
+package provided by your distribution, like "vim-enhanced" on RedHat/CentOS
+or "vim" or "vim-gnome" on Debian/Ubuntu.
+
+If you are unable to obtain a "vim" binary that includes GUI support, but
+have a "gvim" binary available, you can probably launch Vim with GUI support
+anyway by calling gvim with 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.
+ *csapprox-palettes*
+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*
+ When CSApprox is run, the 'verbose' option will be temporarily raised to
+ the value held in this variable unless it is already greater. The default
+ value is 1, which allows CSApprox to default to warning whenever something
+ is wrong, even if it is recoverable, but allows the user to quiet us if he
+ wants by changing this variable to 0. The most important messages will be
+ shown at verbosity level 1; some less important ones will be shown at
+ higher verbosity levels.
+
+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.35 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*
+
+ 1.00 04 Oct 2008 First public release
+
+ 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:
707 plugin/CSApprox.vim
@@ -0,0 +1,707 @@
+" CSApprox: Make gvim-only colorschemes work transparently in terminal vim
+" Maintainer: Matthew Wozniski (mjw@drexel.edu)
+" Date: Sun, 05 Oct 2008 00:26:22 -0400
+" Version: 1.00
+" 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
+ let s:verbose = 1
+ if exists("g:CSApprox_verbose_level")
+ let s:verbose = g:CSApprox_verbose_level
+ endif
+
+ if has('gui_running') && s:verbose > 1
+ echomsg "Not loading CSApprox in gui mode."
+ elseif ! has('gui') && s:verbose > 0
+ echomsg "CSApprox needs gui support - not loading."
+ endif
+
+ unlet s:verbose
+
+ 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 hex = printf("%02x%02x%02x", a:r, a:g, a:b)
+
+ let greys = (&t_Co == 88 ? s:urxvt_greys : s:xterm_greys)
+
+ if &t_Co == 88
+ let colors = s:urxvt_colors
+ let type = 'urxvt'
+ elseif ((&term ==# 'xterm' || &term =~# '^screen')
+ \ && exists('g:CSApprox_konsole'))
+ \ || &term =~? '^konsole'
+ let colors = s:konsole_colors
+ let type = 'konsole'
+ elseif ((&term ==# 'xterm' || &term =~# '^screen')
+ \ && exists('g:CSApprox_eterm'))
+ \ || &term =~? '^eterm'
+ let colors = s:eterm_colors
+ let type = 'eterm'
+ else
+ let colors = s:xterm_colors
+ let type = 'xterm'
+ endif
+
+ if !exists('s:approximator_cache_'.type)
+ let s:approximator_cache_{type} = {}
+ endif
+
+ let rv = get(s:approximator_cache_{type}, hex, -1)
+ if rv != -1
+ return rv
+ endif
+
+ " Only obtain sorted list once
+ if !exists("s:".type."_greys_colors")
+ let s:{type}_greys_colors = sort(greys + colors, "s:IntCompare")
+ endif
+
+ let greys_colors = s:{type}_greys_colors
+
+ let r = s:NearestElemInList(a:r, greys_colors)
+ let g = s:NearestElemInList(a:g, greys_colors)
+ let b = s:NearestElemInList(a:b, greys_colors)
+
+ let len = len(colors)
+ if (r == g && g == b && index(greys, r) > 0)
+ let rv = 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)
+ let rv = index(colors, r) * len * len
+ \ + index(colors, g) * len
+ \ + index(colors, b)
+ \ + 16
+ endif
+
+ let s:approximator_cache_{type}[hex] = rv
+ return rv
+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-1)
+ if (a:elem <= (a:list[i] + a:list[i+1]) / 2)
+ return a:list[i]
+ endif
+ endfor
+ return a:list[len-1]
+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 = 0
+ while 1
+ let i += 1
+
+ " Only interested in groups that exist and aren't linked
+ if synIDtrans(i) == 0
+ break
+ endif
+
+ " Handle vim bug allowing groups with name == "" to be created
+ if synIDtrans(i) != i || len(synIDattr(i, "name")) == 0
+ continue
+ endif
+
+ let rv[i] = {}
+ let rv[i].name = synIDattr(i, "name")
+
+ for where in [ "term", "cterm", "gui" ]
+ let rv[i][where] = {}
+ for attr in [ "fg", "bg", "sp", "bold", "italic",
+ \ "reverse", "underline", "undercurl" ]
+ let rv[i][where][attr] = synIDattr(i, attr, where)
+ endfor
+
+ if s:NeedRedirFallback()
+ redir => temp
+ exe 'sil hi ' . rv[i].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 rv[i][where]["sp"] = temp
+ endif
+ endfor
+ 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 the max of
+" g:CSApprox_verbose_level (default 1) and &verbose 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
+
+ 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
+
+ " Similarly, the global variable "syntax_cmd" must be set to something vim
+ " doesn't recognize, lest vim helpfully switch all colors back to the
+ " default whenever the Normal group is changed (in syncolor.vim)...
+ if exists("g:syntax_cmd")
+ let syntax_cmd = g:syntax_cmd
+ endif
+ let g:syntax_cmd = "PLEASE DON'T CHANGE ANY COLORS!!!"
+
+ " 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
+
+ " Set 'verbose' set to the maximum of &verbose and CSApprox_verbose_level
+ exe max([&vbs, g:CSApprox_verbose_level]) 'verbose call s:CSApproxImpl()'
+ finally
+ if exists("colors_name")
+ let g:colors_name = colors_name
+ endif
+
+ unlet g:syntax_cmd
+ if exists("syntax_cmd")
+ let g:syntax_cmd = syntax_cmd
+ endif
+
+ let &lz = savelz
+ 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 && !has('gui_running') && &t_Co != 256 && &t_Co != 88
+ echomsg "CSApprox skipped; terminal only has" &t_Co "colors, not 88/256"
+ echomsg "Try checking :help csapprox-terminal for workarounds"
+ 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
+
+ " We need to set the Normal group first so 'bg' and 'fg' work as colors
+ call sort(modified, "s:SortNormalFirst")
+
+ " then set each modified color's cterm attributes to match gui
+ for hlid in modified
+ call s:SetCtermFromGui(highlights[hlid])
+ endfor
+
+ " and finally, store the new highlights for use in the next iteration
+ let s:highlights = 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}'),'^$','=','')
Please sign in to comment.
Something went wrong with that request. Please try again.