Skip to content

Commit

Permalink
Index documents on modification
Browse files Browse the repository at this point in the history
  • Loading branch information
vinistock committed Nov 29, 2024
1 parent 0a82b93 commit 826bd1a
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 4 deletions.
11 changes: 8 additions & 3 deletions lib/ruby_indexer/lib/ruby_indexer/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ def initialize
)

@configuration = T.let(RubyIndexer::Configuration.new, Configuration)

@initial_indexing_complete = T.let(false, T::Boolean)
end

# Register an included `hook` that will be executed when `module_name` is included into any namespace
Expand All @@ -56,8 +58,8 @@ def register_included_hook(module_name, &hook)
(@included_hooks[module_name] ||= []) << hook
end

sig { params(uri: URI::Generic).void }
def delete(uri)
sig { params(uri: URI::Generic, skip_require_tree: T::Boolean).void }
def delete(uri, skip_require_tree: false)
key = uri.to_s
# For each constant discovered in `path`, delete the associated entry from the index. If there are no entries
# left, delete the constant from the index.
Expand All @@ -80,6 +82,7 @@ def delete(uri)
end

@uris_to_entries.delete(key)
return if skip_require_tree

require_path = uri.require_path
@require_paths_tree.delete(require_path) if require_path
Expand Down Expand Up @@ -358,11 +361,13 @@ def index_all(uris: @configuration.indexable_uris, &block)
# existing index values, meaning it may contain 'stale' entries. This check ensures that the user is aware of this
# behavior and can take appropriate action.
# binding.break
if @entries.any?
if @initial_indexing_complete
raise IndexNotEmptyError,
"The index is not empty. To prevent invalid entries, `index_all` can only be called once."
end

@initial_indexing_complete = true

RBSIndexer.new(self).index_ruby_core
# Calculate how many paths are worth 1% of progress
progress_step = (uris.length / 100.0).ceil
Expand Down
3 changes: 2 additions & 1 deletion lib/ruby_indexer/test/index_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2061,7 +2061,8 @@ def test_build_non_redundant_name
end

def test_prevents_multiple_calls_to_index_all
# For this test class, `index_all` is already called once in `setup`.
@index.index_all

assert_raises(Index::IndexNotEmptyError) do
@index.index_all
end
Expand Down
12 changes: 12 additions & 0 deletions lib/ruby_lsp/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,18 @@ def run_combined_requests(message)
document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
inlay_hint = Requests::InlayHints.new(document, T.must(@store.features_configuration.dig(:inlayHint)), dispatcher)

# Re-index the file as it is modified. This mode of indexing updates entries only. Require path trees are only
# updated on save
@global_state.index.delete(uri, skip_require_tree: true)
RubyIndexer::DeclarationListener.new(
@global_state.index,
dispatcher,
parse_result,
uri,
collect_comments: true,
)

dispatcher.dispatch(parse_result.value)

# Store all responses retrieve in this round of visits in the cache and then return the response for the request
Expand Down
58 changes: 58 additions & 0 deletions test/server_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,64 @@ def test_cancelling_requests_returns_expected_error_code
assert_equal("Request 1 was cancelled", error.message)
end

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

# Simulate opening a file. First, send the notification to open the file with a class inside
@server.process_message({
method: "textDocument/didOpen",
params: {
textDocument: {
uri: uri,
text: +"class Foo\nend",
version: 1,
languageId: "ruby",
},
},
})
# Fire the automatic features requests to trigger indexing
@server.process_message({
id: 1,
method: "textDocument/documentSymbol",
params: { textDocument: { uri: uri } },
})

entries = index["Foo"]
assert_equal(1, entries.length)

# Modify the file without saving
@server.process_message({
method: "textDocument/didChange",
params: {
textDocument: { uri: uri, version: 2 },
contentChanges: [
{ text: " def bar\n end\n", range: { start: { line: 1, character: 0 }, end: { line: 1, character: 0 } } },
],
},
})

# 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!

# Trigger the automatic features again
@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)
end

private

def with_uninstalled_rubocop(&block)
Expand Down

0 comments on commit 826bd1a

Please sign in to comment.