власні кольори для vim — це складно?

| щоденник, комп'ютери, linux, vim, підказка, поновлюється

я награвся різними колірними темами для vim: котрісь дуже сподобались, більшість — ні; переважно уникав тем «клоуна знудило» і пробував стримані, навіть однотонні. насамкінець спробував знайти мінімалістичну «прозору» тему, котра б просто покладалась на кольори терміналу, і додавала може один колірний акцент. точно такого, як хотів, не знайшлося, і захотілося розібратися, як швиденько змайструвати тему самотужки.

натхнення

найближче до того, що мені сподобалось наприкінці моїх пошуків:

  • quiet (maxence weynans) — одна з нових стандартних тем vim, повністю однотонна з помірним контрастом і одним колірним акцентом; «прозора» — в тому сенсі, що уникає змін тла й покладається на базові 16 кольорів терміналу, а отже добре узгоджується з темою терміналу; недоліки: мені не подобається фуксиновий акцент і невиразність майже повністю одноманітного тексту;
  • bruin (romain lafourcade) — контрастна й більше типографічна, ніж колірна тема: агресивно використовує атрибути тексту (жирний, нахилений, підкреслений) і яскравість, ніж кольори; «прозора»; з недоліків (як на мій смак) — місцями надмірна контрастність і брак консистентності у використанні кольорів;
  • warlock (martin hardselius) — монохромна тема в тепло-сірих тонах і з помірним контрастом, що повністю уникає кольорів; з недоліків — брак акценту, ані кольором, ані текстовими атрибутами, «непрозорість» (вигляд відрізняється від терміналу під vim’ом), і мені не подобається використання різного тла.

порівняння: quiet, warlock, bruin

можна було би взяти будь-яку з цих трьох тем і трішечки адаптувати для себе. проте мені було цікаво, як влаштовані теми vim і чи складно зробити щось «з нуля».

що я хочу?

хотілося б поєднати найкращі риси трьох перелічених тем:

  • «прозорість» для палітри терміналу під vim’ом;
  • монохромна основа: відтінки сірого;
  • синтаксична підсвітка — стримана, за рахунок тонів
  • один колірний акцент, як у quiet, але з іншим кольором;
  • не надмірна контрастність (хоча це залежатиме від терміналу);
  • помітно тьмяніший за основний відтінок для нумерації рядків та недрукованих символів.

робоча назва для першої спроби — embers, як у «ashes and embers» (попіл та жар), тому що тема буде переважно темна в тонах від чорного до білого, з червоним акцентом (і, можливо, трохи жовтого для окремих елементів інтерфейсу).

підготовка

підготовка робочої теки:

~> mkdir -p ~/projects/vim/vim-embers/colors
~> cd ~/projects/vim/vim-embers
# порожній файл майбутньої теми
…vim-embers> touch colors/embers.vim
# лінк на цей файл там, де vim шукає теми
…vim-embers> ln -s $HOME/projects/vim/vim-embers/colors/embers.vim $HOME/.vim/colors/embers.vim
# git, тому що без нього неправильно
…vim-embers> git init

за взірець варто брати стандартну тему: вони, як правило, дуже якісні, добре структуровані, і дотримуються вельми суворих обмежень; в мене quiet лежить тут:

…vim-embers> ls -l /usr/share/vim/vim*/colors/quiet.vim
-rw-r--r-- 1 root root 38959 січ  2 13:36 /usr/share/vim/vim91/colors/quiet.vim
…vim-embers> cp /usr/share/vim/vim91/colors/quiet.vim ~/projects/vim/vim-embers/colors

невелика функція (додати до .vimrc) для визначення колірної групи слова/символа під курсором (:help highlight):

" функція для визначення групи highlight слова під курсором
" використання: call SynGroup()
" або <Leader>h (зазвичай \h) в нормальному режимі
function! SynGroup()
    let l:s = synID(line('.'), col('.'), 1)
    echo synIDattr(l:s, 'name') . ' -> ' . synIDattr(synIDtrans(l:s), 'name')
endfun
nnoremap <Leader>h :call SynGroup()<CR>

скрипт colortest.vim для відображення кольорів за назвами:

:source $VIMRUNTIME/syntax/colortest.vim

скрипт hitest.vim для відображення всіх активних груп з використанням відповідних стилів:

:source $VIMRUNTIME/syntax/hitest.vim

анатомія колірної теми

опційний заголовок-коментар:

" Name:         quiet
" Description:  A mostly monochrome colorscheme, with a few niceties.
" ...

далі короткий блок для скидання поточної колірної теми й підсвітки синтаксису, якщо є:

hi clear
if exists("syntax_on")
    syntax reset
endif

і назва теми:

let g:colors_name = 'quiet'

g: в назві змінної визначає контекст (глобальний) змінної. далі в quiet йде блок, котрий зв’язує семантичні групи між собою:

hi! link Terminal Normal

загальний сенс рядка: для семантичної групи terminal встановити такі ж атрибути, як у normal. hi — це highlight (див. :help highlight); знак оклику в hi! link... потрібен, бо за замовчуванням зв’язування груп не працює, якщо перша група вже має якісь атрибути (кольори, атрибути тексту, — наприклад, встановлені іншою темою чи в .vimrc), тому треба примусово встановити прив’язку. тобто деінде в файлі теми має бути інший рядок, що фактично задає стиль групі normal, або декілька:

hi Normal guifg=#dadada guibg=#000000 gui=NONE cterm=NONE
...
hi Normal ctermfg=253 ctermbg=16 cterm=NONE
...
hi Normal term=NONE
  • guifg, guibg, gui — колір символа, тла, додаткові атрибути в графічному інтерфейсі;
  • ctermfg, ctermbg, cterm — те саме в кольоровому терміналі (cterm)
  • term — атрибути тексту для базового терміналу.

ці три рядки можна було би об’єднати, і деякі теми так і влаштовані:

hi Normal term=NONE ctermfg=253 ctermbg=16 cterm=NONE guifg=#dadada guibg=#000000 gui=NONE

…але найчастіше визначення стилів згруповано в умовний блок: окремо для графічного інтерфейсу, для кольорового терміналу і для базового терміналу. ось так у quiet (я не певен, що тут не наплутано трохи):

let s:t_Co = has('gui_running') ? -1 : (&t_Co ?? 0)
  if (has('termguicolors') && &termguicolors) || has('gui_running')
    ...                 " gui (termguicolor)?
  endif

if s:t_Co >= 256        " повноколірний термінал?
  ...
  unlet s:t_Co
  finish
endif

if s:t_Co >= 16         " кольоровий термінал 256 кольорів?
  ...
  unlet s:t_Co
  finish
endif

if s:t_Co >= 8          " кольоровий термінал 16 кольорів?
  ...
  unlet s:t_Co
  finish

if s:t_Co >= 0          " базовий термінал?
  ...
  unlet s:t_Co
  finish
endif
  • t_Co — глобальна опція, містить кількість кольорів в терміналі (:help t_Co, також :echo &t_Co); наскільки я розумію, не має впливу на vim, якщо той в режимі termguicolors (тому треба перевіряти окремо);
  • s:t_Co — локальна змінна (локальна для скрипта/теми), в яку завантажується доступна кількість кольорів для терміналу (&t_Co), або -1 для графічного інтерфейсу.

всередині кожного такого блоку — різні налаштування для темного і світлого тла; фактично тема quiet наче містить дві: темну й світлу.

if &background ==# 'dark'   " темна тема
    ...
else                        " світла тема
    ...
endif

нарешті, групи highlight… (:help highlight-groups) їх багато; частина з них — стандартні (документація каже, що повна тема має обов’язково призначати їм стилі); інші — стосуються синтаксичної підсвітки різних мов і є опційними.

попіл та жар

отже, почнімо. заголовок:

" Name:         embers
" Description:  монохромна тема в віддінках сірого з типографічним виділенням та єдиним колірним
"               акцентом червого кольору (попіл та жар) і прозорим тлом; покладається на основні
"               кольори (ansi) задля кращої інтеграції з темою термінального емулятора.
" Author:       tivasyk <tivasyk@gmail.com>
" Website:      https://blog.malynka.duckdns.org
" Last Updated: 2024-02-20

прибирання, і назва теми:

hi clear
if exists("syntax_on")
    syntax reset
endif

let g:colors_name = 'embers'

далі буде блок з визначенням базових стилів; на відміну від того, як це зроблено в quiet та інших темах, я хочу визначити базові стилі взагалі окремо. отже…

let s:t_Co = has('gui_running') ? -1 : (&t_Co ?? 0)
if (has('termguicolors') && &termguicolors) || has('gui_running')
    " графічний інтерфейс або повноколірний емулятор терміналу

    unlet s:t_Co
    finish

elseif s:t_Co >= 256
    " багатоколірний термінал

    unlet s:t_Co
    finish

elseif s:t_Co >= 16
    " термінал 256 кольорів

    unlet s:t_Co
    finish

elseif s:t_Co >= 8
    " термінал 16 кольорів

    unlet s:t_Co
    finish

else
    " ймовірно, простий термінал без підтримки кольороів

    unlet s:t_Co
    finish
endif

додаткові матеріяли

далі буде…