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.
324 lines
9.2 KiB
324 lines
9.2 KiB
6 years ago
|
" ============================================================================
|
||
|
" 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
|