Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Add support for file operations in CodeActions #1766

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions ycmd/completers/language_server/language_server_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3590,21 +3590,41 @@ 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.
for uri in sorted( workspace_edit[ 'changes' ].keys() ):
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' ],
Expand Down
6 changes: 5 additions & 1 deletion ycmd/completers/language_server/language_server_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) ) ),
Expand Down
56 changes: 49 additions & 7 deletions ycmd/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)"""

Expand All @@ -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' )
Expand Down Expand Up @@ -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 = {
Expand All @@ -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
Expand Down
Loading