Skip to content
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
25 changes: 22 additions & 3 deletions lib/ruby_lsp/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -428,8 +428,22 @@ def text_document_did_close(message)
def text_document_did_change(message)
params = message[:params]
text_document = params[:textDocument]
uri = text_document[:uri]

@store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
# Text synchronization notifications are normally not parsed by the main thread. Parse here so we can determine
# whether the edit affects declarations before an editor sends another request.
document = @global_state.synchronize do
@store.push_edits(uri: uri, edits: params[:contentChanges], version: text_document[:version])
updated_document = @store.get(uri)
updated_document.parse! if updated_document.is_a?(RubyDocument)
updated_document
end

return unless document.is_a?(RubyDocument) && document.should_index?

# Populate the same request caches as run_combined_requests while indexing. If the editor asks for one of these
# features after the edit, it can use the results from this traversal instead of traversing the AST again.
compute_combined_requests(uri, document)
end

#: (Hash[Symbol, untyped] message) -> void
Expand Down Expand Up @@ -475,6 +489,13 @@ def run_combined_requests(message)
return
end

compute_combined_requests(uri, document)

send_message(Result.new(id: message[:id], response: document.cache_get(message[:method])))
end

#: (URI::Generic uri, RubyDocument | ERBDocument document) -> void
def compute_combined_requests(uri, document)
parse_result = document.parse_result

# Run requests for the document
Expand Down Expand Up @@ -521,8 +542,6 @@ def run_combined_requests(message)
.perform,
)
document.cache_set("textDocument/inlayHint", inlay_hint.perform)

send_message(Result.new(id: message[:id], response: document.cache_get(message[:method])))
end

alias_method :text_document_document_symbol, :run_combined_requests
Expand Down
31 changes: 16 additions & 15 deletions test/server_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -423,10 +423,11 @@ def test_backtrace_is_printed_to_stderr_on_exceptions
content = log.params.message

assert_match(/boom/, content)
server_path = Regexp.escape(File.expand_path("../lib/ruby_lsp/server.rb", __dir__))
if RUBY_VERSION >= "3.4"
assert_match(%r{ruby-lsp/lib/ruby_lsp/server\.rb:\d+:in 'RubyLsp::Server#process_message'}, content)
assert_match(/#{server_path}:\d+:in 'RubyLsp::Server#process_message'/, content)
else
assert_match(%r{ruby-lsp/lib/ruby_lsp/server\.rb:\d+:in `process_message'}, content)
assert_match(/#{server_path}:\d+:in `process_message'/, content)
end
end

Expand Down Expand Up @@ -979,7 +980,7 @@ def test_requests_cancelled_during_processing_are_deleted_from_cancelled_request
assert_empty(@server.instance_variable_get(:@cancelled_requests))
end

def test_unsaved_changes_are_indexed_when_computing_automatic_features
def test_unsaved_changes_are_indexed_without_combined_requests
uri = URI("file:///foo.rb")
index = @server.global_state.index

Expand Down Expand Up @@ -1016,25 +1017,23 @@ def test_unsaved_changes_are_indexed_when_computing_automatic_features
},
})

# Parse the document after it was modified. This occurs automatically when we receive a text document request, to
# avoid parsing the document multiple times, but that depends on request coming in through the STDIN pipe, which
# isn't reproduced here. Parsing manually matches what happens normally
store = @server.instance_variable_get(:@store)
store.get(uri).parse!
# The edit should update the index without waiting for a combined feature request.
entries = index["Foo"]
assert_equal(1, entries.length)

# Trigger the automatic features again
entries = index["bar"]
assert_equal(1, entries.length)

# A combined request should be served from the results computed during didChange.
index.expects(:handle_change).never
@server.process_message({
id: 2,
method: "textDocument/documentSymbol",
params: { textDocument: { uri: uri } },
})

# There should still only be one entry for each declaration, but we should have picked up the new ones
entries = index["Foo"]
assert_equal(1, entries.length)

entries = index["bar"]
assert_equal(1, entries.length)
result = find_message(RubyLsp::Result, id: 2)
assert_equal(1, result.response.length)
end

def test_ancestors_are_recomputed_even_on_unsaved_changes
Expand Down Expand Up @@ -1080,6 +1079,8 @@ class Foo
},
})

assert_equal(["Foo::<Class:Foo>"], index.linearized_ancestors_of("Foo::<Class:Foo>"))

# Parse the document after it was modified. This occurs automatically when we receive a text document request, to
# avoid parsing the document multiple times, but that depends on request coming in through the STDIN pipe, which
# isn't reproduced here. Parsing manually matches what happens normally
Expand Down
Loading