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 ) ] )
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)])
def BuildExtraData(completion_data): extra_data = {} fixit = completion_data.fixit_ if fixit.chunks: extra_data.update(responses.BuildFixItResponse([fixit])) if completion_data.DocString(): extra_data['doc_string'] = completion_data.DocString() return extra_data
def OrganizeImports(self, request_data): workspace_edit = self.GetCommandResponse( request_data, 'java.edit.organizeImports', [lsp.FilePathToUri(request_data['filepath'])]) fixit = language_server_completer.WorkspaceEditToFixIt( request_data, workspace_edit) return responses.BuildFixItResponse([fixit])
def WorkspaceEditToFixIt_test(): if utils.OnWindows(): filepath = 'C:\\test.test' uri = 'file:///c:/test.test' else: filepath = '/test.test' uri = 'file:/test.test' contents = 'line1\nline2\nline3' request_data = RequestWrap( BuildRequest(filetype='ycmtest', filepath=filepath, contents=contents)) # We don't support versioned documentChanges assert_that( lsc.WorkspaceEditToFixIt(request_data, {'documentChanges': []}), equal_to(None)) workspace_edit = { 'changes': { uri: [ { 'newText': 'blah', 'range': { 'start': { 'line': 0, 'character': 5 }, 'end': { 'line': 0, 'character': 5 }, } }, ] } } response = responses.BuildFixItResponse( [lsc.WorkspaceEditToFixIt(request_data, workspace_edit, 'test')]) print('Response: {0}'.format(response)) print('Type Response: {0}'.format(type(response))) assert_that( response, has_entries({ 'fixits': contains( has_entries({ 'text': 'test', 'chunks': contains( ChunkMatcher('blah', LocationMatcher(filepath, 1, 6), LocationMatcher(filepath, 1, 6))) })) }))
def _RefactorRename(self, request_data, args): request = self._DefaultParameters(request_data) if len(args) != 1: raise ValueError('Please specify a new name to rename it to.\n' 'Usage: RefactorRename <new name>') request['RenameTo'] = args[0] request['WantsTextChanges'] = True response = self._GetResponse('/rename', request) fixit = _ModifiedFilesToFixIt(response['Changes'], request_data) return responses.BuildFixItResponse([fixit])
def BuildExtraData(completion_data): extra_data = {} fixit = completion_data.fixit_ if fixit.chunks: extra_data.update(responses.BuildFixItResponse([fixit])) try: if completion_data.DocString(): extra_data['doc_string'] = completion_data.DocString() except UnicodeDecodeError: pass return extra_data
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 {}
def _FixIt( self, request_data ): request = self._DefaultParameters( request_data ) result = self._GetResponse( '/fixcodeissue', request ) replacement_text = result[ "Text" ] location = CsharpDiagnosticLocation( request_data['line_num'], request_data['column_num'], request_data['filepath'] ) fixits = [ CsharpFixIt( location, _BuildChunks( request_data, replacement_text ) ) ] return responses.BuildFixItResponse( fixits )
def _RefactorRename(self, request_data, args): if len(args) < 1: raise RuntimeError('Must specify a new name') new_name = args[0] with self._jedi_lock: refactoring = self._GetJediScript(request_data).rename( line=request_data['line_num'], column=request_data['column_codepoint'] - 1, new_name=new_name) return responses.BuildFixItResponse( [_RefactoringToFixIt(refactoring)])
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 )
def _FixIt(self, request_data, args): self._Reload(request_data) filepath = request_data['filepath'] line_num = request_data['line_num'] fixits = [] with self._latest_diagnostics_for_file_lock: for diagnostic in self._latest_diagnostics_for_file[filepath]: if diagnostic.location_.line_number_ != line_num: continue fixits.extend(diagnostic.fixits_) return responses.BuildFixItResponse(fixits)
def _FixIt(self, request_data): request = self._DefaultParameters(request_data) request['WantsTextChanges'] = True result = self._GetResponse('/getcodeactions', request) fixits = [] for i, code_action_name in enumerate(result['CodeActions']): fixit = responses.UnresolvedFixIt({'index': i}, code_action_name) fixits.append(fixit) if len(fixits) == 1: fixit = fixits[0] fixit = {'command': fixit.command, 'resolve': fixit.resolve} return self._ResolveFixIt(request_data, fixit) return responses.BuildFixItResponse(fixits)
def _FixIt(self, request_data): flags, filename = self._FlagsForRequest(request_data) if not flags: raise ValueError(NO_COMPILE_FLAGS_MESSAGE) files = self.GetUnsavedFilesVector(request_data) line = request_data['line_num'] column = request_data['column_num'] fixits = getattr(self._completer, "GetFixItsForLocationInFile")( ToCppStringCompatible(filename), ToCppStringCompatible(request_data['filepath']), line, column, files, flags, True) # don't raise an error if not fixits: - leave that to the client to respond # in a nice way return responses.BuildFixItResponse(fixits)
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)) ])
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 ] )
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 )
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])
def WorkspaceEditToFixIt_test(): if utils.OnWindows(): filepath = 'C:\\test.test' uri = 'file:///c:/test.test' else: filepath = '/test.test' uri = 'file:/test.test' contents = 'line1\nline2\nline3' request_data = RequestWrap( BuildRequest( filetype = 'ycmtest', filepath = filepath, contents = contents ) ) # Null response to textDocument/codeActions is valid assert_that( lsc.WorkspaceEditToFixIt( request_data, None ), equal_to( None ) ) # Empty WorkspaceEdit is not explicitly forbidden assert_that( lsc.WorkspaceEditToFixIt( request_data, {} ), equal_to( None ) ) # We don't support versioned documentChanges workspace_edit = { 'documentChanges': [ { 'textDocument': { 'version': 1, 'uri': uri }, 'edits': [ { 'newText': 'blah', 'range': { 'start': { 'line': 0, 'character': 5 }, 'end': { 'line': 0, 'character': 5 }, } } ] } ] } response = responses.BuildFixItResponse( [ lsc.WorkspaceEditToFixIt( request_data, workspace_edit, 'test' ) ] ) print( f'Response: { response }' ) assert_that( response, has_entries( { 'fixits': contains_exactly( has_entries( { 'text': 'test', 'chunks': contains_exactly( ChunkMatcher( 'blah', LocationMatcher( filepath, 1, 6 ), LocationMatcher( filepath, 1, 6 ) ) ) } ) ) } ) ) workspace_edit = { 'changes': { uri: [ { 'newText': 'blah', 'range': { 'start': { 'line': 0, 'character': 5 }, 'end': { 'line': 0, 'character': 5 }, } }, ] } } response = responses.BuildFixItResponse( [ lsc.WorkspaceEditToFixIt( request_data, workspace_edit, 'test' ) ] ) print( f'Response: { response }' ) print( f'Type Response: { type( response ) }' ) assert_that( response, has_entries( { 'fixits': contains_exactly( has_entries( { 'text': 'test', 'chunks': contains_exactly( ChunkMatcher( 'blah', LocationMatcher( filepath, 1, 6 ), LocationMatcher( filepath, 1, 6 ) ) ) } ) ) } ) )
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']]) ])
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']]) ])