tyru / current-func-info.vim

Get current function name
http://www.vim.org/scripts/script.php?script_id=3197
63 stars 14 forks source link

cfi#format()をstatuslineに表示した状態で.cファイルをvimdiffで開くとカーソルが動かせなくなってしまう #3

Closed tyru closed 5 years ago

tyru commented 13 years ago

https://twitter.com/#!/eldesh/status/116697621569089536

@tyru current-func-info.vim を使わせてもらってます。多分バグだと思うんですが、cfi# format をstatuslineに表示した状態で.cファイルをvimdiffで開くとカーソルが動かせなくなってしまいます。

https://twitter.com/#!/eldesh/status/116714444649803776

@tyru Kaoriya.Vim73 で.vimrcをstatusline設定とBundle 'tyru/current-func-info.vim'のみにして確認しました。let line='%{cfi#format("[%s()]","[global]")}'です。

https://twitter.com/#!/eldesh/status/116716639994650624

@tyru 再現したコードですwこれと全く同じファイルをdiffsplitで開くとカーソルが4行目以降に移動できないです ideone.com/6nHcA

tyru commented 11 years ago

@eldesh 最新版ではこのバグ直ってるみたいです。 もしまだ使って頂けてるようなら再現するかどうか確認してくれると助かります。

tyru commented 11 years ago

@eldesh やっぱり先にcloseしてしまったので、まだ再現するようであればre-openしてください。

eldesh commented 11 years ago

c8891ff で似たような現象が再現しました。 vim74-kaoriya-win32を使って確認しています。

空のmain関数だけのファイルでは問題が出なくなりましたが、ある程度以上大きいファイル同士のdiffsplitを取るとカーソルが関数定義の末尾(の閉じカッコ '}')からそれ以上下に動かなることがあります。

// reopenはリポジトリのCollaborator権限が必要なようです

tyru commented 11 years ago

試しにVimのソースコードのsrc/ex_cmds.csrc/ex_cmds2.cでやってみた所、重くはなりますが再現しませんでした。 できればサンプルコードを示してもらえると助かります。

mnishz commented 5 years ago

cfi 使わせていただいています。大変便利で助かっています。 上のコードは既に見ることができない?ので同件かどうかは定かではないのですが、 現象が似ているのでこちらに書き込みさせていただきました。

私の場合 diffsplit ではないのですが、以下のコマンドを実行後、 :let &statusline = '%{cfi#format("%s", "")}' :new test.c 挿入モードで以下のようにタイプすると、閉じ括弧 ) を入力した時点でハングします。

#include <stdio.h>

int main(void)

調べてみた感じでは ftplugin/c/cfi.vimwhile 1 で無限ループに陥っているようです。

function! s:finder.find_begin() "{{{
    let NONE = []
    let [orig_lnum, orig_col] = [line('.'), col('.')]

    let vb = &vb
    setlocal vb t_vb=
    try
        " Jump to function-like word, and check arguments, and block.
        while 1
            if search(s:FUNCTION_PATTERN, 'bW') == 0
                return NONE
            endif
            " Function name when filetype=c has nothing about syntax info.
            " (without this condition, if-statement is recognized as function)
            if !empty(synstack(line('.'), col('.')))
                continue
            endif
            let funcname = get(matchlist(getline('.'), s:FUNCTION_PATTERN), 1, '')
            if funcname ==# ''
                return NONE
            endif
            for [fn; args] in [
            \   ['search', '(', 'W'],
            \   ['searchpair', '(', '', ')'],
            \]
                if call(fn, args) == 0
                    return NONE
                endif
            endfor
            if join(getline('.', '$'), '')[col('.') :] =~# '\s*[^;]'
                let self.temp.funcname = funcname
                break
            endif
        endwhile
    finally
        let &vb = vb
    endtry

    if search('{') == 0
        return NONE
    endif
    if line('.') == orig_lnum && col('.') == orig_col
        return NONE
    endif
    return [line('.'), col('.')]
endfunction "}}}

ここは search(s:FUNCTION_PATTERN, 'bW') で関数っぽいものを探して、 empty(synstack(line('.'), col('.'))) で関数の definition かどうか判定して (loopして) いる箇所が肝だと理解しています。 その後 funcname を取得して、searchpair() で閉じ括弧の位置に移動して、 join(getline('.', '$'), '')[col('.') :] =~# '\s*[^;]' の判定が真であれば確定とするようですが、 (この判定は関数の declaration を除外している?) 今回のようなケースだとこれが偽になってしまい loop の先頭に戻ります。 このときカーソルが閉じ括弧の位置になっているため、 次の search(s:FUNCTION_PATTERN, 'bW') も最初と同じ関数を指してしまい無限 loop になっているようです。

私が考え付いた解決方法としては、以下の 2 つです。

  1. join() の判定が偽を返したら、その他のチェックと同様に return NONE する。 (これは empty(synstack()) が真となった時点で definition であることは確定で、残りの処理は念のためのチェックという考え方)
  2. join() の判定が偽を返したら、関数名の先頭に cursor() で移動してさらに前方検索する。 (これは empty(synstack()) が真でも definition 確定ではなくて、この場合前方に本来の definition があるはずという考え方)

私が empty(synstack()) の意味をきちんと理解できていないので、どちらが適切なのかよく分かっていません。。

mnishz commented 5 years ago

@tyru 見る余力がないだけかと思いますが、念のため ping させていただきます。

tyru commented 5 years ago

related #26

mnishz commented 5 years ago

今なら empty(synstack()) が何を見ているのかが分かります。一番外側にある関数かどうかを見ているんですね。例えば、

int main(void)
{
    // foobar()
    return 0;
}

みたいなときに、foobar() のコメントの箇所では empty() が偽になって、main() の関数の先頭では empty() が真になりますね。

  1. join() の判定が偽を返したら、その他のチェックと同様に return NONE する。

とりあえずこっちの patch を考えてみました。これは、「empty(synstack()) が真であれば関数名であることは間違いない」と考えて、join(getline('.', '$'), '')[col('.') :] =~# '^\s*;' のときは関数宣言なので return NONE、それ以外の時は関数名として確定、という考えです。

diff --git a/ftplugin/c/cfi.vim b/ftplugin/c/cfi.vim
index 704518d..f945bbb 100644
--- a/ftplugin/c/cfi.vim
+++ b/ftplugin/c/cfi.vim
@@ -54,7 +54,10 @@ function! s:finder.find_begin() "{{{
                     return NONE
                 endif
             endfor
-            if join(getline('.', '$'), '')[col('.') :] =~# '\s*[^;]'
+            if join(getline('.', '$'), '')[col('.') :] =~# '^\s*;'
+                " Function declaration
+                return NONE
+            else
                 let self.temp.funcname = funcname
                 break
             endif

2 のほうの patch も考えてみます。

mnishz commented 5 years ago
  1. join() の判定が偽を返したら、関数名の先頭に cursor() で移動してさらに前方検索する。

の patch です。Fix 的にはこちらのほうが分かりやすいと思いますが、修正としては 1 のほうが適切のような気がします。

diff --git a/ftplugin/c/cfi.vim b/ftplugin/c/cfi.vim
index 704518d..fda6241 100644
--- a/ftplugin/c/cfi.vim
+++ b/ftplugin/c/cfi.vim
@@ -37,6 +37,7 @@ function! s:finder.find_begin() "{{{
             if search(s:FUNCTION_PATTERN, 'bW') == 0
                 return NONE
             endif
+            let saved_cursor = getcurpos()
             " Function name when filetype=c has nothing about syntax info.
             " (without this condition, if-statement is recognized as function)
             if !empty(synstack(line('.'), col('.')))
@@ -57,6 +58,8 @@ function! s:finder.find_begin() "{{{
             if join(getline('.', '$'), '')[col('.') :] =~# '\s*[^;]'
                 let self.temp.funcname = funcname
                 break
+            else
+                call setpos('.', saved_cursor)
             endif
         endwhile
     finally
tyru commented 5 years ago

@mnishz ありがとうございます! 1 の PR 作ってもらえないでしょうか?

mnishz commented 5 years ago

PR 出しました!よろしくお願いします。

tyru commented 5 years ago

PR 出してもらったので close します