def BuildRequestData(filepath=None): """Build request for the current buffer or the buffer corresponding to |filepath| if specified.""" current_filepath = vimsupport.GetCurrentBufferFilepath() working_dir = GetCurrentDirectory() if filepath and current_filepath != filepath: # Cursor position is irrelevant when filepath is not the current buffer. return { 'filepath': filepath, 'line_num': 1, 'column_num': 1, 'working_dir': working_dir, 'file_data': vimsupport.GetUnsavedAndSpecifiedBufferData(filepath) } line, column = vimsupport.CurrentLineAndColumn() return { 'filepath': current_filepath, 'line_num': line + 1, 'column_num': column + 1, 'working_dir': working_dir, 'file_data': vimsupport.GetUnsavedAndSpecifiedBufferData(current_filepath) }
def BuildRequestData( buffer_number = None ): """Build request for the current buffer or the buffer with number |buffer_number| if specified.""" working_dir = GetCurrentDirectory() current_buffer = vim.current.buffer if buffer_number and current_buffer.number != buffer_number: # Cursor position is irrelevant when filepath is not the current buffer. buffer_object = vim.buffers[ buffer_number ] filepath = vimsupport.GetBufferFilepath( buffer_object ) return { 'filepath': filepath, 'line_num': 1, 'column_num': 1, 'working_dir': working_dir, 'file_data': vimsupport.GetUnsavedAndSpecifiedBufferData( buffer_object, filepath ) } current_filepath = vimsupport.GetBufferFilepath( current_buffer ) line, column = vimsupport.CurrentLineAndColumn() return { 'filepath': current_filepath, 'line_num': line + 1, 'column_num': column + 1, 'working_dir': working_dir, 'file_data': vimsupport.GetUnsavedAndSpecifiedBufferData( current_buffer, current_filepath ) }
def PreviousIdentifier(): line_num, column_num = vimsupport.CurrentLineAndColumn() buffer = vim.current.buffer line = buffer[line_num] end_column = column_num while end_column > 0 and not utils.IsIdentifierChar(line[end_column - 1]): end_column -= 1 # Look at the previous line if we reached the end of the current one if end_column == 0: try: line = buffer[line_num - 1] except: return "" end_column = len(line) while end_column > 0 and not utils.IsIdentifierChar( line[end_column - 1]): end_column -= 1 start_column = end_column while start_column > 0 and utils.IsIdentifierChar(line[start_column - 1]): start_column -= 1 if end_column - start_column < MIN_NUM_CHARS: return "" return line[start_column:end_column]
def ShowDetailedDiagnostic(self): current_line, current_column = vimsupport.CurrentLineAndColumn() # CurrentLineAndColumn() numbers are 0-based, clang numbers are 1-based current_line += 1 current_column += 1 current_file = vim.current.buffer.name if not self.diagnostic_store: vimsupport.PostVimMessage("No diagnostic for current line!") return diagnostics = self.diagnostic_store[current_file][current_line] if not diagnostics: vimsupport.PostVimMessage("No diagnostic for current line!") return closest_diagnostic = None distance_to_closest_diagnostic = 999 for diagnostic in diagnostics: distance = abs(current_column - diagnostic.column_number_) if distance < distance_to_closest_diagnostic: distance_to_closest_diagnostic = distance closest_diagnostic = diagnostic vimsupport.EchoText(closest_diagnostic.long_formatted_text_)
def OmniCompleter_GetCompletions_RestoreCursorPositionAfterOmnifuncCall_test( ycm): # This omnifunc moves the cursor to the test definition like # ccomplete#Complete would. def Omnifunc(findstart, base): vimsupport.SetCurrentLineAndColumn(0, 0) if findstart: return 5 return ['length'] current_buffer = VimBuffer('buffer', contents=['String test', '', 'test.'], filetype=FILETYPE, omnifunc=Omnifunc) with MockVimBuffers([current_buffer], [current_buffer], (3, 5)): ycm.SendCompletionRequest() assert_that(vimsupport.CurrentLineAndColumn(), contains(2, 5)) assert_that( ycm.GetCompletionResponse(), has_entries({ 'completions': ToBytesOnPY2([{ 'word': 'length' }]), 'completion_start_column': 6 }))
def OmniCompleter_GetCompletions_MoveCursorPositionAtStartColumn_test(ycm): # This omnifunc relies on the cursor being moved at the start column when # called the second time like LanguageClient#complete from the # LanguageClient-neovim plugin. def Omnifunc(findstart, base): if findstart: return 5 if vimsupport.CurrentColumn() == 5: return ['length'] return [] current_buffer = VimBuffer('buffer', contents=['String test', '', 'test.le'], filetype=FILETYPE, omnifunc=Omnifunc) with MockVimBuffers([current_buffer], [current_buffer], (3, 7)): ycm.SendCompletionRequest() assert_that(vimsupport.CurrentLineAndColumn(), contains(2, 7)) assert_that( ycm.GetCompletionResponse(), has_entries({ 'completions': ToBytesOnPY2([{ 'word': 'length' }]), 'completion_start_column': 6 }))
def OnCursorMoved(self): line, _ = vimsupport.CurrentLineAndColumn() line += 1 # Convert to 1-based if line != self._previous_line_number: self._previous_line_number = line if self._user_options['echo_current_diagnostic']: self._EchoDiagnosticForLine(line)
def _GetJediScript(self): contents = '\n'.join(vim.current.buffer) line, column = vimsupport.CurrentLineAndColumn() # Jedi expects lines to start at 1, not 0 line += 1 filename = vim.current.buffer.name return jedi.Script(contents, line, column, filename)
def _DefaultParameters(self): """ Some very common request parameters """ line, column = vimsupport.CurrentLineAndColumn() parameters = {} parameters['line'], parameters['column'] = line + 1, column + 1 parameters['buffer'] = '\n'.join(vim.current.buffer) parameters['filename'] = vim.current.buffer.name return parameters
def CandidatesFromStoredRequest(self): if not self.completions_future: return [] lines = vim.current.buffer line_num, cursor = vimsupport.CurrentLineAndColumn() for i in range(0, line_num - 1): cursor += len(lines[i]) + 1 # that +1 is for the "\n" char count = 0 positions = {} text = "\n".join(lines) if cursor > DISTANCE_RANGE: text = text[cursor - DISTANCE_RANGE:] cursor = DISTANCE_RANGE if len(text) > cursor + DISTANCE_RANGE: text = text[:cursor + DISTANCE_RANGE] for match in self.identifier_regex.finditer(text): count += 1 identifier = match.group() position = match.start() if identifier in positions: positions[identifier].append(position) else: positions[identifier] = [position] completions = self.completions_future.GetResults() # all completions_with_distance = [] rest = [] for word in completions: if len(word) > MIN_NUM_CHARS: if word in positions: distance = min( [abs(cursor - pos) for pos in positions[word]]) count_factor = (len(positions[word])) / float(count) distance -= distance * count_factor completions_with_distance.append((word, distance)) else: rest.append(word) completions_with_distance.sort(key=lambda pair: pair[1]) completions = [pair[0] for pair in completions_with_distance] + rest # We will never have duplicates in completions so with 'dup':1 we tell Vim # to add this candidate even if it's a duplicate of an existing one (which # will never happen). This saves us some expensive string matching # operations in Vim. return [{ 'word': x, 'dup': 1 } for x in completions[:MAX_IDENTIFIER_COMPLETIONS_RETURNED]]
def CandidatesFromStoredRequest( self ): if self.completions_cache: return self.completions_cache.filtered_completions else: self.completions_cache = CompletionsCache() self.completions_cache.raw_completions = self.CandidatesFromStoredRequestInner() self.completions_cache.line, _ = vimsupport.CurrentLineAndColumn() self.completions_cache.column = self.completion_start_column return self.completions_cache.raw_completions
def OnCursorMoved(self): if self._user_options['echo_current_diagnostic']: line, _ = vimsupport.CurrentLineAndColumn() line += 1 # Convert to 1-based if (not self.ShouldUpdateDiagnosticsUINow() and self._diag_message_needs_clearing): # Clear any previously echo'd diagnostic in insert mode self._EchoDiagnosticText(line, None, None) elif line != self._previous_diag_line_number: self._EchoDiagnosticForLine(line)
def _GetCompletions(self): """ Ask server for completions """ line, column = vimsupport.CurrentLineAndColumn() parameters = {} parameters['line'], parameters['column'] = line + 1, column + 1 parameters['buffer'] = '\n'.join(vim.current.buffer) parameters['filename'] = vim.current.buffer.name completions = self._GetResponse('/autocomplete', parameters) return completions if completions != None else []
def ComputeCandidatesInner(self, request_data): if not self._omnifunc: return [] # Calling directly the omnifunc may move the cursor position. This is the # case with the default Vim omnifunc for C-family languages # (ccomplete#Complete) which calls searchdecl to find a declaration. This # function is supposed to move the cursor to the found declaration but it # doesn't when called through the omni completion mapping (CTRL-X CTRL-O). # So, we restore the cursor position after the omnifunc calls. line, column = vimsupport.CurrentLineAndColumn() try: start_column = vimsupport.GetIntValue(self._omnifunc + '(1,"")') if start_column < 0: # FIXME: Technically, if the returned value is -1 we should raise an # error. return [] # Use the start column calculated by the omnifunc, rather than our own # interpretation. This is important for certain languages where our # identifier detection is either incorrect or not compatible with the # behaviour of the omnifunc. Note: do this before calling the omnifunc # because it affects the value returned by 'query'. request_data['start_column'] = start_column + 1 # Vim internally moves the cursor to the start column before calling again # the omnifunc. Some omnifuncs like the one defined by the # LanguageClient-neovim plugin depend on this behavior to compute the list # of candidates. vimsupport.SetCurrentLineAndColumn(line, start_column) omnifunc_call = [ self._omnifunc, "(0,'", vimsupport.EscapeForVim(request_data['query']), "')" ] items = vim.eval(''.join(omnifunc_call)) if isinstance(items, dict) and 'words' in items: items = items['words'] if not hasattr(items, '__iter__'): raise TypeError(OMNIFUNC_NOT_LIST) return list(filter(bool, items)) except (TypeError, ValueError, vim.error) as error: vimsupport.PostVimMessage(OMNIFUNC_RETURNED_BAD_VALUE + ' ' + str(error)) return [] finally: vimsupport.SetCurrentLineAndColumn(line, column)
def BuildRequestData( include_buffer_data = True ): line, column = vimsupport.CurrentLineAndColumn() filepath = vimsupport.GetCurrentBufferFilepath() request_data = { 'line_num': line + 1, 'column_num': column + 1, 'filepath': filepath } if include_buffer_data: request_data[ 'file_data' ] = vimsupport.GetUnsavedAndCurrentBufferData() return request_data
def _LocationForGoTo(self, goto_function): filename = vim.current.buffer.name if not filename: return None flags = self.flags.FlagsForFile(filename) if not flags: vimsupport.PostVimMessage( 'Still no compile flags, can\'t compile.') return None files = self.GetUnsavedFilesVector() line, column = vimsupport.CurrentLineAndColumn() # Making the line & column 1-based instead of 0-based line += 1 column += 1 return getattr(self.completer, goto_function)(filename, line, column, files, flags)
def BuildRequestData(start_column=None, query=None, include_buffer_data=True): line, column = vimsupport.CurrentLineAndColumn() filepath = vimsupport.GetCurrentBufferFilepath() request_data = { 'filetypes': vimsupport.CurrentFiletypes(), 'line_num': line, 'column_num': column, 'start_column': start_column, 'line_value': vim.current.line, 'filepath': filepath } if include_buffer_data: request_data['file_data'] = vimsupport.GetUnsavedAndCurrentBufferData() if query: request_data['query'] = query return request_data
def _EchoDiagnostic(self): line, _ = vimsupport.CurrentLineAndColumn() line += 1 # Convert to 1-based self._EchoDiagnosticForLine(line)
def CacheValid(self, start_column): completion_line, _ = vimsupport.CurrentLineAndColumn() completion_column = start_column return completion_line == self.line and completion_column == self.column
def UpdateSignatureHelp(state, signature_info): # noqa if not ShouldUseSignatureHelp(): return state signatures = signature_info.get('signatures') or [] if not signatures: if state.popup_win_id: # TODO/FIXME: Should we use popup_hide() instead ? vim.eval("popup_close( {} )".format(state.popup_win_id)) return SignatureHelpState(None, SignatureHelpState.INACTIVE) if state.state != SignatureHelpState.ACTIVE: state.anchor = vimsupport.CurrentLineAndColumn() state.state = SignatureHelpState.ACTIVE # Generate the buffer as a list of lines buf_lines = _MakeSignatureHelpBuffer(signature_info) screen_pos = vimsupport.ScreenPositionForLineColumnInWindow( vim.current.window, state.anchor[0] + 1, # anchor 0-based state.anchor[1] + 1) # anchor 0-based # Simulate 'flip' at the screen boundaries by using screenpos and hiding the # signature help menu if it overlaps the completion popup (pum). # # FIXME: revert to cursor-relative positioning and the 'flip' option when that # is implemented (if that is indeed better). # By default display above the anchor line = int(screen_pos['row']) - 1 # -1 to display above the cur line pos = "botleft" cursor_line = vimsupport.CurrentLineAndColumn()[0] + 1 if int(screen_pos['row']) <= len(buf_lines): # No room at the top, display below line = int(screen_pos['row']) + 1 pos = "topleft" # Don't allow the popup to overlap the cursor if (pos == 'topleft' and line < cursor_line and line + len(buf_lines) >= cursor_line): line = 0 # Don't allow the popup to overlap the pum if line > 0 and GetIntValue('pumvisible()'): pum_line = GetIntValue(vim.eval('pum_getpos().row')) + 1 if pos == 'botleft' and pum_line <= line: line = 0 elif (pos == 'topleft' and pum_line >= line and pum_line < (line + len(buf_lines))): line = 0 if line <= 0: # Nowhere to put it so hide it if state.popup_win_id: # TODO/FIXME: Should we use popup_hide() instead ? vim.eval("popup_close( {} )".format(state.popup_win_id)) return SignatureHelpState(None, SignatureHelpState.INACTIVE) if int(screen_pos['curscol']) <= 1: col = 1 else: # -1 for padding, # -1 for the trigger character inserted (the anchor is set _after_ the # character is inserted, so we remove it). # FIXME: multi-byte characters would be wrong. Need to set anchor before # inserting the char ? col = int(screen_pos['curscol']) - 2 if col <= 0: col = 1 options = { "line": line, "col": col, "pos": pos, "wrap": 0, "flip": 1, "padding": [0, 1, 0, 1], # Pad 1 char in X axis to match completion menu } if not state.popup_win_id: state.popup_win_id = GetIntValue( vim.eval("popup_create( {}, {} )".format(json.dumps(buf_lines), json.dumps(options)))) else: vim.eval('popup_settext( {}, {} )'.format(state.popup_win_id, json.dumps(buf_lines))) # Should do nothing if already visible vim.eval('popup_move( {}, {} )'.format(state.popup_win_id, json.dumps(options))) vim.eval('popup_show( {} )'.format(state.popup_win_id)) return state
def UpdateSignatureHelp(state, signature_info): # noqa if not ShouldUseSignatureHelp(): return state signatures = signature_info.get('signatures') or [] if not signatures: if state.popup_win_id: # TODO/FIXME: Should we use popup_hide() instead ? vim.eval(f"popup_close( { state.popup_win_id } )") return SignatureHelpState(None, SignatureHelpState.INACTIVE) if state.state != SignatureHelpState.ACTIVE: state.anchor = vimsupport.CurrentLineAndColumn() state.state = SignatureHelpState.ACTIVE # Generate the buffer as a list of lines buf_lines = _MakeSignatureHelpBuffer(signature_info) screen_pos = vimsupport.ScreenPositionForLineColumnInWindow( vim.current.window, state.anchor[0] + 1, # anchor 0-based state.anchor[1] + 1) # anchor 0-based # Simulate 'flip' at the screen boundaries by using screenpos and hiding the # signature help menu if it overlaps the completion popup (pum). # # FIXME: revert to cursor-relative positioning and the 'flip' option when that # is implemented (if that is indeed better). # By default display above the anchor line = int(screen_pos['row']) - 1 # -1 to display above the cur line pos = "botleft" cursor_line = vimsupport.CurrentLineAndColumn()[0] + 1 if int(screen_pos['row']) <= len(buf_lines): # No room at the top, display below line = int(screen_pos['row']) + 1 pos = "topleft" # Don't allow the popup to overlap the cursor if (pos == 'topleft' and line < cursor_line and line + len(buf_lines) >= cursor_line): line = 0 # Don't allow the popup to overlap the pum if line > 0 and GetIntValue('pumvisible()'): pum_line = GetIntValue('pum_getpos().row') + 1 if pos == 'botleft' and pum_line <= line: line = 0 elif (pos == 'topleft' and pum_line >= line and pum_line < (line + len(buf_lines))): line = 0 if line <= 0: # Nowhere to put it so hide it if state.popup_win_id: # TODO/FIXME: Should we use popup_hide() instead ? vim.eval(f"popup_close( { state.popup_win_id } )") return SignatureHelpState(None, SignatureHelpState.INACTIVE) if int(screen_pos['curscol']) <= 1: col = 1 else: # -1 for padding, # -1 for the trigger character inserted (the anchor is set _after_ the # character is inserted, so we remove it). # FIXME: multi-byte characters would be wrong. Need to set anchor before # inserting the char ? col = int(screen_pos['curscol']) - 2 if col <= 0: col = 1 options = { "line": line, "col": col, "pos": pos, "wrap": 0, # NOTE: We *dont'* use "cursorline" here - that actually uses PMenuSel, # which is just too invasive for us (it's more selected item than actual # cursorline. So instead, we manually set 'cursorline' in the popup window # and enable sytax based on the current file syntax) "flip": 1, "padding": [0, 1, 0, 1], # Pad 1 char in X axis to match completion menu } if not state.popup_win_id: state.popup_win_id = GetIntValue( f'popup_create( { json.dumps( buf_lines ) }, ' f'{ json.dumps( options ) } )') else: vim.eval(f'popup_settext( { state.popup_win_id }, ' f'{ json.dumps( buf_lines ) } )') # Should do nothing if already visible vim.eval( f'popup_move( { state.popup_win_id }, { json.dumps( options ) } )') vim.eval(f'popup_show( { state.popup_win_id } )') syntax = utils.ToUnicode(vim.current.buffer.options['syntax']) active_signature = int(signature_info.get('activeSignature', 0)) vim.eval(f"win_execute( { state.popup_win_id }, " f"'set syntax={ syntax } cursorline | " f"call cursor( [ { active_signature + 1 }, 1 ] )' )") return state
def ComputeCandidatesInner(self, request_data): if not self._omnifunc: return [] # Calling directly the omnifunc may move the cursor position. This is the # case with the default Vim omnifunc for C-family languages # (ccomplete#Complete) which calls searchdecl to find a declaration. This # function is supposed to move the cursor to the found declaration but it # doesn't when called through the omni completion mapping (CTRL-X CTRL-O). # So, we restore the cursor position after the omnifunc calls. line, column = vimsupport.CurrentLineAndColumn() try: start_column = vimsupport.GetIntValue(self._omnifunc + '(1,"")') # Vim only stops completion if the value returned by the omnifunc is -3 or # -2. In other cases, if the value is negative or greater than the current # column, the start column is set to the current column; otherwise, the # value is used as the start column. if start_column in (-3, -2): return [] if start_column < 0 or start_column > column: start_column = column # Use the start column calculated by the omnifunc, rather than our own # interpretation. This is important for certain languages where our # identifier detection is either incorrect or not compatible with the # behaviour of the omnifunc. Note: do this before calling the omnifunc # because it affects the value returned by 'query'. request_data['start_column'] = start_column + 1 # Vim internally moves the cursor to the start column before calling again # the omnifunc. Some omnifuncs like the one defined by the # LanguageClient-neovim plugin depend on this behavior to compute the list # of candidates. vimsupport.SetCurrentLineAndColumn(line, start_column) omnifunc_call = [ self._omnifunc, "(0,'", vimsupport.EscapeForVim(request_data['query']), "')" ] items = vim.eval(''.join(omnifunc_call)) if isinstance(items, dict) and 'words' in items: items = items['words'] if not hasattr(items, '__iter__'): raise TypeError(OMNIFUNC_NOT_LIST) # Vim allows each item of the list to be either a string or a dictionary # but ycmd only supports lists where items are all strings or all # dictionaries. Convert all strings into dictionaries. for index, item in enumerate(items): if not isinstance(item, dict): items[index] = {'word': item} return items except (TypeError, ValueError, vim.error) as error: vimsupport.PostVimMessage(OMNIFUNC_RETURNED_BAD_VALUE + ' ' + str(error)) return [] finally: vimsupport.SetCurrentLineAndColumn(line, column)