def _GoToSymbol(self, request_data, args): request = self._DefaultParameters(request_data) request.update({'Language': 'C#', 'Filter': args[0]}) response = self._GetResponse('/findsymbols', request) quickfixes = response['QuickFixes'] if quickfixes: if len(quickfixes) == 1: ref = quickfixes[0] ref_file = ref['FileName'] ref_line = ref['Line'] lines = GetFileLines(request_data, ref_file) line = lines[min(len(lines), ref_line - 1)] return responses.BuildGoToResponseFromLocation( _BuildLocation(request_data, ref_file, ref_line, ref['Column']), line) else: goto_locations = [] for ref in quickfixes: ref_file = ref['FileName'] ref_line = ref['Line'] lines = GetFileLines(request_data, ref_file) line = lines[min(len(lines), ref_line - 1)] goto_locations.append( responses.BuildGoToResponseFromLocation( _BuildLocation(request_data, ref_file, ref_line, ref['Column']), line)) return goto_locations else: raise RuntimeError('No symbols found')
def BuildFixItChunk(change): filepath = self._ServerPathToAbsolute(change['file']) file_contents = GetFileLines(request_data, filepath) return responses.FixItChunk( change['text'], BuildRange(file_contents, filepath, change['start'], change['end']))
def _CallHierarchy(self, request_data, args): self._Reload(request_data) response = self._SendRequest( f'provideCallHierarchy{ args[ 0 ] }Calls', { 'file': request_data['filepath'], 'line': request_data['line_num'], 'offset': request_data['column_codepoint'] }) goto_response = [] for hierarchy_item in response: description = hierarchy_item.get('from', hierarchy_item.get('to')) filepath = description['file'] start_position = hierarchy_item['fromSpans'][0]['start'] goto_line = start_position['line'] try: line_value = GetFileLines(request_data, filepath)[goto_line - 1] except IndexError: continue goto_column = utils.CodepointOffsetToByteOffset( line_value, start_position['offset']) goto_response.append( responses.BuildGoToResponse(filepath, goto_line, goto_column, description['name'])) if goto_response: return goto_response raise RuntimeError(f'No { args[ 0 ].lower() } calls found.')
def _GoToSymbol( self, request_data, args ): if len( args ) < 1: raise RuntimeError( 'Must specify something to search for' ) query = args[ 0 ] self._Reload( request_data ) filespans = self._SendRequest( 'navto', { 'searchValue': query, 'file': request_data[ 'filepath' ] } ) if not filespans: raise RuntimeError( 'Symbol not found' ) results = [ responses.BuildGoToResponseFromLocation( _BuildLocation( GetFileLines( request_data, fs[ 'file' ] ), fs[ 'file' ], fs[ 'start' ][ 'line' ], fs[ 'start' ][ 'offset' ] ), fs[ 'name' ] ) for fs in filespans ] if len( results ) == 1: return results[ 0 ] return results
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 _BuildTsFormatRange(request_data): filepath = request_data['filepath'] lines = GetFileLines(request_data, filepath) if 'range' not in request_data: return { 'file': filepath, 'line': 1, 'offset': 1, 'endLine': len(lines), 'endOffset': len(lines[-1]) + 1 } start = request_data['range']['start'] start_line_num = start['line_num'] start_line_value = lines[start_line_num - 1] start_codepoint = utils.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 - 1] end_codepoint = utils.ByteOffsetToCodepointOffset(end_line_value, end['column_num']) return { 'file': filepath, 'line': start_line_num, 'offset': start_codepoint, 'endLine': end_line_num, 'endOffset': end_codepoint }
def BuildRefResponse( ref ): filepath = self._ServerPathToAbsolute( ref[ 'file' ] ) return responses.BuildGoToResponseFromLocation( _BuildLocation( GetFileLines( request_data, filepath ), filepath, ref[ 'start' ][ 'line' ], ref[ 'start' ][ 'ch' ] ) )
def _BuildFixItChunksForFile( request_data, new_name, file_replacement ): """Returns a list of FixItChunk for each replacement range for the supplied file.""" # On Windows, TSServer annoyingly returns file path as C:/blah/blah, # whereas all other paths in Python are of the C:\\blah\\blah form. We use # normpath to have python do the conversion for us. file_path = os.path.normpath( file_replacement[ 'file' ] ) file_contents = GetFileLines( request_data, file_path ) return [ _BuildFixItChunkForRange( new_name, file_contents, file_path, r ) for r in file_replacement[ 'locs' ] ]
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)
def _GoToDefinition(self, request_data): query = { 'type': 'definition', } response = self._GetResponse(query, request_data['column_codepoint'], request_data) filepath = self._ServerPathToAbsolute(response['file']) return responses.BuildGoToResponseFromLocation( _BuildLocation(GetFileLines(request_data, filepath), filepath, response['start']['line'], response['start']['ch']))
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)
def _BuildFixItForChanges(request_data, changes): """Returns a list of FixItChunk given a list of TSServer changes.""" chunks = [] for change in changes: # On Windows, TSServer annoyingly returns file path as C:/blah/blah, # whereas all other paths in Python are of the C:\\blah\\blah form. We use # normpath to have python do the conversion for us. file_path = os.path.normpath(change['fileName']) file_contents = GetFileLines(request_data, file_path) for text_change in change['textChanges']: chunks.append( _BuildFixItChunkForRange(text_change['newText'], file_contents, file_path, text_change)) return chunks
def _GoToReferences(self, request_data): self._Reload(request_data) response = self._SendRequest( 'references', { 'file': request_data['filepath'], 'line': request_data['line_num'], 'offset': request_data['column_codepoint'] }) return [ responses.BuildGoToResponseFromLocation( _BuildLocation(GetFileLines(request_data, ref['file']), ref['file'], ref['start']['line'], ref['start']['offset']), ref['lineText']) for ref in response['refs'] ]
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 )
def _GoToDefinition(self, request_data): self._Reload(request_data) filespans = self._SendRequest( 'definition', { 'file': request_data['filepath'], 'line': request_data['line_num'], 'offset': request_data['column_codepoint'] }) if not filespans: raise RuntimeError('Could not find definition.') span = filespans[0] return responses.BuildGoToResponseFromLocation( _BuildLocation(GetFileLines(request_data, span['file']), span['file'], span['start']['line'], span['start']['offset']))
def _GoToImplementation( self, request_data ): self._Reload( request_data ) try: filespans = self._SendRequest( 'implementation', { 'file': request_data[ 'filepath' ], 'line': request_data[ 'line_num' ], 'offset': request_data[ 'column_codepoint' ] } ) except RuntimeError: raise RuntimeError( 'No implementation found.' ) results = [] for span in filespans: filename = span[ 'file' ] start = span[ 'start' ] lines = GetFileLines( request_data, span[ 'file' ] ) line_num = start[ 'line' ] results.append( responses.BuildGoToResponseFromLocation( _BuildLocation( lines, filename, line_num, start[ 'offset' ] ), lines[ line_num - 1 ] ) ) return results
def GetDetailedDiagnostic(self, request_data): self._UpdateServerWithFileContents(request_data) current_line_lsp = request_data['line_num'] - 1 current_file = request_data['filepath'] if not self._latest_diagnostics: return responses.BuildDisplayMessageResponse( 'Diagnostics are not ready yet.') with self._server_info_mutex: diagnostics = list( self._latest_diagnostics[lsp.FilePathToUri(current_file)]) if not diagnostics: return responses.BuildDisplayMessageResponse( 'No diagnostics for current file.') current_column = lsp.CodepointsToUTF16CodeUnits( GetFileLines(request_data, current_file)[current_line_lsp], request_data['column_codepoint']) minimum_distance = None message = 'No diagnostics for current line.' for diagnostic in diagnostics: start = diagnostic['range']['start'] end = diagnostic['range']['end'] if current_line_lsp < start['line'] or end[ 'line'] < current_line_lsp: continue point = {'line': current_line_lsp, 'character': current_column} distance = DistanceOfPointToRange(point, diagnostic['range']) if minimum_distance is None or distance < minimum_distance: message = diagnostic['message'] if distance == 0: break minimum_distance = distance return responses.BuildDisplayMessageResponse(message)