Пример #1
0
    def _Format(self, request_data):
        filepath = request_data['filepath']

        self._Reload(request_data)

        # TODO: support all formatting options. See
        # https://github.com/Microsoft/TypeScript/blob/72e92a055823f1ade97d03d7526dbab8be405dde/lib/protocol.d.ts#L2060-L2077
        # for the list of options. While not standard, a way to support these
        # options, which is already adopted by a number of clients, would be to read
        # the "formatOptions" field in the tsconfig.json file.
        options = request_data['options']
        self._SendRequest(
            'configure', {
                'file': filepath,
                'formatOptions': {
                    'tabSize': options['tab_size'],
                    'indentSize': options['tab_size'],
                    'convertTabsToSpaces': options['insert_spaces'],
                }
            })

        response = self._SendRequest('format',
                                     _BuildTsFormatRange(request_data))

        contents = GetFileLines(request_data, filepath)
        chunks = [
            _BuildFixItChunkForRange(text_edit['newText'], contents, filepath,
                                     text_edit) for text_edit in response
        ]

        location = responses.Location(request_data['line_num'],
                                      request_data['column_num'], filepath)
        return responses.BuildFixItResponse(
            [responses.FixIt(location, chunks)])
Пример #2
0
    def _Format(self, request_data):
        request = self._DefaultParameters(request_data)
        request['WantsTextChanges'] = True
        if 'range' in request_data:
            lines = request_data['lines']
            start = request_data['range']['start']
            start_line_num = start['line_num']
            start_line_value = lines[start_line_num]

            start_codepoint = ByteOffsetToCodepointOffset(
                start_line_value, start['column_num'])

            end = request_data['range']['end']
            end_line_num = end['line_num']
            end_line_value = lines[end_line_num]
            end_codepoint = ByteOffsetToCodepointOffset(
                end_line_value, end['column_num'])
            request.update({
                'line': start_line_num,
                'column': start_codepoint,
                'EndLine': end_line_num,
                'EndColumn': end_codepoint
            })
            result = self._GetResponse('/formatRange', request)
        else:
            result = self._GetResponse('/codeformat', request)

        fixit = responses.FixIt(
            _BuildLocation(request_data, request_data['filepath'],
                           request_data['line_num'],
                           request_data['column_codepoint']),
            _LinePositionSpanTextChangeToFixItChunks(result['Changes'],
                                                     request_data['filepath'],
                                                     request_data))
        return responses.BuildFixItResponse([fixit])
Пример #3
0
 def _ResolveFixIt( self, request_data, unresolved_fixit = None ):
   fixit = unresolved_fixit if unresolved_fixit else request_data[ 'fixit' ]
   if not fixit[ 'resolve' ]:
     return { 'fixits': [ fixit ] }
   fixit = fixit[ 'command' ]
   code_action = fixit[ 'index' ]
   request = self._DefaultParameters( request_data )
   request.update( {
     'CodeAction': code_action,
     'WantsTextChanges': True,
   } )
   response = self._GetResponse( '/runcodeaction', request )
   fixit = responses.FixIt(
     _BuildLocation(
       request_data,
       request_data[ 'filepath' ],
       request_data[ 'line_num' ],
       request_data[ 'column_codepoint' ] ),
     _LinePositionSpanTextChangeToFixItChunks(
       response[ 'Changes' ],
       request_data[ 'filepath' ],
       request_data ),
     response[ 'Text' ] )
   # The sort is necessary to keep the tests stable.
   # Python's sort() is stable, so it won't mess up the order within a file.
   fixit.chunks.sort( key = lambda c: c.range.start_.filename_ )
   return responses.BuildFixItResponse( [ fixit ] )
Пример #4
0
def _ConvertDetailedCompletionData(request_data, completion_data):
    name = completion_data['name']
    display_parts = completion_data['displayParts']
    signature = ''.join([part['text'] for part in display_parts])
    if name == signature:
        extra_menu_info = None
        detailed_info = []
    else:
        # Strip new lines and indentation from the signature to display it on one
        # line.
        extra_menu_info = re.sub('\s+', ' ', signature)
        detailed_info = [signature]

    docs = completion_data.get('documentation', [])
    detailed_info += [doc['text'].strip() for doc in docs if doc]
    detailed_info = '\n\n'.join(detailed_info)

    fixits = None
    if 'codeActions' in completion_data:
        location = responses.Location(request_data['line_num'],
                                      request_data['column_num'],
                                      request_data['filepath'])
        fixits = responses.BuildFixItResponse([
            responses.FixIt(
                location, _BuildFixItForChanges(request_data,
                                                action['changes']),
                action['description'])
            for action in completion_data['codeActions']
        ])

    return responses.BuildCompletionData(insertion_text=name,
                                         extra_menu_info=extra_menu_info,
                                         detailed_info=detailed_info,
                                         kind=completion_data['kind'],
                                         extra_data=fixits)
Пример #5
0
    def _RefactorRename(self, request_data, args):
        if len(args) != 1:
            raise ValueError('Please specify a new name to rename it to.\n'
                             'Usage: RefactorRename <new name>')

        self._Reload(request_data)

        response = self._SendRequest(
            'rename', {
                'file': request_data['filepath'],
                'line': request_data['line_num'],
                'offset': request_data['column_codepoint'],
                'findInComments': False,
                'findInStrings': False,
            })

        if not response['info']['canRename']:
            raise RuntimeError(
                'Value cannot be renamed: '
                f'{ response[ "info" ][ "localizedErrorMessage" ] }')

        # The format of the response is:
        #
        # body {
        #   info {
        #     ...
        #     triggerSpan: {
        #       length: original_length
        #     }
        #   }
        #
        #   locs [ {
        #     file: file_path
        #     locs: [
        #       start: {
        #         line: line_num
        #         offset: offset
        #       }
        #       end {
        #         line: line_num
        #         offset: offset
        #       }
        #     ] }
        #   ]
        # }
        #
        new_name = args[0]
        location = responses.Location(request_data['line_num'],
                                      request_data['column_num'],
                                      request_data['filepath'])

        chunks = []
        for file_replacement in response['locs']:
            chunks.extend(
                _BuildFixItChunksForFile(request_data, new_name,
                                         file_replacement))

        return responses.BuildFixItResponse(
            [responses.FixIt(location, chunks)])
Пример #6
0
    def _TsDiagnosticToYcmdDiagnostic(self, request_data, ts_diagnostic):
        filepath = request_data['filepath']

        ts_fixes = self._SendRequest(
            'getCodeFixes', {
                'file': filepath,
                'startLine': ts_diagnostic['startLocation']['line'],
                'startOffset': ts_diagnostic['startLocation']['offset'],
                'endLine': ts_diagnostic['endLocation']['line'],
                'endOffset': ts_diagnostic['endLocation']['offset'],
                'errorCodes': [ts_diagnostic['code']]
            })
        location = responses.Location(request_data['line_num'],
                                      request_data['column_num'], filepath)

        fixits = []
        for fix in ts_fixes:
            description = fix['description']
            # TSServer returns these fixits for every error in JavaScript files.
            # Ignore them since they are not useful.
            if description in [
                    'Ignore this error message',
                    'Disable checking for this file'
            ]:
                continue

            fixit = responses.FixIt(
                location, _BuildFixItForChanges(request_data, fix['changes']),
                description)
            fixits.append(fixit)

        contents = GetFileLines(request_data, filepath)

        ts_start_location = ts_diagnostic['startLocation']
        ts_start_line = ts_start_location['line']
        start_offset = utils.CodepointOffsetToByteOffset(
            contents[ts_start_line - 1], ts_start_location['offset'])

        ts_end_location = ts_diagnostic['endLocation']
        ts_end_line = ts_end_location['line']
        end_offset = utils.CodepointOffsetToByteOffset(
            contents[ts_end_line - 1], ts_end_location['offset'])

        location_start = responses.Location(ts_start_line, start_offset,
                                            filepath)
        location_end = responses.Location(ts_end_line, end_offset, filepath)

        location_extent = responses.Range(location_start, location_end)

        return responses.Diagnostic([location_extent],
                                    location_start,
                                    location_extent,
                                    ts_diagnostic['message'],
                                    'ERROR',
                                    fixits=fixits)
Пример #7
0
def _RefactoringToFixIt(refactoring):
    """Converts a Jedi Refactoring instance to a single responses.FixIt."""

    # FIXME: refactorings can rename files (apparently). ycmd API doesn't have any
    # interface for that, so we just ignore them.
    changes = refactoring.get_changed_files()
    chunks = []

    # We sort the files to ensure the tests are stable
    for filename in sorted(changes.keys()):
        changed_file = changes[filename]

        # NOTE: This is an internal API. We _could_ use GetFileContents( filename )
        # here, but using Jedi's representation means that it is always consistent
        # with get_new_code()
        old_text = changed_file._module_node.get_code()
        new_text = changed_file.get_new_code()

        # Cache the offsets of all the newlines in the file. These are used to
        # calculate the line/column values from the offsets retuned by the diff
        # scanner
        newlines = [i for i, c in enumerate(old_text) if c == '\n']
        newlines.append(len(old_text))

        sequence_matcher = difflib.SequenceMatcher(a=old_text,
                                                   b=new_text,
                                                   autojunk=False)

        for (operation, old_start, old_end, new_start,
             new_end) in sequence_matcher.get_opcodes():
            # Tag of equal means the range is identical, so nothing to do.
            if operation == 'equal':
                continue

            # operation can be 'insert', 'replace' or 'delete', the offsets actually
            # already cover that in our FixIt API (a delete has an empty new_text, an
            # insert has an empty range), so we just encode the line/column offset and
            # the replacement text extracted from new_text
            chunks.append(
                responses.FixItChunk(
                    new_text[new_start:new_end],
                    # FIXME: new_end must be equal to or after new_start, so we should make
                    # OffsetToPosition take 2 offsets and return them rather than repeating
                    # work
                    responses.Range(
                        _OffsetToPosition(old_start, filename, old_text,
                                          newlines),
                        _OffsetToPosition(old_end, filename, old_text,
                                          newlines))))

    return responses.FixIt(responses.Location(1, 1, 'none'),
                           chunks,
                           '',
                           kind=responses.FixIt.Kind.REFACTOR)
Пример #8
0
def _ApplySuggestionsToFixIt(request_data, command):
    LOGGER.debug('command = %s', command)
    args = command['arguments']
    text_edits = [{'range': args[0]['range'], 'newText': args[1]}]
    uri = args[0]['uri']
    return responses.FixIt(
        responses.Location(request_data['line_num'],
                           request_data['column_num'],
                           request_data['filepath']),
        language_server_completer.TextEditToChunks(request_data, uri,
                                                   text_edits), args[1])
Пример #9
0
def _BuildCompletionFixIts(request_data, entry):
    if 'codeActions' in entry:
        location = responses.Location(request_data['line_num'],
                                      request_data['column_num'],
                                      request_data['filepath'])
        return responses.BuildFixItResponse([
            responses.FixIt(
                location, _BuildFixItForChanges(request_data,
                                                action['changes']),
                action['description']) for action in entry['codeActions']
        ])
    return {}
Пример #10
0
def _ModifiedFilesToFixIt(changes, request_data):
    chunks = []
    for change in changes:
        chunks.extend(
            _LinePositionSpanTextChangeToFixItChunks(change['Changes'],
                                                     change['FileName'],
                                                     request_data))
    # The sort is necessary to keep the tests stable.
    # Python's sort() is stable, so it won't mess up the order within a file.
    chunks.sort(key=lambda c: c.range.start_.filename_)
    return responses.FixIt(
        _BuildLocation(request_data, request_data['filepath'],
                       request_data['line_num'],
                       request_data['column_codepoint']), chunks)
Пример #11
0
    def _FixIt(self, request_data):
        request = self._DefaultParameters(request_data)

        result = self._GetResponse('/fixcodeissue', request)
        replacement_text = result["Text"]
        location = responses.Location(request_data['line_num'],
                                      request_data['column_num'],
                                      request_data['filepath'])
        fixits = [
            responses.FixIt(location,
                            _BuildChunks(request_data, replacement_text))
        ]

        return responses.BuildFixItResponse(fixits)
Пример #12
0
  def _FixIt( self, request_data ):
    request = self._DefaultParameters( request_data )

    result = self._GetResponse( '/fixcodeissue', request )
    replacement_text = result[ "Text" ]
    # Note: column_num is already a byte offset so we don't need to use
    # _BuildLocation.
    location = responses.Location( request_data[ 'line_num' ],
                                   request_data[ 'column_num' ],
                                   request_data[ 'filepath' ] )
    fixits = [ responses.FixIt( location,
                                _BuildChunks( request_data,
                                              replacement_text ) ) ]

    return responses.BuildFixItResponse( fixits )
Пример #13
0
  def _TsDiagnosticToYcmdDiagnostic( self, request_data, ts_diagnostic ):
    filepath = request_data[ 'filepath' ]

    ts_fixes = self._SendRequest( 'getCodeFixes', {
      'file':        filepath,
      'startLine':   ts_diagnostic[ 'startLocation' ][ 'line' ],
      'startOffset': ts_diagnostic[ 'startLocation' ][ 'offset' ],
      'endLine':     ts_diagnostic[ 'endLocation' ][ 'line' ],
      'endOffset':   ts_diagnostic[ 'endLocation' ][ 'offset' ],
      'errorCodes':  [ ts_diagnostic[ 'code' ] ]
    } )
    location = responses.Location( request_data[ 'line_num' ],
                                   request_data[ 'column_num' ],
                                   filepath )

    fixits = [ responses.FixIt( location,
                                _BuildFixItForChanges( request_data,
                                                       fix[ 'changes' ] ),
                                fix[ 'description' ] )
               for fix in ts_fixes ]

    contents = GetFileLines( request_data, filepath )

    ts_start_location = ts_diagnostic[ 'startLocation' ]
    ts_start_line = ts_start_location[ 'line' ]
    start_offset = utils.CodepointOffsetToByteOffset(
      contents[ ts_start_line - 1 ],
      ts_start_location[ 'offset' ] )

    ts_end_location = ts_diagnostic[ 'endLocation' ]
    ts_end_line = ts_end_location[ 'line' ]
    end_offset = utils.CodepointOffsetToByteOffset(
      contents[ ts_end_line - 1 ],
      ts_end_location[ 'offset' ] )

    location_start = responses.Location( ts_start_line, start_offset, filepath )
    location_end = responses.Location( ts_end_line, end_offset, filepath )

    location_extent = responses.Range( location_start, location_end )

    return responses.Diagnostic( [ location_extent ],
                                 location_start,
                                 location_extent,
                                 ts_diagnostic[ 'message' ],
                                 'ERROR',
                                 fixits = fixits )
Пример #14
0
    def _OrganizeImports(self, request_data):
        self._Reload(request_data)

        filepath = request_data['filepath']
        changes = self._SendRequest(
            'organizeImports',
            {'scope': {
                'type': 'file',
                'args': {
                    'file': filepath
                }
            }})

        location = responses.Location(request_data['line_num'],
                                      request_data['column_num'], filepath)
        return responses.BuildFixItResponse([
            responses.FixIt(location,
                            _BuildFixItForChanges(request_data, changes))
        ])
Пример #15
0
    def _Rename(self, request_data, args):
        if len(args) != 1:
            raise ValueError('Please specify a new name to rename it to.\n'
                             'Usage: RefactorRename <new name>')

        query = {
            'type': 'rename',
            'newName': args[0],
        }

        response = self._GetResponse(query, request_data['column_codepoint'],
                                     request_data)

        # Tern response format:
        # 'changes': [
        #     {
        #         'file' (potentially relative path)
        #         'start' {
        #             'line'
        #             'ch' (codepoint offset)
        #         }
        #         'end' {
        #             'line'
        #             'ch' (codepoint offset)
        #         }
        #         'text'
        #     }
        # ]

        # ycmd response format:
        #
        # {
        #     'fixits': [
        #         'chunks': (list<Chunk>) [
        #             {
        #                  'replacement_text',
        #                  'range' (Range) {
        #                      'start_' (Location): {
        #                          'line_number_',
        #                          'column_number_', (byte offset)
        #                          'filename_' (note: absolute path!)
        #                      },
        #                      'end_' (Location): {
        #                          'line_number_',
        #                          'column_number_', (byte offset)
        #                          'filename_' (note: absolute path!)
        #                      }
        #                  }
        #              }
        #         ],
        #         'location' (Location) {
        #              'line_number_',
        #              'column_number_',
        #              'filename_' (note: absolute path!)
        #         }
        #
        #     ]
        # }

        def BuildRange(file_contents, filename, start, end):
            return responses.Range(
                _BuildLocation(file_contents, filename, start['line'],
                               start['ch']),
                _BuildLocation(file_contents, filename, end['line'],
                               end['ch']))

        def BuildFixItChunk(change):
            filepath = self._ServerPathToAbsolute(change['file'])
            file_contents = utils.SplitLines(
                GetFileContents(request_data, filepath))
            return responses.FixItChunk(
                change['text'],
                BuildRange(file_contents, filepath, change['start'],
                           change['end']))

        # From an API perspective, Refactor and FixIt are the same thing - it just
        # applies a set of changes to a set of files. So we re-use all of the
        # existing FixIt infrastructure.
        return responses.BuildFixItResponse([
            responses.FixIt(
                responses.Location(request_data['line_num'],
                                   request_data['column_num'],
                                   request_data['filepath']),
                [BuildFixItChunk(x) for x in response['changes']])
        ])
Пример #16
0
    def _Rename(self, request_data, args):
        if len(args) != 1:
            raise ValueError('Please specify a new name to rename it to.\n'
                             'Usage: RefactorRename <new name>')

        query = {
            'type': 'rename',
            'newName': args[0],
        }

        response = self._GetResponse(query, request_data)

        # Tern response format:
        # 'changes': [
        #     {
        #         'file'
        #         'start' {
        #             'line'
        #             'ch'
        #         }
        #         'end' {
        #             'line'
        #             'ch'
        #         }
        #         'text'
        #     }
        # ]

        # ycmd response format:
        #
        # {
        #     'fixits': [
        #         'chunks': (list<Chunk>) [
        #             {
        #                  'replacement_text',
        #                  'range' (Range) {
        #                      'start_' (Location): {
        #                          'line_number_',
        #                          'column_number_',
        #                          'filename_'
        #                      },
        #                      'end_' (Location): {
        #                          'line_number_',
        #                          'column_number_',
        #                          'filename_'
        #                      }
        #                  }
        #              }
        #         ],
        #         'location' (Location) {
        #              'line_number_',
        #              'column_number_',
        #              'filename_'
        #         }
        #
        #     ]
        # }

        def BuildRange(filename, start, end):
            return responses.Range(
                responses.Location(start['line'] + 1, start['ch'] + 1,
                                   filename),
                responses.Location(end['line'] + 1, end['ch'] + 1, filename))

        def BuildFixItChunk(change):
            return responses.FixItChunk(
                change['text'],
                BuildRange(os.path.abspath(change['file']), change['start'],
                           change['end']))

        # From an API perspective, Refactor and FixIt are the same thing - it just
        # applies a set of changes to a set of files. So we re-use all of the
        # existing FixIt infrastructure.
        return responses.BuildFixItResponse([
            responses.FixIt(
                responses.Location(request_data['line_num'],
                                   request_data['column_num'],
                                   request_data['filepath']),
                [BuildFixItChunk(x) for x in response['changes']])
        ])