rcmdnk's blog
Last update

Fold It Calm: Simple paper folding projects to quieten your mind

vim-markdown で行っているVim上のMarkdownの畳み込みで ちょっと気になる所があったのでいじってみました。

vim-markdownで行ってる畳み込み

vim-markdown ではafter/ftplugin/markdown.vim の中で畳み込みの設定してますが、 これはもともと Original版(plasticboy/vim-markdown) にあったものを取ってきたままのものでした。

これは、基本的に#で指定したり---の下線を引くことで作る 各セクション毎に畳み込む設定です。

ただ、先日のアップデート でYAMLブロックのハイライトを出来る様になって、 ファイルの先頭にあるYAMLブロックをハイライトしてくれる様にはなりましたが、 畳み込みのところでは考慮されておらず、 YAMLブロックの最後の---で閉める所に、上に空白を一行入れないと セクションタイトルとみなされてそれ以下を閉じてしまいます。

特にこれを直したかったのと、 コード部分が長くなる事が多いのでコード部分をたたみ込めたら 便利だな、ということでやってみました。

畳み込みの設定

Vimでの畳込みは

setl foldmethod=expr

とすることでfoldexprで指定される方法で畳み込みを行う様になります。 (該当ファイルに対してだけ設定したいのでsetでなくsetl(setlocal)で。)

このfoldexprに与えられた評価式は各行毎に評価され、 畳み込みの深さを指定します。 そのため、foldexprで与えられた式の中では、行番号として v:lnumという値を使うことが出来ます。

1
setl foldexpr=getline(v:lnum)[0]==\"\\t\"

とすれば、getline(v:lnum)[0]==\"\\t\"の結果は 行の要素の先頭がタブなら1(Vimでは評価が真だと1)、そうでなければ0なので、 タブの行だけ畳み込みの深さが1になってその部分が畳み込まれます。 (0が何も畳み込まれない所、数が大きいほど深い畳み込みの中に入ります。)

また、

1
2
3
4
5
6
7
8
9
func! fold_func(lnum)
  l = getline(a:lnum)
  if l[0] == "\t"
    return 1
  else
    return 0
  endif
  endfunc
setl foldexpr=fold_func(v:lnum)

の様に関数を使っても同じことが出来ます。

畳み込みの深さを指定する返り値には数値以外にも使える物があります。

意味
0 畳み込み無し
1, 2, … 数値の畳み込みの深さを指定
-1 前後の行の浅い方と同じ深さ
"=" 前の行と同じ深さ
"a1", "a2" 数の分だけ前の行より深く設定
"s1", "s2" 次の行の初期状態を数の分だけ前の行より浅く設定
"<1", "<2" 数の深さの畳込みをここで終了
">1", ">2" 数の深さでで畳み込みをここから開始

"s1"について、ヘルプ 1 を見ると

"s1", "s2", ..   subtract one, two, .. from the fold level of the previous line

となってるんですが、実際には現在の行まで畳み込みを同じレベルで行って 次の行で数だけ浅くなる、という動作をします。 (単に書き間違えだと思いますが。。。)

つまり"="なら浅くなった所、"a1"とかなら浅くなったところから開始して 1つ深い所、に設定されます。

"<1"/">1"に関しては、通常は単に1とするのと変わりありませんが、 前後に-1"="があった場合、 それらは1とは評価されず0と評価される様になります。

また、"<1""a1"等を使うと同じ深さの物を並べても別々の畳込みに入れられます。

>1  ---
=
<1  ^^^
>1  ---
=
<1  ^^^
a1  ---
=
s1  ^^^
a1  ---
=
s1  ^^^

各行が上の最初の文字列の様な深さに設定されてるとすると、 右側の---から^^^までの部分でそれぞれ畳み込みが作られます。

Advanced Folding / Learn Vimscript the Hard Way

vim-markdownでの畳み込みのアップデート

新しい畳み込み用の関数はこんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
func! Foldexpr_markdown(lnum)
  " Get syntaxes
  let syn0 = synIDattr(synID(a:lnum-1,1,1), 'name')
  let syn1 = synIDattr(synID(a:lnum,1,1), 'name')
  let syn2 = synIDattr(synID(a:lnum+1,1,1), 'name')

  " YAML block
  if syn1 == 'yamlDelimiter'
    if a:lnum == 1
      return 'a1'
    else
      return 's1'
    endif
  endif
  if syn1 == 'yamlBlockMappingKey'
    return '='
  endif

  " Code
  if syn1 == 'mkdDelimiter' || syn1 == 'liquidTag'
    if syn2 == 'mkdCode'
      return 'a1'
    elseif syn0 == 'mkdCode'
      return 's1'
    endif
  endif

  " Code with vim-markdown-quote-syntax
  if syn1 == 'markdownCodeDelimiter'
    if syn0 == ''
      return 'a1'
    elseif syn2 == ''
      return 's1'
    endif
  endif

  " Code with four spaces
  if syn1 == 'mkdCode'
    if syn0 == ''
      " Avoid one line code
      if syn2 != ''
        return 'a1'
      endif
    elseif syn2 == ''
      return 's1'
    endif
  endif

  " Liquid Comment
  if syn1 == 'liquidComment'
    if syn0 != 'liquidComment'
      return 'a1'
    elseif syn2 != 'liquidComment'
      return 's1'
    endif
  endif

  " Section
  if syn0 =~ 'htmlH[0-9]' || syn1 =~ 'htmlH[0-9]' || syn2 =~ 'htmlH[0-9]'
    " Get lines
    let l0 = getline(a:lnum-1)
    let l1 = getline(a:lnum)
    let l2 = getline(a:lnum+1)

    if syn2 =~ 'htmlH[0-9]'
      if  l2 =~ '^==\+\s*'
        " next line is underlined (level 1)
        return '>1'
      elseif l2 =~ '^--\+\s*'
        " next line is underlined (level 2)
        return '>2'
      endif
    elseif syn1 =~ 'htmlH[0-9]' && l1 =~ '^#'
      " don't include the section title in the fold
      return '-1'
    elseif syn0 =~ 'htmlH[0-9]' && l0 =~ '^#'
      " current line starts with hashes
      return '>'.matchend(l0, '^#\+')
    endif
  endif

  " keep previous foldlevel
  return '='
endfunc

ここでは、各行の情報として、getlineで行の要素を取ってくるのに加えて、 synIDattr(synID(a:lnum,1,1), 'name')として各業の一文字目の ハイライトの名前を使っています。

Vimでハイライト表示を調べる

また、このためにYAMLブロックの区切り文字をyamlDelimiterに変更しました。

この関数内ではSyntaxの中の設定と違って行ごとに行うので、 この区切りからこの区切り、みたいなSyntaxファイルで行ってることと 同じことは出来ません。 (一行評価する毎に全行(もしくは必要な前後の複数行)読み込んで評価する、ということをすれば出来ないことも無いですが。。。)

従って、ここでは区切りを見つけて、その前の行や後ろの行を見ることで どうするべきか決めていたりもします。

yamlDelimiterの場合には必ず1行目に最初が来て、 後は閉じるだけなので1行目なら開始、それ以外なら終了、といった感じ。

コードブロックでは区切りがmkDelimiterliquidTag(Octopressのcodeblock文) の場合があり、これらでは次の行がコードブロック内なら開始行、 前の行がコードブロック内なら終了行、と言った感じ。

こちらは色の設定の関係でcodeDelimiterみたいなのを作成したいとしても それぞれ用にしなくちゃいけない上、そのまま使っても特に問題ないので 他と共通な上のハイライト名で行っています。

また、markdownCodeDelimiter先日のアップデートjoker1007/vim-markdown-quote-syntax に戻したコードブロック内を各言語でハイライトする場合に使う物で、 この場合、ブロック内はmkdCodeではなく、 各コードのハイライトになるのでそれで指定するのは難しいので 前後の行が空白の場合、で行っています。

{% codeblock %}前後に空白が無いときちんとビルド出来ないことが あるので空白が必要なのでこれで良いかと。

スペース字下げによるコードも畳み込めるようにしてありますが、 1行だけの場合にはしないようにしています。 意味が無いのと、もしやろうとすると、開始と終了の行が同じになるので こんな感じではできず、コードの次の空白行まで含めて、 空白行で一つ前がコードでその前が空白、みたいな事をチェックして、 とか必要になって面倒しかないのでやめています。

後はコメント行も畳み込める様にしてあります。

セクションタイトルのとこもシンタックスハイライトの名前を使って分かりやすく。

これでOctopressでブログを書くのが大分やりやすくなりました。

追記: 2014/11/19

見た目はかなり良くなったんですが Syntaxをチェックする際に結構負荷があるらしく、 すべての行で行うため、大きめのファイルを開くと結構時間がかかるのと、 編集したりする際にも負荷がかかってダメだったので、 この設定はオプションにしました。 デフォルトでは上の機能はなしで各セクション毎 (---#を見るだけ)の畳込みだけで、 上記の機能を使いたい場合は

let g:vim_markdown_better_folding=1

.vimrcの中で設定すると有効になります。

追記ここまで

Sponsored Links
Sponsored Links

« Wifiの信頼性を改善したYosemite 10.10.1のリリース Brew-fileを高速化 »

}