Dotfiles.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

323 lines
9.2 KiB

" ============================================================================
" FileName: autocmd/floaterm.vim
" Description:
" Author: voldikss <dyzplus@gmail.com>
" GitHub: https://github.com/voldikss
" ============================================================================
" `hidden` option must be set, otherwise the floating terminal would be wiped
" out, see #17
set hidden
" Note:
" The data structure of the floaterm chain is a double circular linkedlist
" g:floaterm.count is the count of the terminal node
" g:floaterm.index is the pointer
" g:floaterm.head is the HEAD node which only have 'prev' and 'next'
" g:floaterm_node is the node prototype to create a terminal node
let g:floaterm = {}
let g:floaterm.count = 0
let g:floaterm.head = {}
let g:floaterm.head.next = g:floaterm.head
let g:floaterm.head.prev = g:floaterm.head
let g:floaterm.index = g:floaterm.head
let g:floaterm_node = {
\ 'bufnr': 0,
\ 'border_bufnr': 0,
\ 'next': v:null,
\ 'prev': v:null
\ }
if g:floaterm_border_color == v:null
let g:floaterm_border_color = floaterm#util#get_normalfloat_fg()
endif
if g:floaterm_background == v:null
let g:floaterm_background = floaterm#util#get_normalfloat_bg()
endif
" Remove a node if it was closed(the buffer doesn't exist)
function! g:floaterm.kickout() dict abort
if self.count == 0 | return | endif
let self.index.prev.next = self.index.next
let self.index.next.prev = self.index.prev
let self.count -= 1
endfunction
function! g:floaterm.toggle() dict abort
let found_winnr = self.find_term_win()
if found_winnr > 0
if &buftype ==# 'terminal'
execute found_winnr . ' wincmd q'
else
execute found_winnr . ' wincmd w | startinsert'
endif
else
while v:true
if self.count == 0
call self.open(0)
return
endif
" If the current node is HEAD(which doesn't have 'bufnr' key),
" skip and point to the node after HEAD
if self.index == self.head
let self.index = self.head.next
endif
let found_bufnr = self.index.bufnr
if found_bufnr != 0 && bufexists(found_bufnr)
call self.open(found_bufnr)
return
else
call self.kickout()
let self.index = self.index.next
endif
endwhile
endif
endfunction
function! g:floaterm.new() dict abort
call self.hide()
call self.open(0)
endfunction
function! g:floaterm.next() dict abort
call self.hide()
while v:true
if self.count == 0
call floaterm#util#show_msg('No more terminal buffers', 'warning')
return
endif
" If the current node is the end node(whose next node is HEAD),
" skip and point to the HEAD's next node
if self.index.next == self.head
let self.index = self.head.next
else
let self.index = self.index.next
endif
let next_bufnr = self.index.bufnr
if next_bufnr != 0 && bufexists(next_bufnr)
call self.open(next_bufnr)
return
else
call self.kickout()
endif
endwhile
endfunction
function! g:floaterm.prev() dict abort
call self.hide()
while v:true
if self.count == 0
call floaterm#util#show_msg('No more terminal buffers', 'warning')
return
endif
" If the current node is the node after HEAD(whose previous node is HEAD),
" skip and point to the HEAD's prev node(the end node)
if self.index.prev == self.head
let self.index = self.head.prev
else
let self.index = self.index.prev
endif
let prev_bufnr = self.index.bufnr
if prev_bufnr != 0 && bufexists(prev_bufnr)
call self.open(prev_bufnr)
return
else
call self.kickout()
endif
endwhile
endfunction
" Hide the current terminal before opening another terminal window
" Therefore, you cannot have two terminals displayed at once
function! g:floaterm.hide() dict abort
while v:true
let found_winnr = self.find_term_win()
if found_winnr > 0
execute found_winnr . ' wincmd q'
else
break
endif
endwhile
endfunction
" Find if there is a terminal among all opened windows
" If found, hide it or jump into it
function! g:floaterm.find_term_win() abort
let found_winnr = 0
for winnr in range(1, winnr('$'))
if getbufvar(winbufnr(winnr), '&filetype') ==# 'floaterm'
let found_winnr = winnr
endif
endfor
return found_winnr
endfunction
function! g:floaterm.open(found_bufnr) dict abort
let height = g:floaterm_height == v:null ? 0.6 : g:floaterm_height
if type(height) == v:t_float | let height = height * &lines | endif
let height = float2nr(height)
let width = g:floaterm_width == v:null ? 0.6 : g:floaterm_width
if type(width) == v:t_float | let width = width * &columns | endif
let width = float2nr(width)
if g:floaterm_type ==# 'floating'
let [bufnr, border_bufnr] = s:open_floating_terminal(a:found_bufnr, height, width)
else
let bufnr = s:open_floating_normaml(a:found_bufnr, height, width)
let border_bufnr = 0
endif
if bufnr != 0
" Build a terminal node
let node = deepcopy(g:floaterm_node)
let node.bufnr = bufnr
let node.prev = self.index
let node.next = self.index.next
" If current node is the end node, let HEAD's prev point to the new node
if self.index.next == self.head
let self.head.prev = node
endif
let self.index.next = node
let self.index = self.index.next
let self.count += 1
endif
if border_bufnr != 0
let self.index.border_bufnr = border_bufnr
endif
call s:on_open()
endfunction
function! s:on_open() abort
setlocal cursorline
setlocal filetype=floaterm
" Find the true background(not 'hi link') for floating
if has('nvim')
execute 'setlocal winblend=' . g:floaterm_winblend
execute 'hi FloatTermNormal term=NONE guibg='. g:floaterm_background
setlocal winhighlight=NormalFloat:FloatTermNormal,FoldColumn:FloatTermNormal
augroup close_floaterm_window
autocmd!
autocmd TermClose <buffer> if &filetype ==# 'floaterm' |
\ bdelete! |
\ endif
autocmd TermClose,BufHidden <buffer> if exists('g:floaterm.index.border_bufnr')
\ && bufexists(g:floaterm.index.border_bufnr)
\ && g:floaterm.index.border_bufnr != 0 |
\ execute 'bw ' . g:floaterm.index.border_bufnr |
\ endif
augroup END
endif
startinsert
endfunction
function! s:open_floating_terminal(found_bufnr, height, width) abort
let [row, col, vert, hor] = floaterm#util#floating_win_pos(a:width, a:height)
let border_opts = {
\ 'relative': 'editor',
\ 'anchor': vert . hor,
\ 'row': row,
\ 'col': col,
\ 'width': a:width + 2,
\ 'height': a:height + 2,
\ 'style':'minimal'
\ }
let top = g:floaterm_borderchars[4] .
\ repeat(g:floaterm_borderchars[0], a:width) .
\ g:floaterm_borderchars[5]
let mid = g:floaterm_borderchars[3] .
\ repeat(' ', a:width) .
\ g:floaterm_borderchars[1]
let bot = g:floaterm_borderchars[7] .
\ repeat(g:floaterm_borderchars[2], a:width) .
\ g:floaterm_borderchars[6]
let lines = [top] + repeat([mid], a:height) + [bot]
let border_bufnr = nvim_create_buf(v:false, v:true)
call nvim_buf_set_option(border_bufnr, 'synmaxcol', 3000) " #27
call nvim_buf_set_lines(border_bufnr, 0, -1, v:true, lines)
call nvim_open_win(border_bufnr, v:false, border_opts)
" Floating window border highlight
augroup floaterm_border_highlight
autocmd!
autocmd FileType floaterm_border ++once execute printf(
\ 'syn match Border /.*/ | hi Border guibg=%s guifg=%s',
\ g:floaterm_background,
\ g:floaterm_border_color
\ )
augroup END
call nvim_buf_set_option(border_bufnr, 'filetype', 'floaterm_border')
""
" TODO:
" Use 'relative': 'cursor' for the border window
" Use 'relative':'win'(which behaviors not as expected...) for content window
let opts = {
\ 'relative': 'editor',
\ 'anchor': vert . hor,
\ 'row': row + (vert ==# 'N' ? 1 : -1),
\ 'col': col + (hor ==# 'W' ? 1 : -1),
\ 'width': a:width,
\ 'height': a:height,
\ 'style':'minimal'
\ }
if a:found_bufnr > 0
call nvim_open_win(a:found_bufnr, v:true, opts)
return [0, border_bufnr]
else
let bufnr = nvim_create_buf(v:false, v:true)
call nvim_open_win(bufnr, v:true, opts)
terminal
return [bufnr, border_bufnr]
endif
endfunction
function! s:open_floating_normaml(found_bufnr, height, width) abort
if a:found_bufnr > 0
if &lines > 30
execute 'botright ' . a:height . 'split'
execute 'buffer ' . a:found_bufnr
else
botright split
execute 'buffer ' . a:found_bufnr
endif
return
else
if &lines > 30
if has('nvim')
execute 'botright ' . a:height . 'split term://' . &shell
else
botright terminal
resize a:height
endif
else
if has('nvim')
execute 'botright split term://' . &shell
else
botright terminal
endif
endif
return bufnr('%')
endif
endfunction
function! floaterm#start(action) abort
if !floaterm#util#is_floaterm_available()
return
endif
if a:action ==# 'new'
call g:floaterm.new()
elseif a:action ==# 'next'
call g:floaterm.next()
elseif a:action ==# 'prev'
call g:floaterm.prev()
elseif a:action ==# 'toggle'
call g:floaterm.toggle()
endif
endfunction