diff --git a/ycmd/completers/language_server/language_server_completer.py b/ycmd/completers/language_server/language_server_completer.py index 489e15d08c..a294bcb1fe 100644 --- a/ycmd/completers/language_server/language_server_completer.py +++ b/ycmd/completers/language_server/language_server_completer.py @@ -3590,8 +3590,8 @@ def WorkspaceEditToFixIt( request_data, if not workspace_edit: return None + chunks = [] if 'changes' in workspace_edit: - chunks = [] # We sort the filenames to make the response stable. Edits are applied in # strict sequence within a file, but apply to files in arbitrary order. # However, it's important for the response to be stable for the tests. @@ -3599,12 +3599,32 @@ def WorkspaceEditToFixIt( request_data, chunks.extend( TextEditToChunks( request_data, uri, workspace_edit[ 'changes' ][ uri ] ) ) - else: - chunks = [] + if 'documentChanges' in workspace_edit: for text_document_edit in workspace_edit[ 'documentChanges' ]: - uri = text_document_edit[ 'textDocument' ][ 'uri' ] - edits = text_document_edit[ 'edits' ] - chunks.extend( TextEditToChunks( request_data, uri, edits ) ) + kind = text_document_edit.get( 'kind', '' ) + options = text_document_edit.get( 'options', {} ) + if 'edits' in text_document_edit: + uri = text_document_edit[ 'textDocument' ][ 'uri' ] + edits = text_document_edit[ 'edits' ] + chunks.extend( TextEditToChunks( request_data, uri, edits ) ) + elif kind == 'rename': + chunks.append( + responses.RenameChunk( + old_filepath = lsp.UriToFilePath( + text_document_edit[ 'oldUri' ] ), + new_filepath = lsp.UriToFilePath( + text_document_edit[ 'newUri' ] ), + options = options ) ) + elif kind == 'delete': + chunks.append( + responses.DeleteChunk( + lsp.UriToFilePath( text_document_edit[ 'uri' ] ), + options = options ) ) + elif kind == 'create': + chunks.append( + responses.CreateChunk( + lsp.UriToFilePath( text_document_edit[ 'uri' ] ), + options = options ) ) return responses.FixIt( responses.Location( request_data[ 'line_num' ], request_data[ 'column_num' ], diff --git a/ycmd/completers/language_server/language_server_protocol.py b/ycmd/completers/language_server/language_server_protocol.py index 365f8a04c5..c9a2e29377 100644 --- a/ycmd/completers/language_server/language_server_protocol.py +++ b/ycmd/completers/language_server/language_server_protocol.py @@ -307,7 +307,11 @@ def Initialize( request_id, 'didChangeWatchedFiles': { 'dynamicRegistration': True }, - 'workspaceEdit': { 'documentChanges': True, }, + 'workspaceEdit': { + 'resourceOperations': [ 'create', 'rename', 'delete' ], + 'documentChanges': True, + 'failureHandling': 'abort' + }, 'symbol': { 'symbolKind': { 'valueSet': list( range( 1, len( SYMBOL_KIND ) ) ), diff --git a/ycmd/responses.py b/ycmd/responses.py index de87a57f83..e0edced68d 100644 --- a/ycmd/responses.py +++ b/ycmd/responses.py @@ -268,6 +268,47 @@ def __init__( self, location: Location, chunks, text = '', kind = None ): self.kind = kind +class DeleteChunk: + def __init__( self, filepath, options ): + self.filepath = filepath + self.options = options + + def ToYcmdProtocol( self ): + return { + 'filepath': self.filepath, + 'kind': 'delete', + 'options': self.options + } + + +class CreateChunk: + def __init__( self, filepath, options ): + self.filepath = filepath + self.options = options + + def ToYcmdProtocol( self ): + return { + 'filepath': self.filepath, + 'kind': 'create', + 'options': self.options + } + + +class RenameChunk: + def __init__( self, old_filepath, new_filepath, options ): + self.old_filepath = old_filepath + self.new_filepath = new_filepath + self.options = options + + def ToYcmdProtocol( self ): + return { + 'new_filepath': self.new_filepath, + 'old_filepath': self.old_filepath, + 'kind': 'rename', + 'options': self.options + } + + class FixItChunk: """An individual replacement within a FixIt (aka Refactor)""" @@ -276,6 +317,13 @@ def __init__( self, replacement_text: str, range: Range ): self.replacement_text = replacement_text self.range = range + def ToYcmdProtocol( self ): + return { + 'replacement_text': self.replacement_text, + 'range': BuildRangeData( self.range ), + 'kind': 'change' + } + def BuildDiagnosticData( diagnostic ): kind = ( diagnostic.kind_.name if hasattr( diagnostic.kind_, 'name' ) @@ -314,12 +362,6 @@ def BuildFixItResponse( fixits ): can be used to apply arbitrary changes to arbitrary files and is suitable for both quick fix and refactor operations""" - def BuildFixitChunkData( chunk ): - return { - 'replacement_text': chunk.replacement_text, - 'range': BuildRangeData( chunk.range ), - } - def BuildFixItData( fixit ): if hasattr( fixit, 'resolve' ): result = { @@ -331,7 +373,7 @@ def BuildFixItData( fixit ): else: result = { 'location': BuildLocationData( fixit.location ), - 'chunks' : [ BuildFixitChunkData( x ) for x in fixit.chunks ], + 'chunks' : [ x.ToYcmdProtocol() for x in fixit.chunks ], 'text': fixit.text, 'kind': fixit.kind, 'resolve': False