def __init__( self ): self._logger = logging.getLogger( 'ycm' ) self._client_logfile = None self._server_stdout = None self._server_stderr = None self._server_popen = None self._ycmd_keepalive = YcmdKeepalive() self._SetUpLogging() self._SetUpServer() self._ycmd_keepalive.Start()
def __init__(self, user_options): self._user_options = user_options self._user_notified_about_crash = False self._diag_interface = DiagnosticInterface(user_options) self._omnicomp = OmniCompleter(user_options) self._latest_completion_request = None self._latest_file_parse_request = None self._server_stdout = None self._server_stderr = None self._server_popen = None self._filetypes_with_keywords_loaded = set() self._ycmd_keepalive = YcmdKeepalive() self._SetupServer() self._ycmd_keepalive.Start()
def __init__( self ): self._available_completers = {} self._user_options = None self._user_notified_about_crash = False self._omnicomp = None self._buffers = None self._latest_completion_request = None self._logger = logging.getLogger( 'ycm' ) self._client_logfile = None self._server_stdout = None self._server_stderr = None self._server_popen = None self._filetypes_with_keywords_loaded = set() self._ycmd_keepalive = YcmdKeepalive() self._server_is_ready_with_cache = False self._SetUpLogging() self._SetUpServer() self._ycmd_keepalive.Start()
def __init__(self, user_options): self._available_completers = {} self._user_options = user_options self._user_notified_about_crash = False self._diag_interface = DiagnosticInterface(user_options) self._omnicomp = OmniCompleter(user_options) self._latest_file_parse_request = None self._latest_completion_request = None self._latest_diagnostics = [] self._server_stdout = None self._server_stderr = None self._server_popen = None self._filetypes_with_keywords_loaded = set() self._ycmd_keepalive = YcmdKeepalive() self._SetupServer() self._ycmd_keepalive.Start() self._complete_done_hooks = { 'cs': lambda self: self._OnCompleteDone_Csharp() }
def __init__(self, user_options): self._available_completers = {} self._user_options = user_options self._user_notified_about_crash = False self._omnicomp = OmniCompleter(user_options) self._buffers = BufferDict(user_options) self._latest_completion_request = None self._logger = logging.getLogger('ycm') self._client_logfile = None self._server_stdout = None self._server_stderr = None self._server_popen = None self._filetypes_with_keywords_loaded = set() self._ycmd_keepalive = YcmdKeepalive() self._server_is_ready_with_cache = False self._SetupLogging() self._SetupServer() self._ycmd_keepalive.Start() self._complete_done_hooks = { 'cs': lambda self: self._OnCompleteDone_Csharp() }
class YouCompleteMe(object): def __init__(self, user_options): self._available_completers = {} self._user_options = user_options self._user_notified_about_crash = False self._omnicomp = OmniCompleter(user_options) self._buffers = BufferDict(user_options) self._latest_completion_request = None self._logger = logging.getLogger('ycm') self._client_logfile = None self._server_stdout = None self._server_stderr = None self._server_popen = None self._filetypes_with_keywords_loaded = set() self._ycmd_keepalive = YcmdKeepalive() self._server_is_ready_with_cache = False self._SetupLogging() self._SetupServer() self._ycmd_keepalive.Start() self._complete_done_hooks = { 'cs': lambda self: self._OnCompleteDone_Csharp() } def _SetupServer(self): self._available_completers = {} self._user_notified_about_crash = False self._filetypes_with_keywords_loaded = set() self._server_is_ready_with_cache = False server_port = utils.GetUnusedLocalhostPort() # The temp options file is deleted by ycmd during startup with NamedTemporaryFile(delete=False, mode='w+') as options_file: hmac_secret = os.urandom(HMAC_SECRET_LENGTH) options_dict = dict(self._user_options) options_dict['hmac_secret'] = utils.ToUnicode( base64.b64encode(hmac_secret)) options_dict['server_keep_logfiles'] = self._user_options[ 'keep_logfiles'] json.dump(options_dict, options_file) options_file.flush() args = [ paths.PathToPythonInterpreter(), paths.PathToServerScript(), '--port={0}'.format(server_port), '--options_file={0}'.format(options_file.name), '--log={0}'.format(self._user_options['log_level']), '--idle_suicide_seconds={0}'.format( SERVER_IDLE_SUICIDE_SECONDS) ] self._server_stdout = utils.CreateLogfile( SERVER_LOGFILE_FORMAT.format(port=server_port, std='stdout')) self._server_stderr = utils.CreateLogfile( SERVER_LOGFILE_FORMAT.format(port=server_port, std='stderr')) args.append('--stdout={0}'.format(self._server_stdout)) args.append('--stderr={0}'.format(self._server_stderr)) if self._user_options['keep_logfiles']: args.append('--keep_logfiles') self._server_popen = utils.SafePopen(args, stdin_windows=PIPE, stdout=PIPE, stderr=PIPE) BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port) BaseRequest.hmac_secret = hmac_secret self._NotifyUserIfServerCrashed() def _SetupLogging(self): def FreeFileFromOtherProcesses(file_object): if utils.OnWindows(): from ctypes import windll import msvcrt file_handle = msvcrt.get_osfhandle(file_object.fileno()) windll.kernel32.SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0) self._client_logfile = utils.CreateLogfile(CLIENT_LOGFILE_FORMAT) log_level = self._user_options['log_level'] numeric_level = getattr(logging, log_level.upper(), None) if not isinstance(numeric_level, int): raise ValueError('Invalid log level: {0}'.format(log_level)) self._logger.setLevel(numeric_level) handler = logging.FileHandler(self._client_logfile) # On Windows and Python prior to 3.4, file handles are inherited by child # processes started with at least one replaced standard stream, which is the # case when we start the ycmd server (we are redirecting all standard # outputs into a pipe). These files cannot be removed while the child # processes are still up. This is not desirable for a logfile because we # want to remove it at Vim exit without having to wait for the ycmd server # to be completely shut down. We need to make the logfile handle # non-inheritable. See https://www.python.org/dev/peps/pep-0446 for more # details. FreeFileFromOtherProcesses(handler.stream) formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) self._logger.addHandler(handler) def IsServerAlive(self): return_code = self._server_popen.poll() # When the process hasn't finished yet, poll() returns None. return return_code is None def CheckIfServerIsReady(self): if not self._server_is_ready_with_cache and self.IsServerAlive(): with HandleServerException(display=False): self._server_is_ready_with_cache = BaseRequest.GetDataFromHandler( 'ready') return self._server_is_ready_with_cache def IsServerReady(self): return self._server_is_ready_with_cache def _NotifyUserIfServerCrashed(self): if self._user_notified_about_crash or self.IsServerAlive(): return self._user_notified_about_crash = True return_code = self._server_popen.poll() if return_code == server_utils.CORE_UNEXPECTED_STATUS: error_message = CORE_UNEXPECTED_MESSAGE elif return_code == server_utils.CORE_MISSING_STATUS: error_message = CORE_MISSING_MESSAGE elif return_code == server_utils.CORE_PYTHON2_STATUS: error_message = CORE_PYTHON2_MESSAGE elif return_code == server_utils.CORE_PYTHON3_STATUS: error_message = CORE_PYTHON3_MESSAGE elif return_code == server_utils.CORE_OUTDATED_STATUS: error_message = CORE_OUTDATED_MESSAGE else: error_message = EXIT_CODE_UNEXPECTED_MESSAGE.format( code=return_code) server_stderr = '\n'.join( utils.ToUnicode(self._server_popen.stderr.read()).splitlines()) if server_stderr: self._logger.error(server_stderr) error_message = SERVER_SHUTDOWN_MESSAGE + ' ' + error_message self._logger.error(error_message) vimsupport.PostVimMessage(error_message) def ServerPid(self): if not self._server_popen: return -1 return self._server_popen.pid def _ShutdownServer(self): SendShutdownRequest() def RestartServer(self): vimsupport.PostVimMessage('Restarting ycmd server...') self._ShutdownServer() self._SetupServer() def SendCompletionRequest(self, force_semantic=False): request_data = BuildRequestData() request_data['force_semantic'] = force_semantic if (not self.NativeFiletypeCompletionAvailable() and self.CurrentFiletypeCompletionEnabled()): wrapped_request_data = RequestWrap(request_data) if self._omnicomp.ShouldUseNow(wrapped_request_data): self._latest_completion_request = OmniCompletionRequest( self._omnicomp, wrapped_request_data) self._latest_completion_request.Start() return request_data['working_dir'] = utils.GetCurrentDirectory() self._AddExtraConfDataIfNeeded(request_data) self._latest_completion_request = CompletionRequest(request_data) self._latest_completion_request.Start() def CompletionRequestReady(self): return bool(self._latest_completion_request and self._latest_completion_request.Done()) def GetCompletionResponse(self): response = self._latest_completion_request.Response() response['completions'] = base.AdjustCandidateInsertionText( response['completions']) return response def SendCommandRequest(self, arguments, completer): extra_data = {} self._AddExtraConfDataIfNeeded(extra_data) return SendCommandRequest(arguments, completer, extra_data) def GetDefinedSubcommands(self): with HandleServerException(): return BaseRequest.PostDataToHandler(BuildRequestData(), 'defined_subcommands') return [] def GetCurrentCompletionRequest(self): return self._latest_completion_request def GetOmniCompleter(self): return self._omnicomp def FiletypeCompleterExistsForFiletype(self, filetype): try: return self._available_completers[filetype] except KeyError: pass exists_completer = SendCompleterAvailableRequest(filetype) if exists_completer is None: return False self._available_completers[filetype] = exists_completer return exists_completer def NativeFiletypeCompletionAvailable(self): return any([ self.FiletypeCompleterExistsForFiletype(x) for x in vimsupport.CurrentFiletypes() ]) def NativeFiletypeCompletionUsable(self): return (self.CurrentFiletypeCompletionEnabled() and self.NativeFiletypeCompletionAvailable()) def NeedsReparse(self): return self.CurrentBuffer().NeedsReparse() def OnFileReadyToParse(self): if not self.IsServerAlive(): self._NotifyUserIfServerCrashed() return if not self.IsServerReady(): return self._omnicomp.OnFileReadyToParse(None) extra_data = {} self._AddTagsFilesIfNeeded(extra_data) self._AddSyntaxDataIfNeeded(extra_data) self._AddExtraConfDataIfNeeded(extra_data) self.CurrentBuffer().SendParseRequest(extra_data) def OnBufferUnload(self, deleted_buffer_file): SendEventNotificationAsync('BufferUnload', filepath=deleted_buffer_file) def OnBufferVisit(self): extra_data = {} self._AddUltiSnipsDataIfNeeded(extra_data) SendEventNotificationAsync('BufferVisit', extra_data=extra_data) def CurrentBuffer(self): return self._buffers[vimsupport.GetCurrentBufferNumber()] def OnInsertLeave(self): SendEventNotificationAsync('InsertLeave') def OnCursorMoved(self): self.CurrentBuffer().OnCursorMoved() def _CleanLogfile(self): logging.shutdown() if not self._user_options['keep_logfiles']: if self._client_logfile: utils.RemoveIfExists(self._client_logfile) def OnVimLeave(self): self._ShutdownServer() self._CleanLogfile() def OnCurrentIdentifierFinished(self): SendEventNotificationAsync('CurrentIdentifierFinished') def OnCompleteDone(self): complete_done_actions = self.GetCompleteDoneHooks() for action in complete_done_actions: action(self) def GetCompleteDoneHooks(self): filetypes = vimsupport.CurrentFiletypes() for key, value in iteritems(self._complete_done_hooks): if key in filetypes: yield value def GetCompletionsUserMayHaveCompleted(self): latest_completion_request = self.GetCurrentCompletionRequest() if not latest_completion_request or not latest_completion_request.Done( ): return [] completions = latest_completion_request.RawResponse() result = self._FilterToMatchingCompletions(completions, True) result = list(result) if result: return result if self._HasCompletionsThatCouldBeCompletedWithMoreText(completions): # Since the way that YCM works leads to CompleteDone called on every # character, return blank if the completion might not be done. This won't # match if the completion is ended with typing a non-keyword character. return [] result = self._FilterToMatchingCompletions(completions, False) return list(result) def _FilterToMatchingCompletions(self, completions, full_match_only): """Filter to completions matching the item Vim said was completed""" completed = vimsupport.GetVariableValue('v:completed_item') for completion in completions: item = ConvertCompletionDataToVimData(completion) match_keys = (["word", "abbr", "menu", "info"] if full_match_only else ['word']) def matcher(key): return (utils.ToUnicode(completed.get(key, "")) == utils.ToUnicode( item.get(key, ""))) if all([matcher(i) for i in match_keys]): yield completion def _HasCompletionsThatCouldBeCompletedWithMoreText(self, completions): completed_item = vimsupport.GetVariableValue('v:completed_item') if not completed_item: return False completed_word = utils.ToUnicode(completed_item['word']) if not completed_word: return False # Sometimes CompleteDone is called after the next character is inserted. # If so, use inserted character to filter possible completions further. text = vimsupport.TextBeforeCursor() reject_exact_match = True if text and text[-1] != completed_word[-1]: reject_exact_match = False completed_word += text[-1] for completion in completions: word = utils.ToUnicode( ConvertCompletionDataToVimData(completion)['word']) if reject_exact_match and word == completed_word: continue if word.startswith(completed_word): return True return False def _OnCompleteDone_Csharp(self): completions = self.GetCompletionsUserMayHaveCompleted() namespaces = [self._GetRequiredNamespaceImport(c) for c in completions] namespaces = [n for n in namespaces if n] if not namespaces: return if len(namespaces) > 1: choices = [ "{0} {1}".format(i + 1, n) for i, n in enumerate(namespaces) ] choice = vimsupport.PresentDialog("Insert which namespace:", choices) if choice < 0: return namespace = namespaces[choice] else: namespace = namespaces[0] vimsupport.InsertNamespace(namespace) def _GetRequiredNamespaceImport(self, completion): if ("extra_data" not in completion or "required_namespace_import" not in completion["extra_data"]): return None return completion["extra_data"]["required_namespace_import"] def GetErrorCount(self): return self.CurrentBuffer().GetErrorCount() def GetWarningCount(self): return self.CurrentBuffer().GetWarningCount() def DiagnosticUiSupportedForCurrentFiletype(self): return any([ x in DIAGNOSTIC_UI_FILETYPES for x in vimsupport.CurrentFiletypes() ]) def ShouldDisplayDiagnostics(self): return bool(self._user_options['show_diagnostics_ui'] and self.DiagnosticUiSupportedForCurrentFiletype()) def _PopulateLocationListWithLatestDiagnostics(self): return self.CurrentBuffer().PopulateLocationList() def FileParseRequestReady(self): # Return True if server is not ready yet, to stop repeating check timer. return (not self.IsServerReady() or self.CurrentBuffer().FileParseRequestReady()) def HandleFileParseRequest(self, block=False): if not self.IsServerReady(): return current_buffer = self.CurrentBuffer() # Order is important here: # FileParseRequestReady has a low cost, while # NativeFiletypeCompletionUsable is a blocking server request if (not current_buffer.IsResponseHandled() and current_buffer.FileParseRequestReady(block) and self.NativeFiletypeCompletionUsable()): if self.ShouldDisplayDiagnostics(): current_buffer.UpdateDiagnostics() else: # YCM client has a hard-coded list of filetypes which are known # to support diagnostics, self.DiagnosticUiSupportedForCurrentFiletype() # # For filetypes which don't support diagnostics, we just want to check # the _latest_file_parse_request for any exception or UnknownExtraConf # response, to allow the server to raise configuration warnings, etc. # to the user. We ignore any other supplied data. current_buffer.GetResponse() # We set the file parse request as handled because we want to prevent # repeated issuing of the same warnings/errors/prompts. Setting this # makes IsRequestHandled return True until the next request is created. # # Note: it is the server's responsibility to determine the frequency of # error/warning/prompts when receiving a FileReadyToParse event, but # it is our responsibility to ensure that we only apply the # warning/error/prompt received once (for each event). current_buffer.MarkResponseHandled() def DebugInfo(self): debug_info = '' if self._client_logfile: debug_info += 'Client logfile: {0}\n'.format(self._client_logfile) extra_data = {} self._AddExtraConfDataIfNeeded(extra_data) debug_info += FormatDebugInfoResponse(SendDebugInfoRequest(extra_data)) debug_info += ('Server running at: {0}\n' 'Server process ID: {1}\n'.format( BaseRequest.server_location, self._server_popen.pid)) if self._server_stdout and self._server_stderr: debug_info += ('Server logfiles:\n' ' {0}\n' ' {1}'.format(self._server_stdout, self._server_stderr)) return debug_info def GetLogfiles(self): logfiles_list = [ self._client_logfile, self._server_stdout, self._server_stderr ] debug_info = SendDebugInfoRequest() if debug_info: completer = debug_info['completer'] if completer: for server in completer['servers']: logfiles_list.extend(server['logfiles']) logfiles = {} for logfile in logfiles_list: logfiles[os.path.basename(logfile)] = logfile return logfiles def _OpenLogfile(self, logfile): # Open log files in a horizontal window with the same behavior as the # preview window (same height and winfixheight enabled). Automatically # watch for changes. Set the cursor position at the end of the file. options = { 'size': vimsupport.GetIntValue('&previewheight'), 'fix': True, 'focus': False, 'watch': True, 'position': 'end' } vimsupport.OpenFilename(logfile, options) def _CloseLogfile(self, logfile): vimsupport.CloseBuffersForFilename(logfile) def ToggleLogs(self, *filenames): logfiles = self.GetLogfiles() if not filenames: vimsupport.PostVimMessage('Available logfiles are:\n' '{0}'.format('\n'.join( sorted(list(logfiles))))) return for filename in set(filenames): if filename not in logfiles: continue logfile = logfiles[filename] if not vimsupport.BufferIsVisibleForFilename(logfile): self._OpenLogfile(logfile) continue self._CloseLogfile(logfile) def CurrentFiletypeCompletionEnabled(self): filetypes = vimsupport.CurrentFiletypes() filetype_to_disable = self._user_options[ 'filetype_specific_completion_to_disable'] if '*' in filetype_to_disable: return False else: return not any([x in filetype_to_disable for x in filetypes]) def ShowDetailedDiagnostic(self): with HandleServerException(): detailed_diagnostic = BaseRequest.PostDataToHandler( BuildRequestData(), 'detailed_diagnostic') if 'message' in detailed_diagnostic: vimsupport.PostVimMessage(detailed_diagnostic['message'], warning=False) def ForceCompileAndDiagnostics(self): if not self.NativeFiletypeCompletionUsable(): vimsupport.PostVimMessage( 'Native filetype completion not supported for current file, ' 'cannot force recompilation.', warning=False) return False vimsupport.PostVimMessage( 'Forcing compilation, this will block Vim until done.', warning=False) self.OnFileReadyToParse() self.HandleFileParseRequest(block=True) vimsupport.PostVimMessage('Diagnostics refreshed', warning=False) return True def ShowDiagnostics(self): if not self.ForceCompileAndDiagnostics(): return if not self._PopulateLocationListWithLatestDiagnostics(): vimsupport.PostVimMessage('No warnings or errors detected.', warning=False) return if self._user_options['open_loclist_on_ycm_diags']: vimsupport.OpenLocationList(focus=True) def _AddSyntaxDataIfNeeded(self, extra_data): if not self._user_options['seed_identifiers_with_syntax']: return filetype = vimsupport.CurrentFiletypes()[0] if filetype in self._filetypes_with_keywords_loaded: return if self.IsServerReady(): self._filetypes_with_keywords_loaded.add(filetype) extra_data['syntax_keywords'] = list( syntax_parse.SyntaxKeywordsForCurrentBuffer()) def _AddTagsFilesIfNeeded(self, extra_data): def GetTagFiles(): tag_files = vim.eval('tagfiles()') return [ os.path.join(utils.GetCurrentDirectory(), tag_file) for tag_file in tag_files ] if not self._user_options['collect_identifiers_from_tags_files']: return extra_data['tag_files'] = GetTagFiles() def _AddExtraConfDataIfNeeded(self, extra_data): def BuildExtraConfData(extra_conf_vim_data): return dict((expr, vimsupport.VimExpressionToPythonType(expr)) for expr in extra_conf_vim_data) extra_conf_vim_data = self._user_options['extra_conf_vim_data'] if extra_conf_vim_data: extra_data['extra_conf_data'] = BuildExtraConfData( extra_conf_vim_data) def _AddUltiSnipsDataIfNeeded(self, extra_data): # See :h UltiSnips#SnippetsInCurrentScope. try: vim.eval('UltiSnips#SnippetsInCurrentScope( 1 )') except vim.error: return snippets = vimsupport.GetVariableValue('g:current_ulti_dict_info') extra_data['ultisnips_snippets'] = [{ 'trigger': trigger, 'description': snippet['description'] } for trigger, snippet in iteritems(snippets)]
class YouCompleteMe(object): def __init__(self, user_options): self._user_options = user_options self._user_notified_about_crash = False self._diag_interface = DiagnosticInterface(user_options) self._omnicomp = OmniCompleter(user_options) self._latest_file_parse_request = None self._latest_completion_request = None self._server_stdout = None self._server_stderr = None self._server_popen = None self._filetypes_with_keywords_loaded = set() self._ycmd_keepalive = YcmdKeepalive() self._SetupServer() self._ycmd_keepalive.Start() self._complete_done_hooks = { 'cs': lambda (self): self._OnCompleteDone_Csharp() } def _SetupServer(self): self._available_completers = {} server_port = utils.GetUnusedLocalhostPort() # The temp options file is deleted by ycmd during startup with tempfile.NamedTemporaryFile(delete=False) as options_file: hmac_secret = os.urandom(HMAC_SECRET_LENGTH) options_dict = dict(self._user_options) options_dict['hmac_secret'] = base64.b64encode(hmac_secret) json.dump(options_dict, options_file) options_file.flush() args = [ utils.PathToPythonInterpreter(), _PathToServerScript(), '--port={0}'.format(server_port), '--options_file={0}'.format(options_file.name), '--log={0}'.format(self._user_options['server_log_level']), '--idle_suicide_seconds={0}'.format( SERVER_IDLE_SUICIDE_SECONDS) ] if not self._user_options['server_use_vim_stdout']: filename_format = os.path.join(utils.PathToTempDir(), 'server_{port}_{std}.log') self._server_stdout = filename_format.format(port=server_port, std='stdout') self._server_stderr = filename_format.format(port=server_port, std='stderr') args.append('--stdout={0}'.format(self._server_stdout)) args.append('--stderr={0}'.format(self._server_stderr)) if self._user_options['server_keep_logfiles']: args.append('--keep_logfiles') self._server_popen = utils.SafePopen(args, stdin_windows=PIPE, stdout=PIPE, stderr=PIPE) BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port) BaseRequest.hmac_secret = hmac_secret self._NotifyUserIfServerCrashed() def IsServerAlive(self): returncode = self._server_popen.poll() # When the process hasn't finished yet, poll() returns None. return returncode is None def _NotifyUserIfServerCrashed(self): if self._user_notified_about_crash or self.IsServerAlive(): return self._user_notified_about_crash = True if self._server_stderr: try: with open(self._server_stderr, 'r') as server_stderr_file: error_output = ''.join(server_stderr_file.readlines() [:-NUM_YCMD_STDERR_LINES_ON_CRASH]) vimsupport.PostMultiLineNotice( SERVER_CRASH_MESSAGE_STDERR_FILE + error_output) except IOError: vimsupport.PostVimMessage( SERVER_CRASH_MESSAGE_STDERR_FILE_DELETED) else: vimsupport.PostVimMessage(SERVER_CRASH_MESSAGE_SAME_STDERR) def ServerPid(self): if not self._server_popen: return -1 return self._server_popen.pid def _ServerCleanup(self): if self.IsServerAlive(): self._server_popen.terminate() def RestartServer(self): vimsupport.PostVimMessage('Restarting ycmd server...') self._user_notified_about_crash = False self._ServerCleanup() self._SetupServer() def CreateCompletionRequest(self, force_semantic=False): request_data = BuildRequestData() if (not self.NativeFiletypeCompletionAvailable() and self.CurrentFiletypeCompletionEnabled()): wrapped_request_data = RequestWrap(request_data) if self._omnicomp.ShouldUseNow(wrapped_request_data): self._latest_completion_request = OmniCompletionRequest( self._omnicomp, wrapped_request_data) return self._latest_completion_request request_data['working_dir'] = os.getcwd() self._AddExtraConfDataIfNeeded(request_data) if force_semantic: request_data['force_semantic'] = True self._latest_completion_request = CompletionRequest(request_data) return self._latest_completion_request def SendCommandRequest(self, arguments, completer): if self.IsServerAlive(): return SendCommandRequest(arguments, completer) def GetDefinedSubcommands(self): if self.IsServerAlive(): try: return BaseRequest.PostDataToHandler(BuildRequestData(), 'defined_subcommands') except ServerError: return [] else: return [] def GetCurrentCompletionRequest(self): return self._latest_completion_request def GetOmniCompleter(self): return self._omnicomp def FiletypeCompleterExistsForFiletype(self, filetype): try: return self._available_completers[filetype] except KeyError: pass exists_completer = (self.IsServerAlive() and bool(SendCompleterAvailableRequest(filetype))) self._available_completers[filetype] = exists_completer return exists_completer def NativeFiletypeCompletionAvailable(self): return any([ self.FiletypeCompleterExistsForFiletype(x) for x in vimsupport.CurrentFiletypes() ]) def NativeFiletypeCompletionUsable(self): return (self.CurrentFiletypeCompletionEnabled() and self.NativeFiletypeCompletionAvailable()) def OnFileReadyToParse(self): self._omnicomp.OnFileReadyToParse(None) if not self.IsServerAlive(): self._NotifyUserIfServerCrashed() extra_data = {} self._AddTagsFilesIfNeeded(extra_data) self._AddSyntaxDataIfNeeded(extra_data) self._AddExtraConfDataIfNeeded(extra_data) self._latest_file_parse_request = EventNotification( 'FileReadyToParse', extra_data) self._latest_file_parse_request.Start() def OnBufferUnload(self, deleted_buffer_file): if not self.IsServerAlive(): return SendEventNotificationAsync('BufferUnload', {'unloaded_buffer': deleted_buffer_file}) def OnBufferVisit(self): if not self.IsServerAlive(): return extra_data = {} _AddUltiSnipsDataIfNeeded(extra_data) SendEventNotificationAsync('BufferVisit', extra_data) def OnInsertLeave(self): if not self.IsServerAlive(): return SendEventNotificationAsync('InsertLeave') def OnCursorMoved(self): self._diag_interface.OnCursorMoved() def OnVimLeave(self): self._ServerCleanup() def OnCurrentIdentifierFinished(self): if not self.IsServerAlive(): return SendEventNotificationAsync('CurrentIdentifierFinished') def OnCompleteDone(self): complete_done_actions = self.GetCompleteDoneHooks() for action in complete_done_actions: action(self) def GetCompleteDoneHooks(self): filetypes = vimsupport.CurrentFiletypes() for key, value in self._complete_done_hooks.iteritems(): if key in filetypes: yield value def GetCompletionsUserMayHaveCompleted(self): latest_completion_request = self.GetCurrentCompletionRequest() if not latest_completion_request or not latest_completion_request.Done( ): return [] completions = latest_completion_request.RawResponse() result = self._FilterToMatchingCompletions(completions, True) result = list(result) if result: return result if self._HasCompletionsThatCouldBeCompletedWithMoreText(completions): # Since the way that YCM works leads to CompleteDone called on every # character, return blank if the completion might not be done. This won't # match if the completion is ended with typing a non-keyword character. return [] result = self._FilterToMatchingCompletions(completions, False) return list(result) def _FilterToMatchingCompletions(self, completions, full_match_only): self._PatchBasedOnVimVersion() return self._FilterToMatchingCompletions(completions, full_match_only) def _HasCompletionsThatCouldBeCompletedWithMoreText(self, completions): self._PatchBasedOnVimVersion() return self._HasCompletionsThatCouldBeCompletedWithMoreText( completions) def _PatchBasedOnVimVersion(self): if vimsupport.VimVersionAtLeast("7.4.774"): self._HasCompletionsThatCouldBeCompletedWithMoreText = \ self._HasCompletionsThatCouldBeCompletedWithMoreText_NewerVim self._FilterToMatchingCompletions = \ self._FilterToMatchingCompletions_NewerVim else: self._FilterToMatchingCompletions = \ self._FilterToMatchingCompletions_OlderVim self._HasCompletionsThatCouldBeCompletedWithMoreText = \ self._HasCompletionsThatCouldBeCompletedWithMoreText_OlderVim def _FilterToMatchingCompletions_NewerVim(self, completions, full_match_only): """ Filter to completions matching the item Vim said was completed """ completed = vimsupport.GetVariableValue('v:completed_item') for completion in completions: item = ConvertCompletionDataToVimData(completion) match_keys = (["word", "abbr", "menu", "info"] if full_match_only else ['word']) matcher = lambda key: completed.get(key, "") == item.get(key, "") if all([matcher(i) for i in match_keys]): yield completion def _FilterToMatchingCompletions_OlderVim(self, completions, full_match_only): """ Filter to completions matching the buffer text """ if full_match_only: return # Only supported in 7.4.774+ # No support for multiple line completions text = vimsupport.TextBeforeCursor() for completion in completions: word = completion["insertion_text"] # Trim complete-ending character if needed text = re.sub(r"[^a-zA-Z0-9_]$", "", text) buffer_text = text[-1 * len(word):] if buffer_text == word: yield completion def _HasCompletionsThatCouldBeCompletedWithMoreText_NewerVim( self, completions): completed_item = vimsupport.GetVariableValue('v:completed_item') completed_word = completed_item['word'] if not completed_word: return False # Sometime CompleteDone is called after the next character is inserted # If so, use inserted character to filter possible completions further text = vimsupport.TextBeforeCursor() reject_exact_match = True if text and text[-1] != completed_word[-1]: reject_exact_match = False completed_word += text[-1] for completion in completions: word = ConvertCompletionDataToVimData(completion)['word'] if reject_exact_match and word == completed_word: continue if word.startswith(completed_word): return True return False def _HasCompletionsThatCouldBeCompletedWithMoreText_OlderVim( self, completions): # No support for multiple line completions text = vimsupport.TextBeforeCursor() for completion in completions: word = ConvertCompletionDataToVimData(completion)['word'] for i in range(1, len(word) - 1): # Excluding full word if text[-1 * i:] == word[:i]: return True return False def _OnCompleteDone_Csharp(self): completions = self.GetCompletionsUserMayHaveCompleted() namespaces = [self._GetRequiredNamespaceImport(c) for c in completions] namespaces = [n for n in namespaces if n] if not namespaces: return if len(namespaces) > 1: choices = [ "{0} {1}".format(i + 1, n) for i, n in enumerate(namespaces) ] choice = vimsupport.PresentDialog("Insert which namespace:", choices) if choice < 0: return namespace = namespaces[choice] else: namespace = namespaces[0] vimsupport.InsertNamespace(namespace) def _GetRequiredNamespaceImport(self, completion): if ("extra_data" not in completion or "required_namespace_import" not in completion["extra_data"]): return None return completion["extra_data"]["required_namespace_import"] def DiagnosticsForCurrentFileReady(self): return bool(self._latest_file_parse_request and self._latest_file_parse_request.Done()) def GetDiagnosticsFromStoredRequest(self, qflist_format=False): if self.DiagnosticsForCurrentFileReady(): diagnostics = self._latest_file_parse_request.Response() # We set the diagnostics request to None because we want to prevent # repeated refreshing of the buffer with the same diags. Setting this to # None makes DiagnosticsForCurrentFileReady return False until the next # request is created. self._latest_file_parse_request = None if qflist_format: return vimsupport.ConvertDiagnosticsToQfList(diagnostics) else: return diagnostics return [] def UpdateDiagnosticInterface(self): if (self.DiagnosticsForCurrentFileReady() and self.NativeFiletypeCompletionUsable()): self._diag_interface.UpdateWithNewDiagnostics( self.GetDiagnosticsFromStoredRequest()) def ShowDetailedDiagnostic(self): if not self.IsServerAlive(): return try: debug_info = BaseRequest.PostDataToHandler(BuildRequestData(), 'detailed_diagnostic') if 'message' in debug_info: vimsupport.EchoText(debug_info['message']) except ServerError as e: vimsupport.PostVimMessage(str(e)) def DebugInfo(self): if self.IsServerAlive(): debug_info = BaseRequest.PostDataToHandler(BuildRequestData(), 'debug_info') else: debug_info = 'Server crashed, no debug info from server' debug_info += '\nServer running at: {0}'.format( BaseRequest.server_location) debug_info += '\nServer process ID: {0}'.format(self._server_popen.pid) if self._server_stderr or self._server_stdout: debug_info += '\nServer logfiles:\n {0}\n {1}'.format( self._server_stdout, self._server_stderr) return debug_info def CurrentFiletypeCompletionEnabled(self): filetypes = vimsupport.CurrentFiletypes() filetype_to_disable = self._user_options[ 'filetype_specific_completion_to_disable'] if '*' in filetype_to_disable: return False else: return not any([x in filetype_to_disable for x in filetypes]) def _AddSyntaxDataIfNeeded(self, extra_data): if not self._user_options['seed_identifiers_with_syntax']: return filetype = vimsupport.CurrentFiletypes()[0] if filetype in self._filetypes_with_keywords_loaded: return self._filetypes_with_keywords_loaded.add(filetype) extra_data['syntax_keywords'] = list( syntax_parse.SyntaxKeywordsForCurrentBuffer()) def _AddTagsFilesIfNeeded(self, extra_data): def GetTagFiles(): tag_files = vim.eval('tagfiles()') # getcwd() throws an exception when the CWD has been deleted. try: current_working_directory = os.getcwd() except OSError: return [] return [ os.path.join(current_working_directory, x) for x in tag_files ] if not self._user_options['collect_identifiers_from_tags_files']: return extra_data['tag_files'] = GetTagFiles() def _AddExtraConfDataIfNeeded(self, extra_data): def BuildExtraConfData(extra_conf_vim_data): return dict((expr, vimsupport.VimExpressionToPythonType(expr)) for expr in extra_conf_vim_data) extra_conf_vim_data = self._user_options['extra_conf_vim_data'] if extra_conf_vim_data: extra_data['extra_conf_data'] = BuildExtraConfData( extra_conf_vim_data)
class YouCompleteMe( object ): def __init__( self ): self._available_completers = {} self._user_options = None self._user_notified_about_crash = False self._omnicomp = None self._buffers = None self._latest_completion_request = None self._logger = logging.getLogger( 'ycm' ) self._client_logfile = None self._server_stdout = None self._server_stderr = None self._server_popen = None self._filetypes_with_keywords_loaded = set() self._ycmd_keepalive = YcmdKeepalive() self._server_is_ready_with_cache = False self._SetUpLogging() self._SetUpServer() self._ycmd_keepalive.Start() def _SetUpServer( self ): self._available_completers = {} self._user_notified_about_crash = False self._filetypes_with_keywords_loaded = set() self._server_is_ready_with_cache = False self._message_poll_request = None self._user_options = base.GetUserOptions() self._omnicomp = OmniCompleter( self._user_options ) self._buffers = BufferDict( self._user_options ) self._SetLogLevel() hmac_secret = os.urandom( HMAC_SECRET_LENGTH ) options_dict = dict( self._user_options ) options_dict[ 'hmac_secret' ] = utils.ToUnicode( base64.b64encode( hmac_secret ) ) options_dict[ 'server_keep_logfiles' ] = self._user_options[ 'keep_logfiles' ] # The temp options file is deleted by ycmd during startup. with NamedTemporaryFile( delete = False, mode = 'w+' ) as options_file: json.dump( options_dict, options_file ) server_port = utils.GetUnusedLocalhostPort() BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port ) BaseRequest.hmac_secret = hmac_secret try: python_interpreter = paths.PathToPythonInterpreter() except RuntimeError as error: error_message = ( "Unable to start the ycmd server. {0}. " "Correct the error then restart the server " "with ':YcmRestartServer'.".format( str( error ).rstrip( '.' ) ) ) self._logger.exception( error_message ) vimsupport.PostVimMessage( error_message ) return args = [ python_interpreter, paths.PathToServerScript(), '--port={0}'.format( server_port ), '--options_file={0}'.format( options_file.name ), '--log={0}'.format( self._user_options[ 'log_level' ] ), '--idle_suicide_seconds={0}'.format( SERVER_IDLE_SUICIDE_SECONDS ) ] self._server_stdout = utils.CreateLogfile( SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stdout' ) ) self._server_stderr = utils.CreateLogfile( SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stderr' ) ) args.append( '--stdout={0}'.format( self._server_stdout ) ) args.append( '--stderr={0}'.format( self._server_stderr ) ) if self._user_options[ 'keep_logfiles' ]: args.append( '--keep_logfiles' ) self._server_popen = utils.SafePopen( args, stdin_windows = PIPE, stdout = PIPE, stderr = PIPE ) def _SetUpLogging( self ): def FreeFileFromOtherProcesses( file_object ): if utils.OnWindows(): from ctypes import windll import msvcrt file_handle = msvcrt.get_osfhandle( file_object.fileno() ) windll.kernel32.SetHandleInformation( file_handle, HANDLE_FLAG_INHERIT, 0 ) self._client_logfile = utils.CreateLogfile( CLIENT_LOGFILE_FORMAT ) handler = logging.FileHandler( self._client_logfile ) # On Windows and Python prior to 3.4, file handles are inherited by child # processes started with at least one replaced standard stream, which is the # case when we start the ycmd server (we are redirecting all standard # outputs into a pipe). These files cannot be removed while the child # processes are still up. This is not desirable for a logfile because we # want to remove it at Vim exit without having to wait for the ycmd server # to be completely shut down. We need to make the logfile handle # non-inheritable. See https://www.python.org/dev/peps/pep-0446 for more # details. FreeFileFromOtherProcesses( handler.stream ) formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' ) handler.setFormatter( formatter ) self._logger.addHandler( handler ) def _SetLogLevel( self ): log_level = self._user_options[ 'log_level' ] numeric_level = getattr( logging, log_level.upper(), None ) if not isinstance( numeric_level, int ): raise ValueError( 'Invalid log level: {0}'.format( log_level ) ) self._logger.setLevel( numeric_level ) def IsServerAlive( self ): # When the process hasn't finished yet, poll() returns None. return bool( self._server_popen ) and self._server_popen.poll() is None def CheckIfServerIsReady( self ): if not self._server_is_ready_with_cache and self.IsServerAlive(): self._server_is_ready_with_cache = BaseRequest().GetDataFromHandler( 'ready', display_message = False ) return self._server_is_ready_with_cache def IsServerReady( self ): return self._server_is_ready_with_cache def NotifyUserIfServerCrashed( self ): if ( not self._server_popen or self._user_notified_about_crash or self.IsServerAlive() ): return self._user_notified_about_crash = True return_code = self._server_popen.poll() logfile = os.path.basename( self._server_stderr ) if return_code == server_utils.CORE_UNEXPECTED_STATUS: error_message = CORE_UNEXPECTED_MESSAGE.format( logfile = logfile ) elif return_code == server_utils.CORE_MISSING_STATUS: error_message = CORE_MISSING_MESSAGE elif return_code == server_utils.CORE_PYTHON2_STATUS: error_message = CORE_PYTHON2_MESSAGE elif return_code == server_utils.CORE_PYTHON3_STATUS: error_message = CORE_PYTHON3_MESSAGE elif return_code == server_utils.CORE_OUTDATED_STATUS: error_message = CORE_OUTDATED_MESSAGE else: error_message = EXIT_CODE_UNEXPECTED_MESSAGE.format( code = return_code, logfile = logfile ) error_message = SERVER_SHUTDOWN_MESSAGE + ' ' + error_message self._logger.error( error_message ) vimsupport.PostVimMessage( error_message ) def ServerPid( self ): if not self._server_popen: return -1 return self._server_popen.pid def _ShutdownServer( self ): SendShutdownRequest() def RestartServer( self ): vimsupport.PostVimMessage( 'Restarting ycmd server...' ) self._ShutdownServer() self._SetUpServer() def SendCompletionRequest( self, force_semantic = False ): request_data = BuildRequestData() request_data[ 'force_semantic' ] = force_semantic if not self.NativeFiletypeCompletionUsable(): wrapped_request_data = RequestWrap( request_data ) if self._omnicomp.ShouldUseNow( wrapped_request_data ): self._latest_completion_request = OmniCompletionRequest( self._omnicomp, wrapped_request_data ) self._latest_completion_request.Start() return self._AddExtraConfDataIfNeeded( request_data ) self._latest_completion_request = CompletionRequest( request_data ) self._latest_completion_request.Start() def CompletionRequestReady( self ): return bool( self._latest_completion_request and self._latest_completion_request.Done() ) def GetCompletionResponse( self ): response = self._latest_completion_request.Response() response[ 'completions' ] = base.AdjustCandidateInsertionText( response[ 'completions' ] ) return response def SendCommandRequest( self, arguments, modifiers, has_range, start_line, end_line ): final_arguments = [] for argument in arguments: # The ft= option which specifies the completer when running a command is # ignored because it has not been working for a long time. The option is # still parsed to not break users that rely on it. if argument.startswith( 'ft=' ): continue final_arguments.append( argument ) extra_data = { 'options': { 'tab_size': vimsupport.GetIntValue( 'shiftwidth()' ), 'insert_spaces': vimsupport.GetBoolValue( '&expandtab' ) } } if has_range: extra_data.update( vimsupport.BuildRange( start_line, end_line ) ) self._AddExtraConfDataIfNeeded( extra_data ) return SendCommandRequest( final_arguments, modifiers, self._user_options[ 'goto_buffer_command' ], extra_data ) def GetDefinedSubcommands( self ): subcommands = BaseRequest().PostDataToHandler( BuildRequestData(), 'defined_subcommands' ) return subcommands if subcommands else [] def GetCurrentCompletionRequest( self ): return self._latest_completion_request def GetOmniCompleter( self ): return self._omnicomp def FiletypeCompleterExistsForFiletype( self, filetype ): try: return self._available_completers[ filetype ] except KeyError: pass exists_completer = SendCompleterAvailableRequest( filetype ) if exists_completer is None: return False self._available_completers[ filetype ] = exists_completer return exists_completer def NativeFiletypeCompletionAvailable( self ): return any( self.FiletypeCompleterExistsForFiletype( x ) for x in vimsupport.CurrentFiletypes() ) def NativeFiletypeCompletionUsable( self ): disabled_filetypes = self._user_options[ 'filetype_specific_completion_to_disable' ] return ( vimsupport.CurrentFiletypesEnabled( disabled_filetypes ) and self.NativeFiletypeCompletionAvailable() ) def NeedsReparse( self ): return self.CurrentBuffer().NeedsReparse() def UpdateWithNewDiagnosticsForFile( self, filepath, diagnostics ): bufnr = vimsupport.GetBufferNumberForFilename( filepath ) if bufnr in self._buffers and vimsupport.BufferIsVisible( bufnr ): # Note: We only update location lists, etc. for visible buffers, because # otherwise we default to using the current location list and the results # are that non-visible buffer errors clobber visible ones. self._buffers[ bufnr ].UpdateWithNewDiagnostics( diagnostics ) else: # The project contains errors in file "filepath", but that file is not # open in any buffer. This happens for Language Server Protocol-based # completers, as they return diagnostics for the entire "project" # asynchronously (rather than per-file in the response to the parse # request). # # There are a number of possible approaches for # this, but for now we simply ignore them. Other options include: # - Use the QuickFix list to report project errors? # - Use a special buffer for project errors # - Put them in the location list of whatever the "current" buffer is # - Store them in case the buffer is opened later # - add a :YcmProjectDiags command # - Add them to errror/warning _counts_ but not any actual location list # or other # - etc. # # However, none of those options are great, and lead to their own # complexities. So for now, we just ignore these diagnostics for files not # open in any buffer. pass def OnPeriodicTick( self ): if not self.IsServerAlive(): # Server has died. We'll reset when the server is started again. return False elif not self.IsServerReady(): # Try again in a jiffy return True if not self._message_poll_request: self._message_poll_request = MessagesPoll() if not self._message_poll_request.Poll( self ): # Don't poll again until some event which might change the server's mind # about whether to provide messages for the current buffer (e.g. buffer # visit, file ready to parse, etc.) self._message_poll_request = None return False # Poll again in a jiffy return True def OnFileReadyToParse( self ): if not self.IsServerAlive(): self.NotifyUserIfServerCrashed() return if not self.IsServerReady(): return extra_data = {} self._AddTagsFilesIfNeeded( extra_data ) self._AddSyntaxDataIfNeeded( extra_data ) self._AddExtraConfDataIfNeeded( extra_data ) self.CurrentBuffer().SendParseRequest( extra_data ) def OnBufferUnload( self, deleted_buffer_number ): SendEventNotificationAsync( 'BufferUnload', deleted_buffer_number ) def UpdateMatches( self ): self.CurrentBuffer().UpdateMatches() def OnBufferVisit( self ): extra_data = {} self._AddUltiSnipsDataIfNeeded( extra_data ) SendEventNotificationAsync( 'BufferVisit', extra_data = extra_data ) def CurrentBuffer( self ): return self._buffers[ vimsupport.GetCurrentBufferNumber() ] def OnInsertLeave( self ): SendEventNotificationAsync( 'InsertLeave' ) def OnCursorMoved( self ): self.CurrentBuffer().OnCursorMoved() def _CleanLogfile( self ): logging.shutdown() if not self._user_options[ 'keep_logfiles' ]: if self._client_logfile: utils.RemoveIfExists( self._client_logfile ) def OnVimLeave( self ): self._ShutdownServer() self._CleanLogfile() def OnCurrentIdentifierFinished( self ): SendEventNotificationAsync( 'CurrentIdentifierFinished' ) def OnCompleteDone( self ): completion_request = self.GetCurrentCompletionRequest() if completion_request: completion_request.OnCompleteDone() def GetErrorCount( self ): return self.CurrentBuffer().GetErrorCount() def GetWarningCount( self ): return self.CurrentBuffer().GetWarningCount() def DiagnosticUiSupportedForCurrentFiletype( self ): return any( x in DIAGNOSTIC_UI_FILETYPES or x in DIAGNOSTIC_UI_ASYNC_FILETYPES for x in vimsupport.CurrentFiletypes() ) def ShouldDisplayDiagnostics( self ): return bool( self._user_options[ 'show_diagnostics_ui' ] and self.DiagnosticUiSupportedForCurrentFiletype() ) def _PopulateLocationListWithLatestDiagnostics( self ): return self.CurrentBuffer().PopulateLocationList() def FileParseRequestReady( self ): # Return True if server is not ready yet, to stop repeating check timer. return ( not self.IsServerReady() or self.CurrentBuffer().FileParseRequestReady() ) def HandleFileParseRequest( self, block = False ): if not self.IsServerReady(): return current_buffer = self.CurrentBuffer() # Order is important here: # FileParseRequestReady has a low cost, while # NativeFiletypeCompletionUsable is a blocking server request if ( not current_buffer.IsResponseHandled() and current_buffer.FileParseRequestReady( block ) and self.NativeFiletypeCompletionUsable() ): if self.ShouldDisplayDiagnostics(): # Forcefuly update the location list, etc. from the parse request when # doing something like :YcmDiags current_buffer.UpdateDiagnostics( block is True ) else: # YCM client has a hard-coded list of filetypes which are known # to support diagnostics, self.DiagnosticUiSupportedForCurrentFiletype() # # For filetypes which don't support diagnostics, we just want to check # the _latest_file_parse_request for any exception or UnknownExtraConf # response, to allow the server to raise configuration warnings, etc. # to the user. We ignore any other supplied data. current_buffer.GetResponse() # We set the file parse request as handled because we want to prevent # repeated issuing of the same warnings/errors/prompts. Setting this # makes IsRequestHandled return True until the next request is created. # # Note: it is the server's responsibility to determine the frequency of # error/warning/prompts when receiving a FileReadyToParse event, but # it is our responsibility to ensure that we only apply the # warning/error/prompt received once (for each event). current_buffer.MarkResponseHandled() def ShouldResendFileParseRequest( self ): return self.CurrentBuffer().ShouldResendParseRequest() def DebugInfo( self ): debug_info = '' if self._client_logfile: debug_info += 'Client logfile: {0}\n'.format( self._client_logfile ) extra_data = {} self._AddExtraConfDataIfNeeded( extra_data ) debug_info += FormatDebugInfoResponse( SendDebugInfoRequest( extra_data ) ) debug_info += 'Server running at: {0}\n'.format( BaseRequest.server_location ) if self._server_popen: debug_info += 'Server process ID: {0}\n'.format( self._server_popen.pid ) if self._server_stdout and self._server_stderr: debug_info += ( 'Server logfiles:\n' ' {0}\n' ' {1}'.format( self._server_stdout, self._server_stderr ) ) return debug_info def GetLogfiles( self ): logfiles_list = [ self._client_logfile, self._server_stdout, self._server_stderr ] extra_data = {} self._AddExtraConfDataIfNeeded( extra_data ) debug_info = SendDebugInfoRequest( extra_data ) if debug_info: completer = debug_info[ 'completer' ] if completer: for server in completer[ 'servers' ]: logfiles_list.extend( server[ 'logfiles' ] ) logfiles = {} for logfile in logfiles_list: logfiles[ os.path.basename( logfile ) ] = logfile return logfiles def _OpenLogfile( self, logfile ): # Open log files in a horizontal window with the same behavior as the # preview window (same height and winfixheight enabled). Automatically # watch for changes. Set the cursor position at the end of the file. options = { 'size': vimsupport.GetIntValue( '&previewheight' ), 'fix': True, 'focus': False, 'watch': True, 'position': 'end' } vimsupport.OpenFilename( logfile, options ) def _CloseLogfile( self, logfile ): vimsupport.CloseBuffersForFilename( logfile ) def ToggleLogs( self, *filenames ): logfiles = self.GetLogfiles() if not filenames: sorted_logfiles = sorted( list( logfiles ) ) try: logfile_index = vimsupport.SelectFromList( 'Which logfile do you wish to open (or close if already open)?', sorted_logfiles ) except RuntimeError as e: vimsupport.PostVimMessage( str( e ) ) return logfile = logfiles[ sorted_logfiles[ logfile_index ] ] if not vimsupport.BufferIsVisibleForFilename( logfile ): self._OpenLogfile( logfile ) else: self._CloseLogfile( logfile ) return for filename in set( filenames ): if filename not in logfiles: continue logfile = logfiles[ filename ] if not vimsupport.BufferIsVisibleForFilename( logfile ): self._OpenLogfile( logfile ) continue self._CloseLogfile( logfile ) def ShowDetailedDiagnostic( self ): detailed_diagnostic = BaseRequest().PostDataToHandler( BuildRequestData(), 'detailed_diagnostic' ) if detailed_diagnostic and 'message' in detailed_diagnostic: vimsupport.PostVimMessage( detailed_diagnostic[ 'message' ], warning = False ) def ForceCompileAndDiagnostics( self ): if not self.NativeFiletypeCompletionUsable(): vimsupport.PostVimMessage( 'Native filetype completion not supported for current file, ' 'cannot force recompilation.', warning = False ) return False vimsupport.PostVimMessage( 'Forcing compilation, this will block Vim until done.', warning = False ) self.OnFileReadyToParse() self.HandleFileParseRequest( block = True ) vimsupport.PostVimMessage( 'Diagnostics refreshed', warning = False ) return True def ShowDiagnostics( self ): if not self.ForceCompileAndDiagnostics(): return if not self._PopulateLocationListWithLatestDiagnostics(): vimsupport.PostVimMessage( 'No warnings or errors detected.', warning = False ) return if self._user_options[ 'open_loclist_on_ycm_diags' ]: vimsupport.OpenLocationList( focus = True ) def _AddSyntaxDataIfNeeded( self, extra_data ): if not self._user_options[ 'seed_identifiers_with_syntax' ]: return filetype = vimsupport.CurrentFiletypes()[ 0 ] if filetype in self._filetypes_with_keywords_loaded: return if self.IsServerReady(): self._filetypes_with_keywords_loaded.add( filetype ) extra_data[ 'syntax_keywords' ] = list( syntax_parse.SyntaxKeywordsForCurrentBuffer() ) def _AddTagsFilesIfNeeded( self, extra_data ): def GetTagFiles(): tag_files = vim.eval( 'tagfiles()' ) return [ os.path.join( utils.GetCurrentDirectory(), tag_file ) for tag_file in tag_files ] if not self._user_options[ 'collect_identifiers_from_tags_files' ]: return extra_data[ 'tag_files' ] = GetTagFiles() def _AddExtraConfDataIfNeeded( self, extra_data ): def BuildExtraConfData( extra_conf_vim_data ): extra_conf_data = {} for expr in extra_conf_vim_data: try: extra_conf_data[ expr ] = vimsupport.VimExpressionToPythonType( expr ) except vim.error: message = ( "Error evaluating '{expr}' in the 'g:ycm_extra_conf_vim_data' " "option.".format( expr = expr ) ) vimsupport.PostVimMessage( message, truncate = True ) self._logger.exception( message ) return extra_conf_data extra_conf_vim_data = self._user_options[ 'extra_conf_vim_data' ] if extra_conf_vim_data: extra_data[ 'extra_conf_data' ] = BuildExtraConfData( extra_conf_vim_data ) def _AddUltiSnipsDataIfNeeded( self, extra_data ): # See :h UltiSnips#SnippetsInCurrentScope. try: vim.eval( 'UltiSnips#SnippetsInCurrentScope( 1 )' ) except vim.error: return snippets = vimsupport.GetVariableValue( 'g:current_ulti_dict_info' ) extra_data[ 'ultisnips_snippets' ] = [ { 'trigger': trigger, 'description': snippet[ 'description' ] } for trigger, snippet in iteritems( snippets ) ]
class YouCompleteMe(object): def __init__(self, user_options): self._available_completers = {} self._user_options = user_options self._user_notified_about_crash = False self._diag_interface = DiagnosticInterface(user_options) self._omnicomp = OmniCompleter(user_options) self._latest_file_parse_request = None self._latest_completion_request = None self._latest_diagnostics = [] self._server_stdout = None self._server_stderr = None self._server_popen = None self._filetypes_with_keywords_loaded = set() self._ycmd_keepalive = YcmdKeepalive() self._SetupServer() self._ycmd_keepalive.Start() self._complete_done_hooks = { 'cs': lambda self: self._OnCompleteDone_Csharp() } def _SetupServer(self): self._available_completers = {} self._user_notified_about_crash = False server_port = utils.GetUnusedLocalhostPort() # The temp options file is deleted by ycmd during startup with NamedTemporaryFile(delete=False, mode='w+') as options_file: hmac_secret = os.urandom(HMAC_SECRET_LENGTH) options_dict = dict(self._user_options) options_dict['hmac_secret'] = utils.ToUnicode( base64.b64encode(hmac_secret)) json.dump(options_dict, options_file) options_file.flush() args = [ paths.PathToPythonInterpreter(), paths.PathToServerScript(), '--port={0}'.format(server_port), '--options_file={0}'.format(options_file.name), '--log={0}'.format(self._user_options['server_log_level']), '--idle_suicide_seconds={0}'.format( SERVER_IDLE_SUICIDE_SECONDS) ] filename_format = os.path.join(utils.PathToCreatedTempDir(), 'server_{port}_{std}.log') self._server_stdout = filename_format.format(port=server_port, std='stdout') self._server_stderr = filename_format.format(port=server_port, std='stderr') args.append('--stdout={0}'.format(self._server_stdout)) args.append('--stderr={0}'.format(self._server_stderr)) if self._user_options['server_keep_logfiles']: args.append('--keep_logfiles') self._server_popen = utils.SafePopen(args, stdin_windows=PIPE, stdout=PIPE, stderr=PIPE) BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port) BaseRequest.hmac_secret = hmac_secret self._NotifyUserIfServerCrashed() def IsServerAlive(self): returncode = self._server_popen.poll() # When the process hasn't finished yet, poll() returns None. return returncode is None def _NotifyUserIfServerCrashed(self): if self._user_notified_about_crash or self.IsServerAlive(): return self._user_notified_about_crash = True try: vimsupport.CheckFilename(self._server_stderr) stderr_message = STDERR_FILE_MESSAGE except RuntimeError: stderr_message = STDERR_FILE_DELETED_MESSAGE message = SERVER_SHUTDOWN_MESSAGE return_code = self._server_popen.poll() if return_code == server_utils.CORE_UNEXPECTED_STATUS: message += ' ' + CORE_UNEXPECTED_MESSAGE + ' ' + stderr_message elif return_code == server_utils.CORE_MISSING_STATUS: message += ' ' + CORE_MISSING_MESSAGE elif return_code == server_utils.CORE_PYTHON2_STATUS: message += ' ' + CORE_PYTHON2_MESSAGE elif return_code == server_utils.CORE_PYTHON3_STATUS: message += ' ' + CORE_PYTHON3_MESSAGE elif return_code == server_utils.CORE_OUTDATED_STATUS: message += ' ' + CORE_OUTDATED_MESSAGE else: message += ' ' + stderr_message vimsupport.PostVimMessage(message) def ServerPid(self): if not self._server_popen: return -1 return self._server_popen.pid def _ShutdownServer(self): if self.IsServerAlive(): SendShutdownRequest() def RestartServer(self): self._CloseLogs() vimsupport.PostVimMessage('Restarting ycmd server...') self._ShutdownServer() self._SetupServer() def CreateCompletionRequest(self, force_semantic=False): request_data = BuildRequestData() if (not self.NativeFiletypeCompletionAvailable() and self.CurrentFiletypeCompletionEnabled()): wrapped_request_data = RequestWrap(request_data) if self._omnicomp.ShouldUseNow(wrapped_request_data): self._latest_completion_request = OmniCompletionRequest( self._omnicomp, wrapped_request_data) return self._latest_completion_request request_data['working_dir'] = os.getcwd() self._AddExtraConfDataIfNeeded(request_data) if force_semantic: request_data['force_semantic'] = True self._latest_completion_request = CompletionRequest(request_data) return self._latest_completion_request def GetCompletions(self): request = self.GetCurrentCompletionRequest() request.Start() while not request.Done(): try: if vimsupport.GetBoolValue('complete_check()'): return {'words': [], 'refresh': 'always'} except KeyboardInterrupt: return {'words': [], 'refresh': 'always'} results = base.AdjustCandidateInsertionText(request.Response()) return {'words': results, 'refresh': 'always'} def SendCommandRequest(self, arguments, completer): if self.IsServerAlive(): return SendCommandRequest(arguments, completer) def GetDefinedSubcommands(self): if self.IsServerAlive(): try: return BaseRequest.PostDataToHandler(BuildRequestData(), 'defined_subcommands') except ServerError: return [] else: return [] def GetCurrentCompletionRequest(self): return self._latest_completion_request def GetOmniCompleter(self): return self._omnicomp def FiletypeCompleterExistsForFiletype(self, filetype): try: return self._available_completers[filetype] except KeyError: pass if not self.IsServerAlive(): return False exists_completer = SendCompleterAvailableRequest(filetype) if exists_completer is None: return False self._available_completers[filetype] = exists_completer return exists_completer def NativeFiletypeCompletionAvailable(self): return any([ self.FiletypeCompleterExistsForFiletype(x) for x in vimsupport.CurrentFiletypes() ]) def NativeFiletypeCompletionUsable(self): return (self.CurrentFiletypeCompletionEnabled() and self.NativeFiletypeCompletionAvailable()) def OnFileReadyToParse(self): if not self.IsServerAlive(): self._NotifyUserIfServerCrashed() return self._omnicomp.OnFileReadyToParse(None) extra_data = {} self._AddTagsFilesIfNeeded(extra_data) self._AddSyntaxDataIfNeeded(extra_data) self._AddExtraConfDataIfNeeded(extra_data) self._latest_file_parse_request = EventNotification( 'FileReadyToParse', extra_data) self._latest_file_parse_request.Start() def OnBufferUnload(self, deleted_buffer_file): if not self.IsServerAlive(): return SendEventNotificationAsync('BufferUnload', {'unloaded_buffer': deleted_buffer_file}) def OnBufferVisit(self): if not self.IsServerAlive(): return extra_data = {} self._AddUltiSnipsDataIfNeeded(extra_data) SendEventNotificationAsync('BufferVisit', extra_data) def OnInsertLeave(self): if not self.IsServerAlive(): return SendEventNotificationAsync('InsertLeave') def OnCursorMoved(self): self._diag_interface.OnCursorMoved() def OnVimLeave(self): self._ShutdownServer() def OnCurrentIdentifierFinished(self): if not self.IsServerAlive(): return SendEventNotificationAsync('CurrentIdentifierFinished') def OnCompleteDone(self): complete_done_actions = self.GetCompleteDoneHooks() for action in complete_done_actions: action(self) def GetCompleteDoneHooks(self): filetypes = vimsupport.CurrentFiletypes() for key, value in iteritems(self._complete_done_hooks): if key in filetypes: yield value def GetCompletionsUserMayHaveCompleted(self): latest_completion_request = self.GetCurrentCompletionRequest() if not latest_completion_request or not latest_completion_request.Done( ): return [] completions = latest_completion_request.RawResponse() result = self._FilterToMatchingCompletions(completions, True) result = list(result) if result: return result if self._HasCompletionsThatCouldBeCompletedWithMoreText(completions): # Since the way that YCM works leads to CompleteDone called on every # character, return blank if the completion might not be done. This won't # match if the completion is ended with typing a non-keyword character. return [] result = self._FilterToMatchingCompletions(completions, False) return list(result) def _FilterToMatchingCompletions(self, completions, full_match_only): self._PatchBasedOnVimVersion() return self._FilterToMatchingCompletions(completions, full_match_only) def _HasCompletionsThatCouldBeCompletedWithMoreText(self, completions): self._PatchBasedOnVimVersion() return self._HasCompletionsThatCouldBeCompletedWithMoreText( completions) def _PatchBasedOnVimVersion(self): if vimsupport.VimVersionAtLeast("7.4.774"): self._HasCompletionsThatCouldBeCompletedWithMoreText = \ self._HasCompletionsThatCouldBeCompletedWithMoreText_NewerVim self._FilterToMatchingCompletions = \ self._FilterToMatchingCompletions_NewerVim else: self._FilterToMatchingCompletions = \ self._FilterToMatchingCompletions_OlderVim self._HasCompletionsThatCouldBeCompletedWithMoreText = \ self._HasCompletionsThatCouldBeCompletedWithMoreText_OlderVim def _FilterToMatchingCompletions_NewerVim(self, completions, full_match_only): """Filter to completions matching the item Vim said was completed""" completed = vimsupport.GetVariableValue('v:completed_item') for completion in completions: item = ConvertCompletionDataToVimData(completion) match_keys = (["word", "abbr", "menu", "info"] if full_match_only else ['word']) def matcher(key): return (utils.ToUnicode(completed.get(key, "")) == utils.ToUnicode( item.get(key, ""))) if all([matcher(i) for i in match_keys]): yield completion def _FilterToMatchingCompletions_OlderVim(self, completions, full_match_only): """ Filter to completions matching the buffer text """ if full_match_only: return # Only supported in 7.4.774+ # No support for multiple line completions text = vimsupport.TextBeforeCursor() for completion in completions: word = completion["insertion_text"] # Trim complete-ending character if needed text = re.sub(r"[^a-zA-Z0-9_]$", "", text) buffer_text = text[-1 * len(word):] if buffer_text == word: yield completion def _HasCompletionsThatCouldBeCompletedWithMoreText_NewerVim( self, completions): completed_item = vimsupport.GetVariableValue('v:completed_item') if not completed_item: return False completed_word = utils.ToUnicode(completed_item['word']) if not completed_word: return False # Sometimes CompleteDone is called after the next character is inserted. # If so, use inserted character to filter possible completions further. text = vimsupport.TextBeforeCursor() reject_exact_match = True if text and text[-1] != completed_word[-1]: reject_exact_match = False completed_word += text[-1] for completion in completions: word = utils.ToUnicode( ConvertCompletionDataToVimData(completion)['word']) if reject_exact_match and word == completed_word: continue if word.startswith(completed_word): return True return False def _HasCompletionsThatCouldBeCompletedWithMoreText_OlderVim( self, completions): # No support for multiple line completions text = vimsupport.TextBeforeCursor() for completion in completions: word = utils.ToUnicode( ConvertCompletionDataToVimData(completion)['word']) for i in range(1, len(word) - 1): # Excluding full word if text[-1 * i:] == word[:i]: return True return False def _OnCompleteDone_Csharp(self): completions = self.GetCompletionsUserMayHaveCompleted() namespaces = [self._GetRequiredNamespaceImport(c) for c in completions] namespaces = [n for n in namespaces if n] if not namespaces: return if len(namespaces) > 1: choices = [ "{0} {1}".format(i + 1, n) for i, n in enumerate(namespaces) ] choice = vimsupport.PresentDialog("Insert which namespace:", choices) if choice < 0: return namespace = namespaces[choice] else: namespace = namespaces[0] vimsupport.InsertNamespace(namespace) def _GetRequiredNamespaceImport(self, completion): if ("extra_data" not in completion or "required_namespace_import" not in completion["extra_data"]): return None return completion["extra_data"]["required_namespace_import"] def GetErrorCount(self): return self._diag_interface.GetErrorCount() def GetWarningCount(self): return self._diag_interface.GetWarningCount() def DiagnosticUiSupportedForCurrentFiletype(self): return any([ x in DIAGNOSTIC_UI_FILETYPES for x in vimsupport.CurrentFiletypes() ]) def ShouldDisplayDiagnostics(self): return bool(self._user_options['show_diagnostics_ui'] and self.DiagnosticUiSupportedForCurrentFiletype()) def PopulateLocationListWithLatestDiagnostics(self): # Do nothing if loc list is already populated by diag_interface if not self._user_options['always_populate_location_list']: self._diag_interface.PopulateLocationList(self._latest_diagnostics) return bool(self._latest_diagnostics) def UpdateDiagnosticInterface(self): self._diag_interface.UpdateWithNewDiagnostics(self._latest_diagnostics) def FileParseRequestReady(self, block=False): return bool(self._latest_file_parse_request and (block or self._latest_file_parse_request.Done())) def HandleFileParseRequest(self, block=False): # Order is important here: # FileParseRequestReady has a low cost, while # NativeFiletypeCompletionUsable is a blocking server request if (self.FileParseRequestReady(block) and self.NativeFiletypeCompletionUsable()): if self.ShouldDisplayDiagnostics(): self._latest_diagnostics = self._latest_file_parse_request.Response( ) self.UpdateDiagnosticInterface() else: # YCM client has a hard-coded list of filetypes which are known # to support diagnostics, self.DiagnosticUiSupportedForCurrentFiletype() # # For filetypes which don't support diagnostics, we just want to check # the _latest_file_parse_request for any exception or UnknownExtraConf # response, to allow the server to raise configuration warnings, etc. # to the user. We ignore any other supplied data. self._latest_file_parse_request.Response() # We set the file parse request to None because we want to prevent # repeated issuing of the same warnings/errors/prompts. Setting this to # None makes FileParseRequestReady return False until the next # request is created. # # Note: it is the server's responsibility to determine the frequency of # error/warning/prompts when receiving a FileReadyToParse event, but # it our responsibility to ensure that we only apply the # warning/error/prompt received once (for each event). self._latest_file_parse_request = None def ShowDetailedDiagnostic(self): if not self.IsServerAlive(): return try: debug_info = BaseRequest.PostDataToHandler(BuildRequestData(), 'detailed_diagnostic') if 'message' in debug_info: vimsupport.PostVimMessage(debug_info['message'], warning=False) except ServerError as e: vimsupport.PostVimMessage(str(e)) def DebugInfo(self): if self.IsServerAlive(): debug_info = BaseRequest.PostDataToHandler(BuildRequestData(), 'debug_info') else: debug_info = 'Server crashed, no debug info from server' debug_info += '\nServer running at: {0}'.format( BaseRequest.server_location) debug_info += '\nServer process ID: {0}'.format(self._server_popen.pid) if self._server_stderr or self._server_stdout: debug_info += '\nServer logfiles:\n {0}\n {1}'.format( self._server_stdout, self._server_stderr) return debug_info def _OpenLogs(self, stdout=True, stderr=True): # Open log files in a horizontal window with the same behavior as the # preview window (same height and winfixheight enabled). Automatically # watch for changes. Set the cursor position at the end of the file. options = { 'size': vimsupport.GetIntValue('&previewheight'), 'fix': True, 'watch': True, 'position': 'end' } if stdout: vimsupport.OpenFilename(self._server_stdout, options) if stderr: vimsupport.OpenFilename(self._server_stderr, options) def _CloseLogs(self, stdout=True, stderr=True): if stdout: vimsupport.CloseBuffersForFilename(self._server_stdout) if stderr: vimsupport.CloseBuffersForFilename(self._server_stderr) def ToggleLogs(self, stdout=True, stderr=True): if (stdout and vimsupport.BufferIsVisibleForFilename(self._server_stdout) or stderr and vimsupport.BufferIsVisibleForFilename( self._server_stderr)): return self._CloseLogs(stdout=stdout, stderr=stderr) # Close hidden logfile buffers if any to keep a clean state self._CloseLogs(stdout=stdout, stderr=stderr) try: self._OpenLogs(stdout=stdout, stderr=stderr) except RuntimeError as error: vimsupport.PostVimMessage( 'YouCompleteMe encountered an error when ' 'opening logs: {0}.'.format(error)) def CurrentFiletypeCompletionEnabled(self): filetypes = vimsupport.CurrentFiletypes() filetype_to_disable = self._user_options[ 'filetype_specific_completion_to_disable'] if '*' in filetype_to_disable: return False else: return not any([x in filetype_to_disable for x in filetypes]) def _AddSyntaxDataIfNeeded(self, extra_data): if not self._user_options['seed_identifiers_with_syntax']: return filetype = vimsupport.CurrentFiletypes()[0] if filetype in self._filetypes_with_keywords_loaded: return self._filetypes_with_keywords_loaded.add(filetype) extra_data['syntax_keywords'] = list( syntax_parse.SyntaxKeywordsForCurrentBuffer()) def _AddTagsFilesIfNeeded(self, extra_data): def GetTagFiles(): tag_files = vim.eval('tagfiles()') # getcwd() throws an exception when the CWD has been deleted. try: current_working_directory = os.getcwd() except OSError: return [] return [ os.path.join(current_working_directory, x) for x in tag_files ] if not self._user_options['collect_identifiers_from_tags_files']: return extra_data['tag_files'] = GetTagFiles() def _AddExtraConfDataIfNeeded(self, extra_data): def BuildExtraConfData(extra_conf_vim_data): return dict((expr, vimsupport.VimExpressionToPythonType(expr)) for expr in extra_conf_vim_data) extra_conf_vim_data = self._user_options['extra_conf_vim_data'] if extra_conf_vim_data: extra_data['extra_conf_data'] = BuildExtraConfData( extra_conf_vim_data) def _AddUltiSnipsDataIfNeeded(self, extra_data): # See :h UltiSnips#SnippetsInCurrentScope. # Errors when evaluating Vim expressions are not caught by Python # try/except blocks prior to Vim 7.4.107. We check that the UltiSnips # function exists for these versions. # TODO: remove this when bumping version requirement to 7.4.107 or greater. if (not vimsupport.VimVersionAtLeast("7.4.107") and not vimsupport.GetBoolValue( "exists( '*UltiSnips#SnippetsInCurrentScope' )")): return try: vim.eval('UltiSnips#SnippetsInCurrentScope( 1 )') except vim.error: return snippets = vimsupport.GetVariableValue('g:current_ulti_dict_info') extra_data['ultisnips_snippets'] = [{ 'trigger': trigger, 'description': snippet['description'] } for trigger, snippet in iteritems(snippets)]
class YouCompleteMe(object): def __init__(self, user_options): self._user_options = user_options self._user_notified_about_crash = False self._diag_interface = DiagnosticInterface(user_options) self._omnicomp = OmniCompleter(user_options) self._latest_completion_request = None self._latest_file_parse_request = None self._server_stdout = None self._server_stderr = None self._server_popen = None self._filetypes_with_keywords_loaded = set() self._ycmd_keepalive = YcmdKeepalive() self._SetupServer() self._ycmd_keepalive.Start() def _SetupServer(self): server_port = utils.GetUnusedLocalhostPort() # The temp options file is deleted by ycmd during startup with tempfile.NamedTemporaryFile(delete=False) as options_file: hmac_secret = os.urandom(HMAC_SECRET_LENGTH) options_dict = dict(self._user_options) options_dict['hmac_secret'] = base64.b64encode(hmac_secret) json.dump(options_dict, options_file) options_file.flush() args = [ utils.PathToPythonInterpreter(), _PathToServerScript(), '--port={0}'.format(server_port), '--options_file={0}'.format(options_file.name), '--log={0}'.format(self._user_options['server_log_level']), '--idle_suicide_seconds={0}'.format( SERVER_IDLE_SUICIDE_SECONDS) ] if not self._user_options['server_use_vim_stdout']: filename_format = os.path.join(utils.PathToTempDir(), 'server_{port}_{std}.log') self._server_stdout = filename_format.format(port=server_port, std='stdout') self._server_stderr = filename_format.format(port=server_port, std='stderr') args.append('--stdout={0}'.format(self._server_stdout)) args.append('--stderr={0}'.format(self._server_stderr)) if self._user_options['server_keep_logfiles']: args.append('--keep_logfiles') self._server_popen = utils.SafePopen(args, stdout=PIPE, stderr=PIPE) BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port) BaseRequest.hmac_secret = hmac_secret self._NotifyUserIfServerCrashed() def _IsServerAlive(self): returncode = self._server_popen.poll() # When the process hasn't finished yet, poll() returns None. return returncode is None def _NotifyUserIfServerCrashed(self): if self._user_notified_about_crash or self._IsServerAlive(): return self._user_notified_about_crash = True if self._server_stderr: try: with open(self._server_stderr, 'r') as server_stderr_file: error_output = ''.join(server_stderr_file.readlines() [:-NUM_YCMD_STDERR_LINES_ON_CRASH]) vimsupport.PostMultiLineNotice( SERVER_CRASH_MESSAGE_STDERR_FILE + error_output) except IOError: vimsupport.PostVimMessage( SERVER_CRASH_MESSAGE_STDERR_FILE_DELETED) else: vimsupport.PostVimMessage(SERVER_CRASH_MESSAGE_SAME_STDERR) def ServerPid(self): if not self._server_popen: return -1 return self._server_popen.pid def _ServerCleanup(self): if self._IsServerAlive(): self._server_popen.terminate() def RestartServer(self): vimsupport.PostVimMessage('Restarting ycmd server...') self._user_notified_about_crash = False self._ServerCleanup() self._SetupServer() def CreateCompletionRequest(self, force_semantic=False): # We have to store a reference to the newly created CompletionRequest # because VimScript can't store a reference to a Python object across # function calls... Thus we need to keep this request somewhere. if (not self.NativeFiletypeCompletionAvailable() and self.CurrentFiletypeCompletionEnabled() and self._omnicomp.ShouldUseNow()): self._latest_completion_request = OmniCompletionRequest( self._omnicomp) else: extra_data = {} self._AddExtraConfDataIfNeeded(extra_data) if force_semantic: extra_data['force_semantic'] = True self._latest_completion_request = (CompletionRequest(extra_data) if self._IsServerAlive() else None) return self._latest_completion_request def SendCommandRequest(self, arguments, completer): if self._IsServerAlive(): return SendCommandRequest(arguments, completer) def GetDefinedSubcommands(self): if self._IsServerAlive(): try: return BaseRequest.PostDataToHandler(BuildRequestData(), 'defined_subcommands') except ServerError: return [] else: return [] def GetCurrentCompletionRequest(self): return self._latest_completion_request def GetOmniCompleter(self): return self._omnicomp def NativeFiletypeCompletionAvailable(self): return any([ FiletypeCompleterExistsForFiletype(x) for x in vimsupport.CurrentFiletypes() ]) def NativeFiletypeCompletionUsable(self): return (self.CurrentFiletypeCompletionEnabled() and self.NativeFiletypeCompletionAvailable()) def OnFileReadyToParse(self): self._omnicomp.OnFileReadyToParse(None) if not self._IsServerAlive(): self._NotifyUserIfServerCrashed() extra_data = {} self._AddTagsFilesIfNeeded(extra_data) self._AddSyntaxDataIfNeeded(extra_data) self._AddExtraConfDataIfNeeded(extra_data) self._latest_file_parse_request = EventNotification( 'FileReadyToParse', extra_data) self._latest_file_parse_request.Start() def OnBufferUnload(self, deleted_buffer_file): if not self._IsServerAlive(): return SendEventNotificationAsync('BufferUnload', {'unloaded_buffer': deleted_buffer_file}) def OnBufferVisit(self): if not self._IsServerAlive(): return extra_data = {} _AddUltiSnipsDataIfNeeded(extra_data) SendEventNotificationAsync('BufferVisit', extra_data) def OnInsertLeave(self): if not self._IsServerAlive(): return SendEventNotificationAsync('InsertLeave') def OnCursorMoved(self): self._diag_interface.OnCursorMoved() def OnVimLeave(self): self._ServerCleanup() def OnCurrentIdentifierFinished(self): if not self._IsServerAlive(): return SendEventNotificationAsync('CurrentIdentifierFinished') def DiagnosticsForCurrentFileReady(self): return bool(self._latest_file_parse_request and self._latest_file_parse_request.Done()) def GetDiagnosticsFromStoredRequest(self, qflist_format=False): if self.DiagnosticsForCurrentFileReady(): diagnostics = self._latest_file_parse_request.Response() # We set the diagnostics request to None because we want to prevent # Syntastic from repeatedly refreshing the buffer with the same diags. # Setting this to None makes DiagnosticsForCurrentFileReady return False # until the next request is created. self._latest_file_parse_request = None if qflist_format: return vimsupport.ConvertDiagnosticsToQfList(diagnostics) else: return diagnostics return [] def UpdateDiagnosticInterface(self): if not self.DiagnosticsForCurrentFileReady(): return self._diag_interface.UpdateWithNewDiagnostics( self.GetDiagnosticsFromStoredRequest()) def ShowDetailedDiagnostic(self): if not self._IsServerAlive(): return try: debug_info = BaseRequest.PostDataToHandler(BuildRequestData(), 'detailed_diagnostic') if 'message' in debug_info: vimsupport.EchoText(debug_info['message']) except ServerError as e: vimsupport.PostVimMessage(str(e)) def DebugInfo(self): if self._IsServerAlive(): debug_info = BaseRequest.PostDataToHandler(BuildRequestData(), 'debug_info') else: debug_info = 'Server crashed, no debug info from server' debug_info += '\nServer running at: {0}'.format( BaseRequest.server_location) debug_info += '\nServer process ID: {0}'.format(self._server_popen.pid) if self._server_stderr or self._server_stdout: debug_info += '\nServer logfiles:\n {0}\n {1}'.format( self._server_stdout, self._server_stderr) return debug_info def CurrentFiletypeCompletionEnabled(self): filetypes = vimsupport.CurrentFiletypes() filetype_to_disable = self._user_options[ 'filetype_specific_completion_to_disable'] return not all([x in filetype_to_disable for x in filetypes]) def _AddSyntaxDataIfNeeded(self, extra_data): if not self._user_options['seed_identifiers_with_syntax']: return filetype = vimsupport.CurrentFiletypes()[0] if filetype in self._filetypes_with_keywords_loaded: return self._filetypes_with_keywords_loaded.add(filetype) extra_data['syntax_keywords'] = list( syntax_parse.SyntaxKeywordsForCurrentBuffer()) def _AddTagsFilesIfNeeded(self, extra_data): def GetTagFiles(): tag_files = vim.eval('tagfiles()') current_working_directory = os.getcwd() return [ os.path.join(current_working_directory, x) for x in tag_files ] if not self._user_options['collect_identifiers_from_tags_files']: return extra_data['tag_files'] = GetTagFiles() def _AddExtraConfDataIfNeeded(self, extra_data): def BuildExtraConfData(extra_conf_vim_data): return dict((expr, vimsupport.VimExpressionToPythonType(expr)) for expr in extra_conf_vim_data) extra_conf_vim_data = self._user_options['extra_conf_vim_data'] if extra_conf_vim_data: extra_data['extra_conf_data'] = BuildExtraConfData( extra_conf_vim_data)
class YouCompleteMe: def __init__( self, default_options = {} ): self._logger = logging.getLogger( 'ycm' ) self._client_logfile = None self._server_stdout = None self._server_stderr = None self._server_popen = None self._default_options = default_options self._ycmd_keepalive = YcmdKeepalive() self._SetUpLogging() self._SetUpServer() self._ycmd_keepalive.Start() def _SetUpServer( self ): self._available_completers = {} self._user_notified_about_crash = False self._filetypes_with_keywords_loaded = set() self._server_is_ready_with_cache = False self._message_poll_requests = {} self._latest_completion_request = None self._latest_signature_help_request = None self._signature_help_available_requests = SigHelpAvailableByFileType() self._latest_command_reqeust = None self._signature_help_state = signature_help.SignatureHelpState() self._user_options = base.GetUserOptions( self._default_options ) self._omnicomp = OmniCompleter( self._user_options ) self._buffers = BufferDict( self._user_options ) self._SetLogLevel() hmac_secret = os.urandom( HMAC_SECRET_LENGTH ) options_dict = dict( self._user_options ) options_dict[ 'hmac_secret' ] = utils.ToUnicode( base64.b64encode( hmac_secret ) ) options_dict[ 'server_keep_logfiles' ] = self._user_options[ 'keep_logfiles' ] # The temp options file is deleted by ycmd during startup. with NamedTemporaryFile( delete = False, mode = 'w+' ) as options_file: json.dump( options_dict, options_file ) server_port = utils.GetUnusedLocalhostPort() BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port ) BaseRequest.hmac_secret = hmac_secret try: python_interpreter = paths.PathToPythonInterpreter() except RuntimeError as error: error_message = ( f"Unable to start the ycmd server. { str( error ).rstrip( '.' ) }. " "Correct the error then restart the server " "with ':YcmRestartServer'." ) self._logger.exception( error_message ) vimsupport.PostVimMessage( error_message ) return args = [ python_interpreter, paths.PathToServerScript(), f'--port={ server_port }', f'--options_file={ options_file.name }', f'--log={ self._user_options[ "log_level" ] }', f'--idle_suicide_seconds={ SERVER_IDLE_SUICIDE_SECONDS }' ] self._server_stdout = utils.CreateLogfile( SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stdout' ) ) self._server_stderr = utils.CreateLogfile( SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stderr' ) ) args.append( f'--stdout={ self._server_stdout }' ) args.append( f'--stderr={ self._server_stderr }' ) if self._user_options[ 'keep_logfiles' ]: args.append( '--keep_logfiles' ) self._server_popen = utils.SafePopen( args, stdin_windows = PIPE, stdout = PIPE, stderr = PIPE ) def _SetUpLogging( self ): def FreeFileFromOtherProcesses( file_object ): if utils.OnWindows(): from ctypes import windll import msvcrt file_handle = msvcrt.get_osfhandle( file_object.fileno() ) windll.kernel32.SetHandleInformation( file_handle, HANDLE_FLAG_INHERIT, 0 ) self._client_logfile = utils.CreateLogfile( CLIENT_LOGFILE_FORMAT ) handler = logging.FileHandler( self._client_logfile ) # On Windows and Python prior to 3.4, file handles are inherited by child # processes started with at least one replaced standard stream, which is the # case when we start the ycmd server (we are redirecting all standard # outputs into a pipe). These files cannot be removed while the child # processes are still up. This is not desirable for a logfile because we # want to remove it at Vim exit without having to wait for the ycmd server # to be completely shut down. We need to make the logfile handle # non-inheritable. See https://www.python.org/dev/peps/pep-0446 for more # details. FreeFileFromOtherProcesses( handler.stream ) formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' ) handler.setFormatter( formatter ) self._logger.addHandler( handler ) def _SetLogLevel( self ): log_level = self._user_options[ 'log_level' ] numeric_level = getattr( logging, log_level.upper(), None ) if not isinstance( numeric_level, int ): raise ValueError( f'Invalid log level: { log_level }' ) self._logger.setLevel( numeric_level ) def IsServerAlive( self ): # When the process hasn't finished yet, poll() returns None. return bool( self._server_popen ) and self._server_popen.poll() is None def CheckIfServerIsReady( self ): if not self._server_is_ready_with_cache and self.IsServerAlive(): self._server_is_ready_with_cache = BaseRequest().GetDataFromHandler( 'ready', display_message = False ) return self._server_is_ready_with_cache def IsServerReady( self ): return self._server_is_ready_with_cache def NotifyUserIfServerCrashed( self ): if ( not self._server_popen or self._user_notified_about_crash or self.IsServerAlive() ): return self._user_notified_about_crash = True return_code = self._server_popen.poll() logfile = os.path.basename( self._server_stderr ) # See https://github.com/Valloric/ycmd#exit-codes for the list of exit # codes. if return_code == 3: error_message = CORE_UNEXPECTED_MESSAGE.format( logfile = logfile ) elif return_code == 4: error_message = CORE_MISSING_MESSAGE elif return_code == 7: error_message = CORE_OUTDATED_MESSAGE elif return_code == 8: error_message = NO_PYTHON2_SUPPORT_MESSAGE else: error_message = EXIT_CODE_UNEXPECTED_MESSAGE.format( code = return_code, logfile = logfile ) if return_code != 8: error_message = SERVER_SHUTDOWN_MESSAGE + ' ' + error_message self._logger.error( error_message ) vimsupport.PostVimMessage( error_message ) def ServerPid( self ): if not self._server_popen: return -1 return self._server_popen.pid def _ShutdownServer( self ): SendShutdownRequest() def RestartServer( self ): vimsupport.PostVimMessage( 'Restarting ycmd server...' ) self._ShutdownServer() self._SetUpServer() def SendCompletionRequest( self, force_semantic = False ): request_data = BuildRequestData() request_data[ 'force_semantic' ] = force_semantic if not self.NativeFiletypeCompletionUsable(): wrapped_request_data = RequestWrap( request_data ) if self._omnicomp.ShouldUseNow( wrapped_request_data ): self._latest_completion_request = OmniCompletionRequest( self._omnicomp, wrapped_request_data ) self._latest_completion_request.Start() return self._AddExtraConfDataIfNeeded( request_data ) self._latest_completion_request = CompletionRequest( request_data ) self._latest_completion_request.Start() def CompletionRequestReady( self ): return bool( self._latest_completion_request and self._latest_completion_request.Done() ) def GetCompletionResponse( self ): return self._latest_completion_request.Response() def SignatureHelpAvailableRequestComplete( self, filetype, send_new=True ): """Triggers or polls signature help available request. Returns whether or not the request is complete. When send_new is False, won't send a new request, only return the current status (This is used by the tests)""" if not send_new and filetype not in self._signature_help_available_requests: return False return self._signature_help_available_requests[ filetype ].Done() def SendSignatureHelpRequest( self ): """Send a signature help request, if we're ready to. Return whether or not a request was sent (and should be checked later)""" if not self.NativeFiletypeCompletionUsable(): return False for filetype in vimsupport.CurrentFiletypes(): if not self.SignatureHelpAvailableRequestComplete( filetype ): continue sig_help_available = self._signature_help_available_requests[ filetype ].Response() if sig_help_available == 'NO': continue if sig_help_available == 'PENDING': # Send another /signature_help_available request self._signature_help_available_requests[ filetype ].Start( filetype ) continue if not self._latest_completion_request: return False request_data = self._latest_completion_request.request_data.copy() request_data[ 'signature_help_state' ] = ( self._signature_help_state.IsActive() ) self._AddExtraConfDataIfNeeded( request_data ) self._latest_signature_help_request = SignatureHelpRequest( request_data ) self._latest_signature_help_request.Start() return True return False def SignatureHelpRequestReady( self ): return bool( self._latest_signature_help_request and self._latest_signature_help_request.Done() ) def GetSignatureHelpResponse( self ): return self._latest_signature_help_request.Response() def ClearSignatureHelp( self ): self.UpdateSignatureHelp( {} ) if self._latest_signature_help_request: self._latest_signature_help_request.Reset() def UpdateSignatureHelp( self, signature_info ): self._signature_help_state = signature_help.UpdateSignatureHelp( self._signature_help_state, signature_info ) def _GetCommandRequestArguments( self, arguments, has_range, start_line, end_line ): final_arguments = [] for argument in arguments: # The ft= option which specifies the completer when running a command is # ignored because it has not been working for a long time. The option is # still parsed to not break users that rely on it. if argument.startswith( 'ft=' ): continue final_arguments.append( argument ) extra_data = { 'options': { 'tab_size': vimsupport.GetIntValue( 'shiftwidth()' ), 'insert_spaces': vimsupport.GetBoolValue( '&expandtab' ) } } if has_range: extra_data.update( vimsupport.BuildRange( start_line, end_line ) ) self._AddExtraConfDataIfNeeded( extra_data ) return final_arguments, extra_data def SendCommandRequest( self, arguments, modifiers, has_range, start_line, end_line ): final_arguments, extra_data = self._GetCommandRequestArguments( arguments, has_range, start_line, end_line ) return SendCommandRequest( final_arguments, modifiers, self._user_options[ 'goto_buffer_command' ], extra_data ) def GetCommandResponse( self, arguments ): final_arguments, extra_data = self._GetCommandRequestArguments( arguments, False, 0, 0 ) return GetCommandResponse( final_arguments, extra_data ) def SendCommandRequestAsync( self, arguments ): final_arguments, extra_data = self._GetCommandRequestArguments( arguments, False, 0, 0 ) self._latest_command_reqeust = SendCommandRequestAsync( final_arguments, extra_data ) def GetCommandRequest( self ): return self._latest_command_reqeust def GetDefinedSubcommands( self ): request = BaseRequest() subcommands = request.PostDataToHandler( BuildRequestData(), 'defined_subcommands' ) return subcommands if subcommands else [] def GetCurrentCompletionRequest( self ): return self._latest_completion_request def GetOmniCompleter( self ): return self._omnicomp def FiletypeCompleterExistsForFiletype( self, filetype ): try: return self._available_completers[ filetype ] except KeyError: pass exists_completer = SendCompleterAvailableRequest( filetype ) if exists_completer is None: return False self._available_completers[ filetype ] = exists_completer return exists_completer def NativeFiletypeCompletionAvailable( self ): return any( self.FiletypeCompleterExistsForFiletype( x ) for x in vimsupport.CurrentFiletypes() ) def NativeFiletypeCompletionUsable( self ): disabled_filetypes = self._user_options[ 'filetype_specific_completion_to_disable' ] return ( vimsupport.CurrentFiletypesEnabled( disabled_filetypes ) and self.NativeFiletypeCompletionAvailable() ) def NeedsReparse( self ): return self.CurrentBuffer().NeedsReparse() def UpdateWithNewDiagnosticsForFile( self, filepath, diagnostics ): if not self._user_options[ 'show_diagnostics_ui' ]: return bufnr = vimsupport.GetBufferNumberForFilename( filepath ) if bufnr in self._buffers and vimsupport.BufferIsVisible( bufnr ): # Note: We only update location lists, etc. for visible buffers, because # otherwise we default to using the current location list and the results # are that non-visible buffer errors clobber visible ones. self._buffers[ bufnr ].UpdateWithNewDiagnostics( diagnostics, True ) else: # The project contains errors in file "filepath", but that file is not # open in any buffer. This happens for Language Server Protocol-based # completers, as they return diagnostics for the entire "project" # asynchronously (rather than per-file in the response to the parse # request). # # There are a number of possible approaches for # this, but for now we simply ignore them. Other options include: # - Use the QuickFix list to report project errors? # - Use a special buffer for project errors # - Put them in the location list of whatever the "current" buffer is # - Store them in case the buffer is opened later # - add a :YcmProjectDiags command # - Add them to errror/warning _counts_ but not any actual location list # or other # - etc. # # However, none of those options are great, and lead to their own # complexities. So for now, we just ignore these diagnostics for files not # open in any buffer. pass def OnPeriodicTick( self ): if not self.IsServerAlive(): # Server has died. We'll reset when the server is started again. return False elif not self.IsServerReady(): # Try again in a jiffy return True for w in vim.windows: for filetype in vimsupport.FiletypesForBuffer( w.buffer ): if filetype not in self._message_poll_requests: self._message_poll_requests[ filetype ] = MessagesPoll( w.buffer ) # None means don't poll this filetype if ( self._message_poll_requests[ filetype ] and not self._message_poll_requests[ filetype ].Poll( self ) ): self._message_poll_requests[ filetype ] = None return any( self._message_poll_requests.values() ) def OnFileReadyToParse( self ): if not self.IsServerAlive(): self.NotifyUserIfServerCrashed() return if not self.IsServerReady(): return extra_data = {} self._AddTagsFilesIfNeeded( extra_data ) self._AddSyntaxDataIfNeeded( extra_data ) self._AddExtraConfDataIfNeeded( extra_data ) self.CurrentBuffer().SendParseRequest( extra_data ) def OnFileSave( self, saved_buffer_number ): SendEventNotificationAsync( 'FileSave', saved_buffer_number ) def OnBufferUnload( self, deleted_buffer_number ): SendEventNotificationAsync( 'BufferUnload', deleted_buffer_number ) def UpdateMatches( self ): self.CurrentBuffer().UpdateMatches() def OnFileTypeSet( self ): buffer_number = vimsupport.GetCurrentBufferNumber() filetypes = vimsupport.CurrentFiletypes() self._buffers[ buffer_number ].UpdateFromFileTypes( filetypes ) self.OnBufferVisit() def OnBufferVisit( self ): for filetype in vimsupport.CurrentFiletypes(): # Send the signature help available request for these filetypes if we need # to (as a side effect of checking if it is complete) self.SignatureHelpAvailableRequestComplete( filetype, True ) extra_data = {} self._AddUltiSnipsDataIfNeeded( extra_data ) SendEventNotificationAsync( 'BufferVisit', extra_data = extra_data ) def CurrentBuffer( self ): return self._buffers[ vimsupport.GetCurrentBufferNumber() ] def OnInsertLeave( self ): if ( not self._user_options[ 'update_diagnostics_in_insert_mode' ] and not self.NeedsReparse() ): self.CurrentBuffer().RefreshDiagnosticsUI() SendEventNotificationAsync( 'InsertLeave' ) def OnCursorMoved( self ): self.CurrentBuffer().OnCursorMoved() def _CleanLogfile( self ): logging.shutdown() if not self._user_options[ 'keep_logfiles' ]: if self._client_logfile: utils.RemoveIfExists( self._client_logfile ) def OnVimLeave( self ): self._ShutdownServer() self._CleanLogfile() def OnCurrentIdentifierFinished( self ): SendEventNotificationAsync( 'CurrentIdentifierFinished' ) def OnCompleteDone( self ): completion_request = self.GetCurrentCompletionRequest() if completion_request: completion_request.OnCompleteDone() def ResolveCompletionItem( self, item ): # Note: As mentioned elsewhere, we replace the current completion request # with a resolve request. It's not valid to have simultaneous resolve and # completion requests, because the resolve request uses the request data # from the last completion request and is therefore dependent on it not # having changed. # # The result of this is that self.GetCurrentCompletionRequest() might return # either a completion request of a resolve request and it's the # responsibility of the vimscript code to ensure that it only does one at a # time. This is handled by re-using the same poller for completions and # resolves. completion_request = self.GetCurrentCompletionRequest() if not completion_request: return False request = ResolveCompletionItem( completion_request, item ) if not request: return False self._latest_completion_request = request return True def GetErrorCount( self ): return self.CurrentBuffer().GetErrorCount() def GetWarningCount( self ): return self.CurrentBuffer().GetWarningCount() def _PopulateLocationListWithLatestDiagnostics( self ): return self.CurrentBuffer().PopulateLocationList( self._user_options[ 'open_loclist_on_ycm_diags' ] ) def FileParseRequestReady( self ): # Return True if server is not ready yet, to stop repeating check timer. return ( not self.IsServerReady() or self.CurrentBuffer().FileParseRequestReady() ) def HandleFileParseRequest( self, block = False ): if not self.IsServerReady(): return current_buffer = self.CurrentBuffer() # Order is important here: # FileParseRequestReady has a low cost, while # NativeFiletypeCompletionUsable is a blocking server request if ( not current_buffer.IsResponseHandled() and current_buffer.FileParseRequestReady( block ) and self.NativeFiletypeCompletionUsable() ): if self._user_options[ 'show_diagnostics_ui' ]: # Forcefuly update the location list, etc. from the parse request when # doing something like :YcmDiags async_diags = any( self._message_poll_requests.get( filetype ) for filetype in vimsupport.CurrentFiletypes() ) current_buffer.UpdateDiagnostics( block or not async_diags ) else: # If the user disabled diagnostics, we just want to check # the _latest_file_parse_request for any exception or UnknownExtraConf # response, to allow the server to raise configuration warnings, etc. # to the user. We ignore any other supplied data. current_buffer.GetResponse() # We set the file parse request as handled because we want to prevent # repeated issuing of the same warnings/errors/prompts. Setting this # makes IsRequestHandled return True until the next request is created. # # Note: it is the server's responsibility to determine the frequency of # error/warning/prompts when receiving a FileReadyToParse event, but # it is our responsibility to ensure that we only apply the # warning/error/prompt received once (for each event). current_buffer.MarkResponseHandled() def ShouldResendFileParseRequest( self ): return self.CurrentBuffer().ShouldResendParseRequest() def DebugInfo( self ): debug_info = '' if self._client_logfile: debug_info += f'Client logfile: { self._client_logfile }\n' extra_data = {} self._AddExtraConfDataIfNeeded( extra_data ) debug_info += FormatDebugInfoResponse( SendDebugInfoRequest( extra_data ) ) debug_info += f'Server running at: { BaseRequest.server_location }\n' if self._server_popen: debug_info += f'Server process ID: { self._server_popen.pid }\n' if self._server_stdout and self._server_stderr: debug_info += ( 'Server logfiles:\n' f' { self._server_stdout }\n' f' { self._server_stderr }' ) return debug_info def GetLogfiles( self ): logfiles_list = [ self._client_logfile, self._server_stdout, self._server_stderr ] extra_data = {} self._AddExtraConfDataIfNeeded( extra_data ) debug_info = SendDebugInfoRequest( extra_data ) if debug_info: completer = debug_info[ 'completer' ] if completer: for server in completer[ 'servers' ]: logfiles_list.extend( server[ 'logfiles' ] ) logfiles = {} for logfile in logfiles_list: logfiles[ os.path.basename( logfile ) ] = logfile return logfiles def _OpenLogfile( self, size, mods, logfile ): # Open log files in a horizontal window with the same behavior as the # preview window (same height and winfixheight enabled). Automatically # watch for changes. Set the cursor position at the end of the file. if not size: size = vimsupport.GetIntValue( '&previewheight' ) options = { 'size': size, 'fix': True, 'focus': False, 'watch': True, 'position': 'end', 'mods': mods } vimsupport.OpenFilename( logfile, options ) def _CloseLogfile( self, logfile ): vimsupport.CloseBuffersForFilename( logfile ) def ToggleLogs( self, size, mods, *filenames ): logfiles = self.GetLogfiles() if not filenames: sorted_logfiles = sorted( logfiles ) try: logfile_index = vimsupport.SelectFromList( 'Which logfile do you wish to open (or close if already open)?', sorted_logfiles ) except RuntimeError as e: vimsupport.PostVimMessage( str( e ) ) return logfile = logfiles[ sorted_logfiles[ logfile_index ] ] if not vimsupport.BufferIsVisibleForFilename( logfile ): self._OpenLogfile( size, mods, logfile ) else: self._CloseLogfile( logfile ) return for filename in set( filenames ): if filename not in logfiles: continue logfile = logfiles[ filename ] if not vimsupport.BufferIsVisibleForFilename( logfile ): self._OpenLogfile( size, mods, logfile ) continue self._CloseLogfile( logfile ) def ShowDetailedDiagnostic( self, message_in_popup ): detailed_diagnostic = BaseRequest().PostDataToHandler( BuildRequestData(), 'detailed_diagnostic' ) if detailed_diagnostic and 'message' in detailed_diagnostic: message = detailed_diagnostic[ 'message' ] if message_in_popup and vimsupport.VimSupportsPopupWindows(): window = vim.current.window buffer_number = vimsupport.GetCurrentBufferNumber() diags_on_this_line = self._buffers[ buffer_number ].DiagnosticsForLine( window.cursor[ 0 ] ) lines = message.split( '\n' ) available_columns = vimsupport.GetIntValue( '&columns' ) col = window.cursor[ 1 ] + 1 if col > available_columns - 2: # -2 accounts for padding. col = 0 options = { 'col': col, 'padding': [ 0, 1, 0, 1 ], 'maxwidth': available_columns, 'close': 'click', 'fixed': 0, 'highlight': 'ErrorMsg', 'border': [ 1, 1, 1, 1 ], # Close when moving cursor 'moved': 'expr', } popup_func = 'popup_atcursor' for diag in diags_on_this_line: if message == diag[ 'text' ]: popup_func = 'popup_create' prop = vimsupport.GetTextPropertyForDiag( buffer_number, window.cursor[ 0 ], diag ) options.update( { 'textpropid': prop[ 'id' ], 'textprop': prop[ 'type' ], } ) options.pop( 'col' ) vim.eval( f'{ popup_func }( { lines }, { options } )' ) else: vimsupport.PostVimMessage( message, warning = False ) def ForceCompileAndDiagnostics( self ): if not self.NativeFiletypeCompletionUsable(): vimsupport.PostVimMessage( 'Native filetype completion not supported for current file, ' 'cannot force recompilation.', warning = False ) return False vimsupport.PostVimMessage( 'Forcing compilation, this will block Vim until done.', warning = False ) self.OnFileReadyToParse() self.HandleFileParseRequest( block = True ) vimsupport.PostVimMessage( 'Diagnostics refreshed', warning = False ) return True def ShowDiagnostics( self ): if not self.ForceCompileAndDiagnostics(): return if not self._PopulateLocationListWithLatestDiagnostics(): vimsupport.PostVimMessage( 'No warnings or errors detected.', warning = False ) return if self._user_options[ 'open_loclist_on_ycm_diags' ]: vimsupport.OpenLocationList( focus = True ) def FilterAndSortItems( self, items, sort_property, query, max_items = 0 ): return BaseRequest().PostDataToHandler( { 'candidates': items, 'sort_property': sort_property, 'max_num_candidates': max_items, 'query': vimsupport.ToUnicode( query ) }, 'filter_and_sort_candidates' ) def ToggleSignatureHelp( self ): self._signature_help_state.ToggleVisibility() def _AddSyntaxDataIfNeeded( self, extra_data ): if not self._user_options[ 'seed_identifiers_with_syntax' ]: return filetype = vimsupport.CurrentFiletypes()[ 0 ] if filetype in self._filetypes_with_keywords_loaded: return if self.IsServerReady(): self._filetypes_with_keywords_loaded.add( filetype ) extra_data[ 'syntax_keywords' ] = list( syntax_parse.SyntaxKeywordsForCurrentBuffer() ) def _AddTagsFilesIfNeeded( self, extra_data ): def GetTagFiles(): tag_files = vim.eval( 'tagfiles()' ) return [ ( os.path.join( utils.GetCurrentDirectory(), tag_file ) if not os.path.isabs( tag_file ) else tag_file ) for tag_file in tag_files ] if not self._user_options[ 'collect_identifiers_from_tags_files' ]: return extra_data[ 'tag_files' ] = GetTagFiles() def _AddExtraConfDataIfNeeded( self, extra_data ): def BuildExtraConfData( extra_conf_vim_data ): extra_conf_data = {} for expr in extra_conf_vim_data: try: extra_conf_data[ expr ] = vimsupport.VimExpressionToPythonType( expr ) except vim.error: message = ( f"Error evaluating '{ expr }' in the 'g:ycm_extra_conf_vim_data' " "option." ) vimsupport.PostVimMessage( message, truncate = True ) self._logger.exception( message ) return extra_conf_data extra_conf_vim_data = self._user_options[ 'extra_conf_vim_data' ] if extra_conf_vim_data: extra_data[ 'extra_conf_data' ] = BuildExtraConfData( extra_conf_vim_data ) def _AddUltiSnipsDataIfNeeded( self, extra_data ): # See :h UltiSnips#SnippetsInCurrentScope. try: vim.eval( 'UltiSnips#SnippetsInCurrentScope( 1 )' ) except vim.error: return snippets = vimsupport.GetVariableValue( 'g:current_ulti_dict_info' ) extra_data[ 'ultisnips_snippets' ] = [ { 'trigger': trigger, 'description': snippet[ 'description' ] } for trigger, snippet in snippets.items() ]