Example #1
0
class Buffer(object):
    def __init__(self, bufnr, user_options, async_diags):
        self.number = bufnr
        self._parse_tick = 0
        self._handled_tick = 0
        self._parse_request = None
        self._async_diags = async_diags
        self._diag_interface = DiagnosticInterface(bufnr, user_options)

    def FileParseRequestReady(self, block=False):
        return bool(self._parse_request
                    and (block or self._parse_request.Done()))

    def SendParseRequest(self, extra_data):
        self._parse_request = EventNotification('FileReadyToParse',
                                                extra_data=extra_data)
        self._parse_request.Start()
        # Decrement handled tick to ensure correct handling when we are forcing
        # reparse on buffer visit and changed tick remains the same.
        self._handled_tick -= 1
        self._parse_tick = self._ChangedTick()

    def NeedsReparse(self):
        return self._parse_tick != self._ChangedTick()

    def UpdateDiagnostics(self, force=False):
        if force or not self._async_diags:
            self.UpdateWithNewDiagnostics(self._parse_request.Response())
        else:
            # We need to call the response method, because it might throw an exception
            # or require extra config confirmation, even if we don't actually use the
            # diagnostics.
            self._parse_request.Response()

    def UpdateWithNewDiagnostics(self, diagnostics):
        self._diag_interface.UpdateWithNewDiagnostics(diagnostics)

    def PopulateLocationList(self):
        return self._diag_interface.PopulateLocationList()

    def GetResponse(self):
        return self._parse_request.Response()

    def IsResponseHandled(self):
        return self._handled_tick == self._parse_tick

    def MarkResponseHandled(self):
        self._handled_tick = self._parse_tick

    def OnCursorMoved(self):
        self._diag_interface.OnCursorMoved()

    def GetErrorCount(self):
        return self._diag_interface.GetErrorCount()

    def GetWarningCount(self):
        return self._diag_interface.GetWarningCount()

    def _ChangedTick(self):
        return vimsupport.GetBufferChangedTick(self.number)
Example #2
0
 def SendParseRequest(self, extra_data):
     self._parse_request = EventNotification('FileReadyToParse',
                                             extra_data=extra_data)
     self._parse_request.Start()
     # Decrement handled tick to ensure correct handling when we are forcing
     # reparse on buffer visit and changed tick remains the same.
     self._handled_tick -= 1
     self._parse_tick = self._ChangedTick()
Example #3
0
class Buffer(object):
    def __init__(self, bufnr, user_options):
        self.number = bufnr
        self._parse_tick = 0
        self._handled_tick = 0
        self._parse_request = None
        self._diag_interface = DiagnosticInterface(bufnr, user_options)

    def FileParseRequestReady(self, block=False):
        return bool(self._parse_request
                    and (block or self._parse_request.Done()))

    def SendParseRequest(self, extra_data):
        self._parse_request = EventNotification('FileReadyToParse',
                                                extra_data=extra_data)
        self._parse_request.Start()
        # Decrement handled tick to ensure correct handling when we are forcing
        # reparse on buffer visit and changed tick remains the same.
        self._handled_tick -= 1
        self._parse_tick = self._ChangedTick()

    def NeedsReparse(self):
        return self._parse_tick != self._ChangedTick()

    def UpdateDiagnostics(self):
        self._diag_interface.UpdateWithNewDiagnostics(
            self._parse_request.Response())

    def PopulateLocationList(self):
        return self._diag_interface.PopulateLocationList()

    def GetResponse(self):
        return self._parse_request.Response()

    def IsResponseHandled(self):
        return self._handled_tick == self._parse_tick

    def MarkResponseHandled(self):
        self._handled_tick = self._parse_tick

    def OnCursorMoved(self):
        self._diag_interface.OnCursorMoved()

    def GetErrorCount(self):
        return self._diag_interface.GetErrorCount()

    def GetWarningCount(self):
        return self._diag_interface.GetWarningCount()

    def _ChangedTick(self):
        return vimsupport.GetBufferChangedTick(self.number)
Example #4
0
    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()
Example #5
0
    def SendParseRequest(self, extra_data):
        # Don't send a parse request if one is in progress
        if self._parse_request is not None and not self._parse_request.Done():
            self._should_resend = True
            return

        self._should_resend = False

        self._parse_request = EventNotification('FileReadyToParse',
                                                extra_data=extra_data)
        self._parse_request.Start()
        # Decrement handled tick to ensure correct handling when we are forcing
        # reparse on buffer visit and changed tick remains the same.
        self._handled_tick -= 1
        self._parse_tick = self._ChangedTick()
Example #6
0
class Buffer:
    def __init__(self, bufnr, user_options, filetypes):
        self._number = bufnr
        self._parse_tick = 0
        self._handled_tick = 0
        self._parse_request = None
        self._should_resend = False
        self._diag_interface = DiagnosticInterface(bufnr, user_options)
        self._open_loclist_on_ycm_diags = user_options[
            'open_loclist_on_ycm_diags']
        self._semantic_highlighting = SemanticHighlighting(bufnr, user_options)
        self.inlay_hints = InlayHints(bufnr, user_options)
        self.UpdateFromFileTypes(filetypes)

    def FileParseRequestReady(self, block=False):
        return bool(self._parse_request
                    and (block or self._parse_request.Done()))

    def SendParseRequest(self, extra_data):
        # Don't send a parse request if one is in progress
        if self._parse_request is not None and not self._parse_request.Done():
            self._should_resend = True
            return

        self._should_resend = False

        self._parse_request = EventNotification('FileReadyToParse',
                                                extra_data=extra_data)
        self._parse_request.Start()
        # Decrement handled tick to ensure correct handling when we are forcing
        # reparse on buffer visit and changed tick remains the same.
        self._handled_tick -= 1
        self._parse_tick = self._ChangedTick()

    def NeedsReparse(self):
        return self._parse_tick != self._ChangedTick()

    def ShouldResendParseRequest(self):
        return (self._should_resend
                or (bool(self._parse_request)
                    and self._parse_request.ShouldResend()))

    def UpdateDiagnostics(self, force=False):
        if force or not self._async_diags:
            self.UpdateWithNewDiagnostics(self._parse_request.Response(),
                                          False)
        else:
            # We need to call the response method, because it might throw an exception
            # or require extra config confirmation, even if we don't actually use the
            # diagnostics.
            self._parse_request.Response()

    def UpdateWithNewDiagnostics(self, diagnostics, async_message):
        self._async_diags = async_message
        self._diag_interface.UpdateWithNewDiagnostics(
            diagnostics, not self._async_diags
            and self._open_loclist_on_ycm_diags)

    def UpdateMatches(self):
        self._diag_interface.UpdateMatches()

    def PopulateLocationList(self, open_on_edit=False):
        return self._diag_interface.PopulateLocationList(open_on_edit)

    def GetResponse(self):
        return self._parse_request.Response()

    def IsResponseHandled(self):
        return self._handled_tick == self._parse_tick

    def MarkResponseHandled(self):
        self._handled_tick = self._parse_tick

    def OnCursorMoved(self):
        self._diag_interface.OnCursorMoved()

    def GetErrorCount(self):
        return self._diag_interface.GetErrorCount()

    def GetWarningCount(self):
        return self._diag_interface.GetWarningCount()

    def RefreshDiagnosticsUI(self):
        return self._diag_interface.RefreshDiagnosticsUI()

    def DiagnosticsForLine(self, line_number):
        return self._diag_interface.DiagnosticsForLine(line_number)

    def UpdateFromFileTypes(self, filetypes):
        self._filetypes = filetypes
        # We will set this to true if we ever receive any diagnostics asyncronously.
        self._async_diags = False

    def SendSemanticTokensRequest(self):
        self._semantic_highlighting.SendRequest()

    def SemanticTokensRequestReady(self):
        return self._semantic_highlighting.IsResponseReady()

    def UpdateSemanticTokens(self):
        return self._semantic_highlighting.Update()

    def _ChangedTick(self):
        return vimsupport.GetBufferChangedTick(self._number)
Example #7
0
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)
Example #8
0
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._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 _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 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' ] = utils.GetCurrentDirectory()

    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 ):
    return SendCommandRequest( arguments, completer )


  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 ServerBecomesReady( self ):
    if not self._server_is_ready_with_cache:
      with HandleServerException( display = False ):
        self._server_is_ready_with_cache = BaseRequest.GetDataFromHandler(
            'ready' )
      return self._server_is_ready_with_cache
    return False


  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 = extra_data )
    self._latest_file_parse_request.Start()


  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 OnInsertLeave( self ):
    SendEventNotificationAsync( 'InsertLeave' )


  def OnCursorMoved( self ):
    self._diag_interface.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 ):
    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 DebugInfo( self ):
    debug_info = ''
    if self._client_logfile:
      debug_info += 'Client logfile: {0}\n'.format( self._client_logfile )
    debug_info += FormatDebugInfoResponse( SendDebugInfoRequest() )
    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._server_is_ready_with_cache:
      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 )
    ]
Example #9
0
class YouCompleteMe(object):
    def __init__(self, user_options):
        self._user_options = 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._temp_options_filename = None
        self._SetupServer()

    def _SetupServer(self):
        server_port = utils.GetUnusedLocalhostPort()
        with tempfile.NamedTemporaryFile(delete=False) as options_file:
            self._temp_options_filename = options_file.name
            json.dump(dict(self._user_options), options_file)
            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(
                    self._user_options['server_idle_suicide_seconds'])
            ]

            BaseRequest.server_location = 'http://localhost:' + str(
                server_port)

            if self._user_options['server_use_vim_stdout']:
                self._server_popen = subprocess.Popen(args)
            else:
                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')
                # We need this on Windows otherwise bad things happen. See issue #637.
                stdin = subprocess.PIPE if utils.OnWindows() else None

                with open(self._server_stderr, 'w') as fstderr:
                    with open(self._server_stdout, 'w') as fstdout:
                        self._server_popen = subprocess.Popen(args,
                                                              stdin=stdin,
                                                              stdout=fstdout,
                                                              stderr=fstderr)
        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._IsServerAlive():
            return
        if self._server_stderr:
            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)
        else:
            vimsupport.PostVimMessage(SERVER_CRASH_MESSAGE_SAME_STDERR)

    def ServerPid(self):
        if not self._server_popen:
            return -1
        return self._server_popen.pid

    def RestartServer(self):
        vimsupport.PostVimMessage('Restarting ycmd server...')
        self.OnVimLeave()
        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():
            return BaseRequest.PostDataToHandler(BuildRequestData(),
                                                 'defined_subcommands')
        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 OnVimLeave(self):
        if self._IsServerAlive():
            self._server_popen.terminate()
        os.remove(self._temp_options_filename)

        if not self._user_options['server_keep_logfiles']:
            if self._server_stderr:
                os.remove(self._server_stderr)
            if self._server_stdout:
                os.remove(self._server_stdout)

    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):
        if self.DiagnosticsForCurrentFileReady():
            to_return = 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
            return to_return
        return []

    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)
Example #10
0
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)]
Example #11
0
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()

    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):
        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

        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 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
            # 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 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']
        if '*' in filetype_to_disable:
            return False
        else:
            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()')
            # 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)
Example #12
0
class Buffer:

  def __init__( self, bufnr, user_options, filetypes ):
    self._number = bufnr
    self._parse_tick = 0
    self._handled_tick = 0
    self._parse_request = None
    self._should_resend = False
    self._diag_interface = DiagnosticInterface( bufnr, user_options )
    self.UpdateFromFileTypes( filetypes )


  def FileParseRequestReady( self, block = False ):
    return bool( self._parse_request and
                 ( block or self._parse_request.Done() ) )


  def SendParseRequest( self, extra_data ):
    # Don't send a parse request if one is in progress
    if self._parse_request is not None and not self._parse_request.Done():
      self._should_resend = True
      return

    self._should_resend = False

    self._parse_request = EventNotification( 'FileReadyToParse',
                                             extra_data = extra_data )
    self._parse_request.Start()
    # Decrement handled tick to ensure correct handling when we are forcing
    # reparse on buffer visit and changed tick remains the same.
    self._handled_tick -= 1
    self._parse_tick = self._ChangedTick()


  def NeedsReparse( self ):
    return self._parse_tick != self._ChangedTick()


  def ShouldResendParseRequest( self ):
    return ( self._should_resend
             or ( bool( self._parse_request )
                  and self._parse_request.ShouldResend() ) )


  def UpdateDiagnostics( self, force = False ):
    if force or not self._async_diags:
      self.UpdateWithNewDiagnostics( self._parse_request.Response() )
    else:
      # We need to call the response method, because it might throw an exception
      # or require extra config confirmation, even if we don't actually use the
      # diagnostics.
      self._parse_request.Response()


  def UpdateWithNewDiagnostics( self, diagnostics ):
    self._diag_interface.UpdateWithNewDiagnostics( diagnostics )


  def UpdateMatches( self ):
    self._diag_interface.UpdateMatches()


  def PopulateLocationList( self ):
    return self._diag_interface.PopulateLocationList()


  def GetResponse( self ):
    return self._parse_request.Response()


  def IsResponseHandled( self ):
    return self._handled_tick == self._parse_tick


  def MarkResponseHandled( self ):
    self._handled_tick = self._parse_tick


  def OnCursorMoved( self ):
    self._diag_interface.OnCursorMoved()


  def GetErrorCount( self ):
    return self._diag_interface.GetErrorCount()


  def GetWarningCount( self ):
    return self._diag_interface.GetWarningCount()


  def UpdateFromFileTypes( self, filetypes ):
    self._filetypes = filetypes
    self._async_diags = not any( x in DIAGNOSTIC_UI_FILETYPES
      for x in filetypes )


  def _ChangedTick( self ):
    return vimsupport.GetBufferChangedTick( self._number )