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.state self._AddExtraConfDataIfNeeded( request_data ) self._latest_signature_help_request = SignatureHelpRequest( request_data ) self._latest_signature_help_request.Start() return True return False
def SendSignatureHelpRequest( self ): filetype = vimsupport.CurrentFiletypes()[ 0 ] if not self._signature_help_available_requests[ filetype ].Done(): return sig_help_available = self._signature_help_available_requests[ filetype ].Response() if sig_help_available == 'NO': return if sig_help_available == 'PENDING': # Send another /signature_help_available request self._signature_help_available_requests[ filetype ].Start( filetype ) return if not self.NativeFiletypeCompletionUsable(): return if not self._latest_completion_request: return request_data = self._latest_completion_request.request_data.copy() request_data[ 'signature_help_state' ] = self._signature_help_state.state self._AddExtraConfDataIfNeeded( request_data ) self._latest_signature_help_request = SignatureHelpRequest( request_data ) self._latest_signature_help_request.Start()
class YouCompleteMe: 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._latest_signature_help_request = None self._signature_help_available_requests = SigHelpAvailableByFileType() self._signature_help_state = signature_help.SignatureHelpState() 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_requests = {} 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) # 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): response = self._latest_completion_request.Response() response['completions'] = base.AdjustCandidateInsertionText( response['completions']) return 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.state 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 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): 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) 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 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): 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 _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._user_options['show_diagnostics_ui']: # Forcefuly update the location list, etc. from the parse request when # doing something like :YcmDiags current_buffer.UpdateDiagnostics(block) 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 += '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, 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): 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 snippets.items()]