pub const GATE_DID_OPEN_PARSE: &str = "didOpen.parse";
pub const GATE_DID_SAVE_DIAGNOSTICS: &str = "didSave.diagnostics";
pub const GATE_DID_CHANGE_WATCHED_FILES: &str = "didChangeWatchedFiles.parse";
pub const GATE_WILL_RENAME_FILES: &str = "willRenameFiles.useEdits";
pub const GATE_WILL_DELETE_FILES: &str = "willDeleteFiles.useEdits";
pub const GATE_PREPARE_CALL_HIERARCHY: &str = "prepareCallHierarchy.lookup";
pub const GATE_SELECTION_RANGE: &str = "selectionRange.walk";
pub const GATE_GOTO_IMPLEMENTATION: &str = "gotoImplementation.methodDeclCheck";
pub const GATE_RENAME_VARIABLE: &str = "rename.variable";
pub const GATE_COMPLETION_RESOLVE: &str = "completionResolve.lookup";
pub const GATE_INLAY_HINT_RESOLVE: &str = "inlayHintResolve.lookup";
pub const GATE_ON_TYPE_FORMATTING: &str = "onTypeFormatting.scan";
pub const GATE_PREPARE_RENAME: &str = "prepareRename.walk";
pub const GATE_DOCUMENT_SYMBOL: &str = "documentSymbol.walk";
pub const GATE_FOLDING_RANGE: &str = "foldingRange.walk";
pub const GATE_DOCUMENT_LINK: &str = "documentLink.walk";
pub const GATE_SEMANTIC_TOKENS_FULL: &str = "semanticTokensFull.walk";
pub const GATE_SEMANTIC_TOKENS_RANGE: &str = "semanticTokensRange.walk";
pub const GATE_SEMANTIC_TOKENS_FULL_DELTA: &str = "semanticTokensFullDelta.walk";
pub const GATE_LINKED_EDITING_RANGE: &str = "linkedEditingRange.walk";
pub const GATE_GOTO_TYPE_DEFINITION: &str = "gotoTypeDefinition.walk";
#[cfg(not(feature = "test-hooks"))]
#[derive(Default)]
pub struct DebugGate;
#[cfg(not(feature = "test-hooks"))]
impl DebugGate {
#[inline(always)]
pub fn new() -> Self {
DebugGate
}
#[inline(always)]
pub fn pass(&self, _section: &str) {}
#[inline(always)]
pub fn held_section(&self) -> Option<String> {
None
}
}
#[cfg(feature = "test-hooks")]
pub use real::DebugGate;
#[cfg(feature = "test-hooks")]
mod real {
use std::sync::{Condvar, Mutex};
#[derive(Default)]
struct GateState {
armed: Option<String>,
held: Option<String>,
released: bool,
}
#[derive(Default)]
pub struct DebugGate {
state: Mutex<GateState>,
cv: Condvar,
}
impl DebugGate {
pub fn new() -> Self {
Self::default()
}
pub fn arm(&self, section: &str) {
let mut s = self.state.lock().unwrap();
s.armed = Some(section.to_owned());
s.released = false;
}
pub fn release(&self) {
let mut s = self.state.lock().unwrap();
s.armed = None;
s.released = true;
self.cv.notify_all();
}
pub fn held_section(&self) -> Option<String> {
self.state.lock().unwrap().held.clone()
}
pub fn pass(&self, section: &str) {
let s = self.state.lock().unwrap();
if s.armed.as_deref() != Some(section) {
return;
}
let mut s = s;
s.armed = None;
s.held = Some(section.to_owned());
let (mut s, _timed_out) = self
.cv
.wait_timeout_while(s, std::time::Duration::from_secs(60), |s| !s.released)
.unwrap();
s.held = None;
s.released = false;
}
}
}
#[cfg(all(test, feature = "test-hooks"))]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn unarmed_pass_is_a_no_op() {
let gate = DebugGate::default();
gate.pass("anything");
assert_eq!(gate.held_section(), None);
}
#[test]
fn armed_pass_parks_until_release() {
let gate = Arc::new(DebugGate::default());
gate.arm("sec");
let g = Arc::clone(&gate);
let t = std::thread::spawn(move || g.pass("sec"));
while gate.held_section().as_deref() != Some("sec") {
std::thread::yield_now();
}
assert!(!t.is_finished());
gate.release();
t.join().unwrap();
assert_eq!(gate.held_section(), None);
}
#[test]
fn arm_holds_only_the_named_section_and_only_once() {
let gate = Arc::new(DebugGate::default());
gate.arm("sec");
gate.pass("other"); let g = Arc::clone(&gate);
let t = std::thread::spawn(move || g.pass("sec"));
while gate.held_section().is_none() {
std::thread::yield_now();
}
gate.release();
t.join().unwrap();
gate.pass("sec"); }
}