typeof Diary

VimとかJSとか。

Vimでカラーコードをインクリメントする

Vim Advent Calendar2013 164日目の記事となります。
再開ということで、書きます。何気に3回目。

ゴールデンウィークも終わりましたが、いかがお過ごしでしょうか。
さて、社会人になって早くも1年が経過し、2年目に突入しました。
まとまった休みのありがたみを嫌というほど実感しますね。

さて今回は、初めてVim scriptを書いたというお話です。
同じタイトルの記事を数日前に書いていたのですが、中途半端だったので、
書き直し + VACの記事にしちゃいました。

Vim scriptと正面から向き合う

Vimの使い始めた経緯などは、私とVimを読んでいただければと思います。
あれから月日が経ちましたが、Vim scriptを「やらなきゃ」とは思っていたものの、一歩が踏み出せずにいました。

そこで出会った記事がこちらです。
Vim - プラグインまたはVim scriptを書く楽しさについて語りたい - ぼっち勉強会
一度は目を通していたものの、改めて真剣に読んでみて、かなり背中を押されました。

ネタが浮かばない。がまさに自分で、「やりたい」けど「やれない」の理由は大半がこれです。
そして、世の中には思いついても、ない物の方が少なくて、ほぼ絶対あるんですよね。
ですが、あるからやらないだと、一生身に付きません。

Vim scriptに関しては、下記記事が非常に参考になりました。
Vimスクリプト基礎文法最速マスター - 永遠に未完成
Big Sky :: モテる男のVim Script短期集中講座
Vim Advent Calendar 2013 136日目:Vim script で関数のデフォルト引数を設定したい - C++でゲームプログラミング

大体一度は目を通していたのですが、モチベーションが高い時に読むのと、
普段の状態で読むのとでは、頭への内容の入り方も全然違います。

それと、よく言われますがあとは「ヘルプを読みましょう」ということです。
今回、一番感じたことは、ヘルプを読むことの重要さです。
全てがここにあります。本当に。。。

本題

Vimには<C-a>でインクリメント、<C-x>でデクリメントという、
ちょっとした数値変更にとても便利な機能があります。
8、16進数、アルファベットにも対応ができ、なかなか面白いです。

8進数、アルファベットは置いておいて、16進数に目をつけましょう。
0xを前につけると、16進数と解釈します。
ですが、普段16進数を使う時はどのような時が多いでしょうか。
#RRGGBBの形ではないでしょうか。
最近、仕事の関係で、グラフやマップなど、データの視覚化を扱っており、
style系のプロパティでcolor設定は大体これです。

そこで思いました。
カラーコードのインクリメント、デクリメント。出来たらどうだろう。
(探せば絶対あると思う)

処理を考える
  1. カーソル下のカラーコードを取得
  2. #RRGGBB形式かをチェック
  3. インクリメント or デクリメント
  4. 更新する

ささっと思ったのがこの4つで、初めて書くのには丁度良さそうでした。

無理矢理実装

初めて書いたVim scriptになります。
なかなかひどいコードですが最初ですので…!!

function! s:Color_Fluctuation(type, ...)
  " 第2引数がなければ ''
  let color = get(a:, 1, '')

  " カラーコード
  let colorcode = expand('<cfile>')
  " カーソル位置
  let pos = getpos(".")

  " check
  if !s:Check_ColorCode(colorcode)
    echo colorcode. ' is not color code.'
    return 0
  endif

  " r,g,bで分離
  let red   = str2nr(colorcode[1:2], 16)
  let green = str2nr(colorcode[3:4], 16)
  let blue  = str2nr(colorcode[5:6], 16)
  let c_dict = {'red': red, 'green': green, 'blue': blue}

  if a:type
    " increment
    let new_dict = s:Color_Increment(color, c_dict)
  else
    " decrement
    let new_dict = s:Color_Decrement(color, c_dict)
  endif

  let new_color = s:Get_ColorCode(new_dict)

  " replace
  execute 's/'.colorcode.'/'.new_color.'/g'
  call setpos('.', pos)
endfunction
" ColorCord Increment
function! s:Color_Increment(color, c_dict)
  let max = 255
  let min = 0
  for key in keys(a:c_dict)
    if a:color != ''
      if a:color == key && s:Check_Range(a:c_dict[key], max, min)
        let a:c_dict[key] = a:c_dict[key] + 1
      endif
    else
      if s:Check_Range(a:c_dict[key], max, min)
        let a:c_dict[key] = a:c_dict[key] + 1
      endif
    endif
  endfor

  return a:c_dict
endfunction

" ColorCord Decrement
function! s:Color_Decrement(color, c_dict)
  let max = 256
  let min = 1
  for key in keys(a:c_dict)
    if a:color != ''
      if a:color == key && s:Check_Range(a:c_dict[key], max, min)
        let a:c_dict[key] = a:c_dict[key] - 1
      endif
    else
      if s:Check_Range(a:c_dict[key], max, min)
        let a:c_dict[key] = a:c_dict[key] - 1
      endif
    endif
  endfor

  return a:c_dict
endfunction
" #RRGGBBの形かチェック
function! s:Check_ColorCode(code)
  if a:code =~ '^\#\{1}\x\{6}$'
    return 1
  else
    return 0
  endif
endfunction

" 範囲内かチェック
function! s:Check_Range(color, max, min)
  if a:min <= a:color && a:color < a:max
    return 1
  else
    return 0
  endif
endfunction

function! s:Get_ColorCode(new_dict)
  let s:V = vital#of('vital')
  let s:M = s:V.import('Data.String')
  let code = '#'
  let show = 'Red:'. a:new_dict.red. ' Green:'. a:new_dict.green. ' Blue:'. a:new_dict.blue

  for key in keys(a:new_dict)
    " 10 -> 16
    let val = s:M.nr2hex(a:new_dict[key])

    if strlen(val) == 1
      let val = '0'.val
    elseif  strlen(val) == 0
      let val = '00'
    endif
    let a:new_dict[key] = val
  endfor

  " echo
  echo show

  " create new color code.
  let code = code. a:new_dict.red. a:new_dict.green. a:new_dict.blue
  return code
endfunction

コマンド定義とマッピング

そして、無理矢理には無理矢理なマッピング。。。
<C-A>+<C-X>-にしてあります。

command! -nargs=* ColorIncrement call s:Color_Fluctuation(<f-args>)
nnoremap <C-A> :ColorIncrement 1<CR>
nnoremap <Left> :ColorIncrement 1 red<CR>
nnoremap <Up> :ColorIncrement 1 green<CR>
nnoremap <Right> :ColorIncrement 1 blue<CR>

command! -nargs=* ColorDecrement call s:Color_Fluctuation(<f-args>)
nnoremap <C-X> :ColorDecrement 0<CR>
nnoremap <S-Left> :ColorDecrement 0 red<CR>
nnoremap <S-Down> :ColorDecrement 0 green<CR>
nnoremap <S-Right> :ColorDecrement 0 blue<CR>

左からRGBなので、
インクリメントは、R=<Left> G=<Up> B=<Right> RGB全て=<C-A>
デクリメントは、R=<S-Left> G=<S-Down> B=<S-Right> RGB全て=<C-X>
としました。

動き

NeoBundle 'lilydjwg/colorizer'
colorizerを入れて、色の変化が分かりやすいようにしています。

まとめ

一行まるまる置換しているので、同じ行に同じコードがあれば、全部変わってしまうことと、 よくもまぁこんなに無理矢理書いたなぁと思うことなど、心残りはありますが、
Vim scriptにとりあえず触れてみて、何か作ってみるという点で見れば、得た物は大きかったです。

  • 代入の方法
  • 辞書の使い方
  • 関数定義
  • 呼び出し方
  • 引数へのデフォルト値設定
  • コマンド定義
  • Vital.vimの使い方

結構いろいろ学べています。

コードが綺麗やお行儀なんてものは後からでもどうにでもなりますし、
まずは触れてみて、何でも良いからやってみる。このハードルを超えてみることをオススメします。
写経でも、簡単なものでも、やはりできた作ったが一番のモチベーションです。

とりあえず、何が言いたいかというと!
やめられない 止まらない Vim script

6月にはmomonga.vim#4もありますので、それまでにモチベーションが上げられたことも、
今回良かったことですね。

にしても、最近コード書いてると_の使用率が。。。 以上です。
次回は、せっかくVim scriptに触れたので、まだ触れられずにいる人向けの物が書けたらと思います。

(こっそり修正2 2014/05/14 9:25)

最後に

VAC2013、ぜひ書きましょう!