"============================================================================= " shell.vim --- SpaceVim shell layer " Copyright (c) 2016-2023 Wang Shidong & Contributors " Author: Wang Shidong < wsdjeg@outlook.com > " URL: https://spacevim.org " License: GPLv3 "============================================================================= "" " @section shell, layers-shell " @parentsection layers " SpaceVim uses deol.nvim for shell support in neovim and uses vimshell for " vim. For more info, read |deol| and |vimshell|. " " @subsection layer options " " 1. `default_shell`: config the default shell to be used by shell layer. " " @subsection key bindings " > " Key bindings Description " SPC ' Open or switch to terminal windows " q Hide terminal windows in normal mode " ctrl-` Hide terminal window in terminal mode " < let s:SYSTEM = SpaceVim#api#import('system') let s:FLOAT = SpaceVim#api#import('neovim#floating') let s:WIN = SpaceVim#api#import('vim#window') function! SpaceVim#layers#shell#plugins() abort let plugins = [] if has('nvim') call add(plugins,['Shougo/deol.nvim']) endif call add(plugins,['Shougo/vimshell.vim', { 'on_cmd':['VimShell']}]) return plugins endfunction let s:file = expand(':~') let s:lnum = expand('') + 2 function! SpaceVim#layers#shell#config() abort call SpaceVim#mapping#space#def('nnoremap', ["'"], 'call call(' \ . string(function('s:open_default_shell')) . ', [0])', \ ['open-shell', \ [ \ "[SPC '] is to open or jump to default shell window", \ '', \ 'Definition: ' . s:file . ':' . s:lnum, \ ] \ ], 1) call SpaceVim#mapping#space#def('nnoremap', ["\""], 'call call(' \ . string(function('s:open_default_shell')) . ', [1])', \ ['open-shell-in-buffer-dir', \ [ \ "[SPC \"] is to open or jump to default shell window with the current file's pwd", \ '', \ 'Definition: ' . s:file . ':' . s:lnum, \ ] \ ], 1) if has('nvim') || exists(':tnoremap') == 2 exe 'tnoremap :wincmd l' exe 'tnoremap :wincmd h' exe 'tnoremap :wincmd k' exe 'tnoremap :wincmd j' exe 'tnoremap :bprev' exe 'tnoremap :bnext' exe 'tnoremap :q' if s:SYSTEM.isWindows exe 'tnoremap SpaceVim#layers#shell#terminal()' exe 'tnoremap SpaceVim#layers#shell#ctrl_u()' exe 'tnoremap SpaceVim#layers#shell#ctrl_w()' exe 'tnoremap SpaceVim#layers#shell#ctrl_r()' endif endif if has('nvim') augroup spacevim_layer_shell au! au WinEnter,BufWinEnter term://* startinsert au TermOpen * call s:on_term_open() if has('timers') au TermClose * let g:_spacevim_termclose_abuf = expand('') | call timer_start(5, 'SpaceVim#mapping#close_term_buffer') else au TermClose * let g:_spacevim_termclose_abuf = expand('') | call SpaceVim#mapping#close_term_buffer() endif augroup END endif endfunction function! s:on_term_open() abort startinsert let &l:statusline = SpaceVim#layers#core#statusline#get(1) endfunction " FIXME: func! SpaceVim#layers#shell#terminal() abort let line = getline('.') if isdirectory(line[:-2]) return "exit\" endif return '' endf func! SpaceVim#layers#shell#ctrl_u() abort let line = getline('.') let prompt = getcwd() . '>' return repeat("\", len(line) - len(prompt) + 2) endfunction func! SpaceVim#layers#shell#ctrl_r() abort let reg = getchar() if reg == 43 return @+ endif return "\" endfunction func! SpaceVim#layers#shell#ctrl_w() abort let cursorpos = getcurpos() let line = getline(cursorpos[1])[:cursorpos[2]-1] let str = matchstr(line, '[^ ]*\s*$') return repeat("\", len(str)) endfunction let s:default_shell = 'terminal' let s:default_position = 'top' let s:default_height = 30 " the shell should be cached base on the root of a project, cache the terminal " buffer id in: s:shell_cached_br let s:enable_project_shell = 1 let s:shell_cached_br = {} function! SpaceVim#layers#shell#set_variable(var) abort let s:default_shell = get(a:var, 'default_shell', 'terminal') let s:default_position = get(a:var, 'default_position', 'top') let s:default_height = get(a:var, 'default_height', 30) let s:enable_project_shell = get(a:var, 'enable_project_shell', 1) endfunction function! SpaceVim#layers#shell#get_options() abort return ['default_shell', 'default_position', 'default_height',] endfunction let s:open_terminals_buffers = [] " shell windows shoud be toggleable, and can be hide. function! s:open_default_shell(open_with_file_cwd) abort if a:open_with_file_cwd if getwinvar(winnr(), '&buftype') ==# 'terminal' let path = getbufvar(winbufnr(winnr()), '_spacevim_shell_cwd', SpaceVim#plugins#projectmanager#current_root()) else let path = expand('%:p:h') endif else let path = SpaceVim#plugins#projectmanager#current_root() " if the current file is not in a project, the projectmanager return empty " string. Then use current directory as default cwd. if empty(path) let path = getcwd() endif endif " look for already opened terminal windows let windows = [] windo call add(windows, winnr()) for window in windows if getwinvar(window, '&buftype') ==# 'terminal' exe window . 'wincmd w' if getbufvar(winbufnr(window), '_spacevim_shell_cwd') ==# l:path " startinsert do not work in gvim if has('nvim') startinsert else normal! a endif return else " the opened terminal window is not the one we want. " close it, we're gonna open a new terminal window with the given l:path exe 'wincmd c' break endif endif endfor if s:default_position ==# 'float' && exists('*nvim_open_win') let s:term_win_id = s:FLOAT.open_win(bufnr('%'), v:true, \ { \ 'relative': 'editor', \ 'width' : &columns, \ 'height' : &lines * s:default_height / 100, \ 'row': 0, \ 'col': &lines - (&lines * s:default_height / 100) - 2 \ }) exe win_id2win(s:term_win_id) . 'wincmd w' elseif s:default_position ==# 'center-float' && exists('*nvim_open_win') let s:term_win_id = nvim_open_win(bufnr('%'), v:true, \ { \ 'relative': 'editor', \ 'width' : &columns - 10, \ 'height' : &lines - 10, \ 'row': 5, \ 'border' : 'single', \ 'col': 5 \ }) exe win_id2win(s:term_win_id) . 'wincmd w' call nvim_set_option_value('winhighlight', 'NormalFloat:Normal,FloatBorder:WinSeparator', { 'win' : s:term_win_id}) else " no terminal window found. Open a new window let cmd = s:default_position ==# 'float' ? \ 'topleft split' : \ s:default_position ==# 'top' ? \ 'topleft split' : \ s:default_position ==# 'bottom' ? \ 'botright split' : \ s:default_position ==# 'right' ? \ 'rightbelow vsplit' : 'leftabove vsplit' exe cmd let lines = &lines * s:default_height / 100 if lines < winheight(0) && (s:default_position ==# 'top' || s:default_position ==# 'bottom') exe 'resize ' . lines endif endif let w:shell_layer_win = 1 for open_terminal in s:open_terminals_buffers if bufexists(open_terminal) if getbufvar(open_terminal, '_spacevim_shell_cwd') ==# l:path exe 'silent b' . open_terminal " clear the message if has('nvim') startinsert else normal! a endif return endif else " remove closed buffer from list call remove(s:open_terminals_buffers, 0) endif endfor " no terminal window with l:path as cwd has been found, let's open one if s:default_shell ==# 'terminal' if exists(':terminal') if has('nvim') if s:SYSTEM.isWindows let shell = empty($SHELL) ? 'cmd.exe' : $SHELL else let shell = empty($SHELL) ? 'bash' : $SHELL endif enew call termopen(shell, {'cwd': l:path}) " @bug cursor is not cleared when open terminal windows. " in neovim-qt when using :terminal to open a shell windows, the orgin " cursor position will be highlighted. switch to normal mode and back " is to clear the highlight. " This seem a bug of neovim-qt in windows. " " cc @equalsraf if s:SYSTEM.isWindows && has('nvim') stopinsert startinsert endif let s:term_buf_nr = bufnr('%') call extend(s:shell_cached_br, {getcwd() : s:term_buf_nr}) else " handle vim terminal if s:SYSTEM.isWindows let shell = empty($SHELL) ? 'cmd.exe' : $SHELL else let shell = empty($SHELL) ? 'bash' : $SHELL endif let s:term_buf_nr = term_start(shell, {'cwd': l:path, 'curwin' : 1, 'term_finish' : 'close'}) endif call add(s:open_terminals_buffers, s:term_buf_nr) let b:_spacevim_shell = shell let b:_spacevim_shell_cwd = l:path " use WinEnter autocmd to update statusline doautocmd WinEnter setlocal nobuflisted nonumber norelativenumber " use q to hide terminal buffer in vim, if vimcompatible mode is not " enabled, and smart quit is on. if !empty(g:spacevim_windows_smartclose) && !g:spacevim_vimcompatible exe 'nnoremap ' . g:spacevim_windows_smartclose . ' :hide' endif startinsert else echo ':terminal is not supported in this version' endif elseif s:default_shell ==# 'VimShell' VimShell imap exit(vimshell_enter) endif endfunction function! SpaceVim#layers#shell#close_terminal() abort for terminal_bufnr in s:open_terminals_buffers if bufexists(terminal_bufnr) exe 'silent bd!' . terminal_bufnr endif endfor endfunction function! SpaceVim#layers#shell#health() abort call SpaceVim#layers#shell#plugins() call SpaceVim#layers#shell#config() return 1 endfunction function! SpaceVim#layers#shell#loadable() abort return 1 endfunction