Class: RubyLsp::Requests::CodeActionResolve

Inherits:
BaseRequest
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/ruby_lsp/requests/code_action_resolve.rb

Overview

Code action resolve demo

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.

Example: Extract to variable

# Before:
1 + 1 # Select the text and use Refactor: Extract Variable

# After:
new_variable = 1 + 1
new_variable

Defined Under Namespace

Classes: CodeActionError, Error

Constant Summary collapse

NEW_VARIABLE_NAME =
"new_variable"

Instance Method Summary collapse

Methods inherited from BaseRequest

#full_constant_name, #locate, #range_from_syntax_tree_node, #visible?, #visit_all

Constructor Details

#initialize(document, code_action) ⇒ CodeActionResolve

Returns a new instance of CodeActionResolve.



38
39
40
41
42
# File 'lib/ruby_lsp/requests/code_action_resolve.rb', line 38

def initialize(document, code_action)
  super(document)

  @code_action = code_action
end

Instance Method Details

#runObject



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/ruby_lsp/requests/code_action_resolve.rb', line 45

def run
  source_range = @code_action.dig(:data, :range)
  return Error::EmptySelection if source_range[:start] == source_range[:end]

  return Error::InvalidTargetRange if @document.syntax_error?

  scanner = @document.create_scanner
  start_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 position
  closest_statements = locate(T.must(@document.tree), start_index, node_types: [SyntaxTree::Statements]).first
  return Error::InvalidTargetRange if closest_statements.nil?

  # Find the node with the end line closest to the requested position, so that we can place the refactor
  # immediately after that closest node
  closest_node = closest_statements.child_nodes.compact.min_by do |node|
    distance = source_range.dig(:start, :line) - (node.location.end_line - 1)
    distance <= 0 ? Float::INFINITY : distance
  end

  # When trying to extract the first node inside of a statements block, then we can just select one line above it
  target_line = if closest_node == closest_statements.child_nodes.first
    closest_node.location.start_line - 1
  else
    closest_node.location.end_line
  end

  lines = @document.source.lines
  indentation = T.must(T.must(lines[target_line - 1])[/\A */]).size

  target_range = {
    start: { line: target_line, character: indentation },
    end: { line: target_line, character: indentation },
  }

  variable_source = if T.must(lines[target_line]).strip.empty?
    "\n#{" " * indentation}#{NEW_VARIABLE_NAME} = #{extracted_source}"
  else
    "#{NEW_VARIABLE_NAME} = #{extracted_source}\n#{" " * indentation}"
  end

  Interface::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