Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

76 Add support for asyncomplete #77

Merged
merged 13 commits into from
Nov 28, 2017
85 changes: 71 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,74 @@ MacVim!

Tmux complete is automatically integrated with the following plugins:

- [neocomplete](https://github.com/Shougo/neocomplete.vim): You can see tmux
completions right in your neocomplete pop-up.
- [asyncomplete](https://github.com/prabirshrestha/asyncomplete.vim)

- [neocomplcache](https://github.com/Shougo/neocomplcache.vim): You can see tmux
completions right in your neocomplcache pop-up.
To see tmux completions in your asyncomplete pop-up you will need the async
plugin as well:

- [deoplete](https://github.com/Shougo/deoplete.nvim): You can see tmux
completions right in your deoplete pop-up.
```vim
Plug 'prabirshrestha/async.vim'
Plug 'prabirshrestha/asyncomplete.vim'
Plug 'wellle/tmux-complete.vim'
```

This integration comes with sensible defaults, but you have some options to
fine tune it. To start put a block like this into your vimrc:

```vim
let g:tmuxcomplete#asyncomplete_source_options = {
\ 'name': 'tmuxcomplete',
\ 'whitelist': ['*'],
\ 'config': {
\ 'splitmode': 'words',
\ 'filter_prefix': 1,
\ 'show_incomplete': 1,
\ 'sort_candidates': 0,
\ 'scrollback': 0
\ }
\ }
```

With `name` you can change how it appears in the pop-up. `whitelist` makes
it possible to enable this integration only for certain filetypes.

The `splitmode` can be `words`, `lines`, `ilines`, or `linies,words`.
`ilines` stands for "inner line", starting with a word character (ignoring
special chararcters in front) and `ilines,words` completes both lines and
words.

If `filter_prefix` is enabled, we will filter candidates based on the
entered text, this usually gives faster results. For fuzzy matching this
should be disabled.

If there you are using many tmux windows with a lot of text in it,
completion can be slow. That's why we start showing candidates as soon as
they come in. If you prefer to only see candidates once the list is
complete, you can disable this by setting `show_incomplete`.

`sort_candidates` controls whether we sort candidates from tmux externally.
If it's enabled we can't get early incomplete results. If you have
`show_incomplete` disabled, this might get slightly quicker results and
potentially better sorted completions.

If `scrollback` is positive we will consider that many lines in each tmux
pane's history for completion.

- [neocomplete](https://github.com/Shougo/neocomplete.vim)

You can see tmux completions right in your neocomplete pop-up.

- [neocomplcache](https://github.com/Shougo/neocomplcache.vim)

- [unite](https://github.com/Shougo/unite.vim): You can use tmux complete
as a unite source:
You can see tmux completions right in your neocomplcache pop-up.

- [deoplete](https://github.com/Shougo/deoplete.nvim)

You can see tmux completions right in your deoplete pop-up.

- [unite](https://github.com/Shougo/unite.vim)

You can use tmux complete as a unite source:

```vim
Unite tmuxcomplete " opens a menu containing words from adjacent tmux windows
Expand All @@ -57,22 +114,22 @@ Tmux complete is automatically integrated with the following plugins:

Use your favorite plugin manager.

- [NeoBundle][neobundle]
- [Vim-plug][vim-plug]

```vim
NeoBundle 'wellle/tmux-complete.vim'
Plug 'wellle/tmux-complete.vim'
```

- [Vundle][vundle]
- [NeoBundle][neobundle]

```vim
Bundle 'wellle/tmux-complete.vim'
NeoBundle 'wellle/tmux-complete.vim'
```

- [Vim-plug][vim-plug]
- [Vundle][vundle]

```vim
Plug 'wellle/tmux-complete.vim'
Bundle 'wellle/tmux-complete.vim'
```

- [Pathogen][pathogen]
Expand Down
113 changes: 113 additions & 0 deletions autoload/asyncomplete/sources/tmuxcomplete.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
let s:defaultopts = {
\ 'name': 'tmuxcomplete',
\ 'whitelist': ['*'],
\ 'completor': function('asyncomplete#sources#tmuxcomplete#completor'),
\ }

" 'splitmode' can be 'words', 'lines', 'ilines', or 'linies,words'
" 'ilines' is inner line, starting with a word character (ignoring special
" chararcters in front)
" 'ilines,words' completes both lines and words
" more combinations can be added per request
"
" if 'filter_prefix' is enabled, we will filter candidates based on the entered
" text, this usually gives faster results. for fuzzy matching this should be
" disabled
"
" if there you are using many tmux windows with a lot of text in it completion
" can be slow. that's why we start showing candidates as soon as they come in
" if you prefer to only see candidates once the list is complete, you can
" disable this by setting 'show_incomplete'
"
" 'sort_candidates' controls whether we sort candidates from tmux externally.
" if it's enabled we can't get early incomplete results. if you have
" 'show_incomplete' disabled, this might get slightly quicker results and
" potentially better sorted completions.
"
" if 'scrollback' is positive we will consider that many lines in each tmux
" pane's history for completion
let s:defaultconfig = {
\ 'splitmode': 'words',
\ 'filter_prefix': 1,
\ 'show_incomplete': 1,
\ 'sort_candidates': 0,
\ 'scrollback': 0
\ }

function! asyncomplete#sources#tmuxcomplete#register(opts)
let l:opts = extend(copy(s:defaultopts), a:opts)
call asyncomplete#register_source(l:opts)
endfunction

function! asyncomplete#sources#tmuxcomplete#completor(opt, ctx)
" taken from asyncomplete-buffer
let l:kw = matchstr(a:ctx.typed, '\w\+$')
let l:kwlen = len(l:kw)
if l:kwlen < 1
return
endif
" echom '#completor for ' . l:kw

let l:config = extend(copy(s:defaultconfig), get(a:opt, 'config', {}))
" echom 'config ' . string(l:config)

let l:params = {
\ 'name': a:opt.name,
\ 'ctx': a:ctx,
\ 'startcol': a:ctx.col - l:kwlen,
\ 'config': l:config,
\ 'kw': l:kw,
\ 'raw': [],
\ 'mapped': []
\ }

" Add first empty candidate as incomplete to allow adding more
" completions later, even if the context changed in between.
call s:complete(l:params, [''], 1)

if !l:config.filter_prefix
let l:kw = ''
endif

let l:cmd = tmuxcomplete#getcommandlist(l:kw, l:config.scrollback, l:config.splitmode)
if !l:config.sort_candidates
call add(l:cmd, '-n')
endif
" echom 'cmd ' . string(l:cmd)

let l:jobid = async#job#start(l:cmd, {
\ 'on_stdout': function('s:stdout', [l:params]),
\ 'on_exit': function('s:exit', [l:params]),
\ })
endfunction

function! s:stdout(params, id, data, event) abort
" echom '#stdout for ' . a:params.kw . ' with ' . (len(a:data) < 5 ? string(a:data) : string(a:data[0 : 1]) . ' ..' . len(a:data) . '.. ' . string(a:data[-2 : -1]))

call extend(a:params.raw, a:data) " to be mapped differently again on exit

if a:params.config.show_incomplete
" surround with pipes while incomplete
call extend(a:params.mapped, map(copy(a:data), '{"word":v:val,"menu":"|' . a:params.name . '|"}'))
call s:complete(a:params, a:params.mapped, 1)
endif
endfunction

function! s:exit(params, id, data, event) abort
" echom '#exit for ' . a:params.kw . ' with ' . a:data

if a:data != 0 " command failed
" echom 'failed with ' . a:data
" set candidates as complete to stop completing on context changes
call s:complete(a:params, [''], 0)
return
endif

" surround with brackends when complete
let l:mapped = map(a:params.raw, '{"word":v:val,"menu":"[' . a:params.name . ']"}')
call s:complete(a:params, l:mapped, 0)
endfunction

function! s:complete(params, candidates, incomplete)
call asyncomplete#complete(a:params.name, a:params.ctx, a:params.startcol, a:candidates, a:incomplete)
endfunction
55 changes: 43 additions & 12 deletions autoload/tmuxcomplete.vim
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,39 @@ endfunction

let s:script = expand('<sfile>:h:h') . "/sh/tmuxcomplete"

function! s:build_command(base, capture_args, splitmode)
function! s:build_command(base, capture_args, splitmode, as)
let pattern = '^' . escape(a:base, '*^$][.\') . '.'
let list_args = get(g:, 'tmuxcomplete#list_args', '-a')
let grep_args = tmuxcomplete#grepargs(a:base)

let command = 'sh ' . shellescape(s:script)
let command .= ' -p ' . shellescape(pattern)
let command .= ' -s ' . shellescape(a:splitmode)
let command .= ' -l ' . shellescape(list_args)
let command .= ' -c ' . shellescape(a:capture_args)
let command .= ' -g ' . shellescape(grep_args)
let command = s:newcommand(a:as)
let command = s:addcommand2(command, a:as, 'sh', s:script)
let command = s:addcommand2(command, a:as, '-p', pattern)
let command = s:addcommand2(command, a:as, '-s', a:splitmode)
let command = s:addcommand2(command, a:as, '-l', list_args)
let command = s:addcommand2(command, a:as, '-c', a:capture_args)
let command = s:addcommand2(command, a:as, '-g', grep_args)

if $TMUX_PANE !=# "" " if running inside tmux
let command .= ' -e' " exclude current pane
if $TMUX_PANE !=# "" " if running inside tmux
let command = s:addcommand1(command, a:as, '-e') " exclude current pane
endif

return command
endfunction

function! tmuxcomplete#getcommand(base, splitmode)
return s:build_command(a:base, s:capture_args, a:splitmode)
return s:build_command(a:base, s:capture_args, a:splitmode, 'string')
endfunction

function! tmuxcomplete#getcommandlist(base, scrollback, splitmode)
let capture_args = s:capture_args . ' -S -' . a:scrollback
return s:build_command(a:base, capture_args, a:splitmode, 'list')
endfunction

function! tmuxcomplete#completions(base, capture_args, splitmode)
let command = s:build_command(a:base, a:capture_args, a:splitmode)
let command = s:build_command(a:base, a:capture_args, a:splitmode, 'string')

let completions = system(command)
let completions = system(command) " TODO: use systemlist()?
if v:shell_error != 0
return []
endif
Expand Down Expand Up @@ -97,6 +103,31 @@ function! tmuxcomplete#grepargs(base)
return '-i'
endfunction

function! s:newcommand(as)
if a:as == 'list'
return []
else " string
return ''
endif
endfunction

function! s:addcommand1(command, as, value)
if a:as == 'list'
return add(a:command, a:value)
else " string
return (a:command == '' ? '' : a:command . ' ') . a:value
endif
endfunction

function! s:addcommand2(command, as, key, value)
if a:as == 'list'
call add(a:command, a:key)
return add(a:command, a:value) " no escaping here
else " string
return (a:command == '' ? '' : a:command . ' ') . a:key . ' ' . shellescape(a:value)
endif
endfunction

" for integration with completion frameworks
function! tmuxcomplete#gather_candidates()
return tmuxcomplete#complete(0, '')
Expand Down
5 changes: 4 additions & 1 deletion plugin/tmuxcomplete.vim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
if exists("g:tmuxcomplete#loaded") || &cp || v:version < 700
finish
endif
let g:tmuxcomplete#loaded = '0.1.1' " version number
let g:tmuxcomplete#loaded = '0.1.3' " version number

function! s:init()
let trigger = get(g:, 'tmuxcomplete#trigger', 'completefunc')
Expand All @@ -15,6 +15,9 @@ function! s:init()
else
echoerr "tmux-complete: unknown trigger: '" . trigger . "'"
endif

let s:options = get(g:, 'tmuxcomplete#asyncomplete_source_options', {})
autocmd User asyncomplete_setup call asyncomplete#sources#tmuxcomplete#register(s:options)
endfunction

call s:init()
Loading