diff --git a/.travis.yml b/.travis.yml index c3bf0aa64..eac636a70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,10 @@ -sudo: false -dist: trusty +dist: xenial language: python python: - 3.6 + - 3.7 install: - eval "$(curl -Ss https://raw.githubusercontent.com/neovim/bot-ci/master/scripts/travis-setup.sh) nightly-x64" diff --git a/Makefile b/Makefile index b31f3f7ed..be8a2c4d3 100644 --- a/Makefile +++ b/Makefile @@ -5,11 +5,7 @@ export THEMIS_HOME := ./vim-themis install: vim-themis - pip install pynvim --upgrade - pip install pytest --upgrade - pip install flake8 --upgrade - pip install mypy --upgrade - pip install vim-vint --upgrade + pip install --upgrade -r test/requirements.txt lint: vint --version diff --git a/README.md b/README.md index 578428818..8d0d0c4c1 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ denite.nvim [![Build Status](https://travis-ci.org/Shougo/denite.nvim.svg?branch=master)](https://travis-ci.org/Shougo/denite.nvim) +Note: Denite.nvim does not define any of default mappings. You need to define +them. + ## About @@ -88,9 +91,28 @@ You must install "pynvim" module with pip **Note:** You need to do 1. and 2. with the common-arch files (x86 or x64). +## Examples + +```vim +" Define mappings +autocmd FileType denite call s:denite_my_settings() +function! s:denite_my_settings() abort + nnoremap + \ denite#do_map('do_action') + nnoremap d + \ denite#do_map('do_action', 'delete') + nnoremap p + \ denite#do_map('do_action', 'preview') + nnoremap q + \ denite#do_map('quit') + nnoremap i + \ denite#do_map('open_filter_buffer') + nnoremap + \ denite#do_map('toggle_select').'j' +endfunction +``` + + ## Screenshots -![file/rec source](https://user-images.githubusercontent.com/13142418/34324674-b8ddd5b8-e840-11e7-9b77-d94e1b084bda.gif) -![SpaceVim Guide](https://user-images.githubusercontent.com/13142418/34324752-e5a89900-e842-11e7-9f87-6d8789ba3871.gif) -![colorscheme source](https://user-images.githubusercontent.com/13142418/34324786-f4dd39a2-e843-11e7-97ef-7a48ee04d27b.gif) -![floating window](https://user-images.githubusercontent.com/1239245/54329573-8b047380-4655-11e9-8b9a-ab4a91f4768f.gif) +![denite new UI](https://user-images.githubusercontent.com/1239245/58742567-a155ea80-8460-11e9-9925-09082def2c80.gif) diff --git a/autoload/denite.vim b/autoload/denite.vim index 1d9170500..ff0ea47b2 100644 --- a/autoload/denite.vim +++ b/autoload/denite.vim @@ -22,18 +22,6 @@ function! denite#get_status(name) abort return !exists('b:denite_statusline') ? '' : \ get(b:denite_statusline, a:name, '') endfunction -function! denite#get_status_mode() abort - return denite#get_status('mode') -endfunction -function! denite#get_status_sources() abort - return denite#get_status('sources') -endfunction -function! denite#get_status_path() abort - return denite#get_status('path') -endfunction -function! denite#get_status_linenr() abort - return denite#get_status('linenr') -endfunction function! s:start(sources, user_context) abort if denite#initialize() @@ -46,10 +34,52 @@ function! s:start(sources, user_context) abort call setpos('.', pos) let args = [a:sources, a:user_context] - return denite#util#rpcrequest('_denite_start', args) + return denite#util#rpcrequest('_denite_start', args, v:false) endfunction function! denite#do_action(context, action_name, targets) abort let args = [a:context, a:action_name, a:targets] - return denite#util#rpcrequest('_denite_do_action', args) + return denite#util#rpcrequest('_denite_do_action', args, v:false) +endfunction + +function! denite#do_map(name, ...) abort + let args = denite#util#convert2list(get(a:000, 0, [])) + let esc = (mode() ==# 'i' ? "\" : '') + return printf(esc . ":\call denite#_call_map(%s, %s, %s)\", + \ string(a:name), 'v:false', string(args)) +endfunction +function! denite#_call_map(name, is_async, args) abort + let is_filter = &l:filetype ==# 'denite-filter' + + if is_filter + call denite#filter#_move_to_parent(a:is_async) + endif + + if &l:filetype !=# 'denite' + return + endif + + let args = denite#util#convert2list(a:args) + + call denite#util#rpcrequest( + \ (a:is_async ? '_denite_do_async_map' : '_denite_do_map'), + \ [bufnr('%'), a:name, args], a:is_async) + + if is_filter + let denite_statusline = get(b:, 'denite_statusline', {}) + + call win_gotoid(g:denite#_filter_winid) + + if &l:filetype ==# 'denite-filter' + let b:denite_statusline = denite_statusline + else + stopinsert + endif + endif +endfunction +function! denite#call_map(name, ...) abort + call denite#_call_map(a:name, v:false, get(a:000, 0, [])) +endfunction +function! denite#call_async_map(name, ...) abort + call denite#_call_map(a:name, v:true, get(a:000, 0, [])) endfunction diff --git a/autoload/denite/_main.py b/autoload/denite/_main.py new file mode 100644 index 000000000..72367ce95 --- /dev/null +++ b/autoload/denite/_main.py @@ -0,0 +1,62 @@ +# ============================================================================ +# FILE: _main.py +# AUTHOR: Shougo Matsushita +# License: MIT license +# ============================================================================ + +import sys +import io + +from importlib.util import find_spec +if find_spec('pynvim'): + from pynvim import attach +else: + from neovim import attach + + +def attach_vim(serveraddr): + if len(serveraddr.split(':')) == 2: + serveraddr, port = serveraddr.split(':') + port = int(port) + vim = attach('tcp', address=serveraddr, port=port) + else: + vim = attach('socket', path=serveraddr) + + # sync path + for path in vim.call( + 'globpath', vim.options['runtimepath'], + 'rplugin/python3', 1).split('\n'): + sys.path.append(path) + # Remove current path + del sys.path[0] + + return vim + + +class RedirectStream(io.IOBase): + def __init__(self, handler): + self.handler = handler + + def write(self, line): + self.handler(line) + + def writelines(self, lines): + self.handler('\n'.join(lines)) + + +def main(serveraddr): + vim = attach_vim(serveraddr) + from denite.child import Child + from denite.util import error_tb + stdout = sys.stdout + sys.stdout = RedirectStream(lambda data: vim.out_write(data)) + sys.stderr = RedirectStream(lambda data: vim.err_write(data)) + try: + child = Child(vim) + child.main_loop(stdout) + except Exception as exc: + error_tb(vim, 'Error in child: %r' % exc) + + +if __name__ == '__main__': + main(sys.argv[1]) diff --git a/autoload/denite/custom.vim b/autoload/denite/custom.vim index 6da043118..5ba0313e1 100644 --- a/autoload/denite/custom.vim +++ b/autoload/denite/custom.vim @@ -118,11 +118,14 @@ endfunction function! denite#custom#_call_action(kind, name, context) abort let custom = denite#custom#_get().action + let new_context = {} for key in denite#util#split(a:kind) if has_key(custom, key) && has_key(custom[key], a:name) - call call(custom[key][a:name][0], [a:context]) + let new_context = call(custom[key][a:name][0], [a:context]) endif endfor + + return new_context endfunction function! s:set_custom(dest, name_or_dict, value) abort diff --git a/autoload/denite/filter.vim b/autoload/denite/filter.vim new file mode 100644 index 000000000..25a3beded --- /dev/null +++ b/autoload/denite/filter.vim @@ -0,0 +1,213 @@ +"============================================================================= +" FILE: filter.vim +" AUTHOR: Shougo Matsushita +" License: MIT license +"============================================================================= + +function! denite#filter#_open(context, parent, entire_len, is_async) abort + let id = win_findbuf(g:denite#_filter_bufnr) + if !empty(id) + call win_gotoid(id[0]) + call cursor(line('$'), 0) + else + call s:new_filter_buffer(a:context) + endif + + let g:denite#_filter_parent = a:parent + let g:denite#_filter_entire_len = a:entire_len + + call s:init_buffer() + + " Set the current input + if getline('$') ==# '' + call setline('$', a:context['input']) + else + call append('$', a:context['input']) + endif + + if a:context['prompt'] !=# '' && strwidth(a:context['prompt']) <= 2 + call s:init_prompt(a:context) + endif + + call s:stop_timer() + + if g:denite#_filter_entire_len < + \ a:context['max_dynamic_update_candidates'] && + \ a:context['filter_updatetime'] > 0 + let g:denite#_filter_candidates_timer = timer_start( + \ a:context['filter_updatetime'], + \ {-> s:filter_async()}, {'repeat': -1}) + let g:denite#_filter_buffer_timer = timer_start( + \ a:context['filter_updatetime'], + \ {-> s:update_buffer()}, {'repeat': -1}) + endif + + augroup denite-filter + autocmd! + autocmd InsertLeave call s:update() + augroup END + + call cursor(line('$'), 0) + startinsert! +endfunction + +function! s:init_buffer() abort + setlocal bufhidden=hide + setlocal buftype=nofile + setlocal colorcolumn= + setlocal foldcolumn=0 + setlocal hidden + setlocal nobuflisted + setlocal nofoldenable + setlocal nolist + setlocal nomodeline + setlocal nonumber + setlocal norelativenumber + setlocal nospell + setlocal noswapfile + setlocal nowrap + setlocal winfixheight + + resize 1 + + nnoremap (denite_filter_update) + \ :call async_update() + inoremap (denite_filter_update) + \ :call async_update() + nnoremap (denite_filter_quit) + \ :call quit() + + nmap (denite_filter_update) + nmap q (denite_filter_quit) + imap (denite_filter_update) + + setfiletype denite-filter +endfunction + +function! s:new_filter_buffer(context) abort + if a:context['split'] ==# 'floating' && exists('*nvim_open_win') + call nvim_open_win(bufnr('%'), v:true, { + \ 'relative': 'editor', + \ 'row': a:context['winrow'] + a:context['winheight'], + \ 'col': str2nr(a:context['wincol']), + \ 'width': str2nr(a:context['winwidth']), + \ 'height': 1, + \}) + silent edit denite-filter + let &l:winhighlight = 'Normal:' . a:context['highlight_filter_background'] + else + silent execute a:context['filter_split_direction'] 'split' 'denite-filter' + endif + let g:denite#_filter_winid = win_getid() + let g:denite#_filter_bufnr = bufnr('%') +endfunction + +function! s:init_prompt(context) abort + let name = 'denite_filter_prompt' + let id = 2000 + if exists('*sign_define') + call sign_define(name, { + \ 'text': a:context['prompt'], + \ 'texthl': a:context['highlight_prompt'] + \ }) + call sign_unplace('', {'id': id, 'buffer': bufnr('%')}) + call sign_place(id, '', name, bufnr('%'), {'lnum': line('$')}) + else + execute printf('sign define %s text=%s texthl=%s', + \ name, a:context['prompt'], a:context['highlight_prompt']) + execute printf('silent! sign unplace %d buffer=%s', + \ id, bufnr('%')) + execute printf('sign place %d name=%s line=%d buffer=%d', + \ id, name, line('$'), bufnr('%')) + endif +endfunction + +function! s:filter_async() abort + if &filetype !=# 'denite-filter' + return + endif + + call denite#util#rpcrequest('_denite_do_async_map', + \ [g:denite#_filter_parent, 'filter_async', [getline('.')]], v:true) +endfunction + +function! s:update_buffer() abort + if &filetype !=# 'denite-filter' + return + endif + + call denite#filter#_move_to_parent(v:true) + + call denite#call_map('update_buffer') + + let denite_statusline = get(b:, 'denite_statusline', {}) + + noautocmd call win_gotoid(g:denite#_filter_winid) + + if &l:filetype ==# 'denite-filter' + let b:denite_statusline = denite_statusline + endif +endfunction + +function! s:update() abort + if &filetype !=# 'denite-filter' + return + endif + + let input = getline('.') + + call denite#filter#_move_to_parent(v:true) + + call denite#call_map('filter', input) + + noautocmd call win_gotoid(g:denite#_filter_winid) +endfunction + +function! s:async_update() abort + if &filetype !=# 'denite-filter' + return + endif + + let input = getline('.') + + call s:quit() + + call denite#call_async_map('filter', input) +endfunction + +function! s:quit() abort + if winnr('$') ==# 1 + buffer # + else + close! + endif + + call denite#filter#_move_to_parent(v:false) + + call s:stop_timer() +endfunction + +function! denite#filter#_move_to_parent(is_async) abort + let id = win_findbuf(g:denite#_filter_parent) + if empty(id) + return + endif + + if a:is_async + " Note: noautocmd for statusline flicker + noautocmd call win_gotoid(id[0]) + else + call win_gotoid(id[0]) + endif +endfunction + +function! s:stop_timer() abort + if exists('g:denite#_filter_candidates_timer') + call timer_stop(g:denite#_filter_candidates_timer) + unlet g:denite#_filter_candidates_timer + endif + if exists('g:denite#_filter_buffer_timer') + call timer_stop(g:denite#_filter_buffer_timer) + unlet g:denite#_filter_buffer_timer + endif +endfunction diff --git a/autoload/denite/helper.vim b/autoload/denite/helper.vim index 47f52d806..7c9f8222f 100644 --- a/autoload/denite/helper.vim +++ b/autoload/denite/helper.vim @@ -217,5 +217,5 @@ function! denite#helper#_get_wininfo() abort endfunction function! denite#helper#_get_preview_window() abort return len(filter(range(1, winnr('$')), - \ "getwinvar(v:val, '&previewwindow') == 1")) + \ "getwinvar(v:val, '&previewwindow') ==# 1")) endfunction diff --git a/autoload/denite/init.vim b/autoload/denite/init.vim index f55c7cacb..187d5a5e6 100644 --- a/autoload/denite/init.vim +++ b/autoload/denite/init.vim @@ -49,6 +49,7 @@ function! denite#init#_initialize() abort endif call _denite_init() endif + call s:initialize_variables() catch if denite#util#has_yarp() if !has('nvim') && !exists('*neovim_rpc#serveraddr') @@ -68,30 +69,47 @@ function! denite#init#_initialize() abort endif return 1 endtry + + let g:denite#_update_candidates_timer = timer_start(300, + \ {-> denite#call_async_map('update_candidates')}, {'repeat': -1}) + let g:denite#_update_buffer_timer = timer_start(300, + \ {-> denite#call_map('update_buffer')}, {'repeat': -1}) +endfunction +function! s:initialize_variables() abort + let g:denite#_previewed_buffers = {} + let g:denite#_candidates = [] + let g:denite#_ret = {} + let g:denite#_async_ret = {} + let g:denite#_filter_bufnr = -1 + let g:denite#_serveraddr = + \ denite#util#has_yarp() ? + \ neovim_rpc#serveraddr() : v:servername + if g:denite#_serveraddr ==# '' + " Use NVIM_LISTEN_ADDRESS + let g:denite#_serveraddr = $NVIM_LISTEN_ADDRESS + endif endfunction function! denite#init#_user_options() abort return { - \ 'auto_accel': v:false, \ 'auto_action': '', \ 'auto_resize': v:false, \ 'auto_resume': v:false, \ 'buffer_name': 'default', \ 'cursor_pos': '', - \ 'cursor_wrap': v:false, - \ 'cursor_shape': (has('gui_running') ? v:true : v:false), \ 'cursorline': v:true, \ 'default_action': 'default', \ 'direction': 'botright', \ 'do': '', \ 'empty': v:true, \ 'expand': v:false, - \ 'highlight_cursor': 'Cursor', + \ 'filter_split_direction': 'botright', + \ 'filter_updatetime': 300, + \ 'highlight_filter_background': 'NormalFloat', \ 'highlight_matched_range': 'Underlined', \ 'highlight_matched_char': 'Search', - \ 'highlight_mode_normal': 'WildMenu', - \ 'highlight_mode_insert': 'CursorLine', \ 'highlight_preview_line': 'Search', + \ 'highlight_prompt': 'Special', \ 'highlight_window_background': 'NormalFloat', \ 'ignorecase': v:true, \ 'immediately': v:false, @@ -99,43 +117,33 @@ function! denite#init#_user_options() abort \ 'input': '', \ 'matchers': '', \ 'max_candidate_width': 200, - \ 'mode': '', + \ 'max_dynamic_update_candidates': 20000, \ 'path': getcwd(), \ 'previewheight': &previewheight, - \ 'prompt': '#', - \ 'prompt_highlight': 'Statement', + \ 'prompt': '', \ 'post_action': 'none', \ 'quick_move': '', \ 'refresh': v:false, \ 'resume': v:false, \ 'reversed': v:false, \ 'root_markers': '', - \ 'scroll': 0, \ 'smartcase': v:false, \ 'sorters': '', \ 'split': 'horizontal', \ 'source_names': '', + \ 'start_filter': v:false, \ 'statusline': v:true, - \ 'updatetime': 100, - \ 'skiptime': 500, \ 'unique': v:false, - \ 'use_default_mappings': v:true, \ 'vertical_preview': v:false, \ 'wincol': &columns / 4, \ 'winheight': 20, - \ 'winrow': &lines / 3, + \ 'winrow': &lines / 2 - 10, \ 'winwidth': 90, \ 'winminheight': -1, \} endfunction function! denite#init#_deprecated_options() abort - return { - \ 'auto_highlight': '', - \ 'auto_preview': '', - \ 'select': 'cursor_pos', - \ 'force_quit': '', - \ 'quit': '', - \} + return {} endfunction function! denite#init#_python_version_check() abort diff --git a/autoload/denite/util.vim b/autoload/denite/util.vim index 6349339e2..01933e5b3 100644 --- a/autoload/denite/util.vim +++ b/autoload/denite/util.vim @@ -125,8 +125,8 @@ function! denite#util#open(filename) abort endfunction function! denite#util#cd(path) abort - if exists('*nvim_set_current_dir') - call nvim_set_current_dir(a:path) + if exists('*chdir') + call chdir(a:path) else silent execute 'lcd' fnameescape(a:path) endif @@ -222,7 +222,7 @@ endfunction function! denite#util#has_yarp() abort return !has('nvim') endfunction -function! denite#util#rpcrequest(method, args) abort +function! denite#util#rpcrequest(method, args, is_async) abort if !denite#init#_check_channel() return -1 endif @@ -231,8 +231,16 @@ function! denite#util#rpcrequest(method, args) abort if g:denite#_yarp.job_is_dead return -1 endif - return g:denite#_yarp.request(a:method, a:args) + if a:is_async + return g:denite#_yarp.notify(a:method, a:args) + else + return g:denite#_yarp.request(a:method, a:args) + endif else - return rpcrequest(g:denite#_channel_id, a:method, a:args) + if a:is_async + return rpcnotify(g:denite#_channel_id, a:method, a:args) + else + return rpcrequest(g:denite#_channel_id, a:method, a:args) + endif endif endfunction diff --git a/doc/denite.txt b/doc/denite.txt index 27849c553..30008cd53 100644 --- a/doc/denite.txt +++ b/doc/denite.txt @@ -1,6 +1,6 @@ *denite.txt* Dark powered asynchronous unite all interfaces for NeoVim/Vim. -Version: 2.1 +Version: 3.0 Author: Shougo License: MIT license @@ -13,7 +13,6 @@ Configuration Examples |denite-examples| Interface |denite-interface| Commands |denite-commands| Key mappings |denite-key-mappings| - Default key mappings |denite-default-key-mappings| Functions |denite-functions| Options |denite-options| Sources |denite-sources| @@ -103,7 +102,6 @@ You can enable Python3 interface with pip > If you want to read for Neovim-python/python3 interface install documentation, you should read |nvim-python| and the Wiki. -https://github.com/zchee/deoplete-jedi/wiki/Setting-up-Python-for-Neovim You can check the Python3 installation |:checkhealth| command. @@ -125,6 +123,23 @@ You must install "pynvim" module with pip > ============================================================================== EXAMPLES *denite-examples* > + " Define mappings + autocmd FileType denite call s:denite_my_settings() + function! s:denite_my_settings() abort + nnoremap + \ denite#do_map('do_action') + nnoremap d + \ denite#do_map('do_action', 'delete') + nnoremap p + \ denite#do_map('do_action', 'preview') + nnoremap q + \ denite#do_map('quit') + nnoremap i + \ denite#do_map('open_filter_buffer') + nnoremap + \ denite#do_map('toggle_select').'j' + endfunction + " Change file/rec command. call denite#custom#var('file/rec', 'command', \ ['ag', '--follow', '--nocolor', '--nogroup', '-g', '']) @@ -141,20 +156,6 @@ EXAMPLES *denite-examples* " Read bellow on this file to learn more about scantree.py call denite#custom#var('file/rec', 'command', ['scantree.py']) - " Change mappings. - call denite#custom#map( - \ 'insert', - \ '', - \ '', - \ 'noremap' - \) - call denite#custom#map( - \ 'insert', - \ '', - \ '', - \ 'noremap' - \) - " Change matchers. call denite#custom#source( \ 'file_mru', 'matchers', ['matcher/fuzzy', 'matcher/project_files']) @@ -244,9 +245,6 @@ EXAMPLES *denite-examples* call denite#custom#alias('source', 'file/rec/py', 'file/rec') call denite#custom#var('file/rec/py', 'command',['scantree.py']) - " Change default prompt - call denite#custom#option('default', 'prompt', '>') - " Change ignore_globs call denite#custom#filter('matcher/ignore_globs', 'ignore_globs', \ [ '.git/', '.ropeproject/', '__pycache__/', @@ -321,539 +319,91 @@ COMMANDS *denite-commands* ------------------------------------------------------------------------------ KEY MAPPINGS *denite-key-mappings* -The denite use the lambdalisue/neovim-prompt to provide a rich command-line -like interface. -Report issues to https://github.com/lambdalisue/neovim-prompt when the issues -are on the neovim-prompt's builtin actions/mappings. - - *denite-map-append* - - Append, like |a| in normal mode in vim buffer. - - *denite-map-append_to_line* - - Append to line, like |A| in normal mode in vim buffer. - - *denite-map-assign_next_matched_text* - - Recall next command-line from history that matches pattern in - front of the cursor. - Like |c_| in a native Vim's command-line. - Note: this is a neovim-prompt's builtin action. - - *denite-map-assign_next_text* - - Recall next command-line from history. - Like |c_| in a native Vim's command-line. - Note: this is a neovim-prompt's builtin action. - - *denite-map-assign_previous_matched_text* - - Recall previous command-line from history that matches - pattern in front of the cursor. - Like |c_| in a native Vim's command-line. - Note: this is a neovim-prompt's builtin action. - - *denite-map-assign_previous_text* - - Recall previous command-line from history. - Like |c_| in a native Vim's command-line. - Note: this is a neovim-prompt's builtin action. - - *denite-map-change_line* - - Change line, like |cc| in normal mode in vim buffer. - - *denite-map-change_char* - - Change character like |s| in normal mode in vim buffer. - - *denite-map-change_path* - - Change and restart Denite buffer. - - *denite-map-change_sorters* - + *denite-map-change_sorters* +change_sorters:{arg} Change |denite-option-sorters| to {arg}. If |denite-option-sorters| is {arg}, it will be disabled. - *denite-map-change_word* - - Change word, like |cw| in normal mode in vim buffer. - - *denite-map-choose_action* - + *denite-map-choose_action* +choose_action Choose and fire the action. - *denite-map-delete_char_after_caret* - - Delete a character after the caret. - Note: this is a neovim-prompt's builtin action. - - *denite-map-delete_char_before_caret* - - Delete a character before the caret. - Note: this is a neovim-prompt's builtin action. - - *denite-map-delete_char_under_caret* - - Delete a character under the caret. - Note: this is a neovim-prompt's builtin action. - - *denite-map-delete_word_after_caret* - - Delete a word after the caret. - Note: this is a neovim-prompt's builtin action. - - *denite-map-delete_word_before_caret* - - Delete a word before the caret. - Note: this is a neovim-prompt's builtin action. - - *denite-map-delete_word_under_caret* - - Delete a word under the caret. - Note: this is a neovim-prompt's builtin action. - - *denite-map-delete_text_after_caret* - - Delete a text after the caret. - Note: this is a neovim-prompt's builtin action. - - *denite-map-delete_text_before_caret* - - Delete a text before the caret. - Note: this is a neovim-prompt's builtin action. - - *denite-map-delete_entire_text* - - Delete an entire text. - Note: this is a neovim-prompt's builtin action. - - *denite-map-do_action* - + *denite-map-do_action* +do_action:{action} Close current Denite buffer and fire {action} action. You can find the actions list in |denite-kinds|. + If {action} is empty, "default" will be used. - *denite-map-do_previous_action* - + *denite-map-do_previous_action* +do_previous_action Fire the previous action. - *denite-map-enter_mode* - - Enter to {mode} mode. - - *denite-map-input_command_line* - - Input or overstrike a text through the Vim's native - command-line. - See also |denite-map-toggle_insert_mode|. - - *denite-map-insert_digraph* - - Insert or overstrike a |digraph| at the caret. - Like |c_| in a Vim's native command-line. - See also |denite-map-toggle_insert_mode|. - Note: this is a neovim-prompt's builtin action. - - *denite-map-insert_to_head* - - Insert at head of line, like |I| in normal mode in vim buffer. - - *denite-map-insert_special* - - Insert or overstrike a special character at the caret. - Like |c_| in a Vim's native command-line. - See also |denite-map-toggle_insert_mode|. - Note: this is a neovim-prompt's builtin action. - - *denite-map-insert_word* - - Insert {word} characters. Use this mapping action to define - a mode independent mapping. If you need a mapping which insert - {word} only in insert mode, directly assign {word} like: -> - call denite#custom#map('insert', '', 'hello!', 'noremap') -< - *denite-map-jump_to_next_by* - - Jump to the next candidate with a different value of {word}. - For example, -> - -< - will jump to the next result in a different file in grep. - - *denite-map-jump_to_previous_by* - - Jump to the previous candidate with a different value of - {word}. - - *denite-map-jump_to_next_source* - - Jump to the first candidate of the next source. - - *denite-map-jump_to_previous_source* - - Jump to the first candidate of the previous source. - - *denite-map-leave_mode* - - Return to the previous mode or close current Denite buffer. - - *denite-map-caret_to_end_of_word* - - Move caret to end of word, like |e| in normal mode in vim - buffer. + *denite-map-filter* +filter:{string} + Filter the candidates by {string}. + Note: It is used for filtering UI. - *denite-map-caret_to_next_word* - - Move caret to beginning of next word, like |w| in normal mode - in vim buffer. - - *denite-map-move_caret_to_left* - - Move the caret to a one character left. - Note: this is a neovim-prompt's builtin action. - - *denite-map-move_caret_to_one_word_left* - - Move the caret to a one word left. - Note: this is a neovim-prompt's builtin action. - - *denite-map-move_caret_to_left_anchor* - - Move the caret like |F| in Vim's normal mode. - Note: this is a neovim-prompt's builtin action. - - *denite-map-move_caret_to_right* - - Move the caret to a one character right. - Note: this is a neovim-prompt's builtin action. - - *denite-map-move_caret_to_one_word_right* - - Move the caret to a one word right. - Note: this is a neovim-prompt's builtin action. - - *denite-map-move_caret_to_right_anchor* - - Move the caret like |f| in Vim's normal mode. - Note: this is a neovim-prompt's builtin action. - - *denite-map-move_caret_to_head* - - Move the caret to the head (a start of the text.) - Note: this is a neovim-prompt's builtin action. - - *denite-map-move_caret_to_lead* - - Move the caret to the lead (a first printable character). - Note: this is a neovim-prompt's builtin action. - - *denite-map-move_caret_to_tail* - - Move the caret to the tail (a end of the text). - Note: this is a neovim-prompt's builtin action. - - *denite-map-move_to_first_line* - - Move to first line. - - *denite-map-move_to_last_line* - - Move to last line. - - *denite-map-move_to_next_line* - - Move to next line. - - *denite-map-move_to_previous_line* - - Move to previous line. - - *denite-map-move_to_top* - - Move to the top visible candidate like |H|. - - *denite-map-move_to_middle* - - Move to the middle visible candidate like |M|. - - *denite-map-move_to_bottom* - - Move to the bottom visible candidate like |L|. - - *denite-map-move_up_path* - + *denite-map-move_up_path* +move_up_path Move to the upper path and restart Denite buffer. - *denite-map-multiple_mappings* - - Fire the multiple {mappings} mappings. - {mappings} must be separated by ",". - And you must remove "<" and ">" from mappings. - - Example: > - call denite#custom#map('normal', '', - \ '', 'noremap') -< - *denite-map-nop* - + *denite-map-nop* +nop No operation. - *denite-map-paste_from_register* - - Paste the text from a specified register. - Like |c_| in a Vim's native command-line. - Note: this is a neovim-prompt's builtin action. - - *denite-map-paste_from_default_register* - - Paste the text from a default register (|v:register|). - Note: this is a neovim-prompt's builtin action. + *denite-map-open_filter_buffer* +open_filter_buffer + Open filter buffer to filtering. - *denite-map-print_messages* - + *denite-map-print_messages* +print_messages Output denite messages to |:messages|. Note: You cannot read the messages in Denite window. It is for debug only. - *denite-map-quick_move* - + *denite-map-quick_move* +quick_move Move to the selected candidate with using quick match. - *denite-map-quit* - + *denite-map-quit* +quit Close current Denite buffer. - *denite-map-redraw* - + *denite-map-redraw* +redraw Clear the cache and redraw the candidates. - *denite-map-restart* - + *denite-map-restart* +restart Restart Denite buffer. - *denite-map-restore_sources* - + *denite-map-restore_sources* +restore_sources Move back to the previous sources. Note: It does not restore the cursor position. - *denite-map-suspend* - - Suspend current Denite buffer. - You can resume the Denite buffer by mapping. - - *denite-map-scroll_window_downwards* - - Scroll the window downwards. - - *denite-map-scroll_window_down_one_line* - - Scroll the window one line down like |CTRL-E|. - - *denite-map-scroll_window_upwards* - - Scroll the window upwards. - - *denite-map-scroll_window_up_one_line* - - Scroll the window one line up like |CTRL-Y|. - - *denite-map-scroll_page_forwards* - - Scroll the page forwards. - - *denite-map-scroll_page_backwards* - - Scroll the page backwards. - - *denite-map-scroll_down* - - Scroll down. - - *denite-map-scroll_up* - - Scroll up. - - *denite-map-scroll_cursor_to_top* - - Scroll the cursor to the top of the window like |zt|. - - *denite-map-scroll_cursor_to_middle* - - Scroll the cursor to the middle of the window like |zz|. - - *denite-map-scroll_cursor_to_bottom* - - Scroll the cursor to the bottom of the window like |zb|. - - *denite-map-smart_delete_char_before_caret* - - If no character being typed, |denite-map-quit|. - Otherwise, |denite-map-delete_char_before_caret|. - - *denite-map-toggle_insert_mode* - - Toggle insert mode (insert or overstrike). - Like |c_| in a Vim's native command-line. - Note: this is a neovim-prompt's builtin action. - - *denite-map-toggle_select* - + *denite-map-toggle_select* +toggle_select Toggle cursor candidate select. - *denite-map-toggle_select_all* - + *denite-map-toggle_select_all* +toggle_select_all Toggle all candidates. - *denite-map-toggle_select_down* - - Toggle cursor candidate select and move to next line. - - *denite-map-toggle_select_up* - - Toggle cursor candidate select and move to previous line. - - *denite-map-toggle_matchers* - + *denite-map-toggle_matchers* +toggle_matchers:{arg} Change |denite-option-matchers| to {arg}. If |denite-option-matchers| is {arg}, it will be disabled. - *denite-map-wincmd* - - Suspend and execute 'wincmd %arg%' - Note: 'arg' supports only h,j,k,l,w,W,t,b,p. - - *denite-map-yank_to_register* - - Yank a text into a specified register. - Note: this is a neovim-prompt's builtin action. - - *denite-map-yank_to_default_register* - - Yank a text into a default register (|v:register|). - Note: this is a neovim-prompt's builtin action. - - *denite-default-key-mappings* -Following keymappings are the default keymappings. - -Note: when user hit , denite stop any operations and close its window. - -All mode mappings (override by mode mappings). -{key} {mapping} --------- ----------------------------- - - - - - - -"insert" mode mappings. -{key} {mapping} --------- ----------------------------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"normal" mode mappings. -{key} {mapping} --------- ----------------------------- -$ -* -. -0 - - - - - - - - -P -W -b -h -j -k -l -p -t -w - -A -G -H -I -L -M -P -S -U -X -a -b -cc -cw -d -e -gg -h -i -j -k -l -n -p -q -s -t -u -w -x -y -zb -zt -zz - - - ------------------------------------------------------------------------------ FUNCTIONS *denite-functions* +denite#call_map({map-name}[, {args}]) *denite#call_map()* + Fire {map-name} mapping with {args}. You can find the + mappings list in |denite-mappings|. + {args} behavior depends on {map-name}. + Note: It can be used in denite-filter buffer. + *denite#custom#action()* denite#custom#action({kind}, {name}, {func}[, {options}]) Define {name} action for {kind}. @@ -886,115 +436,9 @@ denite#custom#option({buffer-name}, {dict}) substituted to "_", and "-" prefix is removed. > call denite#custom#option('default', { - \ 'prompt': '>', \ 'short_source_names': v:true \ }) - *denite#custom#map()* -denite#custom#map({mode}, {lhs}, {rhs}[, {params}]) - Set {lhs} mapping to {rhs} in {mode} mode. - If {mode} is "_", the mapping is used in any mode with lower - priority than a mode specific mappings. - The {params} is a space separated |String| which may contain - - "noremap" - Like a Vim's |noremap|. Without this param, {rhs} will - recursively solved. - - "expr" - Like a Vim's |:map-|, {rhs} is assumed as an expression. - The expression is evaluated to obtain an actual {rhs}. - - "nowait" - Like a Vim's |:map-|, return {rhs} immediately when - the mapping has registered with "nowait" params. - - The followings are example usages: -> - " Use existing map to define a custom map - call denite#custom#map('insert', '', '') - call denite#custom#map('insert', '', '') - call denite#custom#map('insert', '', '') - call denite#custom#map('insert', '', '') - - " Use mapping action directly - call denite#custom#map( - \ 'insert', - \ '', - \ '', - \ 'noremap' - \) - - " Insert a current date by expr mapping - function! Date() abort - return strftime("%Y-%m-%d %a") - endfunction - call denite#custom#map( - \ 'insert', - \ '', - \ 'Date()', - \ 'noremap expr' - \) - - " become 'Ctrl-wCtrl-w' instead of 'Fail' - " become 'Success' instead of 'Ctrl-yCtrl-y' - " Because of 'nowait' - call denite#custom#map( - \ 'insert', - \ '', - \ 'Ctrl-w', - \ 'noremap nowait' - \) - call denite#custom#map( - \ 'insert', - \ '', - \ 'Fail', - \ 'noremap' - \) - call denite#custom#map( - \ 'insert', - \ '', - \ 'Ctrl-y', - \ 'noremap' - \) - call denite#custom#map( - \ 'insert', - \ '', - \ 'Success', - \ 'noremap' - \) - call denite#custom#map( - \ 'insert', - \ '%', - \ 'bufname(b:denite_context["bufnr"])', - \ 'noremap expr' - \) - - function! ToggleSorter(sorter) abort - let sorters = split(b:denite_context.sorters, ',') - let idx = index(sorters, a:sorter) - if idx < 0 - call add(sorters, a:sorter) - else - call remove(sorters, idx) - endif - let b:denite_new_context = {} - let b:denite_new_context.sorters = join(sorters, ',') - return '' - endfunction - call denite#custom#map('insert', '', - \ 'ToggleSorter("sorter/reverse")', 'noremap expr nowait') -< - Note: the posterior mappings have higher priority. - Note: cannot be used while is equal to in - neovim-prompt internally. - Note: cannot be used in custom mapping. - See |denite-key-mappings|. - Note: the mapping can get the context by "b:denite_context" - variable. - Note: the mapping can change the context by - "b:denite_new_context" variable. - *denite#custom#source()* denite#custom#source({source-name}, {option-name}, {value}) Set {source-name} source specialized {option-name} to {value}. @@ -1034,32 +478,34 @@ denite#do_action({context}, {action-name}, {targets}) Runs an action {action-name} against {targets}. This will mainly be used in |denite#custom#action()|. +denite#do_map({map-name}[, {args}]) *denite#do_map()* + Fire {map-name} mapping with {args}. You can find the + mappings list in |denite-mappings|. + {args} behavior depends on {map-name}. + Note: It is only used to define mappings. + Note: It can be used in denite-filter buffer. > + + autocmd FileType denite-filter + \ call s:denite_filter_my_settings() + function! s:denite_filter_my_settings() abort + inoremap + \ denite#do_map('quit') + nnoremap + \ denite#do_map('quit') + endfunction + denite#get_status({name}) *denite#get_status()* Returns the {name} part of the status string. It is useful to customize the statusline. + Note: It also works in the Denite filter window. The available status names: - "mode": Current mode + "input": Current filtering text "sources": Current sources and candidates number "path": Specified |denite-option-path| - "linenr": Equivalent to "line_cursor/line_total" - "raw_mode": Current mode, without any processing "buffer_name": Current |denite-option-buffer-name| - "line_cursor": Cursor position. Part of "linenr". "line_total": Total candidates. Part of "linenr". -denite#get_status_mode() *denite#get_status_mode()* - Please use |denite#get_status()| instead. - -denite#get_status_sources() *denite#get_status_sources()* - Please use |denite#get_status()| instead. - -denite#get_status_path() *denite#get_status_path()* - Please use |denite#get_status()| instead. - -denite#get_status_linenr() *denite#get_status_linenr()* - Please use |denite#get_status()| instead. - *denite#initialize()* denite#initialize() Initialize denite and sources. @@ -1087,11 +533,6 @@ OPTIONS *denite-options* Note: If you use both {option-name} and -no-{option-name} in the same denite buffer, it is undefined. - *denite-option-auto-accel* --auto-accel - Accelerate the cursor moving automatically. - Default: false - *denite-option-auto-action* -auto-action={action} When you select a candidate, it runs the action automatically. @@ -1127,17 +568,6 @@ OPTIONS *denite-options* Note: "$" is the last candidate. Default: "" - *denite-option-cursor-shape* --cursor-shape - Enable 'guicursor' support. - Default: v:true if has("gui_running") - - *denite-option-cursor-wrap* --cursor-wrap - If you move when the cursor reaches the top or bottom edge, - the cursor moves to the opposite side. - Default: false - *denite-option-direction* -direction={direction} Specify the Denite window direction as {direction}. @@ -1170,6 +600,25 @@ OPTIONS *denite-options* Note: It is useful for file sources. Default: false + *denite-option-filter-split-direction* +-filter-split-direction={direction} + Specify the Denite filter window direction as {direction}. + Note: If |denite-option-split| is "floating", it is ignored. + + Default: "botright" + + *denite-option-filter-updatetime* +-filter-updatetime={time} + Specify the update time in the Denite filter window. + If it is less than equal 0, the feature will be disabled. + + Default: 300 + + *denite-option-highlight-filter-background* +-highlight-filter-background + Change backgroud color group in floating filter window. + Default: "NormalFloat" + *denite-option-highlight-matched-char* -highlight-matched-char Matched characters highlight. @@ -1180,18 +629,16 @@ OPTIONS *denite-options* Matched range highlight. Default: "Underlined" - *denite-option-highlight-mode-{mode}* --highlight-mode-{mode} - Change |hl-CursorLine| in {mode}. - Default: - "CursorLine" in "insert" mode - "WildMenu" in "normal" mode - *denite-option-highlight-preview-line* -highlight-preview-line Previewed line highlight. Default: "Search" + *denite-option-highlight-prompt* +-highlight-prompt + Prompt highlight in filter window. + Default: "Special" + *denite-option-highlight-window-background* -highlight-window-background Change backgroud color group in floating window. @@ -1210,10 +657,10 @@ OPTIONS *denite-options* *denite-option-immediately-1* -immediately-1 - If the number of candidates is exactly one, it runs the default action + If the number of candidates is exactly one, it runs the + default action immediately. immediately. Default: false - *denite-option-input* -input={input-text} Specify an initial narrowing text. @@ -1231,10 +678,12 @@ OPTIONS *denite-options* Note: Denite skip the match after the width. Default: 200 - *denite-option-mode* --mode={mode} - Specify the default mode. - Default: "insert" + *denite-option-max-dynamic-update-candidates* +-max-dynamic-update-candidates={length} + If the candidates are more than it, denite will ignore the + dynamic filtering. + + Default: 20000 *denite-option-path* -path={input-text} @@ -1247,11 +696,17 @@ OPTIONS *denite-options* "quit": quit the denite buffer. "open": open the denite buffer. - "suspend": suspend the denite buffer. Otherwise: nothing. Default: "none" + *denite-option-prompt* +-prompt={prompt-text} + Specify the prompt in filter window. + Note: It must one or two characters. + + Default: "" + *denite-option-quick-move* -quick-move={action} It runs |denite-map-quick_move| automatically. @@ -1335,39 +790,21 @@ OPTIONS *denite-options* Default: "" + *denite-option-start-filter* +-start-filter + Start filtering on default. + Default: false + *denite-option-statusline* -statusline Enable statusline. Default: true - *denite-option-skiptime* --skiptime={time} - Specify the filtering skiptime when narrowing in huge - candidates(over 20000). - Default: 500 - *denite-option-unique* -unique Unique the candidates by word attribute. Default: false - *denite-option-updatetime* --updatetime={time} - Specify the buffer updatetime when narrowing. - Default: 100 - - *denite-option-use-default-mappings* --use-default-mappings - Enable default key mappings. - - Note: that fundamental mappings including or are - also provided by the default key mappings. So disabling this - option without proper custom mappings would make denite - useless. Use to quit the denite if you accidentally - disable this option. - - Default: true - *denite-option-vertical-preview* -vertical-preview Open the preview window vertically. @@ -1394,7 +831,7 @@ OPTIONS *denite-options* Set the row position of the Denite window if |denite-option-split| is "floating". - Default: &lines / 3 + Default: &lines / 2 - 10 *denite-option-winwidth* -winwidth={window-width} @@ -2004,6 +1441,11 @@ is_public_context (Bool) (Optional) If it is True, the source context can be accessed by action. + *denite-source-attribute-is_volatile* +is_volatile + (Bool) (Optional) + If it is True, the source candidates is not be cached. + *denite-source-attribute-kind* kind (String) or (Kind) (Optional) The candidates kind name or source specific Kind object. @@ -2235,9 +1677,7 @@ A: Yes. Q: I want to change the cursor line color in Denite buffer. A: denite uses 'cursorline' feature. -So you can change the color by |hl-CursorLine| highlight or -|denite-option-highlight-mode-{mode}|. > - Denite file/rec -highlight-mode-insert=Search +So you can change the color by |hl-CursorLine| highlight. > Q: I want to use unite source in denite. @@ -2292,23 +1732,6 @@ optimized for modern multi core processors. It uses 4 processes heavily. More than a quad core CPU is recommended. A dual core CPU is very slow. And you can change |denite-option-updatetime| option. -Q: I want to use the cursor keys to move the candidates. - -A: > - call denite#custom#map( - \ 'insert', - \ '', - \ '', - \ 'noremap' - \) - call denite#custom#map( - \ 'insert', - \ '', - \ '', - \ 'noremap' - \) - - Q: I want to use grep source as interactive mode. The interactive mode gives you a prompt, and searches once the input is complete like helm-swoop.el. @@ -2333,30 +1756,6 @@ Q: Can I make denite window always use 40% of the current window? A: > Denite file -winheight=`40*winheight(0)/100` -Q: Alt keymappings does not work. -> - call denite#custom#map( - \ 'insert', - \ '', - \ '', - \ 'noremap' - \) - -A: -> - function! s:getchar() abort - redraw | echo 'Press any key: ' - let c = getchar() - while c ==# "\" - redraw | echo 'Press any key: ' - let c = getchar() - endwhile - redraw | echomsg printf('Raw: "%s" | Char: "%s"', c, nr2char(c)) - endfunction - command! GetChar call s:getchar() -< -Please execute :GetChar and press . -If it is the same of g, your environment does not support . Q: I want to narrow by path in grep source. @@ -2381,9 +1780,72 @@ Q: I want to use better matcher. A: You can use fruzzy matcher instead. https://github.com/raghur/fruzzy +Q: Not able to get to insert mode to filter. Esc does not work to hide denite +buffer. Enter does move cursor one line down. No nice input line. What to do? + +A: Denite.nvim does not define any of default mappings. You need to define +them. |denite-examples| + +Q: I want to move the cursor in denite filter window. + +A: Really? It is not recommended. > + + autocmd FileType denite-filter call s:denite_filter_my_settings() + function! s:denite_filter_my_settings() abort + inoremap + \ p:call cursor(line('.')+1,0)pA + inoremap + \ p:call cursor(line('.')-1,0)pA + endfunction + +Note: This is very slow in Vim8 environment. + +Q: I want to use multiple action in denite buffer. + +A: You can use |denite#call_map()| for it. +> + autocmd FileType denite + \ call s:denite_my_settings() + function! s:denite_my_settings() abort + nnoremap + \ :call denite_quickfix() + endfunction + function! s:denite_quickfix() + call denite#call_map('toggle_select_all') + call denite#call_map('do_action', 'quickfix') + endfunction +< +Q: I want to use external statusline plugin like lightline/vim-airline etc. + +A: You should disable the internal statusline. > + + call denite#custom#option('_', 'statusline', v:false) + +Q: I want to change current sources in the custom action. + +A: You need to return new context in action function. +> + function! s:candidate_file_rec(context) + let path = a:context['targets'][0]['action__path'] + let narrow_dir = denite#util#path2directory(path) + let sources_queue = a:context['sources_queue'] + [[ + \ {'name': 'file/rec', 'args': [narrow_dir]}, + \ ]] + return {'sources_queue': sources_queue} + endfunction + call denite#custom#action('buffer,directory,file,openable', + \ 'candidate_file_rec', function('s:candidate_file_rec')) +< ============================================================================== COMPATIBILITY *denite-compatibility* +2019.05.01 +* Version 3.0 +* Remove prompt.nvim. +* "-use-default-mappings" option is deprecated. +* Remove suspend feature. +* Change status names in denite#get_status(). + 2019.03.24 * "-auto-highlight" and "-auto-preview" options are deprecated. Please use "-auto-action" option instead. diff --git a/plugin/denite.vim b/plugin/denite.vim index 1fc60b513..04f8e12d5 100644 --- a/plugin/denite.vim +++ b/plugin/denite.vim @@ -9,19 +9,19 @@ if exists('g:loaded_denite') endif let g:loaded_denite = 1 -command! -nargs=+ -range -complete=customlist,denite#helper#complete +command! -nargs=+ -range -bar -complete=customlist,denite#helper#complete \ Denite \ call denite#helper#call_denite('Denite', \ , , ) -command! -nargs=+ -range -complete=customlist,denite#helper#complete +command! -nargs=+ -range -bar -complete=customlist,denite#helper#complete \ DeniteCursorWord \ call denite#helper#call_denite('DeniteCursorWord', \ , , ) -command! -nargs=+ -range -complete=customlist,denite#helper#complete +command! -nargs=+ -range -bar -complete=customlist,denite#helper#complete \ DeniteBufferDir \ call denite#helper#call_denite('DeniteBufferDir', \ , , ) -command! -nargs=+ -range -complete=customlist,denite#helper#complete +command! -nargs=+ -range -bar -complete=customlist,denite#helper#complete \ DeniteProjectDir \ call denite#helper#call_denite('DeniteProjectDir', \ , , ) diff --git a/rplugin/python3/denite/__init__.py b/rplugin/python3/denite/__init__.py index f42a8aae1..aa17e0244 100644 --- a/rplugin/python3/denite/__init__.py +++ b/rplugin/python3/denite/__init__.py @@ -33,8 +33,16 @@ def start(self, args): self._rplugin.start(args) @vim.rpc_export('_denite_do_action', sync=True) - def take_action(self, args): - return self._rplugin.take_action(args) + def do_action(self, args): + return self._rplugin.do_action(args) + + @vim.rpc_export('_denite_do_map', sync=True) + def do_map(self, args): + return self._rplugin.do_map(args) + + @vim.rpc_export('_denite_do_async_map', sync=False) + def do_async_map(self, args): + return self._rplugin.do_map(args) if find_spec('yarp'): @@ -48,3 +56,9 @@ def _denite_start(args): def _denite_do_action(args): return global_denite.take_action(args) + + def _denite_do_map(args): + return global_denite.do_map(args) + + def _denite_do_async_map(args): + return global_denite.do_map(args) diff --git a/rplugin/python3/denite/aprocess.py b/rplugin/python3/denite/aprocess.py new file mode 100644 index 000000000..ad7ddb403 --- /dev/null +++ b/rplugin/python3/denite/aprocess.py @@ -0,0 +1,27 @@ +# ============================================================================ +# FILE: aprocess.py +# AUTHOR: Shougo Matsushita +# License: MIT license +# ============================================================================ + +import asyncio + + +class Process(asyncio.SubprocessProtocol): + + def __init__(self, plugin): + self._plugin = plugin + self._vim = plugin._vim + + def connection_made(self, transport): + self._unpacker = self._plugin._connect_stdin( + transport.get_pipe_transport(0)) + + def pipe_data_received(self, fd, data): + unpacker = self._unpacker + unpacker.feed(data) + for child_out in unpacker: + self._plugin._queue_out.put(child_out) + + def process_exited(self): + pass diff --git a/rplugin/python3/denite/base/source.py b/rplugin/python3/denite/base/source.py index 580b741db..67a2de82c 100644 --- a/rplugin/python3/denite/base/source.py +++ b/rplugin/python3/denite/base/source.py @@ -25,6 +25,7 @@ def __init__(self, vim): self.context = {} self.vars = {} self.is_public_context = False + self.is_volatile = False def highlight(self): pass diff --git a/rplugin/python3/denite/child.py b/rplugin/python3/denite/child.py index 5d8f5149f..3d2160ac9 100644 --- a/rplugin/python3/denite/child.py +++ b/rplugin/python3/denite/child.py @@ -9,8 +9,10 @@ import_rplugins, expand, split_input, abspath) import copy +import msgpack import os import re +import sys import time from os.path import normpath, normcase from collections import ChainMap @@ -26,6 +28,36 @@ def __init__(self, vim): self._kinds = {} self._runtimepath = '' self._current_sources = [] + self._unpacker = msgpack.Unpacker( + encoding='utf-8', + unicode_errors='surrogateescape') + self._packer = msgpack.Packer( + use_bin_type=True, + encoding='utf-8', + unicode_errors='surrogateescape') + + def main_loop(self, stdout): + while True: + feed = sys.stdin.buffer.raw.read(102400) + if feed is None: + continue + if feed == b'': + # EOF + return + + self._unpacker.feed(feed) + + for child_in in self._unpacker: + name = child_in['name'] + args = child_in['args'] + queue_id = child_in['queue_id'] + + ret = self.main(name, args, queue_id) + if ret: + # _ret = self._vim.vars['denite#_ret'] + # _ret[queue_id] = ret + print(ret) + self._vim.vars['denite#_ret'] = ret def main(self, name, args, queue_id): ret = None @@ -47,8 +79,6 @@ def main(self, name, args, queue_id): ret = self.get_action(args[0], args[1], args[2]) elif name == 'get_action_names': ret = self.get_action_names(args[0], args[1]) - elif name == 'is_async': - ret = self.is_async() return ret def start(self, context): @@ -76,7 +106,6 @@ def gather_candidates(self, context): ctx['is_redraw'] = context['is_redraw'] ctx['messages'] = context['messages'] ctx['error_messages'] = context['error_messages'] - ctx['mode'] = context['mode'] ctx['input'] = context['input'] ctx['prev_input'] = context['input'] ctx['event'] = 'gather' @@ -103,7 +132,6 @@ def on_init(self, context): source.context = copy.copy(context) source.context['args'] = args source.context['is_async'] = False - source.context['is_skipped'] = False source.context['is_interactive'] = False source.context['all_candidates'] = [] source.context['candidates'] = [] @@ -163,7 +191,10 @@ def filter_candidates(self, context): pattern = '' statuses = [] candidates = [] - for status, partial, patterns in self._filter_candidates(context): + total_entire_len = 0 + for status, partial, patterns, entire_len in self._filter_candidates( + context): + total_entire_len += entire_len candidates += partial statuses.append(status) @@ -195,10 +226,11 @@ def filter_candidates(self, context): candidates.reverse() if self.is_async(): statuses.append('[async]') - return (pattern, statuses, candidates) + return [self.is_async(), pattern, statuses, total_entire_len, + candidates] def do_action(self, context, action_name, targets): - action = self.get_action(context, action_name, targets) + action = self._get_action_targets(context, action_name, targets) if not action: return True @@ -211,21 +243,25 @@ def do_action(self, context, action_name, targets): } if source.is_public_context else {} context['targets'] = targets - return action['func'](context) if action['func'] else self._vim.call( - 'denite#custom#_call_action', - action['kind'], action['name'], context) + new_context = (action['func'](context) + if action['func'] + else self._vim.call( + 'denite#custom#_call_action', + action['kind'], action['name'], context)) + if new_context: + context.update(new_context) def get_action(self, context, action_name, targets): - actions = set() - action = None - for target in targets: - action = self._get_action(context, action_name, target) - if action: - actions.add(action['name']) - if len(actions) > 1: - self.error('Multiple actions are detected: ' + action_name) - return {} - return action if actions else {} + action = self._get_action_targets(context, action_name, targets) + if not action: + return action + + return { + 'name': action['name'], + 'kind': action['kind'], + 'is_quit': action['is_quit'], + 'is_redraw': action['is_redraw'], + } def get_action_names(self, context, targets): kinds = set() @@ -245,8 +281,7 @@ def get_action_names(self, context, targets): def is_async(self): return len([x for x in self._current_sources - if x.context['is_async'] or x.context['is_skipped'] - ]) > 0 + if x.context['is_async']]) > 0 def debug(self, expr): debug(self._vim, expr) @@ -264,60 +299,66 @@ def _filter_candidates(self, context): ctx['input'] = expand(ctx['input']) if context['smartcase']: ctx['ignorecase'] = re.search(r'[A-Z]', ctx['input']) is None - ctx['mode'] = context['mode'] - ctx['async_timeout'] = 0.03 if ctx['mode'] != 'insert' else 0.02 - if ctx['prev_input'] != ctx['input']: + ctx['async_timeout'] = 0.03 + prev_input = ctx['prev_input'] + if prev_input != ctx['input']: ctx['prev_time'] = time.time() if ctx['is_interactive']: ctx['event'] = 'interactive' ctx['all_candidates'] = self._gather_source_candidates( ctx, source) ctx['prev_input'] = ctx['input'] - entire = ctx['all_candidates'] if ctx['is_async']: ctx['event'] = 'async' - entire += self._gather_source_candidates(ctx, source) - if len(entire) > 20000 and (time.time() - ctx['prev_time'] < - int(context['skiptime']) / 1000.0): - ctx['is_skipped'] = True - yield self._get_source_status( - ctx, source, entire, []), [], [] - continue - if not entire: + ctx['all_candidates'] += self._gather_source_candidates( + ctx, source) + if not ctx['all_candidates']: yield self._get_source_status( - ctx, source, entire, []), [], [] + ctx, source, ctx['all_candidates'], []), [], [], 0 continue - ctx['is_skipped'] = False - partial = [] - ctx['candidates'] = entire - for i in range(0, len(entire), 1000): - ctx['candidates'] = entire[i:i+1000] - matchers = [self._filters[x] for x in - (ctx['matchers'].split(',') if ctx['matchers'] - else source.matchers) - if x in self._filters] - self._match_candidates(ctx, matchers) - partial += ctx['candidates'] - if len(partial) >= source.max_candidates: - break - ctx['candidates'] = partial - for f in [self._filters[x] - for x in source.sorters + source.converters - if x in self._filters]: - ctx['candidates'] = f.filter(ctx) - partial = ctx['candidates'][: source.max_candidates] + candidates = self._filter_source_candidates(ctx, source) + + partial = candidates[: source.max_candidates] + for c in partial: c['source_name'] = source.name c['source_index'] = source.index - ctx['candidates'] = [] patterns = filterfalse(lambda x: x == '', ( self._filters[x].convert_pattern(ctx['input']) for x in source.matchers if self._filters[x])) - yield self._get_source_status( - ctx, source, entire, partial), partial, patterns + status = self._get_source_status(ctx, source, + ctx['all_candidates'], partial) + # Free memory + ctx['candidates'] = [] + + yield status, partial, patterns, len(ctx['all_candidates']) + + def _filter_source_candidates(self, ctx, source): + partial = [] + entire = ctx['all_candidates'] + ctx['candidates'] = entire + + for i in range(0, len(entire), 1000): + ctx['candidates'] = entire[i:i+1000] + matchers = [self._filters[x] for x in + (ctx['matchers'].split(',') if ctx['matchers'] + else source.matchers) + if x in self._filters] + self._match_candidates(ctx, matchers) + partial += ctx['candidates'] + if len(partial) >= source.max_candidates: + break + + ctx['candidates'] = partial + for f in [self._filters[x] + for x in source.sorters + source.converters + if x in self._filters]: + ctx['candidates'] = f.filter(ctx) + + return ctx['candidates'] def _gather_source_candidates(self, context, source): max_len = int(context['max_candidate_width']) * 2 @@ -326,6 +367,18 @@ def _gather_source_candidates(self, context, source): candidate['word'] = candidate['word'][: max_len] return candidates + def _get_action_targets(self, context, action_name, targets): + actions = set() + action = None + for target in targets: + action = self._get_action_target(context, action_name, target) + if action: + actions.add(action['name']) + if len(actions) > 1: + self.error('Multiple actions are detected: ' + action_name) + return {} + return action if actions else {} + def _get_source_status(self, context, source, entire, partial): return (source.get_status(context) if not partial else f'{source.get_status(context)}' @@ -455,7 +508,7 @@ def _get_kind(self, context, target): return kind - def _get_action(self, context, action_name, target): + def _get_action_target(self, context, action_name, target): kind = self._get_kind(context, target) if not kind: return {} diff --git a/rplugin/python3/denite/parent.py b/rplugin/python3/denite/parent.py index 1f976caae..a799edfb7 100644 --- a/rplugin/python3/denite/parent.py +++ b/rplugin/python3/denite/parent.py @@ -4,6 +4,18 @@ # License: MIT license # ============================================================================ +import asyncio +import time +import os +import msgpack +import subprocess +from functools import partial +from pathlib import Path +from queue import Queue + +from denite.aprocess import Process +from denite.util import error_tb, error + class _Parent(object): def __init__(self, vim): @@ -29,19 +41,16 @@ def init_syntax(self, context, is_multi): self._put('init_syntax', [context, is_multi]) def filter_candidates(self, context): - return self._put('filter_candidates', [context]) + return self._get('filter_candidates', [context]) def do_action(self, context, action_name, targets): - return self._put('do_action', [context, action_name, targets]) + return self._get('do_action', [context, action_name, targets]) def get_action(self, context, action_name, targets): - return self._put('get_action', [context, action_name, targets]) + return self._get('get_action', [context, action_name, targets]) def get_action_names(self, context, targets): - return self._put('get_action_names', [context, targets]) - - def is_async(self): - return self._put('is_async', []) + return self._get('get_action_names', [context, targets]) class SyncParent(_Parent): @@ -51,3 +60,99 @@ def _start_process(self): def _put(self, name, args): return self._child.main(name, args, queue_id=None) + + def _get(self, name, args, is_async=False): + return self._put(name, args) + + +class ASyncParent(_Parent): + def _start_process(self): + self._stdin = None + self._queue_id = '' + self._queue_in = Queue() + self._queue_out = Queue() + self._packer = msgpack.Packer( + use_bin_type=True, + encoding='utf-8', + unicode_errors='surrogateescape') + self._unpacker = msgpack.Unpacker( + encoding='utf-8', + unicode_errors='surrogateescape') + + startupinfo = None + if os.name == 'nt': + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + + main = str(Path(__file__).parent.parent.parent.parent.joinpath( + 'autoload', 'denite', '_main.py')) + + self._hnd = self._vim.loop.create_task( + self._vim.loop.subprocess_exec( + partial(Process, self), + self._vim.vars.get('python3_host_prog', 'python3'), + main, + self._vim.vars['denite#_serveraddr'], + stderr=None, + startupinfo=startupinfo)) + + def _connect_stdin(self, stdin): + self._stdin = stdin + return self._unpacker + + # def filter_candidates(self, context): + # if self._queue_id: + # # Use previous id + # queue_id = self._queue_id + # else: + # queue_id = self._put('filter_candidates', [context]) + # if not queue_id: + # return (False, []) + # + # get = self._get(queue_id, True) + # if not get: + # # Skip the next merge_results + # self._queue_id = queue_id + # return [True, '', [], [], 0] + # self._queue_id = '' + # results = get[0] + # return results if results else [False, '', [], [], 0] + + def _put(self, name, args): + if not self._hnd: + return None + + queue_id = str(time.time()) + msg = self._packer.pack({ + 'name': name, 'args': args, 'queue_id': queue_id + }) + self._queue_in.put(msg) + + if self._stdin: + try: + while not self._queue_in.empty(): + self._stdin.write(self._queue_in.get_nowait()) + except BrokenPipeError: + error_tb(self._vim, 'Crash in child process') + error(self._vim, 'stderr=' + str(self._proc.read_error())) + self._hnd = None + return queue_id + + def _get(self, queue_id, is_async=False): + if not self._hnd: + return None + + c = asyncio.sleep(5) + self._vim.run_coroutine(c) + return self._vim.vars['denite#_ret'] + + # outs = [] + # while not self._queue_out.empty(): + # outs.append(self._queue_out.get_nowait()) + # try: + # return [x for x in outs if x['queue_id'] == queue_id] + # except TypeError: + # error_tb(self._vim, + # '"stdout" seems contaminated by sources. ' + # '"stdout" is used for RPC; Please pipe or discard') + # return [] diff --git a/rplugin/python3/denite/prompt/.gitignore b/rplugin/python3/denite/prompt/.gitignore deleted file mode 100644 index 5dc6b925d..000000000 --- a/rplugin/python3/denite/prompt/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -docs/_build -ci-test -module diff --git a/rplugin/python3/denite/prompt/.travis.yml b/rplugin/python3/denite/prompt/.travis.yml deleted file mode 100644 index 72c67bb80..000000000 --- a/rplugin/python3/denite/prompt/.travis.yml +++ /dev/null @@ -1,52 +0,0 @@ -dist: trusty -sudo: false -language: python -python: - - 3.3 - - 3.4 - - 3.5 - - 3.6 -#os: -# - linux -# - osx -#osx_image: xcode8 - -addons: - apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-trusty-4.0 - packages: - - autoconf - - automake - - apport - - build-essential - - clang-4.0 - - cmake - - cscope - - g++-5-multilib - - g++-multilib - - gcc-5-multilib - - gcc-multilib - - gdb - - language-pack-tr - - libc6-dev-i386 - - libtool - - llvm-4.0-dev - - locales - - pkg-config - - unzip - - valgrind - - xclip - -install: - - curl -sL git.io/v18DU | bash - - git clone --depth 1 --single-branch -b ci-test https://github.com/lambdalisue/neovim-prompt ci-test - - pip install coveralls - -script: - - cd ci-test - - PATH="$HOME/neovim/bin:$PATH" sh ./scripts/test.sh - -after_success: - - coveralls diff --git a/rplugin/python3/denite/prompt/LICENSE.md b/rplugin/python3/denite/prompt/LICENSE.md deleted file mode 100644 index f02464156..000000000 --- a/rplugin/python3/denite/prompt/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) since 2016 Alisue, hashnote.net - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/rplugin/python3/denite/prompt/README.md b/rplugin/python3/denite/prompt/README.md deleted file mode 100644 index ca75502d0..000000000 --- a/rplugin/python3/denite/prompt/README.md +++ /dev/null @@ -1,213 +0,0 @@ -neovim-prompt -=============================================================================== -[![Travis CI](https://img.shields.io/travis/lambdalisue/neovim-prompt/master.svg?style=flat-square&label=Travis%20CI)](https://travis-ci.org/lambdalisue/neovim-prompt) -[![Coverage Status](https://coveralls.io/repos/github/lambdalisue/neovim-prompt/badge.svg?branch=master)](https://coveralls.io/github/lambdalisue/neovim-prompt?branch=master) -[![Code Quality](https://img.shields.io/scrutinizer/g/lambdalisue/neovim-prompt/master.svg)](https://scrutinizer-ci.com/g/lambdalisue/neovim-prompt/?branch=master) -![Version 1.0.0](https://img.shields.io/badge/version-1.0.0-yellow.svg?style=flat-square) -![Support Neovim 0.1.6 or above](https://img.shields.io/badge/support-Neovim%200.1.6%20or%20above-green.svg?style=flat-square) -![Support Vim 8.0 or above](https://img.shields.io/badge/support-Vim%208.0.0%20or%20above-yellowgreen.svg?style=flat-square) -[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE.md) -[![Documentation Status](https://readthedocs.org/projects/neovim-prompt/badge/?version=latest)](http://neovim-prompt.readthedocs.io/en/latest/?badge=latest) - - -A customizable command-line prompt module for Neovim/Vim. -This repository is assumed to used as a submodule/subtree package. - - -Usage -------------------------------------------------------------------------------- - -Assume you are 'PETER' and you are making 'my-awesome-vim-plugin'. - -### git submodule - -If you won't touch the code in neovim-prompt, this is a best way to use neovim-prompt in your plugin. - -```sh -git clone https://github.com/PETER/my-awesome-vim-plugin -cd my-awesome-vim-plugin -git submodule add https://github.com/lambdalisue/neovim-prompt rplugin/python3/my_awesome_vim_plugin/prompt -``` - -### git subtree - -It is complicated so if you are not really familiar with git subtree, you should use git submodule instead. - -First of all, fork https://github.com/lambdalisue/neovim-prompt to GitHub. -Then clone your neovim-prompt to the same parent directory to the your plugin like - -```sh -git clone https://github.com/PETER/my-awesome-vim-plugin -git clone https://github.com/PETER/neovim-prompt -``` - -Then, add cloned local repository as a remote repository of 'my-awesome-vim-plugin' like - -```sh -cd my-awesome-vim-plugin -git remote add neovim-prompt ../neovim-prompt -``` - -Then perform the following. Note that following is used for update neovim-prompt to the latest as well. - -```sh -git fetch neovim-prompt -git subtree pull --prefix=rplugin/python3/my_awesome_vim_plugin/prompt neovim-prompt master --squash -``` - -When you change the neovim-prompt code and you think it's valuable for other peoples, make a PR with - -```sh -git fetch neovim-prompt -git subtree push --prefix=rplugin/python3/my_awesome_vim_plugin/prompt neovim-prompt some-awesome-implementation --squash -cd ../neovim-prompt -git checkout some-awesome-implementation -git push -u origin -``` - -Then send me a PR. I'll check the implementations and concepts. -Please try to make clear commits before sending me a PR. - - -### Python code - -If you followed the above, you can use neovim-prompt as like the following. - -```python3 -from .prompt.prompt import ( - Prompt, - STATUS_ACCEPT, - STATUS_CANCEL, - STATUS_INTERRUPT, -) - -# ... - - prompt = Prompt() - status = prompt.start() - if status == STATUS_ACCEPT: - result = prompt.text - # Do what ever you want - elif status == STATUS_CANCEL: - # User hit (or whatever which is mapped to ) - elif status == STATUS_INTERRUPT: - # User hit to interrupt - -# ... -``` - -See [API document](http://neovim-prompt.readthedocs.io/en/latest/?badge=latest) or real world examples for more detail. - - -Tests ----------------------------------------------------------- - -While neovim-prompt is assumed to be used as a git submodule/subtree, the 'master' branch does not contain any code for testing but 'ci-test' branch. - -First of all, clone 'ci-test' branch of neovim-prompt in neovim-prompt repository as - -```sh -git clone https://github.com/lambdalisue/neovim-prompt -cd neovim-prompt -git clone https://github.com/lambdalisue/neovim-prompt --branch ci-test ci-test -``` - -Then cd to 'ci-test' and run `scripts/test.sh` to perform tests. - -```sh -cd ci-test -./scripts/test.sh -``` - -If you implement features, do not forget to add tests in ci-test branch. - - -Builtin actions ----------------------------------------------------------- - -neovim-prompt provides the following builtin actions. - -Name | Description ----- | ----------- -`` | Accept the input and return `STATUS_ACCEPT`. -`` | Cancel the input and return `STATUS_CANCEL`. -`` | Toggle insert mode (insert/overstrike) -`` | Delete a character before the caret -`` | Delete a word before the caret (respect `iskeyword` in Vim) -`` | Delete a character after the caret -`` | Delete a word after the caret (respect `iskeyword` in Vim) -`` | Delete a character under the caret -`` | Delete a word under the caret (respect `iskeyword` in Vim) -`` | Delete test before the caret -`` | Delete test after the caret -`` | Delete entire text -`` | Move the caret to one character left -`` | Move the caret to one word left -`` | Move the caret like `F` in Vim's normal mode -`` | Move the caret to one character right -`` | Move the caret to one word right -`` | Move the caret like `f` in Vim's normal mode -`` | Move the caret to the head -`` | Move the caret to the start of the printable character -`` | Move the caret to the tail -`` | Recall previous command-line from history -`` | Recall next command-line from history -`` | Recall previous command-line from history that matches pattern in front of the caret -`` | Recall next command-line from history that matches pattern in front of the caret -`` | Paste text from a specified register -`` | Paste text from `v:register` -`` | Copy text to a specified register -`` | Copy text to `v:register` -`` | Specify and insert a special character (e.g. `^V`, `^M`) -`` | Specify and insert a digraph character (See `:help digraph`) - -The above actions are defined in [action.py](./action.py) - -Default mappings ----------------------------------------------------------- - -The default mapping is determined from a Vim's native command-line (`:help ex-edit-index`.) - -Key | Action | Params ----- | ----------- | ----- -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` -`` | `` | `noremap` - -The above actions are defined in [keymap.py](./kaymap.py) - -Plugins which use neovim-prompt ----------------------------------------------------------- - -- [lambdalisue/lista.nvim](https://github.com/lambdalisue/lista.nvim) -- [Shougo/denite.nvim](https://github.com/Shougo/denite.nvim) diff --git a/rplugin/python3/denite/prompt/__init__.py b/rplugin/python3/denite/prompt/__init__.py deleted file mode 100644 index f7bcfddcc..000000000 --- a/rplugin/python3/denite/prompt/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""A prompt library for rplugin in Neovim.""" -__author__ = 'lambdalisue' -__license__ = 'MIT' diff --git a/rplugin/python3/denite/prompt/action.py b/rplugin/python3/denite/prompt/action.py deleted file mode 100644 index 1497d7453..000000000 --- a/rplugin/python3/denite/prompt/action.py +++ /dev/null @@ -1,464 +0,0 @@ -"""Prompt action module.""" -import re -from .digraph import Digraph -from .util import getchar, int2char, int2repr, build_keyword_pattern_set - - -ACTION_PATTERN = re.compile( - r'(?P(?:\w+):(?P