class Default(object): @property def is_async(self): return self._denite.is_async() @property def current_mode(self): return self._current_mode def __init__(self, vim): self._vim = vim self._denite = None self._cursor = 0 self._win_cursor = 1 self._selected_candidates = [] self._candidates = [] self._candidates_len = 0 self._result = [] self._context = {} self._current_mode = '' self._mode_stack = [] self._current_mappings = {} self._bufnr = -1 self._winid = -1 self._winrestcmd = '' self._initialized = False self._winheight = 0 self._winwidth = 0 self._winminheight = -1 self._scroll = 0 self._is_multi = False self._matched_pattern = '' self._displayed_texts = [] self._statusline_sources = '' self._prompt = DenitePrompt(self._vim, self._context, weakref.proxy(self)) self._guicursor = '' self._titlestring = '' self._ruler = False self._prev_action = '' self._prev_status = {} self._prev_curpos = [] self._is_suspend = False self._save_window_options = {} self._sources_history = [] def start(self, sources, context): if not self._denite: self._denite = SyncParent(self._vim) self._result = [] context['sources_queue'] = [sources] self._sources_history = [] try: while context['sources_queue']: prev_history = copy.copy(self._sources_history) prev_path = context['path'] self._start(context['sources_queue'][0], context) if prev_history == self._sources_history: self._sources_history.append({ 'sources': context['sources_queue'][0], 'path': prev_path, }) context['sources_queue'].pop(0) context['path'] = self._context['path'] finally: self.cleanup() return self._result def _start(self, sources, context): self._vim.command('silent! autocmd! denite') if re.search(r'\[Command Line\]$', self._vim.current.buffer.name): # Ignore command line window. return if self._initialized and context['resume']: # Skip the initialization if not self._is_suspend: if context['mode']: self._current_mode = context['mode'] update = ('immediately', 'immediately_1', 'cursor_wrap', 'cursor_pos', 'prev_winid', 'quick_move') for key in update: self._context[key] = context[key] if self.check_option(): return self.init_buffer() if context['refresh']: self.redraw() else: if not context['mode']: # Default mode context['mode'] = 'insert' self._context.clear() self._context.update(context) self._context['sources'] = sources self._context['is_redraw'] = False self._current_mode = context['mode'] self._is_multi = len(sources) > 1 if not sources: # Ignore empty sources. error(self._vim, 'Empty sources') return self.init_denite() self.gather_candidates() self.update_candidates() self.init_cursor() if self.check_option(): return self.init_buffer() self._is_suspend = False self.update_displayed_texts() self.change_mode(self._current_mode) self.update_buffer() if self._context['quick_move'] and self.quick_move(): return # Make sure that the caret position is ok self._prompt.caret.locus = self._prompt.caret.tail status = self._prompt.start() if status == STATUS_INTERRUPT: # STATUS_INTERRUPT is returned when user hit <C-c> and the loop has # interrupted. # In this case, denite cancel any operation and close its window. self.quit() return def init_buffer(self): self._prev_status = dict() self._displayed_texts = [] if not self._is_suspend: self._prev_bufnr = self._vim.current.buffer.number self._prev_curpos = self._vim.call('getcurpos') self._prev_wininfo = self._get_wininfo() self._prev_winid = int(self._context['prev_winid']) self._winrestcmd = self._vim.call('winrestcmd') self._scroll = int(self._context['scroll']) if self._scroll == 0: self._scroll = round(self._winheight / 2) if self._context['cursor_shape']: self._guicursor = self._vim.options['guicursor'] self._vim.options['guicursor'] = 'a:None' self._titlestring = self._vim.options['titlestring'] self._ruler = self._vim.options['ruler'] self._switch_buffer() self.resize_buffer() self._winheight = self._vim.current.window.height self._winwidth = self._vim.current.window.width self._options = self._vim.current.buffer.options self._options['buftype'] = 'nofile' self._options['bufhidden'] = 'delete' self._options['swapfile'] = False self._options['buflisted'] = False self._options['modeline'] = False self._options['filetype'] = 'denite' self._options['modifiable'] = True if self._context['split'] == 'floating': # Disable ruler self._vim.options['ruler'] = False self._window_options = self._vim.current.window.options window_options = { 'colorcolumn': '', 'conceallevel': 3, 'concealcursor': 'n', 'cursorcolumn': False, 'foldenable': False, 'foldcolumn': 0, 'list': False, 'number': False, 'relativenumber': False, 'spell': False, 'winfixheight': True, 'wrap': False, } if self._context['cursorline']: window_options['cursorline'] = True self._save_window_options = {} for k, v in window_options.items(): self._save_window_options[k] = self._window_options[k] self._window_options[k] = v self._bufvars = self._vim.current.buffer.vars self._bufnr = self._vim.current.buffer.number self._winid = self._vim.call('win_getid') self._bufvars['denite_statusline'] = {} self._vim.vars['denite#_previewed_buffers'] = {} self._vim.command('silent doautocmd WinEnter') self._vim.command('silent doautocmd BufWinEnter') self._vim.command('doautocmd FileType denite') self.init_syntax() def _switch_buffer(self): split = self._context['split'] if (split != 'no' and self._winid > 0 and self._vim.call('win_gotoid', self._winid)): if (not self._is_suspend and split != 'vertical' and split != 'floating'): # Move the window to bottom self._vim.command('wincmd J') self._winrestcmd = '' else: command = 'edit' if split == 'tab': self._vim.command('tabnew') elif (split == 'floating' and self._vim.call('exists', '*nvim_open_win')): # Use floating window self._vim.call( 'nvim_open_win', self._vim.call('bufnr', '%'), True, { 'relative': 'editor', 'row': int(self._context['winrow']), 'col': int(self._context['wincol']), 'width': int(self._context['winwidth']), 'height': int(self._context['winheight']), }) self._vim.command('highlight NormalFloat guibg=None') elif split != 'no': command = self._get_direction() command += ' vsplit' if split == 'vertical' else ' split' self._vim.call('denite#util#execute_path', f'silent keepalt {command}', '[denite]') def _get_direction(self): direction = self._context['direction'] if direction == 'dynamictop' or direction == 'dynamicbottom': self.update_displayed_texts() winwidth = self._vim.call('winwidth', 0) is_fit = not [ x for x in self._displayed_texts if self._vim.call('strwidth', x) > winwidth ] if direction == 'dynamictop': direction = 'aboveleft' if is_fit else 'topleft' else: direction = 'belowright' if is_fit else 'botright' return direction def _get_wininfo(self): return [ self._vim.options['columns'], self._vim.options['lines'], self._vim.call('win_getid'), ] def _switch_prev_buffer(self): if (self._prev_bufnr == self._bufnr or self._vim.buffers[self._prev_bufnr].name == ''): self._vim.command('enew') else: self._vim.command('buffer ' + str(self._prev_bufnr)) def init_syntax(self): self._vim.command('syntax case ignore') self._vim.command('highlight default link deniteMode ModeMsg') self._vim.command('highlight link deniteMatchedRange ' + self._context['highlight_matched_range']) self._vim.command('highlight link deniteMatchedChar ' + self._context['highlight_matched_char']) self._vim.command('highlight default link ' + 'deniteStatusLinePath Comment') self._vim.command('highlight default link ' + 'deniteStatusLineNumber LineNR') self._vim.command('highlight default link ' + 'deniteSelectedLine Statement') self._vim.command(('syntax match deniteSelectedLine /^[%s].*/' + ' contains=deniteConcealedMark') % (self._context['selected_icon'])) self._vim.command( ('syntax match deniteConcealedMark /^[ %s]/' + ' conceal contained') % (self._context['selected_icon'])) self._denite.init_syntax(self._context, self._is_multi) def init_cursor(self): self._win_cursor = 1 self._cursor = 0 if self._context['reversed']: self.move_to_last_line() def update_candidates(self): (pattern, statuses, self._candidates) = self._denite.filter_candidates(self._context) prev_matched_pattern = self._matched_pattern self._matched_pattern = pattern self._candidates_len = len(self._candidates) self._statusline_sources = ' '.join(statuses) prev_displayed_texts = self._displayed_texts self.update_displayed_texts() updated = (self._displayed_texts != prev_displayed_texts or self._matched_pattern != prev_matched_pattern) if updated and self._denite.is_async() and self._context['reversed']: self.init_cursor() return updated def update_displayed_texts(self): if self._context['auto_resize']: winminheight = int(self._context['winminheight']) if (winminheight is not -1 and self._candidates_len < winminheight): self._winheight = winminheight elif self._candidates_len > int(self._context['winheight']): self._winheight = int(self._context['winheight']) elif self._candidates_len != self._winheight: self._winheight = self._candidates_len self._displayed_texts = [ self.get_candidate_display_text(i) for i in range( self._cursor, min(self._candidates_len, self._cursor + self._winheight)) ] def update_buffer(self): if self._bufnr != self._vim.current.buffer.number: return self.update_status() if self._vim.call('hlexists', 'deniteMatchedRange'): self._vim.command('silent! syntax clear deniteMatchedRange') if self._vim.call('hlexists', 'deniteMatchedChar'): self._vim.command('silent! syntax clear deniteMatchedChar') if self._matched_pattern != '': self._vim.command( r'silent! syntax match deniteMatchedRange /\c%s/ contained' % (regex_convert_py_vim(self._matched_pattern))) self._vim.command(('silent! syntax match deniteMatchedChar /[%s]/ ' 'containedin=deniteMatchedRange contained') % re.sub(r'([\[\]\\^-])', r'\\\1', self._context['input'].replace(' ', ''))) self._vim.current.buffer[:] = self._displayed_texts self.resize_buffer() self.move_cursor() def update_status(self): raw_mode = self._current_mode.upper() cursor_location = self._cursor + self._win_cursor max_len = len(str(self._candidates_len)) linenr = ('{:' + str(max_len) + '}/{:' + str(max_len) + '}').format( cursor_location, self._candidates_len) mode = '-- ' + raw_mode + ' -- ' if self._context['error_messages']: mode = '[ERROR] ' + mode path = '[' + self._context['path'] + ']' status = { 'mode': mode, 'sources': self._statusline_sources, 'path': path, 'linenr': linenr, # Extra 'raw_mode': raw_mode, 'buffer_name': self._context['buffer_name'], 'line_cursor': cursor_location, 'line_total': self._candidates_len, } if status != self._prev_status: self._bufvars['denite_statusline'] = status self._vim.command('redrawstatus') self._prev_status = status if self._context['statusline']: status = ( "%#deniteMode#%{denite#get_status('mode')}%* " + "%{denite#get_status('sources')} %=" + "%#deniteStatusLinePath# %{denite#get_status('path')} %*" + "%#deniteStatusLineNumber#%{denite#get_status('linenr')}%*") if self._context['split'] == 'floating': self._vim.options['titlestring'] = status else: self._window_options['statusline'] = status def update_cursor(self): self.update_displayed_texts() self.update_buffer() def get_display_source_name(self, name): source_names = self._context['source_names'] if not self._is_multi or source_names == 'hide': source_name = '' else: short_name = (re.sub(r'([a-zA-Z])[a-zA-Z]+', r'\1', name) if re.search(r'[^a-zA-Z]', name) else name[:2]) source_name = short_name if source_names == 'short' else name return source_name def get_candidate_display_text(self, index): source_names = self._context['source_names'] candidate = self._candidates[index] terms = [] if self._is_multi and source_names != 'hide': terms.append(self.get_display_source_name( candidate['source_name'])) encoding = self._context['encoding'] abbr = candidate.get('abbr', candidate['word']).encode( encoding, errors='replace').decode(encoding, errors='replace') terms.append(abbr[:int(self._context['max_candidate_width'])]) return (self._context['selected_icon'] if index in self._selected_candidates else ' ') + ' '.join(terms).replace('\n', '') def resize_buffer(self): split = self._context['split'] if split == 'no' or split == 'tab': return winheight = self._winheight winwidth = self._winwidth is_vertical = split == 'vertical' if not is_vertical and self._vim.current.window.height != winheight: self._vim.command('resize ' + str(winheight)) if self._context['reversed']: self._vim.command('normal! zb') elif is_vertical and self._vim.current.window.width != winwidth: self._vim.command('vertical resize ' + str(winwidth)) def check_option(self): if self._context['cursor_pos'].isnumeric(): self.init_cursor() self.move_to_pos(int(self._context['cursor_pos'])) elif re.match(r'\+\d+', self._context['cursor_pos']): for _ in range(int(self._context['cursor_pos'][1:])): self.move_to_next_line() elif re.match(r'-\d+', self._context['cursor_pos']): for _ in range(int(self._context['cursor_pos'][1:])): self.move_to_prev_line() elif self._context['cursor_pos'] == '$': self.move_to_last_line() elif self._context['do'] != '': self.do_command(self._context['do']) return True if (self._candidates and self._context['immediately'] or len(self._candidates) == 1 and self._context['immediately_1']): self.do_immediately() return True return not (self._context['empty'] or self._denite.is_async() or self._candidates) def do_immediately(self): goto = self._winid > 0 and self._vim.call('win_gotoid', self._winid) if goto: # Jump to denite window self.init_buffer() self.update_cursor() self.do_action('default') candidate = self.get_cursor_candidate() echo( self._vim, 'Normal', '[{}/{}] {}'.format(self._cursor + self._win_cursor, self._candidates_len, candidate.get('abbr', candidate['word']))) if goto: # Move to the previous window self.suspend() self._vim.command('wincmd p') def do_command(self, command): self.init_cursor() self._context['post_action'] = 'suspend' while self._cursor + self._win_cursor < self._candidates_len: self.do_action('default', command) self.move_to_next_line() self.quit_buffer() def move_cursor(self): if self._win_cursor > self._vim.call('line', '$'): self._win_cursor = self._vim.call('line', '$') if self._win_cursor != self._vim.call('line', '.'): self._vim.call('cursor', [self._win_cursor, 1]) if self._context['auto_action']: self.do_action(self._context['auto_action']) def change_mode(self, mode): self._current_mode = mode custom = self._context['custom']['map'] use_default_mappings = self._context['use_default_mappings'] highlight = 'highlight_mode_' + mode if highlight in self._context: self._vim.command('highlight! link CursorLine ' + self._context[highlight]) # Clear current keymap self._prompt.keymap.registry.clear() # Apply mode independent mappings if use_default_mappings: self._prompt.keymap.register_from_rules( self._vim, DEFAULT_ACTION_KEYMAP.get('_', [])) self._prompt.keymap.register_from_rules(self._vim, custom.get('_', [])) # Apply mode depend mappings mode = self._current_mode if use_default_mappings: self._prompt.keymap.register_from_rules( self._vim, DEFAULT_ACTION_KEYMAP.get(mode, [])) self._prompt.keymap.register_from_rules(self._vim, custom.get(mode, [])) # Update mode context self._context['mode'] = mode # Update mode indicator self.update_status() def cleanup(self): # Clear previewed buffers if not self._is_suspend and not self._context['has_preview_window']: self._vim.command('pclose!') for bufnr in self._vim.vars['denite#_previewed_buffers'].keys(): if not self._vim.call('win_findbuf', bufnr): self._vim.command('silent bdelete ' + str(bufnr)) self._vim.vars['denite#_previewed_buffers'] = {} clearmatch(self._vim) if not self._context['immediately']: # Redraw to clear prompt self._vim.command('redraw | echo ""') self._vim.command('highlight! link CursorLine CursorLine') if self._context['cursor_shape']: self._vim.command('set guicursor&') self._vim.options['guicursor'] = self._guicursor if self._context['split'] == 'floating': self._vim.options['titlestring'] = self._titlestring self._vim.options['ruler'] = self._ruler def quit_buffer(self): self.cleanup() if self._vim.call('bufwinnr', self._bufnr) < 0: # Denite buffer is already closed return # Restore the window if self._context['split'] == 'no': self._window_options['cursorline'] = False self._switch_prev_buffer() for k, v in self._save_window_options.items(): self._vim.current.window.options[k] = v else: if self._context['split'] == 'tab': self._vim.command('tabclose!') if self._context['split'] != 'tab': self._vim.command('close!') self._vim.call('win_gotoid', self._prev_winid) # Restore the position self._vim.call('setpos', '.', self._prev_curpos) if self._get_wininfo() and self._get_wininfo() == self._prev_wininfo: self._vim.command(self._winrestcmd) def get_cursor_candidate(self): if self._cursor + self._win_cursor > self._candidates_len: return {} return self._candidates[self._cursor + self._win_cursor - 1] def get_selected_candidates(self): if not self._selected_candidates: return [self.get_cursor_candidate() ] if self.get_cursor_candidate() else [] return [self._candidates[x] for x in self._selected_candidates] def redraw(self, is_force=True): self._context['is_redraw'] = is_force if is_force: self.gather_candidates() if self.update_candidates(): self.update_buffer() else: self.update_status() self._context['is_redraw'] = False def quit(self): self._denite.on_close(self._context) self.quit_buffer() self._result = [] return STATUS_ACCEPT def restart(self): self.quit_buffer() self.init_denite() self.gather_candidates() self.init_buffer() self.update_candidates() self.change_mode(self._current_mode) self.update_buffer() def restore_sources(self, context): if not self._sources_history: return history = self._sources_history[-1] context['sources_queue'].append(history['sources']) context['path'] = history['path'] self._sources_history.pop() return STATUS_ACCEPT def init_denite(self): self._mode_stack = [] self._prompt.history.reset() self._denite.start(self._context) self._denite.on_init(self._context) self._initialized = True self._winheight = int(self._context['winheight']) self._winwidth = int(self._context['winwidth']) def gather_candidates(self): self._selected_candidates = [] self._denite.gather_candidates(self._context) def do_action(self, action_name, command=''): candidates = self.get_selected_candidates() if not candidates or not action_name: return self._prev_action = action_name action = self._denite.get_action(self._context, action_name, candidates) if not action: return post_action = self._context['post_action'] is_quit = action['is_quit'] or post_action == 'quit' if is_quit: self.quit() self._denite.do_action(self._context, action_name, candidates) self._result = candidates if command != '': self._vim.command(command) if is_quit and (post_action == 'open' or post_action == 'suspend'): # Re-open denite buffer self.init_buffer() self.change_mode(self._current_mode) self.redraw(False) # Disable quit flag is_quit = False if not is_quit: self._selected_candidates = [] self.redraw(action['is_redraw']) if post_action == 'suspend': self.suspend() self._vim.command('wincmd p') return STATUS_ACCEPT return STATUS_ACCEPT if is_quit else None def choose_action(self): candidates = self.get_selected_candidates() if not candidates: return self._vim.vars['denite#_actions'] = self._denite.get_action_names( self._context, candidates) clear_cmdline(self._vim) action = self._vim.call('input', 'Action: ', '', 'customlist,denite#helper#complete_actions') if action == '': return return self.do_action(action) def move_to_pos(self, pos): self._cursor = int(pos / self._winheight) * self._winheight self._win_cursor = (pos % self._winheight) + 1 self.update_cursor() def move_to_next_line(self): if self._win_cursor + self._cursor < self._candidates_len: if self._win_cursor < self._winheight: self._win_cursor += 1 else: self._cursor += 1 elif self._context['cursor_wrap']: self.move_to_first_line() else: return self.update_cursor() def move_to_prev_line(self): if self._win_cursor > 1: self._win_cursor -= 1 elif self._cursor >= 1: self._cursor -= 1 elif self._context['cursor_wrap']: self.move_to_last_line() else: return self.update_cursor() def move_to_first_line(self): if self._win_cursor > 1 or self._cursor > 0: self._win_cursor = 1 self._cursor = 0 self.update_cursor() def move_to_last_line(self): win_max = min(self._candidates_len, self._winheight) cur_max = self._candidates_len - win_max if self._win_cursor < win_max or self._cursor < cur_max: self._win_cursor = win_max self._cursor = cur_max self.update_cursor() def move_to_top(self): self._win_cursor = 1 self.update_cursor() def move_to_middle(self): self._win_cursor = self._winheight // 2 self.update_cursor() def move_to_bottom(self): self._win_cursor = self._winheight self.update_cursor() def scroll_window_upwards(self): self.scroll_up(self._scroll) def scroll_window_downwards(self): self.scroll_down(self._scroll) def scroll_page_backwards(self): self.scroll_up(self._winheight - 1) def scroll_page_forwards(self): self.scroll_down(self._winheight - 1) def scroll_down(self, scroll): if self._win_cursor + self._cursor < self._candidates_len: if self._win_cursor <= 1: self._win_cursor = 1 self._cursor = min(self._cursor + scroll, self._candidates_len) elif self._win_cursor < self._winheight: self._win_cursor = min(self._win_cursor + scroll, self._candidates_len, self._winheight) else: self._cursor = min(self._cursor + scroll, self._candidates_len - self._win_cursor) else: return self.update_cursor() def scroll_up(self, scroll): if self._win_cursor > 1: self._win_cursor = max(self._win_cursor - scroll, 1) elif self._cursor > 0: self._cursor = max(self._cursor - scroll, 0) else: return self.update_cursor() def scroll_window_up_one_line(self): if self._cursor < 1: return self.scroll_up(1) self._cursor -= 1 self._win_cursor += 1 self.update_cursor() def scroll_window_down_one_line(self): if self._win_cursor <= 1 and self._cursor > 0: return self.scroll_down(1) self._cursor += 1 self._win_cursor -= 1 self.update_cursor() def scroll_cursor_to_top(self): self._cursor += self._win_cursor - 1 self._win_cursor = 1 self.update_cursor() def scroll_cursor_to_middle(self): self.scroll_cursor_to_top() while self._cursor >= 1 and self._win_cursor < self._winheight // 2: self.scroll_window_up_one_line() def scroll_cursor_to_bottom(self): self.scroll_cursor_to_top() while self._cursor >= 1 and self._win_cursor < self._winheight: self.scroll_window_up_one_line() def jump_to_next_by(self, key): keyfunc = self._keyfunc(key) keys = [keyfunc(candidate) for candidate in self._candidates] if not keys or len(set(keys)) == 1: return current_index = self._cursor + self._win_cursor - 1 forward_candidates = self._candidates[current_index:] forward_sources = groupby(forward_candidates, keyfunc) forward_times = len(list(next(forward_sources)[1])) if not forward_times: return remaining_candidates = (self._candidates_len - current_index - forward_times) if next(forward_sources, None) is None: # If the cursor is on the last source self._cursor = 0 self._win_cursor = 1 elif self._candidates_len < self._winheight: # If there is a space under the candidates self._cursor = 0 self._win_cursor += forward_times elif remaining_candidates < self._winheight: self._cursor = self._candidates_len - self._winheight + 1 self._win_cursor = self._winheight - remaining_candidates else: self._cursor += forward_times + self._win_cursor - 1 self._win_cursor = 1 self.update_cursor() def jump_to_prev_by(self, key): keyfunc = self._keyfunc(key) keys = [keyfunc(candidate) for candidate in self._candidates] if not keys or len(set(keys)) == 1: return current_index = self._cursor + self._win_cursor - 1 backward_candidates = reversed(self._candidates[:current_index + 1]) backward_sources = groupby(backward_candidates, keyfunc) current_source = list(next(backward_sources)[1]) try: prev_source = list(next(backward_sources)[1]) except StopIteration: # If the cursor is on the first source last_source = takewhile( lambda candidate: keyfunc(candidate) == keyfunc( self._candidates[-1]), reversed(self._candidates)) len_last_source = len(list(last_source)) if self._candidates_len < self._winheight: self._cursor = 0 self._win_cursor = self._candidates_len - len_last_source + 1 elif len_last_source < self._winheight: self._cursor = self._candidates_len - self._winheight + 1 self._win_cursor = self._winheight - len_last_source else: self._cursor = self._candidates_len - len_last_source self._win_cursor = 1 else: back_times = len(current_source) - 1 + len(prev_source) remaining_candidates = (self._candidates_len - current_index + back_times) if self._candidates_len < self._winheight: self._cursor = 0 self._win_cursor -= back_times elif remaining_candidates < self._winheight: self._cursor = self._candidates_len - self._winheight + 1 self._win_cursor = self._winheight - remaining_candidates else: self._cursor -= back_times - self._win_cursor + 1 self._win_cursor = 1 self.update_cursor() def quick_move(self): def get_quick_move_table(): table = {} context = self._context base = self._win_cursor for [key, number] in context['quick_move_table'].items(): number = int(number) pos = ((base - number) if context['reversed'] else (number + base)) if pos > 0: table[key] = pos return table def quick_move_redraw(table, is_define): bufnr = self._vim.current.buffer.number for [key, number] in table.items(): signid = 2000 + number name = 'denite_quick_move_' + str(number) if is_define: self._vim.command( f'sign define {name} text={key} texthl=Special') self._vim.command(f'sign place {signid} name={name} ' f'line={number} buffer={bufnr}') else: self._vim.command( f'silent! sign unplace {signid} buffer={bufnr}') self._vim.command('silent! sign undefine ' + name) quick_move_table = get_quick_move_table() self._vim.command('echo "Input quick match key: "') quick_move_redraw(quick_move_table, True) self._vim.command('redraw') char = '' while char == '': char = self._vim.call('nr2char', self._vim.call('denite#util#getchar')) quick_move_redraw(quick_move_table, False) if (char not in quick_move_table or quick_move_table[char] > self._winheight): return self._win_cursor = quick_move_table[char] self.update_cursor() if self._context['quick_move'] == 'immediately': self.do_action('default') return True def _keyfunc(self, key): def wrapped(candidate): for k in key, 'action__' + key: try: return str(candidate[k]) except Exception: pass return '' return wrapped def enter_mode(self, mode): if mode == self._current_mode: return self._mode_stack.append(self._current_mode) self.change_mode(mode) def leave_mode(self): if not self._mode_stack: return self.quit() self._current_mode = self._mode_stack[-1] self._mode_stack = self._mode_stack[:-1] self.change_mode(self._current_mode) def suspend(self): if self._bufnr == self._vim.current.buffer.number: if self._context['auto_resume']: self._vim.command('autocmd denite WinEnter <buffer> ' + 'Denite -resume -buffer_name=' + self._context['buffer_name']) for mapping in ['i', 'a', '<CR>']: self._vim.command(f'nnoremap <silent><buffer> {mapping} ' + ':<C-u>Denite -resume -buffer_name=' + f"{self._context['buffer_name']}<CR>") self._is_suspend = True self._options['modifiable'] = False return STATUS_ACCEPT
class Default(object): @property def is_async(self) -> bool: return self._is_async def __init__(self, vim: Nvim) -> None: self._vim = vim self._denite: typing.Optional[SyncParent] = None self._selected_candidates: typing.List[int] = [] self._candidates: Candidates = [] self._cursor = 0 self._entire_len = 0 self._result: typing.List[typing.Any] = [] self._context: UserContext = {} self._bufnr = -1 self._winid = -1 self._winrestcmd = '' self._initialized = False self._winheight = 0 self._winwidth = 0 self._winminheight = -1 self._is_multi = False self._is_async = False self._matched_pattern = '' self._displayed_texts: typing.List[str] = [] self._statusline_sources = '' self._titlestring = '' self._ruler = False self._prev_action = '' self._prev_status: typing.Dict[str, typing.Any] = {} self._prev_curpos: typing.List[typing.Any] = [] self._save_window_options: typing.Dict[str, typing.Any] = {} self._sources_history: typing.List[typing.Any] = [] self._previous_text = '' self._floating = False self._updated = False self._timers: typing.Dict[str, int] = {} def start(self, sources: typing.List[typing.Any], context: UserContext) -> typing.List[typing.Any]: if not self._denite: # if hasattr(self._vim, 'run_coroutine'): # self._denite = ASyncParent(self._vim) # else: self._denite = SyncParent(self._vim) self._result = [] context['sources_queue'] = [sources] self._start_sources_queue(context) return self._result def do_action(self, action_name: str, command: str = '', is_manual: bool = False) -> None: candidates = self._get_selected_candidates() if not self._denite or not candidates or not action_name: return self._prev_action = action_name action = self._denite.get_action( self._context, action_name, candidates) if not action: return post_action = self._context['post_action'] is_quit = action['is_quit'] or post_action == 'quit' if is_quit: self.quit() self._denite.do_action(self._context, action_name, candidates) self._result = candidates if command != '': self._vim.command(command) if is_quit and post_action == 'open': # Re-open denite buffer prev_cursor = self._cursor cursor_candidate = self._get_cursor_candidate() self._init_buffer() self.redraw(False) if cursor_candidate == self._get_candidate(prev_cursor): # Restore the cursor self._move_to_pos(prev_cursor) # Disable quit flag is_quit = False if not is_quit: self._selected_candidates = [] self.redraw(action['is_redraw']) if is_manual and self._context['sources_queue']: self._context['input'] = '' self._context['quick_move'] = '' self._start_sources_queue(self._context) return def redraw(self, is_force: bool = True) -> None: self._context['is_redraw'] = is_force if is_force: self._gather_candidates() if self._update_candidates(): self._update_buffer() else: self._update_status() self._context['is_redraw'] = False def quit(self) -> None: if self._denite: self._denite.on_close(self._context) self._quit_buffer() self._result = [] return def _restart(self) -> None: self._context['input'] = '' self._quit_buffer() self._init_denite() self._gather_candidates() self._init_buffer() self._update_candidates() self._update_buffer() def _start_sources_queue(self, context: UserContext) -> None: if not context['sources_queue']: return self._sources_history.append({ 'sources': context['sources_queue'][0], 'path': context['path'], }) self._start(context['sources_queue'][0], context) if context['sources_queue']: context['sources_queue'].pop(0) context['path'] = self._context['path'] def _start(self, sources: typing.List[typing.Any], context: UserContext) -> None: from denite.ui.map import do_map self._vim.command('silent! autocmd! denite') if re.search(r'\[Command Line\]$', self._vim.current.buffer.name): # Ignore command line window. return resume = self._initialized and context['resume'] if resume: # Skip the initialization update = ('immediately', 'immediately_1', 'cursor_pos', 'prev_winid', 'start_filter', 'quick_move') for key in update: self._context[key] = context[key] self._check_move_option() if self._check_do_option(): return self._init_buffer() if context['refresh']: self.redraw() self._move_to_pos(self._cursor) else: if self._context != context: self._context.clear() self._context.update(context) self._context['sources'] = sources self._context['is_redraw'] = False self._is_multi = len(sources) > 1 if not sources: # Ignore empty sources. error(self._vim, 'Empty sources') return self._init_denite() self._gather_candidates() self._update_candidates() self._init_cursor() self._check_move_option() if self._check_do_option(): return self._init_buffer() self._update_displayed_texts() self._update_buffer() self._move_to_pos(self._cursor) if self._context['quick_move'] and do_map(self, 'quick_move', []): return if self._context['start_filter']: do_map(self, 'open_filter_buffer', []) def _init_buffer(self) -> None: self._prev_status = dict() self._displayed_texts = [] self._prev_bufnr = self._vim.current.buffer.number self._prev_curpos = self._vim.call('getcurpos') self._prev_wininfo = self._get_wininfo() self._prev_winid = int(self._context['prev_winid']) self._winrestcmd = self._vim.call('winrestcmd') self._ruler = self._vim.options['ruler'] self._switch_buffer() self._bufnr = self._vim.current.buffer.number self._winid = self._vim.call('win_getid') self._resize_buffer() self._winheight = self._vim.current.window.height self._winwidth = self._vim.current.window.width self._options = self._vim.current.buffer.options self._options['buftype'] = 'nofile' self._options['bufhidden'] = 'delete' self._options['swapfile'] = False self._options['buflisted'] = False self._options['modeline'] = False self._options['filetype'] = 'denite' self._options['modifiable'] = False if self._floating: # Disable ruler self._vim.options['ruler'] = False self._window_options = self._vim.current.window.options window_options = { 'colorcolumn': '', 'concealcursor': 'inv', 'conceallevel': 3, 'cursorcolumn': False, 'foldcolumn': 0, 'foldenable': False, 'list': False, 'number': False, 'relativenumber': False, 'spell': False, 'winfixheight': True, 'wrap': False, } if self._context['cursorline']: window_options['cursorline'] = True self._save_window_options = {} for k, v in window_options.items(): self._save_window_options[k] = self._window_options[k] self._window_options[k] = v self._bufvars = self._vim.current.buffer.vars self._bufvars['denite'] = { 'buffer_name': self._context['buffer_name'], } self._bufvars['denite_statusline'] = {} self._vim.vars['denite#_previewed_buffers'] = {} self._vim.command('silent doautocmd WinEnter') self._vim.command('silent doautocmd BufWinEnter') self._vim.command('doautocmd FileType denite') if self._context['auto_action']: self._vim.command('autocmd denite ' 'CursorMoved <buffer> ' 'call denite#call_map("auto_action")') self._init_syntax() def _switch_buffer(self) -> None: split = self._context['split'] if (split != 'no' and self._winid > 0 and self._vim.call('win_gotoid', self._winid)): if split != 'vertical' and not self._floating: # Move the window to bottom self._vim.command('wincmd J') self._winrestcmd = '' return self._floating = False command = 'edit' if split == 'tab': self._vim.command('tabnew') elif (split == 'floating' and self._vim.call('exists', '*nvim_open_win')): if self._vim.current.buffer.options['filetype'] != 'denite': self._titlestring = self._vim.options['titlestring'] # Use floating window self._floating = True self._vim.call( 'nvim_open_win', self._vim.call('bufnr', '%'), True, { 'relative': 'editor', 'row': int(self._context['winrow']), 'col': int(self._context['wincol']), 'width': int(self._context['winwidth']), 'height': int(self._context['winheight']), }) elif split != 'no': command = self._get_direction() command += ' vsplit' if split == 'vertical' else ' split' bufname = '[denite]-' + self._context['buffer_name'] if self._vim.call('exists', '*bufadd'): bufnr = self._vim.call('bufadd', bufname) vertical = 'vertical' if split == 'vertical' else '' command = ('buffer' if split in ['no', 'tab', 'floating'] else 'sbuffer') self._vim.command( 'silent keepalt %s %s %s %s' % ( self._get_direction(), vertical, command, bufnr, ) ) else: self._vim.call( 'denite#util#execute_path', f'silent keepalt {command}', bufname) def _get_direction(self) -> str: direction = str(self._context['direction']) if direction == 'dynamictop' or direction == 'dynamicbottom': self._update_displayed_texts() winwidth = self._vim.call('winwidth', 0) is_fit = not [x for x in self._displayed_texts if self._vim.call('strwidth', x) > winwidth] if direction == 'dynamictop': direction = 'aboveleft' if is_fit else 'topleft' else: direction = 'belowright' if is_fit else 'botright' return direction def _get_wininfo(self) -> typing.List[typing.Any]: return [ self._vim.options['columns'], self._vim.options['lines'], self._vim.call('win_getid'), self._vim.call('tabpagebuflist') ] def _switch_prev_buffer(self) -> None: if (self._prev_bufnr == self._bufnr or self._vim.buffers[self._prev_bufnr].name == ''): self._vim.command('enew') else: self._vim.command('buffer ' + str(self._prev_bufnr)) def _init_syntax(self) -> None: self._vim.command('syntax case ignore') self._vim.command('highlight default link deniteInput ModeMsg') self._vim.command('highlight link deniteMatchedRange ' + self._context['highlight_matched_range']) self._vim.command('highlight link deniteMatchedChar ' + self._context['highlight_matched_char']) self._vim.command('highlight default link ' + 'deniteStatusLinePath Comment') self._vim.command('highlight default link ' + 'deniteStatusLineNumber LineNR') self._vim.command('highlight default link ' + 'deniteSelectedLine Statement') if self._floating: self._vim.current.window.options['winhighlight'] = ( 'Normal:' + self._context['highlight_window_background'] ) self._vim.command(('syntax match deniteSelectedLine /^[%s].*/' + ' contains=deniteConcealedMark') % ( self._context['selected_icon'])) self._vim.command(('syntax match deniteConcealedMark /^[ %s]/' + ' conceal contained') % ( self._context['selected_icon'])) if self._denite: self._denite.init_syntax(self._context, self._is_multi) def _update_candidates(self) -> bool: if not self._denite: return False [self._is_async, pattern, statuses, self._entire_len, self._candidates] = self._denite.filter_candidates(self._context) prev_displayed_texts = self._displayed_texts self._update_displayed_texts() prev_matched_pattern = self._matched_pattern self._matched_pattern = pattern prev_statusline_sources = self._statusline_sources self._statusline_sources = ' '.join(statuses) if self._is_async: self._start_timer('update_candidates') else: self._stop_timer('update_candidates') updated = (self._displayed_texts != prev_displayed_texts or self._matched_pattern != prev_matched_pattern or self._statusline_sources != prev_statusline_sources) if updated: self._updated = True self._start_timer('update_buffer') return self._updated def _update_displayed_texts(self) -> None: candidates_len = len(self._candidates) if not self._is_async and self._context['auto_resize']: winminheight = int(self._context['winminheight']) max_height = min(int(self._context['winheight']), self._get_max_height()) if (winminheight is not -1 and candidates_len < winminheight): self._winheight = winminheight elif candidates_len > max_height: self._winheight = max_height elif candidates_len != self._winheight: self._winheight = candidates_len max_source_name_len = 0 if self._candidates: max_source_name_len = max([ len(self._get_display_source_name(x['source_name'])) for x in self._candidates]) self._context['max_source_name_len'] = max_source_name_len self._context['max_source_name_format'] = ( '{:<' + str(self._context['max_source_name_len']) + '}') self._displayed_texts = [ self._get_candidate_display_text(i) for i in range(0, candidates_len) ] def _update_buffer(self) -> None: if self._bufnr != self._vim.current.buffer.number: return self._update_status() if self._vim.call('hlexists', 'deniteMatchedRange'): self._vim.command('silent! syntax clear deniteMatchedRange') if self._vim.call('hlexists', 'deniteMatchedChar'): self._vim.command('silent! syntax clear deniteMatchedChar') if self._matched_pattern != '': self._vim.command( r'silent! syntax match deniteMatchedRange /\c%s/ contained' % (regex_convert_py_vim(self._matched_pattern)) ) self._vim.command(( 'silent! syntax match deniteMatchedChar /[%s]/ ' 'containedin=deniteMatchedRange contained' ) % re.sub( r'([\[\]\\^-])', r'\\\1', self._context['input'].replace(' ', '') )) prev_linenr = self._vim.call('line', '.') prev_candidate = self._get_cursor_candidate() self._options['modifiable'] = True self._vim.vars['denite#_candidates'] = [ x['word'] for x in self._candidates] self._vim.current.buffer[:] = self._displayed_texts self._options['modifiable'] = False self._resize_buffer() self._vim.call('cursor', [prev_linenr, 0]) if self._updated and (self._context['reversed'] or self._previous_text != self._context['input']): self._previous_text = self._context['input'] self._init_cursor() self._move_to_pos(self._cursor) if (self._context['auto_action'] and prev_candidate != self._get_cursor_candidate()): self.do_action(self._context['auto_action']) self._updated = False self._stop_timer('update_buffer') def _update_status(self) -> None: inpt = '' if self._context['input']: inpt = self._context['input'] + ' ' if self._context['error_messages']: inpt = '[ERROR] ' + inpt path = '[' + self._context['path'] + ']' status = { 'input': inpt, 'sources': self._statusline_sources, 'path': path, # Extra 'buffer_name': self._context['buffer_name'], 'line_total': len(self._candidates), } if status == self._prev_status: return self._bufvars['denite_statusline'] = status self._prev_status = status linenr = "printf('%'.(len(line('$'))+2).'d/%d',line('.'),line('$'))" if self._context['statusline']: if self._floating: self._vim.options['titlestring'] = ( "%{denite#get_status('input')}%* " + "%{denite#get_status('sources')} " + " %{denite#get_status('path')}%*" + "%{" + linenr + "}%*") else: self._window_options['statusline'] = ( "%#deniteInput#%{denite#get_status('input')}%* " + "%{denite#get_status('sources')} %=" + "%#deniteStatusLinePath# %{denite#get_status('path')}%*" + "%#deniteStatusLineNumber#%{" + linenr + "}%*") def _get_display_source_name(self, name: str) -> str: source_names = self._context['source_names'] if not self._is_multi or source_names == 'hide': source_name = '' else: short_name = (re.sub(r'([a-zA-Z])[a-zA-Z]+', r'\1', name) if re.search(r'[^a-zA-Z]', name) else name[:2]) source_name = short_name if source_names == 'short' else name return source_name def _get_candidate_display_text(self, index: int) -> str: source_names = self._context['source_names'] candidate = self._candidates[index] terms = [] if self._is_multi and source_names != 'hide': terms.append(self._context['max_source_name_format'].format( self._get_display_source_name(candidate['source_name']))) encoding = self._context['encoding'] abbr = candidate.get('abbr', candidate['word']).encode( encoding, errors='replace').decode(encoding, errors='replace') terms.append(abbr[:int(self._context['max_candidate_width'])]) return (self._context['selected_icon'] # type: ignore if index in self._selected_candidates else ' ') + ' '.join(terms).replace('\n', '') def _get_max_height(self) -> int: return int(self._vim.options['lines']) if not self._floating else ( int(self._vim.options['lines']) - int(self._context['winrow']) - int(self._vim.options['cmdheight'])) def _resize_buffer(self) -> None: split = self._context['split'] if (split == 'no' or split == 'tab' or self._vim.call('winnr', '$') == 1): return winheight = max(self._winheight, 1) winwidth = max(self._winwidth, 1) is_vertical = split == 'vertical' if not is_vertical and self._vim.current.window.height != winheight: if self._floating: self._vim.call('nvim_win_set_config', self._winid, { 'relative': 'editor', 'row': int(self._context['winrow']), 'col': int(self._context['wincol']), 'width': winwidth, 'height': winheight, }) filter_winid = self._vim.vars['denite#_filter_winid'] if self._vim.call('win_id2win', filter_winid) > 0: self._vim.call('nvim_win_set_config', filter_winid, { 'relative': 'editor', 'row': self._context['winrow'] + winheight, 'col': int(self._context['wincol']), }) self._vim.command('resize ' + str(winheight)) if self._context['reversed']: self._vim.command('normal! zb') elif is_vertical and self._vim.current.window.width != winwidth: self._vim.command('vertical resize ' + str(winwidth)) def _check_do_option(self) -> bool: if self._context['do'] != '': self._do_command(self._context['do']) return True elif (self._candidates and self._context['immediately'] or len(self._candidates) == 1 and self._context['immediately_1']): self._do_immediately() return True return not (self._context['empty'] or self._is_async or self._candidates) def _check_move_option(self) -> None: if self._context['cursor_pos'].isnumeric(): self._cursor = int(self._context['cursor_pos']) + 1 elif re.match(r'\+\d+', self._context['cursor_pos']): for _ in range(int(self._context['cursor_pos'][1:])): self._move_to_next_line() elif re.match(r'-\d+', self._context['cursor_pos']): for _ in range(int(self._context['cursor_pos'][1:])): self._move_to_prev_line() elif self._context['cursor_pos'] == '$': self._move_to_last_line() def _do_immediately(self) -> None: goto = self._winid > 0 and self._vim.call( 'win_gotoid', self._winid) if goto: # Jump to denite window self._init_buffer() self.do_action('default') candidate = self._get_cursor_candidate() if not candidate: return echo(self._vim, 'Normal', '[{}/{}] {}'.format( self._cursor, len(self._candidates), candidate.get('abbr', candidate['word']))) if goto: # Move to the previous window self._vim.command('wincmd p') def _do_command(self, command: str) -> None: self._init_cursor() cursor = 1 while cursor < len(self._candidates): self.do_action('default', command) self._move_to_next_line() self._quit_buffer() def _cleanup(self) -> None: self._stop_timer('update_candidates') self._stop_timer('update_buffer') if self._vim.current.buffer.number == self._bufnr: self._cursor = self._vim.call('line', '.') # Clear previewed buffers if not self._context['has_preview_window']: self._vim.command('pclose!') for bufnr in self._vim.vars['denite#_previewed_buffers'].keys(): if not self._vim.call('win_findbuf', bufnr): self._vim.command('silent bdelete ' + str(bufnr)) self._vim.vars['denite#_previewed_buffers'] = {} self._vim.command('highlight! link CursorLine CursorLine') if self._floating: self._vim.options['titlestring'] = self._titlestring self._vim.options['ruler'] = self._ruler def _close_current_window(self) -> None: if self._vim.call('winnr', '$') == 1: self._vim.command('buffer #') else: self._vim.command('close!') def _quit_buffer(self) -> None: self._cleanup() if self._vim.call('bufwinnr', self._bufnr) < 0: # Denite buffer is already closed return winids = self._vim.call('win_findbuf', self._vim.vars['denite#_filter_bufnr']) if winids: # Quit filter buffer self._vim.call('win_gotoid', winids[0]) self._close_current_window() # Move to denite window self._vim.call('win_gotoid', self._winid) # Restore the window if self._context['split'] == 'no': self._window_options['cursorline'] = False self._switch_prev_buffer() for k, v in self._save_window_options.items(): self._vim.current.window.options[k] = v else: if self._context['split'] == 'tab': self._vim.command('tabclose!') if self._context['split'] != 'tab': self._close_current_window() self._vim.call('win_gotoid', self._prev_winid) # Restore the position self._vim.call('setpos', '.', self._prev_curpos) if self._get_wininfo() and self._get_wininfo() == self._prev_wininfo: self._vim.command(self._winrestcmd) clearmatch(self._vim) def _get_cursor_candidate(self) -> Candidate: return self._get_candidate(self._cursor) def _get_candidate(self, pos: int) -> Candidate: if not self._candidates or pos > len(self._candidates): return {} return self._candidates[pos - 1] def _get_selected_candidates(self) -> Candidates: if not self._selected_candidates: return [self._get_cursor_candidate() ] if self._get_cursor_candidate() else [] return [self._candidates[x] for x in self._selected_candidates] def _init_denite(self) -> None: if self._denite: self._denite.start(self._context) self._denite.on_init(self._context) self._initialized = True self._winheight = int(self._context['winheight']) self._winwidth = int(self._context['winwidth']) def _gather_candidates(self) -> None: self._selected_candidates = [] if self._denite: self._denite.gather_candidates(self._context) def _init_cursor(self) -> None: if self._context['reversed']: self._move_to_last_line() self._vim.command('normal! zb') else: self._move_to_first_line() def _move_to_pos(self, pos: int) -> None: self._vim.call('cursor', pos, 0) self._cursor = pos def _move_to_next_line(self) -> None: if self._cursor < len(self._candidates): self._cursor += 1 def _move_to_prev_line(self) -> None: if self._cursor >= 1: self._cursor -= 1 def _move_to_first_line(self) -> None: self._cursor = 1 def _move_to_last_line(self) -> None: self._cursor = len(self._candidates) def _start_timer(self, key: str) -> None: if key in self._timers: return if key == 'update_candidates': self._timers[key] = self._vim.call( 'denite#helper#_start_update_candidates_timer') elif key == 'update_buffer': self._timers[key] = self._vim.call( 'denite#helper#_start_update_buffer_timer') def _stop_timer(self, key: str) -> None: if key not in self._timers: return self._vim.call('timer_stop', self._timers[key]) self._timers.pop(key)
class Default(object): @property def is_async(self): return self._denite.is_async() @property def current_mode(self): return self._current_mode def __init__(self, vim): self._vim = vim self._denite = None self._cursor = 0 self._win_cursor = 1 self._selected_candidates = [] self._candidates = [] self._candidates_len = 0 self._result = [] self._context = {} self._current_mode = '' self._mode_stack = [] self._current_mappings = {} self._bufnr = -1 self._winid = -1 self._winrestcmd = '' self._initialized = False self._winheight = 0 self._winwidth = 0 self._winminheight = -1 self._scroll = 0 self._is_multi = False self._matched_pattern = '' self._displayed_texts = [] self._statusline_sources = '' self._prompt = DenitePrompt( self._vim, self._context, weakref.proxy(self) ) self._guicursor = '' self._titlestring = '' self._ruler = False self._prev_action = '' self._prev_status = {} self._prev_curpos = [] self._is_suspend = False self._save_window_options = {} self._sources_history = [] def start(self, sources, context): if not self._denite: self._denite = SyncParent(self._vim) self._result = [] context['sources_queue'] = [sources] self._sources_history = [] try: while context['sources_queue']: prev_history = copy.copy(self._sources_history) prev_path = context['path'] self._start(context['sources_queue'][0], context) if prev_history == self._sources_history: self._sources_history.append({ 'sources': context['sources_queue'][0], 'path': prev_path, }) context['sources_queue'].pop(0) context['path'] = self._context['path'] finally: self.cleanup() return self._result def _start(self, sources, context): self._vim.command('silent! autocmd! denite') if re.search(r'\[Command Line\]$', self._vim.current.buffer.name): # Ignore command line window. return if self._initialized and context['resume']: # Skip the initialization if not self._is_suspend: if context['mode']: self._current_mode = context['mode'] update = ('immediately', 'immediately_1', 'cursor_wrap', 'cursor_pos', 'prev_winid', 'quick_move') for key in update: self._context[key] = context[key] if self.check_option(): return self.init_buffer() if context['refresh']: self.redraw() else: if not context['mode']: # Default mode context['mode'] = 'insert' self._context.clear() self._context.update(context) self._context['sources'] = sources self._context['is_redraw'] = False self._current_mode = context['mode'] self._is_multi = len(sources) > 1 if not sources: # Ignore empty sources. error(self._vim, 'Empty sources') return self.init_denite() self.gather_candidates() self.update_candidates() self.init_cursor() if self.check_option(): return self.init_buffer() self._is_suspend = False self.update_displayed_texts() self.change_mode(self._current_mode) self.update_buffer() if self._context['quick_move'] and self.quick_move(): return # Make sure that the caret position is ok self._prompt.caret.locus = self._prompt.caret.tail status = self._prompt.start() if status == STATUS_INTERRUPT: # STATUS_INTERRUPT is returned when user hit <C-c> and the loop has # interrupted. # In this case, denite cancel any operation and close its window. self.quit() return def init_buffer(self): self._prev_status = dict() self._displayed_texts = [] if not self._is_suspend: self._prev_bufnr = self._vim.current.buffer.number self._prev_curpos = self._vim.call('getcurpos') self._prev_wininfo = self._get_wininfo() self._prev_winid = int(self._context['prev_winid']) self._winrestcmd = self._vim.call('winrestcmd') self._scroll = int(self._context['scroll']) if self._scroll == 0: self._scroll = round(self._winheight / 2) if self._context['cursor_shape']: self._guicursor = self._vim.options['guicursor'] self._vim.options['guicursor'] = 'a:None' self._titlestring = self._vim.options['titlestring'] self._ruler = self._vim.options['ruler'] self._switch_buffer() self.resize_buffer() self._winheight = self._vim.current.window.height self._winwidth = self._vim.current.window.width self._options = self._vim.current.buffer.options self._options['buftype'] = 'nofile' self._options['bufhidden'] = 'delete' self._options['swapfile'] = False self._options['buflisted'] = False self._options['modeline'] = False self._options['filetype'] = 'denite' self._options['modifiable'] = True if self._context['split'] == 'floating': # Disable ruler self._vim.options['ruler'] = False self._window_options = self._vim.current.window.options window_options = { 'colorcolumn': '', 'conceallevel': 3, 'concealcursor': 'n', 'cursorcolumn': False, 'foldenable': False, 'foldcolumn': 0, 'list': False, 'number': False, 'relativenumber': False, 'spell': False, 'winfixheight': True, 'wrap': False, } if self._context['cursorline']: window_options['cursorline'] = True self._save_window_options = {} for k, v in window_options.items(): self._save_window_options[k] = self._window_options[k] self._window_options[k] = v self._bufvars = self._vim.current.buffer.vars self._bufnr = self._vim.current.buffer.number self._winid = self._vim.call('win_getid') self._bufvars['denite_statusline'] = {} self._vim.vars['denite#_previewed_buffers'] = {} self._vim.command('silent doautocmd WinEnter') self._vim.command('silent doautocmd BufWinEnter') self._vim.command('doautocmd FileType denite') self.init_syntax() def _switch_buffer(self): split = self._context['split'] if (split != 'no' and self._winid > 0 and self._vim.call('win_gotoid', self._winid)): if (not self._is_suspend and split != 'vertical' and split != 'floating'): # Move the window to bottom self._vim.command('wincmd J') self._winrestcmd = '' else: command = 'edit' if split == 'tab': self._vim.command('tabnew') elif (split == 'floating' and self._vim.call('exists', '*nvim_open_win')): # Use floating window self._vim.call( 'nvim_open_win', self._vim.call('bufnr', '%'), True, { 'relative': 'editor', 'row': int(self._context['winrow']), 'col': int(self._context['wincol']), 'width': int(self._context['winwidth']), 'height': int(self._context['winheight']), }) self._vim.command('highlight NormalFloat guibg=None') elif split != 'no': command = self._get_direction() command += ' vsplit' if split == 'vertical' else ' split' self._vim.call( 'denite#util#execute_path', f'silent keepalt {command}', '[denite]') def _get_direction(self): direction = self._context['direction'] if direction == 'dynamictop' or direction == 'dynamicbottom': self.update_displayed_texts() winwidth = self._vim.call('winwidth', 0) is_fit = not [x for x in self._displayed_texts if self._vim.call('strwidth', x) > winwidth] if direction == 'dynamictop': direction = 'aboveleft' if is_fit else 'topleft' else: direction = 'belowright' if is_fit else 'botright' return direction def _get_wininfo(self): return [ self._vim.options['columns'], self._vim.options['lines'], self._vim.call('win_getid'), ] def _switch_prev_buffer(self): if (self._prev_bufnr == self._bufnr or self._vim.buffers[self._prev_bufnr].name == ''): self._vim.command('enew') else: self._vim.command('buffer ' + str(self._prev_bufnr)) def init_syntax(self): self._vim.command('syntax case ignore') self._vim.command('highlight default link deniteMode ModeMsg') self._vim.command('highlight link deniteMatchedRange ' + self._context['highlight_matched_range']) self._vim.command('highlight link deniteMatchedChar ' + self._context['highlight_matched_char']) self._vim.command('highlight default link ' + 'deniteStatusLinePath Comment') self._vim.command('highlight default link ' + 'deniteStatusLineNumber LineNR') self._vim.command('highlight default link ' + 'deniteSelectedLine Statement') self._vim.command(('syntax match deniteSelectedLine /^[%s].*/' + ' contains=deniteConcealedMark') % ( self._context['selected_icon'])) self._vim.command(('syntax match deniteConcealedMark /^[ %s]/' + ' conceal contained') % ( self._context['selected_icon'])) self._denite.init_syntax(self._context, self._is_multi) def init_cursor(self): self._win_cursor = 1 self._cursor = 0 if self._context['reversed']: self.move_to_last_line() def update_candidates(self): (pattern, statuses, self._candidates) = self._denite.filter_candidates(self._context) prev_matched_pattern = self._matched_pattern self._matched_pattern = pattern self._candidates_len = len(self._candidates) self._statusline_sources = ' '.join(statuses) prev_displayed_texts = self._displayed_texts self.update_displayed_texts() updated = (self._displayed_texts != prev_displayed_texts or self._matched_pattern != prev_matched_pattern) if updated and self._denite.is_async() and self._context['reversed']: self.init_cursor() return updated def update_displayed_texts(self): if self._context['auto_resize']: winminheight = int(self._context['winminheight']) if (winminheight is not -1 and self._candidates_len < winminheight): self._winheight = winminheight elif self._candidates_len > int(self._context['winheight']): self._winheight = int(self._context['winheight']) elif self._candidates_len != self._winheight: self._winheight = self._candidates_len self._displayed_texts = [ self.get_candidate_display_text(i) for i in range(self._cursor, min(self._candidates_len, self._cursor + self._winheight)) ] def update_buffer(self): if self._bufnr != self._vim.current.buffer.number: return self.update_status() if self._vim.call('hlexists', 'deniteMatchedRange'): self._vim.command('silent! syntax clear deniteMatchedRange') if self._vim.call('hlexists', 'deniteMatchedChar'): self._vim.command('silent! syntax clear deniteMatchedChar') if self._matched_pattern != '': self._vim.command( r'silent! syntax match deniteMatchedRange /\c%s/ contained' % (regex_convert_py_vim(self._matched_pattern)) ) self._vim.command(( 'silent! syntax match deniteMatchedChar /[%s]/ ' 'containedin=deniteMatchedRange contained' ) % re.sub( r'([\[\]\\^-])', r'\\\1', self._context['input'].replace(' ', '') )) self._vim.current.buffer[:] = self._displayed_texts self.resize_buffer() self.move_cursor() def update_status(self): raw_mode = self._current_mode.upper() cursor_location = self._cursor + self._win_cursor max_len = len(str(self._candidates_len)) linenr = ('{:'+str(max_len)+'}/{:'+str(max_len)+'}').format( cursor_location, self._candidates_len) mode = '-- ' + raw_mode + ' -- ' if self._context['error_messages']: mode = '[ERROR] ' + mode path = '[' + self._context['path'] + ']' status = { 'mode': mode, 'sources': self._statusline_sources, 'path': path, 'linenr': linenr, # Extra 'raw_mode': raw_mode, 'buffer_name': self._context['buffer_name'], 'line_cursor': cursor_location, 'line_total': self._candidates_len, } if status != self._prev_status: self._bufvars['denite_statusline'] = status self._vim.command('redrawstatus') self._prev_status = status if self._context['statusline']: status = ( "%#deniteMode#%{denite#get_status('mode')}%* " + "%{denite#get_status('sources')} %=" + "%#deniteStatusLinePath# %{denite#get_status('path')} %*" + "%#deniteStatusLineNumber#%{denite#get_status('linenr')}%*") if self._context['split'] == 'floating': self._vim.options['titlestring'] = status else: self._window_options['statusline'] = status def update_cursor(self): self.update_displayed_texts() self.update_buffer() def get_display_source_name(self, name): source_names = self._context['source_names'] if not self._is_multi or source_names == 'hide': source_name = '' else: short_name = (re.sub(r'([a-zA-Z])[a-zA-Z]+', r'\1', name) if re.search(r'[^a-zA-Z]', name) else name[:2]) source_name = short_name if source_names == 'short' else name return source_name def get_candidate_display_text(self, index): source_names = self._context['source_names'] candidate = self._candidates[index] terms = [] if self._is_multi and source_names != 'hide': terms.append(self.get_display_source_name( candidate['source_name'])) encoding = self._context['encoding'] abbr = candidate.get('abbr', candidate['word']).encode( encoding, errors='replace').decode(encoding, errors='replace') terms.append(abbr[:int(self._context['max_candidate_width'])]) return (self._context['selected_icon'] if index in self._selected_candidates else ' ') + ' '.join(terms).replace('\n', '') def resize_buffer(self): split = self._context['split'] if split == 'no' or split == 'tab': return winheight = self._winheight winwidth = self._winwidth is_vertical = split == 'vertical' if not is_vertical and self._vim.current.window.height != winheight: self._vim.command('resize ' + str(winheight)) if self._context['reversed']: self._vim.command('normal! zb') elif is_vertical and self._vim.current.window.width != winwidth: self._vim.command('vertical resize ' + str(winwidth)) def check_option(self): if self._context['cursor_pos'].isnumeric(): self.init_cursor() self.move_to_pos(int(self._context['cursor_pos'])) elif re.match(r'\+\d+', self._context['cursor_pos']): for _ in range(int(self._context['cursor_pos'][1:])): self.move_to_next_line() elif re.match(r'-\d+', self._context['cursor_pos']): for _ in range(int(self._context['cursor_pos'][1:])): self.move_to_prev_line() elif self._context['cursor_pos'] == '$': self.move_to_last_line() elif self._context['do'] != '': self.do_command(self._context['do']) return True if (self._candidates and self._context['immediately'] or len(self._candidates) == 1 and self._context['immediately_1']): self.do_immediately() return True return not (self._context['empty'] or self._denite.is_async() or self._candidates) def do_immediately(self): goto = self._winid > 0 and self._vim.call( 'win_gotoid', self._winid) if goto: # Jump to denite window self.init_buffer() self.update_cursor() self.do_action('default') candidate = self.get_cursor_candidate() echo(self._vim, 'Normal', '[{}/{}] {}'.format( self._cursor + self._win_cursor, self._candidates_len, candidate.get('abbr', candidate['word']))) if goto: # Move to the previous window self.suspend() self._vim.command('wincmd p') def do_command(self, command): self.init_cursor() self._context['post_action'] = 'suspend' while self._cursor + self._win_cursor < self._candidates_len: self.do_action('default', command) self.move_to_next_line() self.quit_buffer() def move_cursor(self): if self._win_cursor > self._vim.call('line', '$'): self._win_cursor = self._vim.call('line', '$') if self._win_cursor != self._vim.call('line', '.'): self._vim.call('cursor', [self._win_cursor, 1]) if self._context['auto_action']: self.do_action(self._context['auto_action']) def change_mode(self, mode): self._current_mode = mode custom = self._context['custom']['map'] use_default_mappings = self._context['use_default_mappings'] highlight = 'highlight_mode_' + mode if highlight in self._context: self._vim.command('highlight! link CursorLine ' + self._context[highlight]) # Clear current keymap self._prompt.keymap.registry.clear() # Apply mode independent mappings if use_default_mappings: self._prompt.keymap.register_from_rules( self._vim, DEFAULT_ACTION_KEYMAP.get('_', []) ) self._prompt.keymap.register_from_rules( self._vim, custom.get('_', []) ) # Apply mode depend mappings mode = self._current_mode if use_default_mappings: self._prompt.keymap.register_from_rules( self._vim, DEFAULT_ACTION_KEYMAP.get(mode, []) ) self._prompt.keymap.register_from_rules( self._vim, custom.get(mode, []) ) # Update mode context self._context['mode'] = mode # Update mode indicator self.update_status() def cleanup(self): # Clear previewed buffers if not self._is_suspend and not self._context['has_preview_window']: self._vim.command('pclose!') for bufnr in self._vim.vars['denite#_previewed_buffers'].keys(): if not self._vim.call('win_findbuf', bufnr): self._vim.command('silent bdelete ' + str(bufnr)) self._vim.vars['denite#_previewed_buffers'] = {} clearmatch(self._vim) if not self._context['immediately']: # Redraw to clear prompt self._vim.command('redraw | echo ""') self._vim.command('highlight! link CursorLine CursorLine') if self._context['cursor_shape']: self._vim.command('set guicursor&') self._vim.options['guicursor'] = self._guicursor if self._context['split'] == 'floating': self._vim.options['titlestring'] = self._titlestring self._vim.options['ruler'] = self._ruler def quit_buffer(self): self.cleanup() if self._vim.call('bufwinnr', self._bufnr) < 0: # Denite buffer is already closed return # Restore the window if self._context['split'] == 'no': self._window_options['cursorline'] = False self._switch_prev_buffer() for k, v in self._save_window_options.items(): self._vim.current.window.options[k] = v else: if self._context['split'] == 'tab': self._vim.command('tabclose!') if self._context['split'] != 'tab': self._vim.command('close!') self._vim.call('win_gotoid', self._prev_winid) # Restore the position self._vim.call('setpos', '.', self._prev_curpos) if self._get_wininfo() and self._get_wininfo() == self._prev_wininfo: self._vim.command(self._winrestcmd) def get_cursor_candidate(self): if self._cursor + self._win_cursor > self._candidates_len: return {} return self._candidates[self._cursor + self._win_cursor - 1] def get_selected_candidates(self): if not self._selected_candidates: return [self.get_cursor_candidate() ] if self.get_cursor_candidate() else [] return [self._candidates[x] for x in self._selected_candidates] def redraw(self, is_force=True): self._context['is_redraw'] = is_force if is_force: self.gather_candidates() if self.update_candidates(): self.update_buffer() else: self.update_status() self._context['is_redraw'] = False def quit(self): self._denite.on_close(self._context) self.quit_buffer() self._result = [] return STATUS_ACCEPT def restart(self): self.quit_buffer() self.init_denite() self.gather_candidates() self.init_buffer() self.update_candidates() self.change_mode(self._current_mode) self.update_buffer() def restore_sources(self, context): if not self._sources_history: return history = self._sources_history[-1] context['sources_queue'].append(history['sources']) context['path'] = history['path'] self._sources_history.pop() return STATUS_ACCEPT def init_denite(self): self._mode_stack = [] self._prompt.history.reset() self._denite.start(self._context) self._denite.on_init(self._context) self._initialized = True self._winheight = int(self._context['winheight']) self._winwidth = int(self._context['winwidth']) def gather_candidates(self): self._selected_candidates = [] self._denite.gather_candidates(self._context) def do_action(self, action_name, command=''): candidates = self.get_selected_candidates() if not candidates or not action_name: return self._prev_action = action_name action = self._denite.get_action( self._context, action_name, candidates) if not action: return post_action = self._context['post_action'] is_quit = action['is_quit'] or post_action == 'quit' if is_quit: self.quit() self._denite.do_action(self._context, action_name, candidates) self._result = candidates if command != '': self._vim.command(command) if is_quit and (post_action == 'open' or post_action == 'suspend'): # Re-open denite buffer self.init_buffer() self.change_mode(self._current_mode) self.redraw(False) # Disable quit flag is_quit = False if not is_quit: self._selected_candidates = [] self.redraw(action['is_redraw']) if post_action == 'suspend': self.suspend() self._vim.command('wincmd p') return STATUS_ACCEPT return STATUS_ACCEPT if is_quit else None def choose_action(self): candidates = self.get_selected_candidates() if not candidates: return self._vim.vars['denite#_actions'] = self._denite.get_action_names( self._context, candidates) clear_cmdline(self._vim) action = self._vim.call('input', 'Action: ', '', 'customlist,denite#helper#complete_actions') if action == '': return return self.do_action(action) def move_to_pos(self, pos): self._cursor = int(pos / self._winheight) * self._winheight self._win_cursor = (pos % self._winheight) + 1 self.update_cursor() def move_to_next_line(self): if self._win_cursor + self._cursor < self._candidates_len: if self._win_cursor < self._winheight: self._win_cursor += 1 else: self._cursor += 1 elif self._context['cursor_wrap']: self.move_to_first_line() else: return self.update_cursor() def move_to_prev_line(self): if self._win_cursor > 1: self._win_cursor -= 1 elif self._cursor >= 1: self._cursor -= 1 elif self._context['cursor_wrap']: self.move_to_last_line() else: return self.update_cursor() def move_to_first_line(self): if self._win_cursor > 1 or self._cursor > 0: self._win_cursor = 1 self._cursor = 0 self.update_cursor() def move_to_last_line(self): win_max = min(self._candidates_len, self._winheight) cur_max = self._candidates_len - win_max if self._win_cursor < win_max or self._cursor < cur_max: self._win_cursor = win_max self._cursor = cur_max self.update_cursor() def move_to_top(self): self._win_cursor = 1 self.update_cursor() def move_to_middle(self): self._win_cursor = self._winheight // 2 self.update_cursor() def move_to_bottom(self): self._win_cursor = self._winheight self.update_cursor() def scroll_window_upwards(self): self.scroll_up(self._scroll) def scroll_window_downwards(self): self.scroll_down(self._scroll) def scroll_page_backwards(self): self.scroll_up(self._winheight - 1) def scroll_page_forwards(self): self.scroll_down(self._winheight - 1) def scroll_down(self, scroll): if self._win_cursor + self._cursor < self._candidates_len: if self._win_cursor <= 1: self._win_cursor = 1 self._cursor = min(self._cursor + scroll, self._candidates_len) elif self._win_cursor < self._winheight: self._win_cursor = min( self._win_cursor + scroll, self._candidates_len, self._winheight) else: self._cursor = min( self._cursor + scroll, self._candidates_len - self._win_cursor) else: return self.update_cursor() def scroll_up(self, scroll): if self._win_cursor > 1: self._win_cursor = max(self._win_cursor - scroll, 1) elif self._cursor > 0: self._cursor = max(self._cursor - scroll, 0) else: return self.update_cursor() def scroll_window_up_one_line(self): if self._cursor < 1: return self.scroll_up(1) self._cursor -= 1 self._win_cursor += 1 self.update_cursor() def scroll_window_down_one_line(self): if self._win_cursor <= 1 and self._cursor > 0: return self.scroll_down(1) self._cursor += 1 self._win_cursor -= 1 self.update_cursor() def scroll_cursor_to_top(self): self._cursor += self._win_cursor - 1 self._win_cursor = 1 self.update_cursor() def scroll_cursor_to_middle(self): self.scroll_cursor_to_top() while self._cursor >= 1 and self._win_cursor < self._winheight // 2: self.scroll_window_up_one_line() def scroll_cursor_to_bottom(self): self.scroll_cursor_to_top() while self._cursor >= 1 and self._win_cursor < self._winheight: self.scroll_window_up_one_line() def jump_to_next_by(self, key): keyfunc = self._keyfunc(key) keys = [keyfunc(candidate) for candidate in self._candidates] if not keys or len(set(keys)) == 1: return current_index = self._cursor + self._win_cursor - 1 forward_candidates = self._candidates[current_index:] forward_sources = groupby(forward_candidates, keyfunc) forward_times = len(list(next(forward_sources)[1])) if not forward_times: return remaining_candidates = (self._candidates_len - current_index - forward_times) if next(forward_sources, None) is None: # If the cursor is on the last source self._cursor = 0 self._win_cursor = 1 elif self._candidates_len < self._winheight: # If there is a space under the candidates self._cursor = 0 self._win_cursor += forward_times elif remaining_candidates < self._winheight: self._cursor = self._candidates_len - self._winheight + 1 self._win_cursor = self._winheight - remaining_candidates else: self._cursor += forward_times + self._win_cursor - 1 self._win_cursor = 1 self.update_cursor() def jump_to_prev_by(self, key): keyfunc = self._keyfunc(key) keys = [keyfunc(candidate) for candidate in self._candidates] if not keys or len(set(keys)) == 1: return current_index = self._cursor + self._win_cursor - 1 backward_candidates = reversed(self._candidates[:current_index + 1]) backward_sources = groupby(backward_candidates, keyfunc) current_source = list(next(backward_sources)[1]) try: prev_source = list(next(backward_sources)[1]) except StopIteration: # If the cursor is on the first source last_source = takewhile( lambda candidate: keyfunc(candidate) == keyfunc(self._candidates[-1]), reversed(self._candidates) ) len_last_source = len(list(last_source)) if self._candidates_len < self._winheight: self._cursor = 0 self._win_cursor = self._candidates_len - len_last_source + 1 elif len_last_source < self._winheight: self._cursor = self._candidates_len - self._winheight + 1 self._win_cursor = self._winheight - len_last_source else: self._cursor = self._candidates_len - len_last_source self._win_cursor = 1 else: back_times = len(current_source) - 1 + len(prev_source) remaining_candidates = (self._candidates_len - current_index + back_times) if self._candidates_len < self._winheight: self._cursor = 0 self._win_cursor -= back_times elif remaining_candidates < self._winheight: self._cursor = self._candidates_len - self._winheight + 1 self._win_cursor = self._winheight - remaining_candidates else: self._cursor -= back_times - self._win_cursor + 1 self._win_cursor = 1 self.update_cursor() def quick_move(self): def get_quick_move_table(): table = {} context = self._context base = self._win_cursor for [key, number] in context['quick_move_table'].items(): number = int(number) pos = ((base - number) if context['reversed'] else (number + base)) if pos > 0: table[key] = pos return table def quick_move_redraw(table, is_define): bufnr = self._vim.current.buffer.number for [key, number] in table.items(): signid = 2000 + number name = 'denite_quick_move_' + str(number) if is_define: self._vim.command( f'sign define {name} text={key} texthl=Special') self._vim.command( f'sign place {signid} name={name} ' f'line={number} buffer={bufnr}') else: self._vim.command( f'silent! sign unplace {signid} buffer={bufnr}') self._vim.command('silent! sign undefine ' + name) quick_move_table = get_quick_move_table() self._vim.command('echo "Input quick match key: "') quick_move_redraw(quick_move_table, True) self._vim.command('redraw') char = '' while char == '': char = self._vim.call('nr2char', self._vim.call('denite#util#getchar')) quick_move_redraw(quick_move_table, False) if (char not in quick_move_table or quick_move_table[char] > self._winheight): return self._win_cursor = quick_move_table[char] self.update_cursor() if self._context['quick_move'] == 'immediately': self.do_action('default') return True def _keyfunc(self, key): def wrapped(candidate): for k in key, 'action__' + key: try: return str(candidate[k]) except Exception: pass return '' return wrapped def enter_mode(self, mode): if mode == self._current_mode: return self._mode_stack.append(self._current_mode) self.change_mode(mode) def leave_mode(self): if not self._mode_stack: return self.quit() self._current_mode = self._mode_stack[-1] self._mode_stack = self._mode_stack[:-1] self.change_mode(self._current_mode) def suspend(self): if self._bufnr == self._vim.current.buffer.number: if self._context['auto_resume']: self._vim.command('autocmd denite WinEnter <buffer> ' + 'Denite -resume -buffer_name=' + self._context['buffer_name']) for mapping in ['i', 'a', '<CR>']: self._vim.command(f'nnoremap <silent><buffer> {mapping} ' + ':<C-u>Denite -resume -buffer_name=' + f"{self._context['buffer_name']}<CR>") self._is_suspend = True self._options['modifiable'] = False return STATUS_ACCEPT