FloatingWindowには枠線をつけるオプションがない
border:1px
のようなオプションは用意されていない。ただ、枠線があるように見せることは可能。
下記はこちらの記事で作成したときのもの。カレンダーを表現するのに枠線付きのFloatingWindowが必要だった。ソース。
カレンダーを枠線FloatingWindowで表現
枠線付きのFloatingWindowを作成する
枠線用にもう一枚FloatingWindowを作り、同じ位置にFloatingWindowを重ねることで枠線付きのFloatingWindowを表現する。
function! s:create_contents_window(config) abort
let config = {'relative': 'editor', 'row': a:config.row + 1, 'col': a:config.col + 2, 'width': a:config.width - 4, 'height': a:config.height - 2, 'style': 'minimal'}
let buffer = nvim_create_buf(v:false, v:true)
return nvim_open_win(buffer, v:true, config)
endfunction
function! s:create_border_window(config) abort
let width = a:config.width
let height = a:config.height
let top = "╭" . repeat("─", width - 2) . "╮"
let mid = "│" . repeat(" ", width - 2) . "│"
let bot = "╰" . repeat("─", width - 2) . "╯"
let lines = [top] + repeat([mid], height - 2) + [bot]
let buffer = nvim_create_buf(v:false, v:true)
call nvim_buf_set_lines(buffer, 0, -1, v:true, lines)
return nvim_open_win(buffer, v:true, a:config)
endfunction
function! s:new_window(config) abort
call s:create_border_window(a:config)
call s:create_contents_window(a:config)
endfunction
let config = { 'relative': 'editor', 'row': 30, 'col': 30, 'width': 50, 'height': 20, 'anchor': 'NW', 'style': 'minimal'}
call s:new_window(config)
参考: Feature: optional floating window borders
FloatingWindowにコンテンツを載せる
nvim_buf_set_lines()
を使うと配列で1行ずつセットできるので直感的。下記ではfield
という配列を用意し、一行ずつコンテンツ内容を記載している。
let field =
\ ['今日の日付']
\ + ['']
\ + ['2021/01/31']
call nvim_buf_set_lines(buffer, 0, -1, v:true, field)
function! s:create_contents_window(config, field) abort
let config = {'relative': 'editor', 'row': a:config.row + 1, 'col': a:config.col + 2, 'width': a:config.width - 4, 'height': a:config.height - 2, 'style': 'minimal'}
let buffer = nvim_create_buf(v:false, v:true)
call nvim_buf_set_lines(buffer, 0, -1, v:true, a:field)
return nvim_open_win(buffer, v:true, config)
endfunction
function! s:new_window(config, field) abort
call s:create_border_window(a:config)
call s:create_contents_window(a:config, a:field)
endfunction
let config = { 'relative': 'editor', 'row': 30, 'col': 30, 'width': 50, 'height': 20, 'anchor': 'NW', 'style': 'minimal'}
let field =
\ ['今日の日付']
\ + ['']
\ + ['2021/01/31']
call s:new_window(config, field)
:qで閉じるとき、2回実行する必要がある
追記:
:q
を上書きしなくても、autocmd BufWinLeave
を利用すればシンプルに削除処理を登録できる。
下記をコンテンツウィンドウに設定すればOK。
call nvim_command('autocmd BufWinLeave <buffer> call s:close_window()')
参考:https://www.2n.pl/blog/how-to-write-neovim-plugins-in-lua.md
追記ここまで
枠線用とコンテンツ用の2つのFloatingWindowがあるので、完全に閉じるには2回:q
を実行しないといけない。
ただ、これはちょっと面倒くさいので1回の:q
で両方とも閉じられるようにする。要はコンテンツ用ウィンドウのIDと枠線用ウィンドウのIDを紐付けしておけばいいので色々やり方はある。今回はディクショナリ型を使って実装してみる。
let s:window_ids = {}
function! s:close_window(...)
let contents_window_id = a:0 == 0 ? nvim_get_current_win() : a:1
let border_window_id = get(s:window_ids, contents_window_id, -1)
if border_window_id != -1
call nvim_win_close(contents_window_id, v:true)
call nvim_win_close(border_window_id, v:true)
call remove(s:window_ids, contents_window_id)
endif
endfunction
function! s:new_window(config, field) abort
let border_window_id = s:create_border_window(a:config)
let contents_window_id = s:create_contents_window(a:config, a:field)
let s:window_ids[contents_window_id] = border_window_id
nnoremap <buffer><nowait><silent> :q<CR> :call <SID>close_window()<CR>
return contents_window_id
endfunction
let config = { 'relative': 'editor', 'row': 30, 'col': 30, 'width': 50, 'height': 20, 'anchor': 'NW', 'style': 'minimal'}
let field =
\ ['今日の日付']
\ + ['']
\ + ['2021/01/31']
call s:new_window(config, field)
:q
をnnoremap
で上書きするスタイル。<buffer>
オプションをつけているので、元のウィンドウにはキーバインドが登録されず、FloatingWindowにだけ登録される。s:close_window(...)
では引数を取れるようにしてある。こうすることでコンテンツウィンドウにフォーカスが乗っていなくても、IDさえ知っておけばどこからでも削除できる。
let contents_window_id = s:new_window(config, field)
nnoremap T :call <SID>close_window(contents_window_id)<CR>
移動できるようにする
hjkl
で作成したウィンドウを移動できるようにしてみる。
function! s:move(direction, value)
let contents_window_id = nvim_get_current_win()
let border_window_id = get(s:window_ids, contents_window_id, -1)
for id in [contents_window_id, border_window_id]
let config = nvim_win_get_config(id)
if a:direction == 'x'
let config.col += a:value
else
let config.row += a:value
endif
call nvim_win_set_config(id, config)
endfor
endfunction
function! s:new_window(config, field) abort
let border_window_id = s:create_border_window(a:config)
let contents_window_id = s:create_contents_window(a:config, a:field)
let s:window_ids[contents_window_id] = border_window_id
nnoremap <buffer><nowait><silent> :q<CR> :call <SID>close_window()<CR>
nnoremap <buffer><nowait><silent> l :call <SID>move('x', 1)<CR>
nnoremap <buffer><nowait><silent> h :call <SID>move('x', -1)<CR>
nnoremap <buffer><nowait><silent> j :call <SID>move('y', 1)<CR>
nnoremap <buffer><nowait><silent> k :call <SID>move('y', -1)<CR>
return contents_window_id
endfunction
hjlkでFloatingWindowを移動。ちょっとおもしろい。
これを応用したvim-block-paste
というプラグインを以前作った。
www.rasukarusan.com
最終的なソース
let s:window_ids = {}
function! s:create_contents_window(config, field) abort
let config = {'relative': 'editor', 'row': a:config.row + 1, 'col': a:config.col + 2, 'width': a:config.width - 4, 'height': a:config.height - 2, 'style': 'minimal'}
let buffer = nvim_create_buf(v:false, v:true)
call nvim_buf_set_lines(buffer, 0, -1, v:true, a:field)
return nvim_open_win(buffer, v:true, config)
endfunction
function! s:create_border_window(config) abort
let width = a:config.width
let height = a:config.height
let top = "╭" . repeat("─", width - 2) . "╮"
let mid = "│" . repeat(" ", width - 2) . "│"
let bot = "╰" . repeat("─", width - 2) . "╯"
let lines = [top] + repeat([mid], height - 2) + [bot]
let buffer = nvim_create_buf(v:false, v:true)
call nvim_buf_set_lines(buffer, 0, -1, v:true, lines)
return nvim_open_win(buffer, v:true, a:config)
endfunction
function! s:move(direction, value)
let contents_window_id = nvim_get_current_win()
let border_window_id = get(s:window_ids, contents_window_id, -1)
for id in [contents_window_id, border_window_id]
let config = nvim_win_get_config(id)
if a:direction == 'x'
let config.col += a:value
else
let config.row += a:value
endif
call nvim_win_set_config(id, config)
endfor
endfunction
function! s:close_window(...)
let contents_window_id = a:0 == 0 ? nvim_get_current_win() : a:1
let border_window_id = get(s:window_ids, contents_window_id, -1)
if border_window_id != -1
call nvim_win_close(contents_window_id, v:true)
call nvim_win_close(border_window_id, v:true)
call remove(s:window_ids, contents_window_id)
endif
endfunction
function! s:new_window(config, field) abort
let border_window_id = s:create_border_window(a:config)
let contents_window_id = s:create_contents_window(a:config, a:field)
let s:window_ids[contents_window_id] = border_window_id
nnoremap <buffer><nowait><silent> :q<CR> :call <SID>close_window()<CR>
nnoremap <buffer><nowait><silent> j :call <SID>move('y', 1)<CR>
nnoremap <buffer><nowait><silent> k :call <SID>move('y', -1)<CR>
nnoremap <buffer><nowait><silent> l :call <SID>move('x', 1)<CR>
nnoremap <buffer><nowait><silent> h :call <SID>move('x', -1)<CR>
return contents_window_id
endfunction
let config = { 'relative': 'editor', 'row': 30, 'col': 30, 'width': 50, 'height': 20, 'anchor': 'NW', 'style': 'minimal'}
let field =
\ ['今日の日付']
\ + ['']
\ + ['2021/01/31']
let contents_window_id = s:new_window(config, field)
nnoremap T :call <SID>close_window(contents_window_id)<CR>
終わり
hjklで移動できると楽しいのでぜひやってみてほしい。