読者です 読者をやめる 読者になる 読者になる

pockestrap

Web Programmer's memo

Vim で Go 言語を書いている時、Neosnippet でいい感じに if する

今回は、Neosnippet x Go x if にフォーカスを当てた話をしようと思います。

Neosnippet とは?

みなさん、Neosnippet を知っていますか? && 使っていますか?
Neosnippet はいい感じに snippet を展開してくれる Vim プラグインです。

Neosnippet 自体については、GitHub や下記ブログ記事などを参考にしていただければと思います。

Go言語とは

Go言語とは、プログラミング言語です。

Go言語の特徴の一つに、例外機構が存在しないことがあります。
また、関数が多値を返すことができます。

そのため、Go言語ではエラー情報を関数の呼び出し元に伝える時に戻り値にerrorを含めて返すことが一般的です。

func Foo() (string, error) {
  err := DoSomething()
  if err != nil {
    return "", err
  }
  return "foo", nil
}

このように、errをチェックしてnilでなければそのまま返す、といったコードを書くことがとても多いです。

Neosnippet を使用した開発上のサポート

Neosnippet 標準のスニペット定義では、以下の様なifスニペットが Go言語用に定義されています。

snippet     if
options     head
    if ${1:err != nil} {
        ${0:TARGET}
    }

neosnippet-snippets/go.snip · Shougo/neosnippet-snippets

しかし前述した通りGo言語ではif文を使用する際にエラーチェックをしてエラーを返すことが多いため、私は以下の様にsnippetを上書きして定義していました。

snippet     if
options     head
  if ${1:err != nil} {
      ${0:return err}
  }

これでエラーチェックをはさみたい場合にreturn errまでしてくれるようになりました。便利。

上記手法の問題点

しかし、上記の手法には問題点があります。
それは、多値を返す関数に対応していないことです。

func() (string, error) の様なシグネチャの関数内で先ほどのスニペットを使用しても、return errしか補完されません。
この関数の戻り値はstring, errorなので、return "", errのようなものが展開されることを期待したいです。

解決

そこで、上記の問題を解決したスニペットを作成しました。

Installation


2015/12/23 追記

Shougo/neosnippet-snippets 本体に以下のコードが取り込まれました。

そのため、neosnippet-snippets さえインストールしていれば、インストールのために特別な操作は必要ありません。

また、下記使用例でif<C-k>としていますが、neosnippetにはifeというスニペット名で取り込まれたため、ife<C-k>と入力することでスニペットを展開することが出来ます。

追記ここまで


Neosnippet はインストールされている前提です。
下記の Vim Script を .vimrcに貼り付けてください。

function! GoIfSnip() abort
  let re_func = '\vfunc'
  let re_type = '%(%([.A-Za-z0-9*]|\[|\]|%(%(struct)|%(interface)\{\}))+)'
  let re_rcvr = '%(\s*\(\w+\s+' . re_type . '\))?'
  let re_name = '%(\s*\w+)?'
  let re_arg  = '\(%(\w+%(\s+%(\.\.\.)?' . re_type . ')?\s*,?\s*)*\)'

  let re_ret_v = '%(\w+)'
  let re_ret  = '%(\s*\(?(\s*\*?[a-zA-Z0-9_. ,]+)\)?\s*)?'
  let re_ret_body = '%(' . re_ret_v . '|%(' . re_ret_v  . '\s*' . re_type . ')|' . re_type . '\s*,?\s*)*'
  let re_ret  = '%(\s*\(?\s*(' . re_ret_body . ')\)?\s*)?'
  let re = re_func . re_rcvr . re_name . re_arg . re_ret . '\{'

  let lnum = line('.')
  let ret = ""
  while lnum > 0
    let lnum -= 1

    let ma = matchlist(getline(lnum), re)
    if len(ma) == 0
      continue
    endif
    let ret = ma[1]
    break
  endwhile

  if ret =~ '\v^\s*$'
    return '${0:return}'
  endif

  let rets = []
  for t in split(ret, ',')
    if t =~# '\v^\s*error\s*$'
      let v = 'err'
    elseif t =~# '\v^\s*string\s*$'
      let v = '""'
    elseif t =~# '\v^\s*int\d*\s*$'
      let v = '0'
    elseif t =~# '\v^\s*bool\s*$'
      let v = 'false'
    else
      let v = 'nil'
    endif
    call add(rets, v)
  endfor

  return '${0:return ' . join(rets, ", ") . '}'
endfunction

また、下記の内容を go.snip として適当に Neosnippet のパスが通った場所に配置して下さい。

snippet     if
options     head
  if ${1:err != nil} {
      `GoIfSnip()`
  }

以上です。 そのうちプラグインとして切り出したいと思っています。 しかし、どうしてもneosnippet標準のスニペットに上書きされてしまうので諦めました、誰か解決方法知っている方がいたら教えて下さい…

Usage

使い方は簡単です。 適当な関数の中で、if<C-k>を押します(<C-k>はNeosnippetの設定によって変わりますが、snippetを展開するためのキーです)。以上です!

言葉では説明しづらいので、以下のGif動画を参考にしてください。

f:id:Pocke:20151220132946g:plain

3つの関数内でif<C-k>を入力していますが、全て関数のシグネチャに沿った戻り値が挿入されていることに気がつくと思います。

ほか

ブログ書いている途中にこんな記事が流れてきました、便利そう。

motemen.hatenablog.com