Skip to content

Commit 45aa0d7

Browse files
committed
Optimise didChangeWatchedFiles for large file changes
Right now, didChangeWatchedFiles applies one-by-one on each file. Each time it does this, it marks the file as dirty (when indexing is enabled), then attempts to parse the file. This is non-optimal if there are a large set of related file changes. Each time, we'll mark as dirty, then parse a single file (which will parse its dependencies). Then, move to the next file, mark it as dirty and repeat. But when we mark it as dirty, we've invalidated all the work we've just done. We fix this by deferring parsing to after all the changes have been processed, and the dirty list constructed. This prevents unnecessary invalidation. Also, we now skip ignored files from indexing.
1 parent 958359e commit 45aa0d7

File tree

4 files changed

+73
-42
lines changed

4 files changed

+73
-42
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
99
### Changed
1010

1111
- Sync to upstream Luau 0.666
12+
- Optimized re-indexing on changed files when large number of changes processed at once
13+
- Watched files re-indexing now respects ignore globs specification
1214

1315
### Fixed
1416

src/LanguageServer.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,11 +733,18 @@ void LanguageServer::onDidChangeWorkspaceFolders(const lsp::DidChangeWorkspaceFo
733733

734734
void LanguageServer::onDidChangeWatchedFiles(const lsp::DidChangeWatchedFilesParams& params)
735735
{
736+
Luau::DenseHashMap<WorkspaceFolderPtr, std::vector<lsp::FileEvent>> workspaceChanges{nullptr};
737+
736738
for (const auto& change : params.changes)
737739
{
738740
auto workspace = findWorkspace(change.uri);
739-
workspace->onDidChangeWatchedFiles(change);
741+
if (!workspaceChanges.find(workspace))
742+
workspaceChanges[workspace] = {};
743+
workspaceChanges[workspace].push_back(change);
740744
}
745+
746+
for (const auto& [workspace, changes] : workspaceChanges)
747+
workspace->onDidChangeWatchedFiles(changes);
741748
}
742749

743750
Response LanguageServer::onShutdown([[maybe_unused]] const id_type& id)

src/Workspace.cpp

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,25 @@ void WorkspaceFolder::closeTextDocument(const lsp::DocumentUri& uri)
6767
clearDiagnosticsForFile(uri);
6868
}
6969

70-
void WorkspaceFolder::clearDiagnosticsForFile(const lsp::DocumentUri& uri)
70+
void WorkspaceFolder::clearDiagnosticsForFiles(const std::vector<lsp::DocumentUri>& uris) const
7171
{
7272
if (!client->capabilities.textDocument || !client->capabilities.textDocument->diagnostic)
7373
{
74-
client->publishDiagnostics(lsp::PublishDiagnosticsParams{uri, std::nullopt, {}});
74+
for (const auto& uri : uris)
75+
client->publishDiagnostics(lsp::PublishDiagnosticsParams{uri, std::nullopt, {}});
7576
}
7677
else if (client->workspaceDiagnosticsToken)
7778
{
78-
lsp::WorkspaceDocumentDiagnosticReport documentReport;
79-
documentReport.uri = uri;
80-
documentReport.kind = lsp::DocumentDiagnosticReportKind::Full;
81-
lsp::WorkspaceDiagnosticReportPartialResult report{{documentReport}};
79+
std::vector<lsp::WorkspaceDocumentDiagnosticReport> reports;
80+
reports.reserve(uris.size());
81+
for (const auto& uri : uris)
82+
{
83+
lsp::WorkspaceDocumentDiagnosticReport report;
84+
report.uri = uri;
85+
report.kind = lsp::DocumentDiagnosticReportKind::Full;
86+
reports.push_back(report);
87+
}
88+
lsp::WorkspaceDiagnosticReportPartialResult report{reports};
8289
client->sendProgress({client->workspaceDiagnosticsToken.value(), report});
8390
}
8491
else
@@ -87,52 +94,66 @@ void WorkspaceFolder::clearDiagnosticsForFile(const lsp::DocumentUri& uri)
8794
}
8895
}
8996

90-
void WorkspaceFolder::onDidChangeWatchedFiles(const lsp::FileEvent& change)
97+
void WorkspaceFolder::clearDiagnosticsForFile(const lsp::DocumentUri& uri)
98+
{
99+
clearDiagnosticsForFiles({uri});
100+
}
101+
102+
void WorkspaceFolder::onDidChangeWatchedFiles(const std::vector<lsp::FileEvent>& changes)
91103
{
92-
auto filePath = change.uri.fsPath();
104+
client->sendTrace("workspace: processing " + std::to_string(changes.size()) + " watched files changes");
105+
93106
auto config = client->getConfiguration(rootUri);
94107

95-
platform->onDidChangeWatchedFiles(change);
108+
std::vector<Luau::ModuleName> dirtyFiles;
109+
std::vector<Uri> deletedFiles;
96110

97-
if (filePath.filename() == ".luaurc")
111+
for (const auto& change : changes)
98112
{
99-
client->sendLogMessage(lsp::MessageType::Info, "Acknowledge config changed for workspace " + name + ", clearing configuration cache");
100-
fileResolver.clearConfigCache();
113+
auto filePath = change.uri.fsPath();
101114

102-
// Recompute diagnostics
103-
recomputeDiagnostics(config);
104-
}
105-
else if (filePath.extension() == ".lua" || filePath.extension() == ".luau")
106-
{
107-
// Notify if it was a definitions file
108-
if (isDefinitionFile(filePath, config))
109-
{
110-
client->sendWindowMessage(
111-
lsp::MessageType::Info, "Detected changes to global definitions files. Please reload your workspace for this to take effect");
112-
return;
113-
}
115+
platform->onDidChangeWatchedFiles(change);
114116

115-
// Index the workspace on changes
116-
// We only update the require graph. We do not perform type checking
117-
if (config.index.enabled && isConfigured)
117+
if (filePath.filename() == ".luaurc")
118118
{
119-
auto moduleName = fileResolver.getModuleName(change.uri);
119+
client->sendLogMessage(lsp::MessageType::Info, "Acknowledge config changed for workspace " + name + ", clearing configuration cache");
120+
fileResolver.clearConfigCache();
120121

121-
std::vector<Luau::ModuleName> markedDirty{};
122-
frontend.markDirty(moduleName, &markedDirty);
122+
// Recompute diagnostics
123+
recomputeDiagnostics(config);
124+
}
125+
else if (filePath.extension() == ".lua" || filePath.extension() == ".luau")
126+
{
127+
// Notify if it was a definitions file
128+
if (isDefinitionFile(filePath, config))
129+
{
130+
client->sendWindowMessage(
131+
lsp::MessageType::Info, "Detected changes to global definitions files. Please reload your workspace for this to take effect");
132+
continue;
133+
}
134+
else if (isIgnoredFile(filePath, config))
135+
{
136+
continue;
137+
}
123138

124-
if (change.type == lsp::FileChangeType::Created)
125-
frontend.parse(moduleName);
139+
// Index the workspace on changes
140+
if (config.index.enabled && isConfigured)
141+
{
142+
auto moduleName = fileResolver.getModuleName(change.uri);
143+
frontend.markDirty(moduleName, &dirtyFiles);
144+
}
126145

127-
// Re-check the reverse dependencies
128-
for (const auto& reverseDep : markedDirty)
129-
frontend.parse(reverseDep);
146+
if (change.type == lsp::FileChangeType::Deleted)
147+
deletedFiles.push_back(change.uri);
130148
}
131-
132-
// Clear the diagnostics for the file in case it was not managed
133-
if (change.type == lsp::FileChangeType::Deleted)
134-
clearDiagnosticsForFile(change.uri);
135149
}
150+
151+
// Parse require graph for files if indexing enabled
152+
for (const auto& dirtyModule : dirtyFiles)
153+
frontend.parse(dirtyModule);
154+
155+
// Clear the diagnostics for files in case it was not managed
156+
clearDiagnosticsForFiles(deletedFiles);
136157
}
137158

138159
/// When using lexically_relative, if the root-dir does not match, it will return a default-constructed path.

src/include/LSP/Workspace.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class WorkspaceFolder
6060
const lsp::DocumentUri& uri, const lsp::DidChangeTextDocumentParams& params, std::vector<Luau::ModuleName>* markedDirty = nullptr);
6161
void closeTextDocument(const lsp::DocumentUri& uri);
6262

63-
void onDidChangeWatchedFiles(const lsp::FileEvent& change);
63+
void onDidChangeWatchedFiles(const std::vector<lsp::FileEvent>& changes);
6464

6565
/// Whether the file has been marked as ignored by any of the ignored lists in the configuration
6666
bool isIgnoredFile(const std::filesystem::path& path, const std::optional<ClientConfiguration>& givenConfig = std::nullopt);
@@ -74,12 +74,13 @@ class WorkspaceFolder
7474
void recomputeDiagnostics(const ClientConfiguration& config);
7575
void pushDiagnostics(const lsp::DocumentUri& uri, const size_t version);
7676

77+
void clearDiagnosticsForFiles(const std::vector<lsp::DocumentUri>& uri) const;
7778
void clearDiagnosticsForFile(const lsp::DocumentUri& uri);
7879

7980
void indexFiles(const ClientConfiguration& config);
8081

8182
Luau::CheckResult checkSimple(const Luau::ModuleName& moduleName);
82-
void checkStrict(const Luau::ModuleName& moduleName, bool forAutocomplete = true);
83+
Luau::CheckResult checkStrict(const Luau::ModuleName& moduleName, bool forAutocomplete = true);
8384
// TODO: Clip once new type solver is live
8485
const Luau::ModulePtr getModule(const Luau::ModuleName& moduleName, bool forAutocomplete = false) const;
8586

0 commit comments

Comments
 (0)