Hatena::ブログ(Diary)

永遠に未完成 RSSフィード

2012-02-08

$this-> をラクに入力する

PHP 書いてると、$this-> をよく書く割に Shift を 2 回も使わないといけなくてすごく書きづらい。

あまりに書きづらいので @ で $this-> を出すようにした。確か PHPer って @ は滅多なことがないと使わない*1んだよね。

" after/ftplugin/php.vim
inoremap <buffer> @ $this->

これでよし。と言いたいところだけど、コメントの中で @return とか書くときに誤爆する。ので、コメント内と、ついでに文字列内では展開しないようにした。

" after/ftplugin/php.vim
function! s:at()
  let syntax = synstack(line('.'), col('.'))
  let name = empty(syntax) ? '' : synIDattr(syntax[-1], "name")
  return name =~# 'String\|Comment\|None' ? '@' : '$this->'
endfunction
inoremap <expr> <buffer> @ <SID>at()

今度こそこれでよし。ちなみに s:at() 内の最初の2行は他でも使えそうなので私の環境では name を返すようなグローバル関数にして vimrc で定義してる。

どうしても @ が打ちたくなったら <C-v>@ でできるよ。

追記:

文字列の末尾で展開されてしまっていたので、パターンにNoneを追加。

これだと逆に文字列の直前の @ が展開されないけど、こっちはそこまで問題にはならないはず。

2012-02-01

singleton.vim 作った

Vim を 1 つのインスタンスだけで使うためのプラグインsingleton.vim ってのを作った。

初期版を書いたのはずーっと前だったんだけど、整理しきれてなかったのを最近になってようやく公開できそうな感じにした。

多重起動を避けるって意味では、--remote-tab-silent とか使えばいいんじゃねーの? と言う声が聞こえてきそうだけど、このプラグインを使えば起動側で特に考えなくても動くってのと、+αな機能が付いてる。

ちなみに +clientserver 機能必須

https://github.com/thinca/vim-singleton

使い方

.vimrc に以下の1行を書くだけ。簡単!

call singleton#enable()

.vimrc 内の上の方に書いた方がいいけど、pathogen 系を使っている場合は当然その後じゃないとプラグイン見付からないので注意。

あと、いくつか設定(後述)があるのでそれをする場合は関数を呼ぶ前にする。

この設定をすることで何が起きるかは以下。

ファイルをすでに起動しているVimで開く

Vim でファイルを開いた際に、すでに起動済みの Vim があった場合はそちらで開いて即座に終了する。

g:singleton#ignore_pattern に除外するファイルのパターンを書いておけば、それらのファイルだった場合は普通に開かれる。

diff をすでに起動しているVimで開く

vimdiff や vim -d でファイルを開いた場合、起動済みの Vim で新しいタブページに diff モードで開かれる。

VCSGUI フロントエンドの外部 diff ツールにしている場合なんかは特に便利。

標準入力をすでに起動しているVimに送る

Vim標準入力が渡された場合、それを既存の Vim に送って新しいバッファで開き、自分はすぐに終了する。

$ git diff | vim -

ファイルをすでに起動しているVimで開きつつ、編集が終わるのを待つ

世の中には、一時ファイルをテキストエディタで開いて終了を待つことでインタラクティブインターフェースを提供するタイプのアプリがある。visudo や crontab など古くからあるものや、各種 VCS の コミットメッセージやら git rebase -i なんてのもある。

開こうとしたファイルが g:singleton#entrust_pattern のパターンに一致した場合、singleton.vim はそのファイルを起動済みの Vim で開き、自身は終了を待機する。ここは getchar() で若干無理矢理やってる。

リモートの Vim で編集を行い、バッファを閉じると起動元の Vim は自動的に終了して目的が達成できる。

多重起動を避けると何が嬉しいか

まず、起動にかかる時間が短縮できる。

プラグインを大量に入れるタイプの人は、なんだかんだ言って Vim の起動に数秒から運が悪いと数十秒待たされるだろう。

起動済みの Vim にファイルを送ってしまえば、プラグインの読み込みなどは発生しないのでその分時間が短縮できる。

また、複数の Vim でファイルを開くと互いに連携ができないので非常に扱いづらい。コピペすら困難になる。

最初から1つの Vim だけを使っておけば、yanktmp などのプラグインは不要になる。

既存のプラグイン

実は、Vim 本体に $VIM/macros/editexisting.vim と言う似た感じのプラグインが同梱されている。Bram さんお手製だ。

このプラグインは、1 引数Vim を起動した時に、そのファイルをすでに別の Vim で編集していた場合に限り、そちらで開く。

見ての通り低機能で、これは私が欲しいものとは程遠かった。

なぜ autoload 関数なのか

このプラグインsingleton#enable() を呼んで明示的に有効にする必要がある。

しかし、やろうと思えば plugin 以下で自動的に起動するようにすることもできた。

実はやろうかとも思ったのだけど、以下の理由からとりあえずはやらないことにした。

遅くなる

他の Vim でファイルを開くのなら、vimrc の後半やこのプラグインが読み込まれる前に読み込まれるプラグインの読み込みは無駄になり、その分遅くなる。

副作用が大きい

このプラグインを有効にすると、状況に応じてVimが起動直後に終了することになる。そんな人はそもそもいないと思うけど、何も知らずに入れると怖いかもしれない。


とは言え、plugin で自動起動するように大して害はないと思うのでもし要望があれば入れてもいいかなと思ってる。

制限

端末側で、Vimを起動してvimrc内で終了すると、端末を復元する設定になっていても空白が空いてしまう。これ可能ならばなんとかしたいので回避する方法をご存知の方がいたら教えてください><

と言うわけで

ものがものだけに、正直バグがまだまだ残ってそうで不安ではあるけど、人柱したい人は使ってみてください。

2012-01-30

Re: listchars=trail:$してたら気持ち悪いのでbuffer保存時に行末スペースを削除するようにした

コメント書こうかと思ったけど長くなりそうなのでこっちに。

listchars=trail:$してたら気持ち悪いのでbuffer保存時に行末スペースを削除するようにした - 日々是鍛練

コード転載。

command! -bang -bar -complete=file -nargs=0 DeleteSpaceEachLine
\ execute ':%s/\s\+$//'

function! s:AutoUpdate()
  if expand('%') =~ s:savebuf_regex && !&readonly && &buftype == ''
    silent! DeleteSpaceEachLine
    silent update
  endif
endfunction

autocmd MyAutoCmd CursorHold * call s:AutoUpdate()
set updatetime=500
if !exists('s:savebuf')
  let s:savebuf_regex = '.\+'
endif

気になった部分がいくつかあったので添削してみた。

ちなみに以下の添削後のコードは動作確認してないので間違っているかも。間違い見付けたら教えてください><

コマンド定義
command! -bang -bar -complete=file -nargs=0 DeleteSpaceEachLine
\ execute ':%s/\s\+$//'
  • -bang はコマンドが ! を受け付けるようにするもの。今回は使ってないので要らない。
  • -complete はコマンドライン補完の指定。このコマンドは引数がないので要らない。
  • -nargs=0 はデフォルトなので(好みもあるだろうけど)なくてもよい。
  • 実行しているコマンドは変数などを含んでいないので :execute する必要はない。
  • :s にはマッチしなかったときにエラーを出さない e フラグを追加するといいかも。
    • 下見ると :silent で実行しているからなくても良いけど。
command! -bar DeleteSpaceEachLine %s/\s\+$//e
autocmd
autocmd MyAutoCmd CursorHold * call s:AutoUpdate()

多分これは意図的だとは思うけど、この設定はカーソルを止めてしばらくしたら空白を取り除きつつ自動で保存する設定。

自動保存だと知らない人がコピペするといけないので老婆心ながら指摘。

手動保存の際に行うには BufWritePre を使う。

autocmd MyAutoCmd BufWritePre * call s:AutoUpdate()

この場合は s:AutoUpdate() 内の update の行は必要ない。

自動保存と削除を分けるのもアリかも。

function! s:AutoUpdate()
  if expand('%') =~ s:savebuf_regex && !&readonly && &buftype == ''
    silent update
  endif
endfunction

autocmd MyAutoCmd CursorHold * nested call s:AutoUpdate()
autocmd MyAutoCmd BufWritePre * DeleteSpaceEachLine

nested がないと update したときに BufWritePre が発生しないので注意。

exists()
if !exists('s:savebuf')
  let s:savebuf_regex = '.\+'
endif

この exists() の引数はミス?

if !exists('s:savebuf_regex')
  let s:savebuf_regex = '.\+'
endif

ちなみに私の場合

誰も聞いてないだろうけど、私の該当部分の設定はこんな感じ。

" 実際には listchars には他の指定もある
set list listchars=trail:_

行末空白は発見次第抹消するのでそこまで気になったことはないです。

自動化するとたまにいる空行もインデントする派のインデントも消しかねないので自重。

あ、でも今思ったけど以下のようにすれば大丈夫か。

%s/\S\zs\s\+$//e

これなら自動化はアリかもなー。

2012-01-15

Vital.DateTime 書いた

前々から、あったらいいなーと思ってたので、vital に DateTime ってモジュールを書いた。名前の通り、日時を扱うライブラリ

日付の取り扱いは面倒なことは容易に想像できたけど、やはりすごく面倒だった…てかまだあちこち未完成。

とりあえず簡単に使い方を解説。詳細は help 書いてあるんでそちらを。

DateTime オブジェクト

日時を表す。最小単位は秒。

wikipedia:先発グレゴリオ暦を扱います。ユリウス暦? 何それ。

ファクトリ

使い方は関数名と引数から察してください。

  • now([{timezone}])
  • from_unix_time({unix-time} [, {timezone}])
  • from_date([{year} [, {month} [, {day} [, {hour} [, {minute} [, {second} [, {timezone}])
  • from_format({string}, {format} [, {locale}])
  • from_julian_day({jd} [, {timezone}])
メソッド
  • DateTime.year()
  • DateTime.month()
  • DateTime.day()
  • DateTime.hour()
  • DateTime.minute()
  • DateTime.second()
  • DateTime.timezone()
  • DateTime.timezone({timezone})
  • DateTime.day_of_week()
  • DateTime.day_of_year()
  • DateTime.days_from_era()
  • DateTime.julian_day([{float}])
  • DateTime.seconds_of_day()
  • DateTime.quarter()
  • DateTime.unix_time()
  • DateTime.is_leap_year()
  • DateTime.is({DateTime})
  • DateTime.compareTo({DateTime})
  • DateTime.delta({DateTime})
  • DateTime.to({TimeDelta})
  • DateTime.to({TimeZone})
  • DateTime.format({format} [, {locale}])
  • DateTime.strftime({format})
  • DateTime.to_string()

TimeZone オブジェクト

タイムゾーンを表す。今のところオフセット情報のみを持つ。

名前も扱いたいけど、データを全部ぶち込む必要があるし、どうしようか…。

ファクトリ
  • timezone({timezone})
    • 秒数(Number) か "+0900" のような文字列
    • 名前をサポートすることになったらそれも受け付ける。
メソッド
  • TimeZone.offset()
  • TimeZone.offset_string()
  • TimeZone.offset()
    • 時差を秒単位で返す。
  • TimeZone.minutes()
    • 時差を分単位で返す。
  • TimeZone.hours()
    • 時差を時間単位で返す。
  • TimeZone.sign()
    • 時差の方向を -1, 0, 1 で返す。
  • TimeZone.offset_string()
  • TimeZone.w3c()

TimeDelta オブジェクト

DateTime 同士の差分を表す。

ファクトリ
  • delta()
  • delta({days}, {seconds})
  • delta({value}, {unit}, ...)
    • 値と単位のペア。複数指定可能。
    • 単位は、second, minute, hour, day, week
メソッド
  • TimeDelta.seconds()
  • TimeDelta.minutes()
  • TimeDelta.hours()
  • TimeDelta.days()
  • TimeDelta.weeks()
  • TimeDelta.months()
  • TimeDelta.years()
  • TimeDelta.total_seconds()
  • TimeDelta.sign()
  • TimeDelta.negate()
  • TimeDelta.add({other})
  • TimeDelta.about()
  • TimeDelta.to_string()

その他の関数

  • compare({datetime1}, {datetime2})
  • month_names([{longname} [, {locale})
    • 月の名前の配列を返す。
  • weekday_names([{longname} [, {locale})*Vital.DateTime.weekday_names()*
    • 週の名前の配列を返す。
  • am_pm_names([{lowercase} [, {locale})
  • is_leap_year({year})

format について

from_format() と DateTime.format() で使える format について。

基本的に strftime() を参考にしてます。全部は使えないし完璧ではないですが。

%a %A %b %B %d %c %C %D %e %F %H %I %j %k %l %m %n %M %p %P %r %R %s %S %t %u %U %T %w %y %Y %z %%

help に一通り書いてあるので詳細はそちらで。まあほぼ man strftime のコピペですが。注意点として、%c が独自仕様で "%F %T %z" になってる。もっと一般的なW3CDTFとかに変えるかも。



私自身が日付周りにはそこまで詳しくないので、あちこち間違ってる可能性があります。

間違いや要望等があったら Issues やその他私宛てに遠慮なくどうぞ。

2011-12-28

スクリプトローカルな関数を手軽に呼び出す

この記事は Vim Advent Calendar 2011 の 28 日目の代打です。

急遽代打をやることになった*1けどネタが浮かばなかったので、適当に vimrc の中から便利かもしれない関数晒すことにします。

スクリプトローカルな関数を呼び出したい

Vim script を書いてると、スクリプトローカルな関数コマンドラインモードから直接呼び出して挙動を確認したくなること、ないですか?

まあそれなりにシチュエーションはあるかと思います。あることにします。

Vim script ではがんばればスクリプトローカルな関数を呼び出せますが、正直いちいちがんばりたくないです。

スクリプトローカルな関数を呼び出す関数

私の vimrc から抜粋した以下の関数を vimrc に書いておきます。

" Call a script local function.
" Usage:
" - S('local_func')
"   -> call s:local_func() in current file.
" - S('plugin/hoge.vim:local_func', 'string', 10)
"   -> call s:local_func('string', 10) in *plugin/hoge.vim.
" - S('plugin/hoge:local_func("string", 10)')
"   -> call s:local_func("string", 10) in *plugin/hoge(.vim)?.
function! S(f, ...)
  let [file, func] =a:f =~# ':' ?  split(a:f, ':') : [expand('%:p'), a:f]
  let fname = matchstr(func, '^\w*')

  " Get sourced scripts.
  redir =>slist
  scriptnames
  redir END

  let filepat = '\V' . substitute(file, '\\', '/', 'g') . '\v%(\.vim)?$'
  for s in split(slist, "\n")
    let p = matchlist(s, '^\s*\(\d\+\):\s*\(.*\)$')
    if empty(p)
      continue
    endif
    let [nr, sfile] = p[1 : 2]
    let sfile = fnamemodify(sfile, ':p:gs?\\?/?')
    if sfile =~# filepat &&
    \    exists(printf("*\<SNR>%d_%s", nr, fname))
      let cfunc = printf("\<SNR>%d_%s", nr, func)
      break
    endif
  endfor

  if !exists('nr')
    echoerr Not sourced: ' . file
    return
  elseif !exists('cfunc')
    let file = fnamemodify(file, ':p')
    echoerr printf(
    \    'File found, but function is not defined: %s: %s()', file, fname)
    return
  endif

  return 0 <= match(func, '^\w*\s*(.*)\s*$')
  \      ? eval(cfunc) : call(cfunc, a:000)
endfunction

追記: 一部外部の関数に依存していたのを修正

使い方

コメントにある通りですが、まず、

call S('local_func')

これで、現在編集しているバッファで定義されている s:local_func() を引数なしで呼び出します。

引数を与えたい場合は素直にこう。

call S('local_func', 1, 2, 3)

こういうことも可能です。

call S('local_func(1, 2, 3)')

ただし、この場合 eval() を使うため変数は渡せないので注意。

特定の名前のスクリプトのローカル関数を呼び出したい場合はこう。

call S('plugin/my_plugin.vim:local_func', 1, 2, 3)

この場合、"plugin/my_plugin.vim" で終わるスクリプトを探して、最初に見付かったもののローカル関数を呼び出します。

最後の .vim は省略可能です。

call S('plugin/my_plugin:local_func', 1, 2, 3)

以上

これでスクリプトローカル関数を呼び出し放題ですね!

私は実際にこれを使って vimrc 内で S('twitvim:post_twitter', message) みたいな感じで使ってます。


明日は @ さんです。

*1:と思ったらすでに kaoriya さんが代打してたけど気にしてはいけない