CIRCT 22.0.0git
Loading...
Searching...
No Matches
PendingChanges.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "PendingChanges.h"
10
11namespace circt {
12namespace lsp {
13
14/// Factory: build from server options. Keep mapping 1:1 for clarity.
15DebounceOptions
23
25 std::scoped_lock lock(mu);
26 pending.clear();
27 pool.wait();
28}
29
30void PendingChangesMap::erase(llvm::StringRef key) {
31 std::scoped_lock lock(mu);
32 pending.erase(key);
33}
34
35void PendingChangesMap::erase(const llvm::lsp::URIForFile &uri) {
36 auto file = uri.file();
37 if (!file.empty())
38 erase(file);
39}
40
42 const llvm::lsp::DidChangeTextDocumentParams &params,
43 DebounceOptions options,
44 std::function<void(std::unique_ptr<PendingChanges>)> cb) {
45 enqueueChange(params);
46 debounceAndThen(params, options, std::move(cb));
47}
48
50 const llvm::lsp::DidChangeTextDocumentParams &params) {
51 // Key by normalized LSP file path. If your pipeline allows multiple
52 // spellings (symlinks/case), normalize upstream or canonicalize here.
53 const auto now = std::chrono::steady_clock::now();
54 const std::string key = params.textDocument.uri.file().str();
55
56 std::scoped_lock lock(mu);
58
59 pending.changes.insert(pending.changes.end(), params.contentChanges.begin(),
60 params.contentChanges.end());
61 pending.version = params.textDocument.version;
62 pending.lastChangeTime = now;
63
64 // If this was the first insert after a flush, record start of burst.
65 if (pending.changes.size() == params.contentChanges.size())
66 pending.firstChangeTime = now;
67}
68
70 const llvm::lsp::DidChangeTextDocumentParams &params,
71 DebounceOptions options,
72 std::function<void(std::unique_ptr<PendingChanges>)> cb) {
73 const std::string key = params.textDocument.uri.file().str();
74 const auto scheduleTime = std::chrono::steady_clock::now();
75
76 // If debounce is disabled, run on main thread
77 if (options.disableDebounce) {
78 std::scoped_lock lock(mu);
79 auto it = pending.find(key);
80 if (it == pending.end())
81 return cb(nullptr);
82 return cb(takeAndErase(it));
83 }
84
85 // If debounced, run entirely on the pool; do not block the LSP thread.
86 tasks.async([this, key, scheduleTime, options, cb = std::move(cb)]() {
87 // Simple timer: sleep min-quiet before checking. We rely on the fact
88 // that newer edits can arrive while we sleep, updating lastChangeTime.
89 if (options.debounceMinMs > 0)
90 std::this_thread::sleep_for(
91 std::chrono::milliseconds(options.debounceMinMs));
92
93 std::unique_ptr<PendingChanges>
94 result; // decided under lock, callback after
95
96 {
97 std::scoped_lock lock(mu);
98 auto it = pending.find(key);
99 if (it != pending.end()) {
100 PendingChanges &pc = it->second;
101 const auto now = std::chrono::steady_clock::now();
102
103 // quietSinceSchedule: if no newer edits arrived after we scheduled
104 // this task, then we consider the burst "quiet" and flush now.
105 const bool quietSinceSchedule = (pc.lastChangeTime <= scheduleTime);
106
107 // Apply max-burst cap if configured: force a flush once the total
108 // time since first change exceeds the cap.
109 bool maxWaitExpired = false;
110 if (options.debounceMaxMs > 0) {
111 const auto elapsedMs =
112 std::chrono::duration_cast<std::chrono::milliseconds>(
113 now - pc.firstChangeTime)
114 .count();
115 maxWaitExpired =
116 static_cast<uint64_t>(elapsedMs) >= options.debounceMaxMs;
117 }
118
119 if (quietSinceSchedule || maxWaitExpired)
120 result = takeAndErase(it); // flush now
121 // else: newer edits arrived; obsolete -> result stays null
122 }
123 }
124
125 // Invoke outside the lock to avoid deadlocks and allow heavy work.
126 cb(std::move(result)); // nullptr => obsolete (no flush)
127 });
128}
129
131 auto it = pending.find(key);
132 if (it != pending.end())
133 return it->second;
134 auto inserted = pending.try_emplace(key);
135 return inserted.first->second;
136}
137
138std::unique_ptr<PendingChanges>
139PendingChangesMap::takeAndErase(llvm::StringMap<PendingChanges>::iterator it) {
140 auto out = std::make_unique<PendingChanges>(std::move(it->second));
141 pending.erase(it);
142 return out;
143}
144
145} // namespace lsp
146} // namespace circt
void debounceAndThen(const llvm::lsp::DidChangeTextDocumentParams &params, DebounceOptions options, std::function< void(std::unique_ptr< PendingChanges >)> cb)
Schedule a debounce check on the internal pool and call cb when ready.
std::mutex mu
Guards pending.
llvm::ThreadPoolTaskGroup tasks
llvm::StdThreadPool pool
Internal concurrency used for sleeps + checks.
std::unique_ptr< PendingChanges > takeAndErase(llvm::StringMap< PendingChanges >::iterator it)
NOT thread-safe; caller must hold mu.
llvm::StringMap< PendingChanges > pending
Per-document edit bursts, keyed by file string.
void abort()
Call during server shutdown; Erase all file changes, then clear file map.
void enqueueChange(const llvm::lsp::DidChangeTextDocumentParams &params)
Append new edits for a document key.
void debounceAndUpdate(const llvm::lsp::DidChangeTextDocumentParams &params, DebounceOptions options, std::function< void(std::unique_ptr< PendingChanges >)> cb)
Append new edits for a document key, then start a debounced update thread.
void erase(llvm::StringRef key)
Remove all pending edits for a document key.
PendingChanges & getOrCreateEntry(std::string_view key)
NOT thread-safe; caller must hold mu.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Debounce tuning for document-change bursts.
uint64_t debounceMinMs
Minimum quiet time before we flush.
static DebounceOptions fromLSPOptions(const circt::lsp::LSPServerOptions &opts)
Factory: build from server options. Keep mapping 1:1 for clarity.
bool disableDebounce
If true, flush immediately (no sleep/check).
uint64_t debounceMaxMs
Maximum total burst time (0 = no cap).
const unsigned debounceMinMs
Minimum debounce delay in milliseconds.
const unsigned debounceMaxMs
Maximum debounce delay in milliseconds.
const bool disableDebounce
Disable debouncing entirely (updates applied synchronously).
Accumulated edits + timing for a single document key.
std::chrono::steady_clock::time_point firstChangeTime
std::chrono::steady_clock::time_point lastChangeTime