hasciiの日記

多分プログラミング関係

ovs + lacp でのトラブルシューティングメモ

OpenvSwitch + LACP 構成でのメモ

書きかけのメモです

調査の流れ

Bonding 状態確認

ovs-appctl bond/show [bond]

LACP 状態確認

ovs-appctl lacp/show [bond]

Active な NIC を設定

ovs-appctl bond/set-active-slave [bond] [interface]

http://openvswitch.org/pipermail/discuss/2013-October/011651.html

OpenvSwitch バージョン確認

ovs-vsctl --version

チェックする設定項目

http://openvswitch.org/support/dist-docs/ovs-vswitchd.conf.db.5.html

  • lacp: active or passive or off
  • bond-detect-mode
  • bond-hash-basis
  • lacp-fallback-ab

関係ある?

network, openvswitch のサービスの起動順

http://blog.siliconloons.com/fedora-17-with-open-vswitch/

GNU GLOBAL 使用環境設定

GNU GLOBAL をインストール、多言語対応のためのプラグインセットアップの手順

インストール環境

OS: ubuntu server 14.04

GNU GLOBAL のインストールとセットアップ

Ubuntu のパッケージのバージョンは古いので、ソースコードからビルドする。

sudo apt-get install gcc make automake autoconf flex gperf cvs bison libncurses5-dev texinfo ctags libtool
cvs -z3 -d:pserver:anonymous@cvs.savannah.gnu.org:/sources/global co global 
cd global
sh ./reconf.sh
./configure && make && sudo make install

プラグインによる多言語対応

デフォルトだと対応言語が少ないのでプラグインを入れる。 プラグインには global-pygments-plugin を使用させていただく。

事前設定

sudo pip install Pygments
sudo apt-get install python-pip libtool

後は、global-pygments-plugin の README.md の通りにセットアップ

global-pygments-plugin セットアップ

リポジトリのチェックアウトとビルド

git clone https://github.com/yoshizow/global-pygments-plugin
cd global-pygments-plugin
sh ./reconf.sh
./configure && make && sudo make install

.globalrcサンプル が用意されているのでそれをベースにする。

ローカルyumサーバを立ててそこを参照して RDO で OpenStack をセットアップ

目標

  • インターネットに接続できないサーバに RDO (packstack) を使用して OpenStack を構築できるようにする。
  • 上記サーバが接続可能な yum サーバはローカルに構築する
  • yum サーバ構築時はインターネットに接続可能とする。
  • 各サーバの初期状態は CentOS の 6.5 を minimal インストール (+ zsh, screen, git を yum で入れた状態)

やること

  • ローカルの yum サーバを作成
  • インターネット上にある yum リポジトリとの sync
  • RDO で OpenStack 構築

構成

以下みたいな特殊なネットワーク環境

 +--------------------------+
 |         Router           |
 +--------------------------+
              |
              |
 +--------------------------+
 |       yum server         |
 |         172.16.1.1       |
 +--------------------------+
              |
              |
 +--------------------------+
 |       OpenStack          |
 |         172.16.1.2       |
 +--------------------------+

手順

yum サーバー設定

yum.repos.d に リポジトリを追加

公開するパッケージをダウンロードしてくるリポジトリを登録する。 OpenStack のバージョンに応じて以下のサイトから rdo-relase をインストールする。

http://repos.fedorapeople.org/repos/openstack

ブラウザで開いてインストールしたいリリースバージョンをリポジトリとして登録する。

例) havana-7 の場合

yum install http://repos.fedorapeople.org/repos/openstack/openstack-havana/rdo-release-havana-7.noarch.rpm

以下、havana-7 をインストールした場合として進める。

(参考)

rpm -ql rdo-release-havana-7 を実行してインストールされたファイルの確認

/etc/yum.repos.d の下に以下の repo ファイルが追加される:

  • /etc/yum.repos.d/foreman.repo
  • /etc/yum.repos.d/puppetlabs.repo
  • /etc/yum.repos.d/rdo-release.repo

それぞれの GPG-KEY はそれぞれ以下:

これらのリポジトリから rpm をダウンロードしてローカルに公開すれば良い。

http サーバを準備

http で公開するため、httpd を入れる。 (http サーバなら何でも良いが簡単なので)

yum install httpd
chkconfig httpd on
service httpd start

apache の設定は省略。 その他、selinux やら iptables やら、hosts.allow やらセキュリティポリシに合わせて設定。 ここでは、ローカルの実験用環境なのでノーガード設定。

chkconfig iptables off
service iptables stop
setenforce 0
sed -i -e 's/SELINUX=enforcing/SELINUX=disabled/' /etc/sysconfig/selinux

yum リポジトリをローカルに sync して公開

yum リポジトリメタデータを作成するための createrepoリポジトリからダウンロードするための yum-utils を入れる。

yum install createrepo yum-utils

rdo 関係リポジトリ

追加したリポジトリrpm をローカルに sync する。 コマンドは以下 (sync には時間がかかるので注意) そのまま公開できるように /var/www/html 以下で作業する。

cd /var/www/html
reposync -r openstack-havana -n -t /var/cache/yum
reposync -r puppetlabs-products -n -t /var/cache/yum
reposync -r puppetlabs-deps -n -t /var/cache/yum
createrepo ./openstack-havana
createrepo ./puppetlabs-products
createrepo ./puppetlabs-deps

参考までにファイルサイズ

du -hs ./openstack-havana/
438M    ./openstack-havana/
du -hs ./puppetlabs-products/
101M    ./puppetlabs-products/
du -hs ./puppetlabs-deps/
61M     ./puppetlabs-deps/

(補足情報)

  • reposyncリポジトリで公開されている rpm をダウンロードする。
    • 保存先は <カレントディレクトリ>/<リポジトリ名>/ の下
    • -n をつけると最新のものだけをダウンロードする。 そのリリースバージョンの古いものも欲しかったら指定しない。
    • 他のオプションは man reposync 参照
  • createrepo は指定ディレクトリ以下を yum リポジトリとするための metadata を作成する

CentOS の基本リポジトリ

base, extras, updates のパッケージが必要。 reposync で持ってくる方法と配布されている DVD メディアを使用する方法がある。

reposync する場合

reposync して最新のものを持ってくる。 ただ、ファイルサイズが大きいので回線速度にもよるが結構な時間がかかる。

cd /var/www/html
reposync -r base -n -t /var/cache/yum
reposync -r extras -n -t /var/cache/yum
reposync -r updates -n -t /var/cache/yum
createrepo ./base
createrepo ./extras
createrepo ./updates

参考までにファイルサイズ

du -hs ./base/
5.2G    ./base/
du -hs ./extras/
9.2M    ./extras/
du -hs ./updates/
1.1G    ./updates/
media を使用する場合

配布されている DVD のイメージがあればそれを使用した方が早い。

以下手順。

まず、どうにかしてディスクイメージをホストに持っていく VMware 環境の場合はゲストの仮想ドライブを使用するなり、 ISO の場合はそのままホストにコピーして使用するなり。

マウントして中身コピー

mount /dev/cdrom /mnt
cp -rf /mnt/Packages/ /var/www/html/media

ISO を直接持っていった場合は mount -o loop とか、

DVD2 に入っているものも使うので、DVD1, DVD2 それぞれで上記を実施。

コピーが終わったら createrepo で metadata を生成する。 パッケージ数が多いので時間がかかる、、、

cd /var/www/html/
createrepo ./media

EPEL

epel の一部パッケージも使用するのでその準備。 epel のリポジトリ登録 (参考: https://fedoraproject.org/wiki/EPEL)

yum install http://ftp.jaist.ac.jp/pub/Linux/Fedora/epel/6/i386/epel-release-6-8.noarch.rpm

reposync しても良いが、サイズが大きくダウンロードするにも時間がかかるので (大体 10 GB ぐらい) 必要なものだけをダウンロードしてリポジトリ化する。

rpm のダウンロードには yum の downloadonly plugin を使用する。 これを入れて yum install に --downloadonly オプションを指定するとインストールはせずに /var/cache/yum にダウンロードだけしてくれる。

openstack のコンポーネントのパッケージ指定だけして、パッケージのリストアップは yum にまかせる。

yum install --downloadonly openstack-nova openstack-nova-compute openstack-glance openstack-neutron openstack-swift openstack-keystone openstack-cinder openstack-ceilometer-common openstack-heat python-django-compressor openstack-swift-plugin-swift3 mongodb-server openstack-ceilometer-api
yum install --downloadonly nagios-plugins-nrpe nagios-plugins-ping nagios nrpe

上記で大体の構成で必要なものは揃っているはず、、、足りないものがあったら適宜追加。

cd /var/www/html/
mkdir epel
cp /var/cache/yum/x86_64/6/epel/packages/*.rpm ./epel/

OpenStack 側

yum リポジトリ設定

yum server と同様に rdo-realse をインストール。 ただし、今度は先ほど立てた yum サーバから

yum install http://172.16.1.1/openstack-havana/rdo-release-havana-7.noarch.rpm

openstack-havana と puppetlabs の URL を書き換えてローカルに向ける。

sed -i -e 's/repos.fedorapeople.org\/repos\/openstack\/openstack-havana\/epel-6/172.16.1.1\/openstack-havana/' /etc/yum.repos.d/rdo-release.repo
sed -i -e 's/yum.puppetlabs.com\/el\/6\/products\/\$basearch/172.16.1.1\/puppetlabs-products/' /etc/yum.repos.d/puppetlabs.repo
sed -i -e 's/yum.puppetlabs.com\/el\/6\/dependencies\/\$basearch/172.16.1.1\/puppetlabs-deps/' /etc/yum.repos.d/puppetlabs.repo

foreman.repo は使用しないので無効化しておく

sed -i -e 's/enabled=1/enabled=0/' /etc/yum.repos.d/foreman.repo

yum サーバ上のリポジトリを記載する。 ローカルだし面倒なので GPG-KEY はなし。 epel のリポジトリ名は [epel] にすると packstack に enabled=0 されてしまうので適当な名前を付けること

vi /etc/yum.repos.d/local.repo

media か base, extras, updates かはどの手順で作成したかによって enabled を切り替える。

[media]
name=CentOS Media
baseurl=http://172.16.1.1/media
enabled=0
gpgcheck=0

[base]
name=CentOS Media
baseurl=http://172.16.1.1/base
enabled=1
gpgcheck=0

[extras]
name=CentOS Media
baseurl=http://172.16.1.1/extras
enabled=1
gpgcheck=0

[updates]
name=CentOS Media
baseurl=http://172.16.1.1/updates
enabled=1
gpgcheck=0

[epel-part]
name=Part of EPEL
baseurl=http://172.16.1.1/epel
enabled=1
gpgcheck=0

NameServer 設定

nameserver が設定されていないと packstack が実行できないので 設定する、今回は yum サーバと同じ 172.16.1.1 を nameserver とする。

NameServer 側

dnsmasq を入れて、172.16.1.2 の名前を応答するようにしておく。 (172.16.1.2 のホスト名は rdo-havana)

yum install dnsmasq
echo "listen-address=127.0.0.1,172.16.1.1" >> /etc/dnsmasq.conf
echo "172.16.1.2 rdo-havana" >> /etc/hosts

OpenStack サーバ側

name server のアドレスとして 172.16.1.1 を登録

echo "nameserver 172.16.1.1" >> /etc/resolv.conf

packstack によるセットアップ中は自身の IP アドレスの名前解決を何度も行うので /etc/hosts に書いておく。

echo "172.16.1.2 rdo-havana rdo-havana.local" >> /etc/hosts

もちろん DNS サーバ側に問い合わて解決させても良いけど、 自己解決させた方が無駄に通信を行わない。

packstack 実行

OpenStack をインストールするホストで packstack を実行して、OpenStack をセットアップする。

packstack では answer ファイルを用意することで構成を変更することができるが、 ここでは allinone ですべてつっこむ。 (packstack の使い方についてはここでは割愛。公式のドキュメントがわかりやすいので見てください)

そのまま実行すると、RDO が epel リポジトリyum.repos.d に追加して 接続しようとするためこけるので、--use-epel オプションに n を指定する。

packstack --allinone --use-epel=n

性能によるが、大体 30 分ぐらいでセットアップが終わる。

課題

CirrOS がダウンロードできない。

RDO では本来は最初から使えるイメージとして CirrOS が用意されるはずだが 途中でイメージのダウンロードできないため、CirrOS が無い状態になってしまう。

やらないといけないこと - download.cirros-cloud.net. を名前解決させて 172.16.1.1 に向ける - そこから CirrOS をダウンロードさせる

http プロキシ環境内で OpenDaylight とりあえず動かしたメモ

OpenDaylight をプロキシ越しでしかインターネッツアクセスできない環境内でセットアップしたときのメモ

とりあえず Ubuntu server 12.10 に OpenDaylight 入れて画面確認したところまで

2013/4/14 時点のソースコードで実施

下準備

多分以下の 3 つが入っていれば OK. apt の proxy 設定を /etc/apt/apt.conf で行なっておくこと。

git

  sudo apt-get install git

java

  sudo apt-get install openjdk-7-jdk

maven3

  sudo apt-get install maven
  wget http://ftp.br.debian.org/debian/pool/main/m/maven/maven_3.0.4-3_all.deb
  dpkg -i maven_3.0.4-3_all.deb

ビルド

ソースコードをチェックアウト

  git clone http://git.opendaylight.org/gerrit/p/controller.git

maven のプロキシ設定

ビルド中に依存解決のため http アクセスしまくるのでプロキシ設定しておく

  vi ~/.m2/settings.xml
  <settings>
    <proxies>
      <proxy>
        <active>true</active>
        <protocol>http</protocol>
        <host>プロキシホスト</host>
        <port>ポート番号</port>
        <username>ユーザ名</username>
        <password>パスワード</password>
        <nonProxyHosts>プロキシ使わないドメイン</nonProxyHosts>
      </proxy>
    </proxies>
  </settings>

ビルド実行

デフォ状態だと OutOfMemory でこけたのでメモリ割り当てを増やしておく

  export MAVEN_OPTS='-Xmx1024m -XX:MaxPermSize=256m' 

pom.xml があるディレクトリに移動してテスト、インストール

  cd controller/opendaylight/distribution/opendaylight/
  mvn test

問題なければ

  mvn install

実行

  cd target/<ビルド名>/opendaylight

そのまま実行すると http 越しの beans を読みにいってエラーが出るのでここでも http の Proxy 設定

 vi run.sh
 $JAVA_HOME/bin/java $@ \
     -Djava.io.tmpdir=${basedir}/work/tmp \
     -Dosgi.install.area=${basedir} \
     -Dosgi.configuration.area=${basedir}/configuration \
     -Dosgi.frameworkClassPath=${FWCLASSPATH} \
     -Dosgi.framework=file:${basedir}/lib/org.eclipse.osgi-3.8.1.v20120830-144521.jar \
 +   -Dhttp.proxyHost="プロキシホスト名" \
 +   -Dhttp.proxyPort="ポート番号" \
 +   -Dhttp.proxyUser="ユーザ名" \
 +   -Dhttp.proxyPassword="パスワード" \
     -classpath ${CLASSPATH} \
     org.eclipse.equinox.launcher.Main \
     -console \
     -consoleLog

環境変数 JAVA_HOME を設定してから run.sh を実行

 ./run.sh

httpで 8080 ポートにアクセスすれば画面が見えるはず

Vim で現在カーソルがある行と同じインデントを強調表示

最近ちょっと Vimpython を読む機会があったのだけど、ぱっと見てインデントが同じ行がどこまで分からず読み辛かった。
そんなわけで、現在カーソルがある行と同じ(もしくはそれ以上の)インデントレベルのところを強調表示する方法を模索したのでそのメモ

方法1: vim-indent-guides を使う

インデントを強調表示する、で検索すると出てきたのがこれ。

パッと使った感じ、以下の機能があった

  • 等間隔で列を強調表示可能
  • 色は自由に指定可能

多分もっと高機能っぽいけど、今回はこんなにおおげさな機能はいらなかった(というかデフォルトのままだと情報量が多すぎる。カスタマイズするのは面倒だった)のでパス

方法2: 適宜 highlight パターンを作る

今回とったのはこっち。
現在カーソルがある行の文字列を取得して、highlight 対象とするパターンを返却する簡単な関数を作った。

以下その関数

function! s:indent_pattern()
  let l:line = getline('.')
  let l:indent = matchstr(l:line, '^\zs\s\+\ze\S')
  let l:len = len(l:indent) - 1
  if l:len > 0
    return '^\s\{'. l:len . '}\zs\s\ze'
  else
    return '^\zs\s\ze'
  endif
endfunction

# これだとスペースとタブが入り混じってると変な highlight になるけど気にしない。そんなコード書く方が悪い。

この関数から取得したパターンを matchadd で適当な highlight に追加してやればやりたいことができるはず。
matchadd とか matchdel する仕組みについては、ここ何回か同じような仕組みでほげほげしてきたので、ある程度汎用的に使えるようにした。

以下そのコード

" defined highlight sources
let g:auto_highlights = {}

" control enable/disable auto highlighting
let g:enable_auto_highlight = 1

" disable auto highlight when filetype in below
let g:auto_highlight_disable_file_type = ['unite', 'tmp']

function! s:compare_source(i1, i2)
  return a:i1.priority == a:i2.priority ? 0 : a:i1.priority > a:i2.priority ? 1 : -1
endfunction

function! s:init_window_auto_highlight()
  let l:info = {}
  for highlight_kind in keys(g:auto_highlights)
    let l:info[highlight_kind] ={
          \ 'current_match_pattern' : '',
          \ 'current_match_id' : -1,
          \}
  endfor
  return l:info
endfunction

" function for add highlight source
function! s:define_auto_highlight_source(src)
  if !has_key(g:auto_highlights, a:src.highlight)
    let g:auto_highlights[a:src.highlight] = {
          \ 'sources' : [],
          \}
  endif
  call add(g:auto_highlights[a:src.highlight].sources, a:src)
  call sort(g:auto_highlights[a:src.highlight].sources, 's:compare_source')
endfunction

" main
function! s:start_highlight()
  if !get(g:, "enable_auto_highlight", 0) || index(get(g:, 'auto_highlight_disable_file_type', []), &filetype) >= 0
    return
  endif
  if !exists('w:auto_highlight_info')
    let w:auto_highlight_info = s:init_window_auto_highlight()
  endif
  for [item, value] in items(g:auto_highlights)
    for src in value.sources
      let l:pattern = src.pattern()
      if !empty(l:pattern)
        let l:target = w:auto_highlight_info[item]
        if l:target['current_match_pattern'] != l:pattern
          call s:ClearHighlight(item)
          let l:target['current_match_pattern'] = l:pattern
          let l:target['current_match_id'] = matchadd(item, l:pattern, src.priority)
        endif
        break
      endif
    endfor
  endfor
endfunction

" clear before highlight
function! s:ClearHighlight(kind)
  if exists('w:auto_highlight_info')
    if w:auto_highlight_info[a:kind].current_match_id >= 0
      call matchdelete(w:auto_highlight_info[a:kind].current_match_id)
      let w:auto_highlight_info[a:kind].current_match_id = -1
      let w:auto_highlight_info[a:kind].current_match_pattern = ''
    endif
  endif
endfunction

" toggle enable/disable auto highlight
function! s:toggle_auto_highlight()
  let g:enable_auto_highlight = !get(g:, "enable_auto_highlight", 0)
  call s:CheckEnableHighlightCurrentWord()
endfunction

function! s:CheckEnableHighlightCurrentWord()
  if !g:enable_auto_highlight
    for kind in keys(g:auto_highlights)
      call s:ClearHighlight(kind)
  endfor
  endif
endfunction

command! -bar ToggleCurrentHighlight call s:toggle_auto_highlight()
command! -bar CurrentHighlight call s:start_highlight()

後、適当な autocmd に CurrentHighlight を実行するように設定してやる。
で、これまで作成した highlight を登録

" current word highlight
let s:cword_highlight = {
      \ 'name': 'current_word',
      \ 'highlight': 'CurrentWord',
      \ 'priority': 10,
      \}

function! s:EscapeText( text )
  return substitute( escape(a:text, '\' . '^$.*[~'), "\n", '\\n', 'ge' )
endfunction

function! s:cword_highlight.pattern()
  let l:cwd = expand('<cword>')
  if empty(l:cwd)
    return ''
  else
    let l:regexp = s:EscapeText(l:cwd)
    return l:cwd =~# '^\k\+$' ? '\<' . l:regexp . '\>' : l:regexp
  endif
endfunction
call s:define_auto_highlight_source(s:cword_highlight)

" matchit highlight
let s:matchit_highlight = {
      \ 'name': 'matchit',
      \ 'highlight': 'CurrentWord', 
      \ 'priority': 9,
      \}

function! s:InitMatchit()
  if !exists('b:match_words')
    return
  endif
  let l:mw = filter(split(b:match_words, ',\|:'), 'v:val !~ "^[(){}[\\]]$"')
  let b:reserved_regexp = join(l:mw, '\|')
  let mwre = '\%(' . b:reserved_regexp . '\)'
  let b:mwre = substitute(mwre, "'", "''", 'g')
endfunction

function! s:matchit_highlight.pattern()
  if !exists('b:match_words')
    return ''
  endif
  if !exists('b:reserved_regexp')
    call s:InitMatchit()
  endif
  if expand("<cword>") !~ b:reserved_regexp || empty(b:reserved_regexp)
    return ''
  endif
  let lcs = []
  let wsv = winsaveview()
  while 1
    exe 'normal %'
    let lc = {'line': line('.'), 'col': col('.')}
    if len(lcs) > 0 && (lcs[0] == lc || lcs[-1] == lc)
      break
    endif
    call add(lcs, lc)
  endwhile
  call winrestview(wsv)

  call map(lcs, '"\\%" . v:val.line . "l\\%" . v:val.col . "c"')
  let lcre = join(lcs, '\|')
  " final \& part of the regexp is a hack to improve html
  return '.*\%(' . lcre . '\).*\&' . b:mwre
  "return '.*\%(' . lcre . '\).*\&' . b:mwre . '\&\% (<\_[^>]\+>\|.*\)'
endfunction
call s:define_auto_highlight_source(s:matchit_highlight)

" indent highlight
let s:indent_highlight = {
      \ 'name': 'indent',
      \ 'highlight': 'Indent', 
      \ 'priority': 10,
      \}

function! s:indent_highlight.pattern()
  let l:line = getline('.')
  let l:indent = matchstr(l:line, '^\zs\s\+\ze\S')
  let l:len = len(l:indent) - 1
  if l:len > 0
    return '^\s\{'. l:len . '}\zs\s\ze'
  else
    return '^\zs\s\ze'
  endif
endfunction
call s:define_auto_highlight_source(s:indent_highlight)

作っておいて何だけど多分これ以上 source 増やさない。あんまり情報量増えても害なので…

以下あったら嬉しいかもしれない機能

  • 変数の上にカーソルがあるとき、その変数の定義部分 or 代入部分だけ色を変える

Vim で if endif とか対応するキーワードを強調表示する

昨日の続き

はじめに

カーソルが if とか対応するものが存在するキーワード上にあるとき、endif とか対応するキーワードを強調表示させる。この機能も多分大体の IDE にはあると思う。
これも Vim でコード読むときあったら便利な気がしたので調べた。

これを実現するための要素

  • カーソルの下の単語が if とか for とか対応するワードが存在するものかどうかの判別
  • 対応するキーワードの取得 (できればネストの考慮)

他、前回の内容

コード

実は "matchit highlight" でググれば同じようなことをしている人が見つかった。
(リンクはっていいか不明なので省略、ググってください)
それを参考にしつつ、記述したのが以下のコード

function! s:GetMatchitPattern(cwd)
  if !exists('b:match_words')
    return ''
  endif
  if !exists('b:mw')
    call s:InitMatchit()
  endif
  for pat in b:mw
    if a:cwd =~# pat
      let l:cmw = pat
      break
    endif
  endfor
  if !exists("l:cmw")
    return ''
  endif

  let lcs = []
  let wsv = winsaveview()
  while 1
    exe 'normal %'
    let lc = {'line': line('.'), 'col': col('.')}
    if len(lcs) > 0
      if (lc.line == lcs[0].line && lc.col == lcs[0].col) || (lc.line == lcs[-1].line && lc.col == lcs[-1].col) 
        break
      endif
    endif
    call add(lcs, lc)
  endwhile
  call winrestview(wsv)
  call map(lcs, '"\\%" . v:val.line . "l\\%" . v:val.col . "c"')
  let lcre = join(lcs, '\|')
  return '.*\%(' . lcre . '\).*\&' . b:mwre
endfunction

function! s:InitMatchit()
  if !exists('b:match_words')
    return
  endif
  let b:mw = split(b:match_words, ',\|:')
  let l:mw = filter(b:mw, 'v:val !~ "^[(){}[\\]]$"')
  let mwre = '\%(' . join(l:mw, '\|') . '\)'
  let b:mwre = substitute(mwre, "'", "''", 'g')
endfunction 

(ほとんど参考元と同じ)
matchit.vim を有効にしていないと動作しません
上記の s:GetMatchitPattern の戻り値を前回同様 matchadd で指定してやれば、if, else, endif などが強調表示される。

内容メモ

ポイントは以下

  • b:match_words
  • マッチング用の正規表現に行番号と列番号を使用
  • winsaveview() と exe 'normal %', winrestview()
b:match_words

b:match_words は matchit.vim プラグインが ( や { 以外の言語特有の対応パターンを認識するのに使用している変数。
file type のプラグインなどで、 <開始パターン>[:<中間パターン>]:<終了パターン>[,他パターン...] の形式で定義されている。

例) filetype=vim の場合(一部抜粋)

\<fu\%[nction]\>:\<retu\%[rn]\>:\<endf\%[unction]\>,\<\(wh\%[ile]\|for\)\>:\<brea\%[k]\>:\<con\%[tinue]\>:\<end\(w\%[hile]\|fo\%[r]\)\>

このパターンを使用して、現在のカーソル下の単語が特殊なキーワードかどうか判別できる
その場合は、split() を使用して、',' と '|' で分割してやればよい

マッチング用の正規表現に行番号と列番号を使用

Vim正規表現では、行番号と列番号を指定可能

例えば、"\%100l" のように指定するとそのファイルの 100 行目がマッチング対象となる。
さらに、"\%100l\%2c" と指定すれば、ファイルの 100 行目の 2 文字目がマッチング対象となる。
後は、その文字の後ろに b:match_words から取り出した単語をマッチング対象として加えれば良い。

winsaveview() と exe 'normal %', winrestview()

簡単に説明すると、
1. winsaveview() で現在の画面の状態を保存
2. exe 'normal %' で '%' を押したのと同じ状態にする (この後、現在の行番号と列番号を取得)
3. winrestview() で最初に保存した画面の状態を復元

関連する help

  • b:match_words
  • len()
  • add()
  • exists()
  • line()
  • col()
  • map()
  • filter()
  • split()
  • join()
  • execute
  • winsaveview()
  • winrestview()

課題

  • 相変らず CursorMove 系の autocmd から呼んでいるので、非力な環境だと辛いかも

(しかし、独立して使用可能なタイマー機能が Vim にはない…)

Vim で現在の単語を強調表示する

はじめに

IDE なんかでよくある現在の単語が強調表示されるあの機能が vim でも欲しくなった。
ググっても似たようなことしてる人も見つからないし、簡単な内容なので、vim script の勉強がてら書いてみる

以下ができれば実現できるはず

  • カーソル下の単語取得
  • highlight の設定方法
  • 適当なトリガー設定

コード

もったいぶるほどでもないので書いてしまうと以下のようになった

highlight CurrentWord term=NONE ctermbg=DarkMagenta ctermfg=NONE

function! s:EscapeText( text )
  return substitute( escape(a:text, '\' . '^$.*[~'), "\n", '\\n', 'ge' )
endfunction

function! s:GetCurrentWord()
  let l:cword = expand('<cword>')
  if !empty(l:cword)
    let l:regexp = s:EscapeText(l:cword)
    if l:cword =~# '^\k\+$'
      let l:regexp = '\<' . l:regexp . '\>'
    endif
    return l:regexp
  else
    return ''
  endif
endfunction

function! s:HighlightCurrentWord()
  let l:word = s:GetCurrentWord()
  if !empty(l:word)
    if exists("w:current_match")
      call matchdelete(w:current_match)
    endif
    let w:current_match = matchadd('CurrentWord', l:word, 0)
  endif
endfunction

augroup cwh
  autocmd!
  autocmd CursorMoved,CursorMovedI * call s:HighlightCurrentWord()
augroup END

上の内容を vimrc に記述すればスコープとかは気にしなければ大体思った機能になる

内容メモ

大雑把な内容は
1. カーソル下の単語取得
2. 取得した単語をエスケープしたりマッチングする文字列に変換
3. すでにマッチングしてるものがある場合は highlight 対象から削除
4. highlight にマッチング対象文字列追加

autocmd のカーソル移動系にこの関数を追加

関連する help

上の内容に関係する vim の help

  • eval
  • highlight
  • autocmd
  • escape()
  • matchadd()
  • matchdel()
  • substitute()

課題

  • カーソル移動系から呼ぶと無駄に重い
  • スコープを考慮できたらいいかもしれない
  • if とか while とか matchit 系は対応するキーワードが強調された方が多分便利