The code action resolve request is used to to resolve the edit field for a given code action, if it is not already provided in the textDocument/codeAction response. We can use it for scenarios that require more computation such as refactoring.
# File lib/ruby_lsp/requests/code_action_resolve.rb, line 45defperformreturnError::EmptySelectionif@document.source.empty?source_range = @code_action.dig(:data, :range)
returnError::EmptySelectionifsource_range[:start] ==source_range[:end]
scanner = @document.create_scannerstart_index = scanner.find_char_position(source_range[:start])
end_index = scanner.find_char_position(source_range[:end])
extracted_source = T.must(@document.source[start_index...end_index])
# Find the closest statements node, so that we place the refactor in a valid positionclosest_statements, parent_statements = @document
.locate(@document.tree, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
returnError::InvalidTargetRangeifclosest_statements.nil?||closest_statements.child_nodes.compact.empty?# Find the node with the end line closest to the requested position, so that we can place the refactor# immediately after that closest nodeclosest_node = T.must(closest_statements.child_nodes.compact.min_bydo|node|distance = source_range.dig(:start, :line) - (node.location.end_line-1)
distance<=0?Float::INFINITY:distanceend)
returnError::InvalidTargetRangeifclosest_node.is_a?(Prism::MissingNode)
closest_node_loc = closest_node.location# If the parent expression is a single line block, then we have to extract it inside of the oneline blockifparent_statements.is_a?(Prism::BlockNode) &&parent_statements.location.start_line==parent_statements.location.end_linevariable_source = " #{NEW_VARIABLE_NAME} = #{extracted_source};"character = source_range.dig(:start, :character) -1target_range = {
start: { line:closest_node_loc.end_line-1, character:character },
end: { line:closest_node_loc.end_line-1, character:character },
}
else# If the closest node covers the requested location, then we're extracting a statement nested inside of it. In# that case, we want to place the extraction at the start of the closest node (one line above). Otherwise, we# want to place the extract right below the closest nodeifclosest_node_loc.start_line-1<=source_range.dig(
:start,
:line,
) &&closest_node_loc.end_line-1>=source_range.dig(:end, :line)
indentation_line_number = closest_node_loc.start_line-1target_line = indentation_line_numberelsetarget_line = closest_node_loc.end_lineindentation_line_number = closest_node_loc.end_line-1endlines = @document.source.linesindentation_line = lines[indentation_line_number]
returnError::InvalidTargetRangeunlessindentation_lineindentation = T.must(indentation_line[/\A */]).sizetarget_range = {
start: { line:target_line, character:indentation },
end: { line:target_line, character:indentation },
}
line = lines[target_line]
returnError::InvalidTargetRangeunlesslinevariable_source = ifline.strip.empty?"\n#{" " * indentation}#{NEW_VARIABLE_NAME} = #{extracted_source}"else"#{NEW_VARIABLE_NAME} = #{extracted_source}\n#{" " * indentation}"endendInterface::CodeAction.new(
title:"Refactor: Extract Variable",
edit:Interface::WorkspaceEdit.new(
document_changes: [
Interface::TextDocumentEdit.new(
text_document:Interface::OptionalVersionedTextDocumentIdentifier.new(
uri:@code_action.dig(:data, :uri),
version:nil,
),
edits: [
create_text_edit(source_range, NEW_VARIABLE_NAME),
create_text_edit(target_range, variable_source),
],
),
],
),
)
end