Example #1
0
def _BuildChunks(request_data, new_buffer):
    filepath = request_data['filepath']
    old_buffer = request_data['file_data'][filepath]['contents']
    new_buffer = _FixLineEndings(old_buffer, new_buffer)

    new_length = len(new_buffer)
    old_length = len(old_buffer)
    if new_length == old_length and new_buffer == old_buffer:
        return []
    min_length = min(new_length, old_length)
    start_index = 0
    end_index = min_length
    for i in range(0, min_length - 1):
        if new_buffer[i] != old_buffer[i]:
            start_index = i
            break
    for i in range(1, min_length):
        if new_buffer[new_length - i] != old_buffer[old_length - i]:
            end_index = i - 1
            break
    # To handle duplicates, i.e aba => a
    if (start_index + end_index > min_length):
        start_index -= start_index + end_index - min_length

    replacement_text = new_buffer[start_index:new_length - end_index]

    (start_line, start_column) = _IndexToLineColumn(old_buffer, start_index)
    (end_line, end_column) = _IndexToLineColumn(old_buffer,
                                                old_length - end_index)
    start = responses.Location(start_line, start_column, filepath)
    end = responses.Location(end_line, end_column, filepath)
    return [
        responses.FixItChunk(replacement_text, responses.Range(start, end))
    ]
Example #2
0
def _BuildLocation(split_lines, filename, line_num, column_num):
    if (line_num <= 0 or column_num <= 0 or line_num - 1 >= len(split_lines)):
        return responses.Location(line_num, 0, filename)

    line_value = split_lines[line_num - 1]
    return responses.Location(
        line_num, utils.CodepointOffsetToByteOffset(line_value, column_num),
        filename)
Example #3
0
 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 ) )
Example #4
0
def _BuildFixItChunkForRange(new_name, file_name, source_range):
    """ returns list FixItChunk for a tsserver source range """
    return responses.FixItChunk(
        new_name,
        responses.Range(start=responses.Location(
            source_range['start']['line'], source_range['start']['offset'],
            file_name),
                        end=responses.Location(source_range['end']['line'],
                                               source_range['end']['offset'],
                                               file_name)))
Example #5
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)
Example #6
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)
Example #7
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)])
Example #8
0
def _BuildLocation(file_contents, filename, line, ch):
    # tern returns codepoint offsets, but we need byte offsets, so we must
    # convert
    return responses.Location(line=line + 1,
                              column=utils.CodepointOffsetToByteOffset(
                                  file_contents[line], ch + 1),
                              filename=os.path.realpath(filename))
Example #9
0
def _BuildLocation( request_data, filename, line_num, column_num ):
  contents = utils.SplitLines( GetFileContents( request_data, filename ) )
  line_value = contents[ line_num - 1 ]
  return responses.Location(
      line_num,
      CodepointOffsetToByteOffset( line_value, column_num ),
      filename )
Example #10
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)])
Example #11
0
def _BuildLocation( file_contents, filename, line, offset ):
  return responses.Location(
    line = line,
    # tsserver returns codepoint offsets, but we need byte offsets, so we must
    # convert
    column = utils.CodepointOffsetToByteOffset( file_contents[ line - 1 ],
                                                offset ),
    filename = filename )
Example #12
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 )
Example #13
0
def TsDiagnosticToYcmdDiagnostic(filepath, line_value, ts_diagnostic):
    ts_start_location = ts_diagnostic['startLocation']
    ts_end_location = ts_diagnostic['endLocation']

    start_offset = utils.CodepointOffsetToByteOffset(
        line_value, ts_start_location['offset'])
    end_offset = utils.CodepointOffsetToByteOffset(line_value,
                                                   ts_end_location['offset'])

    location = responses.Location(ts_start_location['line'], start_offset,
                                  filepath)
    location_end = responses.Location(ts_end_location['line'], end_offset,
                                      filepath)

    location_extent = responses.Range(location, location_end)

    return responses.Diagnostic([location_extent], location, location_extent,
                                ts_diagnostic['message'], 'ERROR')
Example #14
0
    def _QuickFixToDiagnostic(self, quick_fix):
        filename = quick_fix["FileName"]

        location = responses.Location(quick_fix["Line"], quick_fix["Column"],
                                      filename)
        location_range = responses.Range(location, location)
        return responses.Diagnostic(list(), location, location_range,
                                    quick_fix["Text"],
                                    quick_fix["LogLevel"].upper())
Example #15
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)
Example #16
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])
Example #17
0
def _BuildLocation(request_data, filename, line_num, column_num):
    if line_num <= 0:
        return None
    # OmniSharp sometimes incorrectly returns 0 for the column number. Assume the
    # column is 1 in that case.
    if column_num <= 0:
        column_num = 1
    contents = GetFileLines(request_data, filename)
    line_value = contents[line_num - 1]
    return responses.Location(
        line_num, CodepointOffsetToByteOffset(line_value, column_num),
        filename)
Example #18
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 {}
Example #19
0
def TsDiagnosticToYcmdDiagnostic(request_data, ts_diagnostic):
    filepath = request_data['filepath']
    contents = request_data['lines']

    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')
Example #20
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)
Example #21
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 )
Example #22
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))
        ])
Example #23
0
def _OffsetToPosition(offset, filename, text, newlines):
    """Convert the 0-based codepoint offset |offset| to a position (line/col) in
  |text|. |filename| is the full path of the file containing |text| and
  |newlines| is a cache of the 0-based character offsets of all the \n
  characters in |text| (plus one extra). Returns responses.Position."""

    for index, newline in enumerate(newlines):
        if newline >= offset:
            start_of_line = newlines[index - 1] + 1 if index > 0 else 0
            column = offset - start_of_line
            line_value = text[start_of_line:newline]
            return responses.Location(
                index + 1, CodepointOffsetToByteOffset(line_value, column + 1),
                filename)

    # Invalid position - it's outside of the text. Just return the last
    # position in the text. This is an internal error.
    LOGGER.error("Invalid offset %s in file %s with text %s and newlines %s",
                 offset, filename, text, newlines)
    raise RuntimeError("Invalid file offset in diff")
Example #24
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']])
        ])
Example #25
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']])
        ])