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.
 
 
 
 
 
 

231 lines
7.0 KiB

" vim:sw=2:
" ============================================================================
" FileName: terminal.vim
" Author: voldikss <dyzplus@gmail.com>
" GitHub: https://github.com/voldikss
" ============================================================================
let s:timer_map = {}
let s:channel_map = {}
function! s:on_floaterm_create(bufnr) abort
call setbufvar(a:bufnr, '&buflisted', 0)
call setbufvar(a:bufnr, '&filetype', 'floaterm')
augroup floaterm_enter_insertmode
autocmd! * <buffer>
autocmd! User FloatermOpen
autocmd User FloatermOpen call floaterm#util#startinsert()
autocmd BufEnter <buffer> call floaterm#util#startinsert()
execute printf(
\ 'autocmd BufHidden,BufWipeout <buffer=%s> call floaterm#window#hide(%s)',
\ a:bufnr,
\ a:bufnr
\ )
if floaterm#config#get(a:bufnr, 'disposable')
execute printf(
\ 'autocmd BufHidden <buffer=%s> call floaterm#terminal#kill(%s)',
\ a:bufnr,
\ a:bufnr
\ )
endif
augroup END
endfunction
" for vim8: a:000 is empty
" for nvim: a:000 is ['exit'](event)
function! s:on_floaterm_close(bufnr, callback, job, data, ...) abort
if a:bufnr == -1
" In vim, buffnr is not known before starting a job, therefore, it's
" impossible to pass the bufnr to a job's callback function. Also change
" callback after a job was spawned seem not feasible. Therefore, iterate s:
" channel_map and get the bufnr whose channel matches the channel of a:job
for [buf, chan] in items(s:channel_map)
if chan == job_getchannel(a:job)
let bufnr = str2nr(buf)
break
endif
endfor
else
let bufnr = a:bufnr
endif
let opener = floaterm#config#get(bufnr, 'opener')
call setbufvar(bufnr, '&bufhidden', 'wipe')
call floaterm#config#set(bufnr, 'jobexists', v:false)
let autoclose = floaterm#config#get(bufnr, 'autoclose')
if (autoclose == 1 && a:data == 0) || (autoclose == 2) || (a:callback isnot v:null)
call floaterm#window#hide(bufnr)
" if the floaterm is created with --silent, delete the buffer explicitly
silent! execute bufnr . 'bdelete!'
" update lightline
silent doautocmd BufDelete
endif
if a:callback isnot v:null
call a:callback(a:job, a:data, 'exit', opener)
endif
endfunction
" config: local configuration of a specific floaterm, including:
" cwd, name, width, height, title, silent, wintype, position, autoclose, etc.
function! floaterm#terminal#open(bufnr, cmd, jobopts, config) abort
" vim8: must close popup can we open and jump to a new window
if !has('nvim') && &filetype == 'floaterm'
call floaterm#window#hide(bufnr('%'))
endif
if !bufexists(a:bufnr)
" change cwd
let savedcwd = getcwd()
if has_key(a:config, 'cwd')
call floaterm#path#chdir(a:config.cwd)
endif
" spawn terminal
let bufnr = s:spawn_terminal(a:cmd, a:jobopts, a:config)
" hide floaterm immediately if silent
if floaterm#config#get(bufnr, 'silent', 0)
call floaterm#window#hide(bufnr)
endif
" restore cwd
call floaterm#path#chdir(savedcwd)
else
let config = floaterm#config#parse(a:bufnr, a:config)
call floaterm#window#open(a:bufnr, config)
let bufnr = a:bufnr
endif
doautocmd User FloatermOpen
return bufnr
endfunction
function! floaterm#terminal#open_existing(bufnr) abort
if !bufexists(a:bufnr)
call floaterm#util#show_msg(printf("Buffer %s doesn't exists", a:bufnr), 'error')
return
endif
let config = floaterm#config#get_all(a:bufnr)
call floaterm#terminal#open(a:bufnr, '', {}, config)
endfunction
function! s:spawn_terminal(cmd, jobopts, config) abort
if has('nvim')
let bufnr = nvim_create_buf(v:false, v:true)
call floaterm#buflist#add(bufnr)
let a:jobopts.on_exit = function(
\ 's:on_floaterm_close',
\ [bufnr, get(a:jobopts, 'on_exit', v:null)]
\ )
let config = floaterm#config#parse(bufnr, a:config)
call floaterm#window#open(bufnr, config)
let ch = termopen(a:cmd, a:jobopts)
let s:channel_map[bufnr] = ch
else
let a:jobopts.exit_cb = function(
\ 's:on_floaterm_close',
\ [-1, get(a:jobopts, 'on_exit', v:null)]
\ )
if has_key(a:jobopts, 'on_exit')
unlet a:jobopts.on_exit
endif
let a:jobopts.hidden = 1
try
" TODO: need refactor
let config = floaterm#config#parse(-1, a:config)
let a:jobopts['term_cols'] = config.width - 2
let a:jobopts['term_rows'] = config.height - 2
let bufnr = term_start(a:cmd, a:jobopts)
catch
call floaterm#util#show_msg('Failed to execute: ' . a:cmd, 'error')
return
endtry
call floaterm#buflist#add(bufnr)
let job = term_getjob(bufnr)
let s:channel_map[bufnr] = job_getchannel(job)
let config = floaterm#config#parse(bufnr, a:config)
call floaterm#window#open(bufnr, config)
endif
call floaterm#config#set(bufnr, 'jobexists', v:true)
call floaterm#config#set(bufnr, 'cmd', a:cmd)
call s:on_floaterm_create(bufnr)
return bufnr
endfunction
function! floaterm#terminal#send(bufnr, cmds) abort
let ch = get(s:channel_map, a:bufnr, v:null)
if empty(ch) || empty(a:cmds)
return
endif
if has('nvim')
call add(a:cmds, '')
call chansend(ch, a:cmds)
let curr_winnr = winnr()
let ch_winnr = bufwinnr(a:bufnr)
if ch_winnr > 0
noautocmd execute ch_winnr . 'wincmd w'
noautocmd execute 'normal! G'
endif
noautocmd execute curr_winnr . 'wincmd w'
else
let newline = "\n"
if has('win32') && bufname(a:bufnr) !~ 'ipython'
let newline = "\r\n"
endif
call ch_sendraw(ch, join(a:cmds, newline) . newline)
endif
endfunction
function! floaterm#terminal#get_bufnr(termname) abort
let buflist = floaterm#buflist#gather()
for bufnr in buflist
let name = floaterm#config#get(bufnr, 'name')
if name ==# a:termname
return bufnr
endif
endfor
return -1
endfunction
function! floaterm#terminal#kill(bufnr) abort
call floaterm#window#hide(a:bufnr)
if has('nvim')
let job = getbufvar(a:bufnr, '&channel')
if jobwait([job], 0)[0] == -1
call jobstop(job)
endif
else
let job = term_getjob(a:bufnr)
if job != v:null && job_status(job) !=# 'dead'
call job_stop(job, 'kill')
endif
endif
call s:ensure_terminal_kill(a:bufnr)
let s:timer_map[a:bufnr] = timer_start(
\ 5,
\ { -> s:ensure_terminal_kill(a:bufnr) },
\ {'repeat': 3}
\ )
endfunction
function! s:ensure_terminal_kill(bufnr) abort
try
if bufexists(a:bufnr)
execute a:bufnr . 'bwipeout!'
else
call timer_stop(s:timer_map[a:bufnr])
call remove(s:timer_map, a:bufnr)
endif
catch
silent! call popup_close(win_getid())
endtry
endfunction
function! floaterm#terminal#jobexists(bufnr) abort
if has('nvim')
let job = getbufvar(a:bufnr, '&channel')
return jobwait([job], 0)[0] == -1
else
let job = term_getjob(a:bufnr)
return job != v:null && job_status(job) != 'dead'
endif
endfunction