467 lines
12 KiB
VimL
467 lines
12 KiB
VimL
" theme.menu.vim: Generates Vim themes menu and organizes themes based
|
|
" upon background colors
|
|
" Maintainer: Erik Falor <rAjsBnFCybe@tzNnvy.Zpbz g?? - NOSPAM>
|
|
" Date: Aug 30, 2007
|
|
" Version: 0.4
|
|
"
|
|
|
|
" Initialization: {{{
|
|
if exists("g:loaded_theme_menu") || &cp
|
|
finish
|
|
endif
|
|
let g:loaded_theme_menu= "0.4"
|
|
let s:keepcpo = &cpo
|
|
set cpo&vim
|
|
"}}}
|
|
|
|
" Script Variables: {{{
|
|
let s:menuFile = strpart(&rtp, 0, stridx(&rtp, ',')) . '/plugin/ColorSchemes.vim'
|
|
let s:menuName = '&ColorSchemes'
|
|
let s:xdigit = '[0123456789ABCDEFabcdef]'
|
|
let s:hexvals = { 0:0, 1:1, 2:2, 3:3,
|
|
\4:4, 5:5, 6:6, 7:7,
|
|
\8:8, 9:9, 'a':10, 'b':11,
|
|
\'c':12, 'd':13, 'e':14, 'f':15,
|
|
\'A':10, 'B':11, 'C':12, 'D':13,
|
|
\'E':14, 'F':15 }
|
|
"}}}
|
|
|
|
" Library Functions {{{
|
|
function! <SID>RGBtoHSV(r, g, b) "{{{
|
|
let h = 0
|
|
let s = 0
|
|
let v = 0
|
|
if (a:b > a:g) && (a:b > a:r)
|
|
let v = a:b
|
|
if v != 0
|
|
let min = 0
|
|
if(a:r > a:g)
|
|
let min = a:g
|
|
else
|
|
let min = a:r
|
|
endif
|
|
|
|
let delta = v - min
|
|
|
|
if delta != 0
|
|
let s = (delta * 255) / v
|
|
let h = 240 + (60 * a:r - 60 * a:g) / delta
|
|
else
|
|
let s = 0
|
|
let h = 240 + (60 * a:r - 60 * a:g)
|
|
endif
|
|
if h < 0
|
|
let h = h + 360
|
|
endif
|
|
else
|
|
let s = 0
|
|
let h = 0
|
|
endif
|
|
elseif a:g > a:r
|
|
let v = a:g
|
|
if v != 0
|
|
let min = 0
|
|
if a:r > a:b
|
|
let min = a:b
|
|
else
|
|
let min = a:r
|
|
endif
|
|
let delta = v - min
|
|
if delta != 0
|
|
let s = (delta * 255) / v
|
|
let h = 120 + (60 * a:b - 60 * a:r) / delta
|
|
else
|
|
let s = 0
|
|
let h = 120 + (60 * a:b - 60 * a:r)
|
|
endif
|
|
if h < 0
|
|
let h = h + 360
|
|
endif
|
|
else
|
|
let s = 0
|
|
let h = 0
|
|
endif
|
|
else
|
|
let v = a:r
|
|
if v != 0
|
|
let min = 0
|
|
if a:g > a:b
|
|
let min = a:b
|
|
else
|
|
let min = a:g
|
|
endif
|
|
let delta = v - min
|
|
if delta != 0
|
|
let s = (delta * 255) / v
|
|
let h = (60 * a:g - 60 * a:b) / delta
|
|
else
|
|
let s = 0
|
|
let h = 60 * a:g - 60 * a:b
|
|
endif
|
|
if h < 0
|
|
let h = h + 360
|
|
endif
|
|
else
|
|
let s = 0
|
|
let h = 0
|
|
endif
|
|
endif
|
|
return [h, s, v]
|
|
endfunction "RGBtoHSV()
|
|
"}}}
|
|
|
|
function! <SID>IsBlack(r, g, b, h, s, v) "{{{
|
|
if a:r == a:g && a:g == a:b && a:b == 0
|
|
return 1
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction "IsBlack()}}}
|
|
|
|
function! <SID>IsWhite(r, g, b, h, s, v) "{{{
|
|
if a:r == a:g && a:g == a:b && a:b == 255
|
|
return 1
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction "IsWhite()}}}
|
|
|
|
function! <SID>IsDarkGrey(r, g, b, h, s, v) "{{{
|
|
let diffRGB = max([a:r, a:g, a:b]) - min([a:r, a:g, a:b])
|
|
let darkGreyFuzz = 20
|
|
if diffRGB <= darkGreyFuzz
|
|
return 1
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction "IsDarkGrey()}}}
|
|
|
|
function! <SID>IsOffWhite(r, g, b, h, s, v) "{{{
|
|
let offWhiteSat = 32
|
|
let offWhiteVal = 255 - 32
|
|
if a:v >= offWhiteVal && a:s <= offWhiteSat
|
|
return 1
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! <SID>IsGrey(r, g, b, h, s, v) "{{{
|
|
let diffRGB = max([a:r, a:g, a:b]) - min([a:r, a:g, a:b])
|
|
let greyFuzz = 28
|
|
let greyVal = 32
|
|
|
|
if diffRGB > greyFuzz
|
|
return 0
|
|
elseif (a:s <= greyFuzz )
|
|
\&& (a:v <= 255 - (greyVal * 1))
|
|
\&& (a:v >= 0 + (greyVal * 1))
|
|
return 1
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! <SID>IsYellow(r, g, b, h, s, v) "{{{
|
|
if a:h > 30 && a:h <= 90
|
|
return 1
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! <SID>IsGreen(r, g, b, h, s, v) "{{{
|
|
if a:h > 90 && a:h <= 180
|
|
return 1
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! <SID>IsCyan(r, g, b, h, s, v) "{{{
|
|
" cyan will be 180 deg +/- 10 deg
|
|
let variance = 10
|
|
if a:h > 180 - variance && a:h < 180 + variance
|
|
return 1
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! <SID>IsBlue(r, g, b, h, s, v) "{{{
|
|
if a:h > 180 && a:h <= 270
|
|
return 1
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! <SID>IsMagenta(r, g, b, h, s, v) "{{{
|
|
if a:h > 270 && a:h <= 330
|
|
return 1
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction }}}
|
|
|
|
function! <SID>IsOrange(r, g, b, h, s, v) "{{{
|
|
"a magic number found through trial and error
|
|
let greenFuzz = 172
|
|
if a:r > a:g && a:b == 0 && a:g < greenFuzz && a:g != 0
|
|
return 1
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! <SID>IsRed(r, g, b, h, s, v) "{{{
|
|
if a:h > 330 || a:h <= 30
|
|
return 1
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! <SID>RgbTxt2Hexes() "{{{
|
|
"read rgb.txt, return dictionary mapping color names to hex triplet
|
|
if exists("g:rgbtxt") && filereadable(g:rgbtxt)
|
|
let rgbtxt = g:rgbtxt
|
|
else
|
|
if has("win32") || has("win64")
|
|
let rgbtxt = expand("$VIMRUNTIME/rgb.txt")
|
|
elseif filereadable("/usr/X11R6/lib/X11/rgb.txt")
|
|
let rgbtxt = "/usr/X11R6/lib/X11/rgb.txt"
|
|
elseif filereadable("/usr/share/X11/rgb.txt")
|
|
let rgbtxt = "/usr/share/X11/rgb.txt"
|
|
endif
|
|
endif
|
|
let rgbdict = {}
|
|
if filereadable(rgbtxt)
|
|
for line in readfile(rgbtxt)
|
|
if line !~ '^\(!\|#\)'
|
|
let l = matchlist(line, '\s*\(\d\+\)\s*\(\d\+\)\s*\(\d\+\)\s*\(.*\)')
|
|
let rgbdict[tolower(l[4])] = printf('%02X%02X%02X', l[1], l[2], l[3])
|
|
endif
|
|
endfor
|
|
"note: vim treats guibg=NONE as guibg=white
|
|
let rgbdict['none'] = 'FFFFFF'
|
|
else
|
|
echoerr "ColorSchemeMenuMaker.vim could not open rgb.txt file at " . rgbtxt
|
|
endif
|
|
return rgbdict
|
|
endfunction "}}}
|
|
|
|
function! <SID>RGBHexToHexes(rgb) "{{{
|
|
let xdigits = '\(' . s:xdigit . '\{2\}\)'
|
|
let pat = '\(#\)\?' . xdigits . xdigits . xdigits
|
|
let l = matchlist(a:rgb, pat)
|
|
if len(l) > 0
|
|
return [ l[2], l[3], l[4] ]
|
|
else
|
|
return []
|
|
endif
|
|
endfunction "}}}
|
|
|
|
function! <SID>RGBHexToInts(rgbList) "{{{
|
|
return map(a:rgbList, '<SID>Hex2Int(v:val)')
|
|
endfunction "}}}
|
|
|
|
function! <SID>Hex2Int(hex) "{{{
|
|
let xdigits = split(a:hex, '\zs')
|
|
return 16 * s:hexvals[xdigits[0]] + s:hexvals[xdigits[1]]
|
|
endfunction "}}}
|
|
|
|
function! <SID>RGB2BoyColor(rgb) "{{{
|
|
let rgbL = <SID>RGBHexToInts(<SID>RGBHexToHexes(a:rgb))
|
|
let r = rgbL[0] | let g = rgbL[1] | let b = rgbL[2]
|
|
let hsvL = <SID>RGBtoHSV(r, g, b)
|
|
let h = hsvL[0] | let s = hsvL[1] | let v = hsvL[2]
|
|
if <SID>IsBlack(r, g, b, h, s, v) == 1 | return 'black' | endif
|
|
if <SID>IsWhite(r, g, b, h, s, v) == 1 | return 'white' | endif
|
|
if <SID>IsGrey(r, g, b, h, s, v) == 1 | return 'grey' | endif
|
|
if <SID>IsOffWhite(r, g, b, h, s, v) == 1 | return 'offwhite' | endif
|
|
if <SID>IsDarkGrey(r, g, b, h, s, v) == 1 | return 'darkgrey' | endif
|
|
if <SID>IsOrange(r, g, b, h, s, v) == 1 | return 'orange' | endif
|
|
if <SID>IsYellow(r, g, b, h, s, v) == 1 | return 'yellow' | endif
|
|
if <SID>IsCyan(r, g, b, h, s, v) == 1 | return 'cyan' | endif
|
|
if <SID>IsGreen(r, g, b, h, s, v) == 1 | return 'green' | endif
|
|
if <SID>IsBlue(r, g, b, h, s, v) == 1 | return 'blue' | endif
|
|
if <SID>IsMagenta(r, g, b, h, s, v) == 1 | return 'magenta' | endif
|
|
if <SID>IsRed(r, g, b, h, s, v) == 1 | return 'red' | endif
|
|
return 'unknown'
|
|
endfunction "}}}
|
|
|
|
function! <SID>GlobThemes() "{{{
|
|
"return list containing paths to all theme files in &runtimepath
|
|
return split(globpath(&rtp, 'colors/*.vim'), '\n')
|
|
endfunction "}}}
|
|
|
|
function! <SID>ScanThemeBackground() "{{{
|
|
"Read each of the theme files and find out which color
|
|
"each theme 'basically' is. Uses the last 'hi Normal'
|
|
"group found to classify by color. Notes those color
|
|
"files that do have more than one 'hi Normal' command.
|
|
let name2hex = <SID>RgbTxt2Hexes()
|
|
let themeColors = {}
|
|
let themeNames = {}
|
|
let i = 0
|
|
let pat = 'hi.*\s\+Normal\s\+.\{-}guibg=\(#\?\)\(\w\+\)'
|
|
for theme in <SID>GlobThemes()
|
|
if filereadable(theme)
|
|
|
|
"DEBUG
|
|
"let i = i + 1
|
|
"if i > 10
|
|
"break
|
|
"endif
|
|
|
|
let higroupfound = 0
|
|
let color = ''
|
|
for line in readfile(theme)
|
|
let bg = matchlist(line, pat)
|
|
if len(bg) > 0
|
|
if bg[1] == '#'
|
|
let color = <SID>RGB2BoyColor(bg[2])
|
|
else
|
|
if has_key(name2hex, tolower(bg[2]))
|
|
let color = <SID>RGB2BoyColor(name2hex[tolower(bg[2])])
|
|
else
|
|
let color = 'unknown'
|
|
endif
|
|
endif
|
|
let higroupfound += 1
|
|
endif
|
|
endfor
|
|
let themename = fnamemodify(theme, ':t:r')
|
|
let letter = toupper(strpart(themename, 0, 1))
|
|
if letter =~ '\d' | let letter = '#' | endif
|
|
|
|
if len(color) < 1
|
|
let color = 'unknown'
|
|
endif
|
|
|
|
"allocate sub-dict if needed
|
|
if !has_key(themeColors, color)
|
|
let themeColors[color] = {}
|
|
endif
|
|
"allocate sub-dict if needed
|
|
if !has_key(themeNames, letter)
|
|
let themeNames[letter] = {}
|
|
endif
|
|
if higroupfound > 1
|
|
"mark themes with many 'hi Normal' commands
|
|
if len(color) > 0
|
|
let themeColors[color][themename] = '*' . themename
|
|
endif
|
|
let themeNames[letter][themename] = '*' . themename
|
|
else
|
|
if len(color) > 0
|
|
let themeColors[color][themename] = themename
|
|
endif
|
|
let themeNames[letter][themename] = themename
|
|
endif
|
|
endif
|
|
endfor
|
|
return [themeColors, themeNames]
|
|
endfunction "}}}
|
|
|
|
function! <SID>BuildMenu(dicts) "{{{
|
|
"puts menu commands into a list
|
|
let menu = []
|
|
call add(menu, '"ColorScheme menu generated ' . strftime("%c", localtime()))
|
|
call add(menu, '')
|
|
call add(menu, '"Themes by color:')
|
|
call add(menu, '')
|
|
"count number of themes categorized by color
|
|
let totThemes = 0
|
|
for i in keys(a:dicts[0])
|
|
let totThemes += len(a:dicts[0][i])
|
|
endfor
|
|
for color in sort(keys(a:dicts[0]))
|
|
let numThemes = len(a:dicts[0][color])
|
|
call add(menu, '')
|
|
call add(menu, '"submenu '. color)
|
|
for theme in sort(keys(a:dicts[0][color]))
|
|
call add(menu, '9000amenu '. s:menuName. '.&Colors\ ('. totThemes . ').'
|
|
\. color . '\ ('. numThemes . ').'
|
|
\. a:dicts[0][color][theme]. ' :colo '. theme . '<CR>')
|
|
endfor
|
|
endfor
|
|
call add(menu, '"Themes by name:')
|
|
call add(menu, '')
|
|
"count number of themes categorized by name
|
|
let totThemes = 0
|
|
for i in keys(a:dicts[1])
|
|
let totThemes += len(a:dicts[1][i])
|
|
endfor
|
|
for letter in sort(keys(a:dicts[1]))
|
|
let numThemes = len(a:dicts[1][letter])
|
|
call add(menu, '')
|
|
call add(menu, '"submenu '. letter)
|
|
for theme in sort(keys(a:dicts[1][letter]))
|
|
call add(menu, 'amenu '. s:menuName. '.&Names\ (' . totThemes . ').'
|
|
\. letter . '\ ('. numThemes .').'
|
|
\. a:dicts[1][letter][theme] . ' :colo '. theme . '<CR>')
|
|
endfor
|
|
endfor
|
|
|
|
call add(menu, '')
|
|
"add a separator and a command to re-init the menu
|
|
call add(menu, 'amenu ' . s:menuName .'.-Sep- :')
|
|
call add(menu, 'amenu ' . s:menuName .'.Reload\ Menu :ReloadColors<CR>')
|
|
call add(menu, 'amenu ' . s:menuName .'.Refresh\ Menu :RefreshColors<CR>')
|
|
call add(menu, '')
|
|
call add(menu, 'command! -nargs=0 ReloadColors call <SID>ReloadColors()')
|
|
call add(menu, 'command! -nargs=0 RefreshColors call <SID>RefreshColors()')
|
|
call add(menu, '')
|
|
call add(menu, 'if !exists("g:running_ReloadColors")')
|
|
call add(menu, ' function! <SID>ReloadColors()')
|
|
call add(menu, ' let g:running_ReloadColors = 1')
|
|
call add(menu, ' aunmenu ' . s:menuName)
|
|
call add(menu, " execute 'source " . s:menuFile . "'")
|
|
call add(menu, ' unlet g:running_ReloadColors')
|
|
call add(menu, " echomsg 'Done Reloading " . s:menuFile . "'")
|
|
call add(menu, ' endfunction')
|
|
call add(menu, 'endif')
|
|
|
|
call add(menu, 'if !exists("g:running_RefreshColors")')
|
|
call add(menu, ' function! <SID>RefreshColors()')
|
|
call add(menu, ' let g:running_RefreshColors = 1')
|
|
call add(menu, ' call WriteColorSchemeMenu()')
|
|
call add(menu, ' call <SID>ReloadColors()')
|
|
call add(menu, ' unlet g:running_RefreshColors')
|
|
call add(menu, " echomsg 'Done Refreshing " . s:menuFile . "'")
|
|
call add(menu, ' endfunction')
|
|
call add(menu, 'endif')
|
|
|
|
return menu
|
|
endfunction "}}}
|
|
|
|
function! WriteColorSchemeMenu() "{{{
|
|
"Builds the menu from the two dicts returned by ScanThemeBackground()
|
|
"Stores menu in first plugin dir specified by &rtp
|
|
let menu = <SID>BuildMenu(<SID>ScanThemeBackground())
|
|
call writefile(menu, s:menuFile)
|
|
endfunction "}}}
|
|
|
|
function! <SID>InitMenu() "{{{
|
|
call WriteColorSchemeMenu()
|
|
execute "source " . s:menuFile
|
|
endfunction "}}}
|
|
|
|
"}}}
|
|
|
|
" Restore &cpo: {{{1
|
|
let &cpo= s:keepcpo
|
|
unlet s:keepcpo
|
|
"}}}1
|
|
|
|
"Detect absence of ColorScheme menu, and generate a new one automatically
|
|
if !filereadable(s:menuFile) "{{{
|
|
echomsg "Creating ColorScheme menu - Please Wait..."
|
|
call <SID>InitMenu()
|
|
echomsg "Done!"
|
|
endif "}}}
|
|
|
|
" vim: tabstop=4 foldmethod=marker
|