rcmdnk's blog
Last update

20221106_vimview2_200_200

Vim/NeoVimの表示で左側にGitの変更状況を表示したり 色々と言語サポートを表示したりすることが出来ますが、 サクッとマウスなどでコピペする際、それらが表示されていると文字として認識されて 余計な文字までコピーされてしまいます。

その辺り一時的に表示を消してコピペをしたり出来るようにする設定について。

消したいもの

消したいものは以下のもの。

  • list: 文末の空白やタブなど
  • nowrap: 長い行を折り返さないで隠す設定
  • signcolumn: Gitの変更やエラーなどの状況。
  • 文末以降のチルダ(EndOfBuffer)
  • coc-pyrightによる型ヒント

他にもプラグインや設定によってはあるかもしれませんが、 現状自分の設定だとこんなものでした。

各種設定変更方法

list

文末の空白やタブなどを可視化する設定。 listcharsで設定された各種状態の可視化をするかしないかを決める値です。

これは

set nolist

としてやれば消えます。

再び見せる場合には

set list

もともとnolistになっている可能性もあるので、最初の状態を取得しておきます。

let g:default_list = &list

setに関する値を取得したい場合には&を使って取ることができます。

これで隠したいときは

set nolist

で必ずnolistにし、戻したいときは

let &list = g:default_list

とすることで、もとの状態に戻します。

setには直接変数を与えることは出来ませんが、上のようにlet &<var>とすることで 変数をsetの設定に渡すことが出来ます。

この設定はウィンドウ(表示されている枠)毎に設定されています。(setでもsetlocalでも変わらない。)

Ref: help nolist

                                            'list' 'nolist' 'list'                  boolean (default off)
                    local to window

nowrap

長い行を複数行にせずにスクロールのような形で画面移動して見に行くようにする設定。

これは

set wrap

とすると画面内に複数行で表示されるようになります。

ただ、これに関しては現状では最初からwrapで運用してますが、一応考慮に入れておきます。

この設定もウィンドウ毎に設定されています。

signcolumn

左側に1列か数列文の情報表示をする設定。

プラグインでGitの変更状態を表示させたり、言語サポートプラグインで文法エラーを表示したりする事ができます。

これを消すには

set signcolumn=no

signcolumnにはautoなどの値が入っているので、最初に

let g:default_signcolumn = &signcolumn

で値を取得しておいて戻す際には

let &signcolumn = g:default_signcolumn

とします。

スペルチェック

追記: 2022/11/06

スペルチェックを有効にしていると間違ったスペルに下線が付いたりします。

これは

set spell

の設定で有効になり、

set nospell

で無効にできます。

この下線はコピペする際には取得されないのでちょっと用途がずれますが、 コードの見た目から邪魔なものをなくしてみたい、という時にも使いたいのでこれも追加。

スペルチェックに関しては別途、

nnoremap <silent> <Leader>s :setlocal spell!<CR>

という設定を入れていて<Leader>sでトグルするようにもしています。

追記ここまで

文末以降のチルダ

短いファイルを表示する際、Vimのウィンドウがファイルよりも長い場合、 ファイル外の下部の部分に関しては~で始まるラインとして表示されます。

これに関しては直接方法はなく、この表示部分のシンタックスグループのテキストの色を 背景と同じにする、ということで消します。

この~EndOfBufferというシンタックスグループで管理されています。

How to change the color of the tilde characters indicating the filler lines after the last line of a buffer in Vim? - Stack Overflow)

通常はこれがNonTextの言うシンタックスグループにリンクされていて、 EndOfBuffer自体では指定をしていません。

NonTextを使っているものがざっと見なかったですが、とりあえずこの両方を 背景と同じ色にしておけばOK。

バックグラウンドの色に関してはIgnoreというシンタックスグループがあり、 この文字の色が背景と同じ色になって見せないようになっています。

これらの色は

let g:default_nontext_fg = synIDattr(hlID('NonText'), 'fg')
let g:default_endofbuffer_fg = synIDattr(hlID('EndOfBuffer'), 'fg')
if g:default_endofbuffer_fg == ""
  let g:default_endofbuffer_fg = g:default_nontext_fg
endif
let g:ignore_fg = synIDattr(hlID('Ignore'), 'fg')

みたいな感じで取得できます。

hlIDというのがグループ名からIDを取得する関数で、synIDattrでそのIDに関する情報を取得できます。 synIDattrの2つ目以降の引数にほしい情報名を入れて取得。 ここではfg、つまりはテキストの色を取得しています。(2つ目以降、複数を指定することも可能。)

EndOfBufferに関しては指定が無いこともあるので、後で戻す際に空欄のままだとエラーになるので もし指定がない場合はNonTextのものを入れておきます。

これで、隠したいときは

execute "highlight NonText ctermfg=" . g:ignore_fg . " guifg=" . g:ignore_fg
execute "highlight EndOfBuffer ctermfg=" . g:ignore_fg . " guifg=" . g:ignore_fg

としてIgnoreの色にします。 highlightに対して直接変数を与えることはできませんが、コマンドを文字列にしてからexecuteに渡すことで変数を使うことが出来ます。

一応ctermfg, guifg両方を指定していますがgvimを使わないとかターミナルでは使わないとかあればどちらかだけでも。

これを元に戻すには

execute "highlight NonText ctermfg=" . g:default_nontext_fg . " guifg=" . g:default_nontext_fg
execute "highlight EndOfBuffer ctermfg=" . g:default_endofbuffer_fg . " guifg=" . g:default_endofbuffer_fg

とすればOK。

これらのハイライト設定は常にグローバルで、 バッファごととかウィンドウごとにはなりません。

頑張ればできるかもしれませんが、結構面倒な感じなので現状では変更はグローバルな変更の状態になっています。

Vim Highlight Setting Local - Stack Overflow

追記: 2022/11/06

シンタックスグループに関しては以下のような関数を用意して取得するように更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function! s:get_orig_highlight(group)
  let l:id = hlID(a:group)->synIDtrans()
  let l:fg = synIDattr(l:id, 'fg')
  let l:bg = synIDattr(l:id, 'bg')
  let l:orig = ''
  if l:fg != ''
    let l:orig = l:orig . ' ctermfg=' . l:fg . ' guifg=' . l:fg
  endif
  if l:bg != ''
    let l:orig = l:orig . ' ctermbg=' . l:bg . ' guibg=' . l:bg
  endif
  if l:orig == ''
    return ''
  endif
  return 'highlight ' . a:group . l:orig
endfunction

hlIDで取得したIDからsynIDtransを使うことで そのIDが別のグループにリンクされているとそのリンク先の情報が得られます。

もし、そのリンク先も別のグループにリンクされていたとすると 最終的なリンク先までsynIDtrans一発でたどり着くことができます。

もしリンクされていなければそのままそのIDが返されます。

これを使って

1
let g:default_endofbuffer = s:get_orig_highlight('EndOfBuffer')

とすることで元の状態を保持しておいて後で戻すための情報をコマンドとして保存しておきます。

基本的に背景色は無いグループですが、一応変わる可能性も考えて 背景色も保存して変更するようにしました。

これで、

1
2
3
4
5
6
7
8
9
10
11
12
13
if b:current_view == 0
  let b:current_view = 1
  ...
  execute g:default_nontext
  execute g:default_endofbuffer
  ...
else
  let b:current_view = 0
  ...
  highlight! link NonText Ignore
  highlight! link EndOfBuffer Ignore
  ...
endif

のように、ハイライトがかかっているときには必要なものをIgnoreにリンクして 戻すときは最初に保存したコマンドを実行する、という形にすることでスッキリ出来ました。

追記ここまで

coc-pyrightによる型ヒント

coc.nvimのサポートプラグインのcoc-pyrightでは最近型ヒントが付けられていない部分に対して 型ヒントを表示してくれる機能が追加されました。

1
2
def add(a: int, b: int):
    return a + b

みたいな関数があると、返り値はintが予想されますが、これに対して、

1
2
def add(a: int, b: int): -> int
    return a + b

のように表示してくれます。(-> intの部分)

追記: 2022/11/06

NeoVimだとこのように表示されますが、 Vimだと下の実際の文法と同じ様な表示になります。

-> intの部分を非文字として追加して表示することが出来るようです。 その場合にはコピペするとそのまま使えるコードになります。

追記ここまで

関数の返り値の他、変数の型ヒントも表示してくれます。

ただし、きちんと型ヒントを書くと

1
2
def add(a: int, b: int) -> int:
    return a + b

のように:より前に書かないといけないので、coc-pyrightで表示されたものをそのままコピペするとエラーになります。

これは~/.vim/coc-settings.json(~/.config/nvim/coc-settings.json)の中で、

"pyright.inlayHints.functionReturnTypes": false,
"pyright.inlayHints.variableTypes": false,

とすると無効化出来ます。

有効にしている場合には(調べた限り)それぞれを変更することは出来ませんが、

CocCommand document.toggleInlayHint

というコマンドを実行することで表示を消したり表示したりすることが出来ます。 現状では表示する、消す、といったコマンドはないようでトグルするだけです。

この設定はバッファごと(つまりは開いたファイルごと)の変更になります。

追記: 2022/11/06

Inlay Hintは言語によってあるものとないものがあり、ないものを開いてこのコマンドを使うと

[coc.nvim] Inlay hint provider not found for current document

と言ったエラーが出ます。 無視してしまっても良いのですが、これに関しては、

1
2
3
if CocHasProvider('inlayHint') == v:true
  CocCommand document.toggleInlayHint
endif

のようにinlayHintというProviderがあるかないか、を調べる関数があるのでそれを使うことで余計なエラーを出さずに実行できるようになります。

追記ここまで

coc.nvimのErrorなどに対する下線など

追記: 2022/11/06

coc.nvimで文法エラーなどを検出するとその部分に上のsigncolumよる表示とともに 該当の部分に下線が引かれたりします。

これもコピペには直接作用しませんが、すっきり表示するために消したいな、と思い設定を入れてみました。

これらはCocXXXHighlight(XXX=Unused, Error, Warning, Info, Hint)というグループがあり これらが下線や取り消し線、背景色を薄く塗るグループにリンクされています。

https://github.com/neoclide/coc.nvim/blob/2692b894a2d4c17d2def60969e6af078dcda1a12/plugin/coc.vim#L511

上のリンクにあるように、これらは

`hi default link CocErrorHighlight

のようにdefault linkが使われているため、なんらか変更行ったあとにhighlight clearすることで戻すことが出来ます。

なのでこれらに関しては最初に設定は取得せずに、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if b:current_view == 0
  let b:current_view = 1
  ...
  if dein#tap('coc.nvim')
    for name in ['Unused', 'Deprecated', 'Error', 'Warning', 'Info', 'Hint']
      exe 'highlight clear Coc' . name . 'Highlight'
    endfor
    ...
  endif
  ...
else
  let b:current_view = 0
  ...
  if dein#tap('coc.nvim')
    for name in ['Unused', 'Deprecated', 'Error', 'Warning', 'Info', 'Hint']
      exe 'highlight Coc' . name . 'Highlight ctermbg=NONE guibg=NONE cterm=NONE gui=NONE guisp=NONE'
    endfor
  endif
  ...
endif

のように、ハイライトがついているときはcterm, ctermbgなどをNONEにしてしまい、 戻すときはclearで戻すようにしました。

追記ここまで

coc.nvimのpopupやその他もろもろ

追記: 2022/11/06

coc.nvimがつけるものが結構色々あって、一つはErrorなどになっている部分にカーソルがあるとpopupが出ることがあります。

その状態でコピペするとその部分がとれてしまうので、一度別の場所にカーソルを動かすなどしないといけません。

これに関しては

call coc#float#close_all()

を呼ぶことで消すことが出来ます。

これらのポップアップはカーソルをもう一度移動して戻すと再表示されてしまいますが、 一時的には消した状態を維持できます。

ポップアップが出ないようにcoc.nvim自体を無効化することも考えられますが、 それがちょっとうまくいきませんでした。

CocDisableCocEnableというコマンドがあって無効化、有効化が出来るんですが、 これらのコマンドを実行しても残るものがあって、 下にあるようにclearmatchesなどを使って一通り消せるとはあり、もしこれでうまく行けば シンタックスグループなども逐次変更せずにすみます。

ただ、Inlay Hintに関しては独立しているのか残ってしまいました。

Cleanup syntax highlighting on :CocDisable / :CocRestart · Issue #1268 · neoclide/coc.nvim

さらに微妙なのが、なぜかCocDisableを最初にした直後はdocument.toggleInlayHintをしても消えず、 その後このコマンドを実行するとCocEnableCocDisableの状態に関係なくtoggleが実行されます。

これによってどうやってもうまく制御出来なかったのでCocDisableを使うのはやめました。

b:coc_enabledb:coc_diagnostic_disableといった状態を制御する変数も用意されていますがこれらも 思ったようには使えず。

また、clearmatchesを使ってしまうと戻すのに別途コマンドが色々必要になることもあって結構面倒です。

なので最終的にはcoc.nvim自体は有効にしたまま必要なシンタックスグループなどを直接変更するなどして対処しています。

追記ここまで

変更設定

以下の様な関数とキーバインドを用意してさっと変えられるようにします。

.vimrc
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
function! s:toggle_view()
  if exists('g:default_list') == 0
    let g:default_list = &list
    let g:default_wrap = &wrap
    let g:default_signcolumn = &signcolumn
    let g:default_nontext_fg = synIDattr(hlID('NonText'), 'fg')
    let g:default_endofbuffer_fg = synIDattr(hlID('EndOfBuffer'), 'fg')
    if g:default_endofbuffer_fg == ""
      let g:default_endofbuffer_fg = g:default_nontext_fg
    endif
    let g:ignore_fg = synIDattr(hlID('Ignore'), 'fg')
  endif
  if exists('b:current_view') == 0
    let b:current_view = 1
  endif
  if b:current_view == 0
    let b:current_view = 1
    let &list = g:default_list
    let &wrap = g:default_list
    let &signcolumn = g:default_signcolumn
    execute "highlight NonText ctermfg=" . g:default_nontext_fg . " guifg=" . g:default_nontext_fg
    execute "highlight EndOfBuffer ctermfg=" . g:default_endofbuffer_fg . " guifg=" . g:default_endofbuffer_fg
  else
    let b:current_view = 0
    set nolist
    set wrap
    set signcolumn=no
    execute "highlight NonText ctermfg=" . g:ignore_fg . " guifg=" . g:ignore_fg
    execute "highlight EndOfBuffer ctermfg=" . g:ignore_fg . " guifg=" . g:ignore_fg
  endif
  if dein#tap('coc.nvim')
    CocCommand document.toggleInlayHint
  endif
endfunction
command! ToggleView call s:toggle_view()
nnoremap <silent> <Leader>c :ToggleView<CR>

この設定で<Leader>cと押すたびに表示変更が行われます。 (<Leader>はデフォルトではバックスラッシュ。自分の設定では,になっているのでこのトグルは,cで行う。)

coc.nvimに関してはプラグインが入っている場合にのみ実行されるようにしています。

ここではdein.vimを使って管理しているのでそれを使ってチェック。

一番最初にこの関数が実行された際にデフォルトの表示設定をグローバル変数に記録しておきます。

トグルの状態に関してはb:current_viewというバッファ変数に記録しておきます。

ちょっとやっかいなのは

  • listなどsetに関するもの: ウィンドウ(w)
  • highlight: グローバル(g)
  • cocのInlayHint: バッファ(b)

でそれぞれの状態の変更範囲が違うところ。

ToggleViewを実行すると、highlightはすべての箇所で変更されますが、 listなどは現在のウィンドウのみで変更されます。 さらにcocのInlayHintに関してはバッファ単位なので、もし同じファイルを2つのウィンドウに分けて表示している場合にはその両方で変更されます。

ホントはすべてウィンドウ毎に変更できれば良いのですが、かなり面倒なので現状ではこのような感じまでで。

InlayHintに関してはOn/Offを指定する方法はないため、これがバッファごとということを踏まえて 状態の保存の変数はb:current_viedwというバッファ変数にしてこの状態と揃えるようにしています。

これによって、一時的にlistだけが消えたウィンドウなどが出来てしまいますが、 そのウィンドウで<Leader>cを2回以上押せばすべてが揃って表示、または非表示の状態にすることが出来ます。

20221025_vimview0.jpg

これを

20221025_vimview1.jpg

こんな感じに。

追記: 2022/11/06

アップデート版は以下の通り

.vimrc
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
function! s:get_orig_highlight(group)
  let l:id = hlID(a:group)->synIDtrans()
  let l:fg = synIDattr(l:id, 'fg')
  let l:bg = synIDattr(l:id, 'bg')
  let l:orig = ''
  if l:fg != ''
    let l:orig = l:orig . ' ctermfg=' . l:fg . ' guifg=' . l:fg
  endif
  if l:bg != ''
    let l:orig = l:orig . ' ctermbg=' . l:bg . ' guibg=' . l:bg
  endif
  if l:orig == ''
    return ''
  endif
  return 'highlight ' . a:group . l:orig
endfunction

function! s:toggle_view()
  if exists('g:default_list') == 0
    let g:default_list = &list
    let g:default_wrap = &wrap
    let g:default_signcolumn = &signcolumn
    let g:default_spell = &spell
    let g:default_nontext = s:get_orig_highlight('NonText')
    let g:default_endofbuffer = s:get_orig_highlight('EndOfBuffer')
  endif
  if exists('b:current_view') == 0
    let b:current_view = 1
  endif
  if b:current_view == 0
    let b:current_view = 1
    let &list = g:default_list
    let &wrap = g:default_wrap
    let &signcolumn = g:default_signcolumn
    let &spell = g:default_spell
    execute g:default_nontext
    execute g:default_endofbuffer
    if dein#tap('coc.nvim')
      for name in ['Unused', 'Deprecated', 'Error', 'Warning', 'Info', 'Hint']
        exe 'highlight clear Coc' . name . 'Highlight'
      endfor
      if CocHasProvider('inlayHint') == v:true
        CocCommand document.toggleInlayHint
      endif
    endif
  else
    let b:current_view = 0
    set nolist
    set wrap
    set signcolumn=no
    set nospell
    highlight! link NonText Ignore
    highlight! link EndOfBuffer Ignore
    if dein#tap('coc.nvim')
      for name in ['Unused', 'Deprecated', 'Error', 'Warning', 'Info', 'Hint']
        exe 'highlight Coc' . name . 'Highlight ctermbg=NONE guibg=NONE cterm=NONE gui=NONE guisp=NONE'
      endfor
      if CocHasProvider('inlayHint') == v:true
        CocCommand document.toggleInlayHint
      endif
      call coc#float#close_all()
    endif
  endif
endfunction
command! ToggleView call s:toggle_view()
nnoremap <silent> <Leader>c :ToggleView<CR>

NeoVimでは

20221106_vimview2.jpg

20221106_vimview3.jpg

Vimでは

20221106_vimview4.jpg

20221106_vimview5.jpg

の状態に。

コードちょっと変更してエラーとかを少し追加してあります。 popupの例も。

あと、CocErrorHighlightなどによる下線が何故か最初の例だと出てませんが、ここだと__main__の右の行末のスペースなどの部分に下線が出ているものがあって、 これもちゃんと消えてるのが確認出来ると思います。

何故か最初の例だとmain関数がif文の中にあったので移動。。。

追記ここまで

Sponsored Links
Sponsored Links

« coc.nvimのサポートプラグインを管理する Homebrewで古いバージョンを使う(awscli@macが動かない) »

}