1use core::fmt::{Debug, Display};
2use core::ops::{Add, Sub};
3use std::borrow::Cow;
4use std::ffi::OsStr;
5use std::path::Path;
6use std::sync::atomic::{AtomicBool, Ordering};
7
8use dashmap::try_result::TryResult;
9use ropey::RopeSlice;
10use tower_lsp_server::ls_types::*;
11use tracing::warn;
12use xmlparser::{StrSpan, TextPos, Token};
13
14mod visitor;
15pub use visitor::PreTravel;
16
17mod catch_panic;
18pub use catch_panic::CatchPanic;
19
20use crate::index::PathSymbol;
21use crate::prelude::*;
22
23#[cfg(not(windows))]
24pub use std::fs::canonicalize as strict_canonicalize;
25
26#[macro_export]
28macro_rules! some {
29 ($opt:expr) => {
30 match $opt {
31 Some(it) => it,
32 None => {
33 tracing::trace!(concat!(stringify!($opt), " = None"));
34 return Ok(None);
35 }
36 }
37 };
38}
39
40#[macro_export]
41macro_rules! dig {
42 () => { None };
43 ($start:expr, $($rest:tt)+) => {
44 $crate::dig!(@inner Some($start), $($rest)+)
45 };
46 (@inner $node:expr, $kind:ident($idx:literal).$($rest:tt)+) => {
47 $crate::dig!(
48 @inner
49 if let Some(node) = $node && let Some(child) = $crate::utils::python_nth_named_child_matching::<$idx>(node, stringify!($kind)) { Some(child) } else { None },
50 $($rest)+
51 )
52 };
53 (@inner $node:expr, $kind:ident.$($rest:tt)+) => {
54 $crate::dig!(@inner $node, $kind(0).$($rest)+)
55 };
56 (@inner $node:expr, $kind:ident($idx:literal)) => {
57 if let Some(node) = $node && let Some(child) = $crate::utils::python_nth_named_child_matching::<$idx>(node, stringify!($kind)) { Some(child) } else { None }
58 };
59 (@inner $node:expr, $kind:ident) => {
60 $crate::dig!(@inner $node, $kind(0))
61 };
62}
63
64#[macro_export]
66macro_rules! ok {
67 ($res:expr $(,)?) => {
68 anyhow::Context::context($res, concat!($crate::loc!(), " ", stringify!($res)))?
69 };
70 ($res:expr, $($tt:tt)+) => {
71 anyhow::Context::with_context($res, || $crate::format_loc!($($tt)+))?
72 }
73}
74
75#[macro_export]
76macro_rules! await_did_open_document {
77 ($self:expr, $path:expr) => {
78 let mut blocker = None;
79 {
80 if let Some(document) = $self
81 .document_map
82 .try_get($path)
83 .expect($crate::format_loc!("deadlock"))
84 && document.setup.should_wait()
85 {
86 blocker = Some(std::sync::Arc::clone(&document.setup));
87 }
88 }
89 if let Some(blocker) = blocker {
90 blocker.wait($crate::loc!()).await;
91 }
92 };
93}
94
95#[repr(transparent)]
97#[derive(SmartDefault)]
98#[cfg(false)]
99struct EarlyReturn<'a, T>(
100 #[default(None)] Option<Box<dyn FnOnce() -> BoxFuture<'a, T> + 'a + Send>>,
104);
105
106#[cfg(false)]
107impl<'a, T> EarlyReturn<'a, T> {
108 pub fn lift<F, Fut>(&mut self, closure: F)
110 where
111 F: FnOnce() -> Fut + 'a + Send,
112 Fut: Future<Output = T> + 'a + Send,
113 {
114 self.0 = Some(Box::new(move || Box::pin(async move { closure().await })));
115 }
116 #[inline]
117 pub fn is_none(&self) -> bool {
118 self.0.is_none()
119 }
120 pub fn call(self) -> Option<BoxFuture<'a, T>> {
121 Some(self.0?())
122 }
123}
124
125#[derive(Clone, Debug)]
127pub struct MinLoc {
128 pub path: PathSymbol,
129 pub range: Range,
130}
131
132impl Display for MinLoc {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 f.write_fmt(format_args!(
135 "{}:{}:{}",
136 self.path,
137 self.range.start.line + 1,
138 self.range.start.character + 1
139 ))
140 }
141}
142
143impl From<MinLoc> for Location {
144 fn from(value: MinLoc) -> Self {
145 Location {
146 uri: format!("file://{}", value.path).parse().unwrap(),
147 range: value.range,
148 }
149 }
150}
151
152pub struct SpanAdapter<T>(T);
153pub struct RopeAdapter<'a, T>(T, RopeSlice<'a>);
154
155#[inline]
160pub fn span_conv<T, U>(src: T) -> U
161where
162 U: From<SpanAdapter<T>>,
163{
164 SpanAdapter(src).into()
165}
166
167#[inline]
173pub fn rope_conv<T, U>(src: T, rope: RopeSlice<'_>) -> U
174where
175 for<'a> U: From<RopeAdapter<'a, T>>,
176{
177 RopeAdapter(src, rope).into()
178}
179
180impl<'a> From<RopeAdapter<'a, ByteOffset>> for Position {
181 fn from(value: RopeAdapter<'a, ByteOffset>) -> Self {
182 let RopeAdapter(offset, rope) = value;
183 let line = rope.byte_to_line_idx(offset.0, LINE_TYPE);
184 let line_start_byte = rope.line_to_byte_idx(line, LINE_TYPE);
185 let line_start_char = rope.byte_to_char_idx(line_start_byte);
186 let char_offset = rope.byte_to_char_idx(offset.0);
187 let column = char_offset - line_start_char;
188 Position::new(line as u32, column as u32)
189 }
190}
191
192impl<'a> From<RopeAdapter<'a, Position>> for ByteOffset {
193 fn from(value: RopeAdapter<'a, Position>) -> Self {
194 let RopeAdapter(position, rope) = value;
195 let CharOffset(char_offset) = position_to_char(position, rope);
196 let byte_offset = rope.char_to_byte_idx(char_offset);
197 ByteOffset(byte_offset)
198 }
199}
200
201impl<'a> From<RopeAdapter<'a, Range>> for CharRange {
202 fn from(value: RopeAdapter<'a, Range>) -> Self {
203 let RopeAdapter(range, rope) = value;
204 let start = position_to_char(range.start, rope);
205 let end = position_to_char(range.end, rope);
206 start..end
207 }
208}
209impl<'a> From<RopeAdapter<'a, Range>> for ByteRange {
210 fn from(value: RopeAdapter<'a, Range>) -> Self {
211 let RopeAdapter(range, rope) = value;
212 let start = rope_conv(range.start, rope);
213 let end = rope_conv(range.end, rope);
214 start..end
215 }
216}
217
218impl<'a> From<RopeAdapter<'a, ByteRange>> for Range {
219 fn from(value: RopeAdapter<'a, ByteRange>) -> Self {
220 let RopeAdapter(range, rope) = value;
221 let start = rope_conv(range.start, rope);
222 let end = rope_conv(range.end, rope);
223 Range { start, end }
224 }
225}
226
227fn position_to_char(position: Position, rope: RopeSlice<'_>) -> CharOffset {
228 let line_offset_in_byte = rope.line_to_byte_idx(position.line as usize, LINE_TYPE);
229 let line_offset_in_char = rope.byte_to_char_idx(line_offset_in_byte);
230 CharOffset(line_offset_in_char + position.character as usize)
231}
232
233impl From<SpanAdapter<TextPos>> for Position {
234 #[inline]
235 fn from(value: SpanAdapter<TextPos>) -> Self {
236 let SpanAdapter(position) = value;
237 Position {
238 line: position.row - 1_u32,
239 character: position.col - 1_u32,
240 }
241 }
242}
243
244impl From<SpanAdapter<tree_sitter::Range>> for Range {
245 #[inline]
246 fn from(value: SpanAdapter<tree_sitter::Range>) -> Self {
247 let SpanAdapter(range) = value;
248 Range {
249 start: Position {
250 line: range.start_point.row as u32,
251 character: range.start_point.column as u32,
252 },
253 end: Position {
254 line: range.end_point.row as u32,
255 character: range.end_point.column as u32,
256 },
257 }
258 }
259}
260
261pub fn token_span<'r, 't>(token: &'r Token<'t>) -> &'r StrSpan<'t> {
262 match token {
263 Token::Declaration { span, .. }
264 | Token::ProcessingInstruction { span, .. }
265 | Token::Comment { span, .. }
266 | Token::DtdStart { span, .. }
267 | Token::EmptyDtd { span, .. }
268 | Token::EntityDeclaration { span, .. }
269 | Token::DtdEnd { span, .. }
270 | Token::ElementStart { span, .. }
271 | Token::Attribute { span, .. }
272 | Token::ElementEnd { span, .. }
273 | Token::Text { text: span, .. }
274 | Token::Cdata { span, .. } => span,
275 }
276}
277
278pub fn cow_split_once<'src>(
282 mut src: Cow<'src, str>,
283 sep: &str,
284) -> Result<(Cow<'src, str>, Cow<'src, str>), Cow<'src, str>> {
285 match src {
286 Cow::Borrowed(inner) => inner
287 .split_once(sep)
288 .map(|(lhs, rhs)| (Cow::Borrowed(lhs), Cow::Borrowed(rhs)))
289 .ok_or(src),
290 Cow::Owned(ref mut inner) => {
291 let Some(offset) = inner.find(sep) else {
292 return Err(src);
293 };
294 let mut rhs = inner.split_off(offset);
295 rhs.replace_range(0..sep.len(), "");
296 Ok((Cow::Owned(core::mem::take(inner)), Cow::Owned(rhs)))
297 }
298 }
299}
300
301#[inline(always)]
302#[cold]
303pub const fn cold_path() {}
304
305#[inline(always)]
307pub const fn likely(cond: bool) -> bool {
308 if cond {
309 true
310 } else {
311 cold_path();
312 false
313 }
314}
315
316#[inline(always)]
317pub const fn unlikely(cond: bool) -> bool {
318 if cond {
319 cold_path();
320 true
321 } else {
322 false
323 }
324}
325
326#[allow(clippy::disallowed_methods)]
328#[inline]
329pub fn python_next_named_sibling(mut node: Node) -> Option<Node> {
330 loop {
331 node = node.next_named_sibling()?;
332 if likely(node.kind() != "comment") {
333 return Some(node);
334 }
335 }
336}
337
338#[allow(clippy::disallowed_methods)]
339#[inline]
340pub fn python_nth_named_child_matching<'node, const NTH: usize>(
341 mut node: Node<'node>,
342 kind: &'static str,
343) -> Option<Node<'node>> {
344 let mut idx = 0;
345 node = node.named_child(0)?;
346 loop {
347 if idx == NTH && likely(node.kind() == kind) {
348 return Some(node);
349 }
350 if likely(node.kind() != "comment") {
351 idx += 1;
352 }
353 node = node.next_named_sibling()?;
354 }
355}
356
357#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
358#[repr(transparent)]
359pub struct ByteOffset(pub usize);
360pub type ByteRange = core::ops::Range<ByteOffset>;
361
362impl From<usize> for ByteOffset {
363 #[inline]
364 fn from(value: usize) -> Self {
365 ByteOffset(value as _)
366 }
367}
368
369#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
370#[repr(transparent)]
371pub struct CharOffset(pub usize);
372pub type CharRange = core::ops::Range<CharOffset>;
373
374pub trait RangeExt {
375 type Unit;
376 fn map_unit<F, V>(self, op: F) -> std::ops::Range<V>
377 where
378 F: FnMut(Self::Unit) -> V;
379
380 fn shrink(self, value: Self::Unit) -> std::ops::Range<Self::Unit>
381 where
382 Self: Sized,
383 Self::Unit: Add<Self::Unit, Output = Self::Unit> + Sub<Self::Unit, Output = Self::Unit> + Copy;
384
385 fn contains_end(&self, value: Self::Unit) -> bool
386 where
387 Self::Unit: PartialOrd;
388}
389
390pub trait Erase {
391 fn erase(&self) -> core::ops::Range<usize>;
392 fn intersects(&self, other: core::ops::Range<usize>) -> bool {
393 let this = self.erase();
394 this.end >= other.start || this.start < other.end
395 }
396}
397
398impl Erase for ByteRange {
399 #[inline]
400 fn erase(&self) -> core::ops::Range<usize> {
401 self.clone().map_unit(|unit| unit.0)
402 }
403}
404
405impl Erase for CharRange {
406 #[inline]
407 fn erase(&self) -> core::ops::Range<usize> {
408 self.clone().map_unit(|unit| unit.0)
409 }
410}
411
412impl<T> RangeExt for core::ops::Range<T> {
413 type Unit = T;
414
415 #[inline]
416 fn map_unit<F, V>(self, mut op: F) -> core::ops::Range<V>
417 where
418 F: FnMut(Self::Unit) -> V,
419 {
420 op(self.start)..op(self.end)
421 }
422
423 fn shrink(self, value: Self::Unit) -> core::ops::Range<Self::Unit>
424 where
425 Self: Sized,
426 Self::Unit: Add<Self::Unit, Output = Self::Unit> + Sub<Self::Unit, Output = Self::Unit> + Copy,
427 {
428 self.start + value..self.end - value
429 }
430
431 #[inline]
432 fn contains_end(&self, value: Self::Unit) -> bool
433 where
434 Self::Unit: PartialOrd,
435 {
436 self.contains(&value) || self.end == value
437 }
438}
439
440#[macro_export]
441macro_rules! loc {
442 () => {
443 concat!("[", file!(), ":", line!(), ":", column!(), "]")
444 };
445}
446
447#[macro_export]
448macro_rules! errloc {
449 ($msg:literal $(, $($tt:tt)* )?) => {
450 ::anyhow::anyhow!(concat!($crate::loc!(), " ", $msg) $(, $($tt)* )?)
451 }
452}
453
454#[macro_export]
457macro_rules! format_loc {
458 ($tpl:literal) => {
459 concat!($crate::loc!(), " ", $tpl)
460 };
461 ($tpl:literal $($tt:tt)*) => {
462 format!($crate::format_loc!($tpl) $($tt)*)
463 };
464}
465
466#[derive(Default)]
467pub struct MaxVec<T>(Vec<T>);
468
469impl<T> MaxVec<T> {
470 pub fn new(limit: usize) -> Self {
471 MaxVec(Vec::with_capacity(limit))
472 }
473 #[inline]
474 fn remaining_space(&self) -> usize {
475 self.0.capacity().saturating_sub(self.0.len())
476 }
477 #[inline]
478 pub fn has_space(&self) -> bool {
479 self.remaining_space() > 0
480 }
481 pub fn extend(&mut self, items: impl Iterator<Item = T>) {
482 self.0.extend(items.take(self.remaining_space()));
483 }
484 pub fn push_checked(&mut self, item: T) {
485 if self.has_space() {
486 self.0.push(item);
487 }
488 }
489 #[inline]
490 pub fn into_inner(self) -> Vec<T> {
491 self.0
492 }
493}
494
495impl<T> std::convert::AsMut<[T]> for MaxVec<T> {
496 #[inline]
497 fn as_mut(&mut self) -> &mut [T] {
498 &mut self.0
499 }
500}
501
502impl<T> std::ops::Deref for MaxVec<T> {
503 type Target = Vec<T>;
504 #[inline]
505 fn deref(&self) -> &Self::Target {
506 &self.0
507 }
508}
509
510pub trait TryResultExt {
511 type Result: Sized;
512 fn expect(self, msg: &str) -> Option<Self::Result>;
514}
515
516impl<T: Sized> TryResultExt for TryResult<T> {
517 type Result = T;
518 fn expect(self, msg: &str) -> Option<Self::Result> {
519 match self {
520 TryResult::Present(item) => Some(item),
521 TryResult::Absent => None,
522 TryResult::Locked => panic!("{msg}"),
523 }
524 }
525}
526
527#[cfg(test)]
528pub fn init_for_test() {
529 use std::sync::Once;
530 use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
531
532 static INIT: Once = Once::new();
533 INIT.call_once(|| {
534 tracing_subscriber::registry()
535 .with(tracing_subscriber::fmt::layer())
536 .with(EnvFilter::from("info,odoo_lsp=trace"))
537 .init();
538 });
539}
540
541#[derive(Default)]
542pub struct Semaphore {
543 should_wait: AtomicBool,
544 notifier: tokio::sync::Notify,
545}
546
547impl Semaphore {
548 pub fn init_semaphore() -> Self {
549 Self {
550 should_wait: AtomicBool::new(true),
551 ..Default::default()
552 }
553 }
554}
555
556pub struct Blocker<'a>(&'a Semaphore);
557
558impl Semaphore {
559 #[must_use]
560 #[track_caller]
561 pub fn block(&self, context: &'static str) -> Blocker<'_> {
562 if self
563 .should_wait
564 .compare_exchange(false, true, Ordering::Acquire, Ordering::Acquire)
565 .is_err()
566 {
567 panic!(
568 "[{context}] thread={:?} attempted to lock {:p} which is already locked, it should call wait() first",
569 std::thread::current().id(),
570 self
571 );
572 }
573 info!(
574 "[{context}] thread={:?} acquired lock on {:p}",
575 std::thread::current().id(),
576 self
577 );
578 Blocker(self)
579 }
580
581 #[must_use]
584 pub unsafe fn block_unchecked(&self, context: &'static str) -> Blocker<'_> {
585 info!(
586 "[{context}] thread={:?} force-acquired lock on {:p}",
587 std::thread::current().id(),
588 self
589 );
590 Blocker(self)
591 }
592
593 pub const WAIT_LIMIT: std::time::Duration = std::time::Duration::from_secs(2);
594
595 pub async fn wait(&self, context: &'static str) {
597 while self.should_wait.load(Ordering::Relaxed) {
598 tokio::select! {
599 _ = self.notifier.notified() => return,
600 _ = tokio::time::sleep(Self::WAIT_LIMIT) => {
601 warn!("[{context}] WAIT_LIMIT elapsed (thread={:?}, lock={self:p})", std::thread::current().id());
602 }
603 }
604 }
605 }
606
607 #[inline]
608 pub fn should_wait(&self) -> bool {
609 self.should_wait.load(Ordering::Relaxed)
610 }
611}
612
613impl Drop for Blocker<'_> {
614 #[track_caller]
615 fn drop(&mut self) {
616 info!(
617 "thread={:?} releasing lock on {:p}",
618 std::thread::current().id(),
619 self.0
620 );
621 self.0.should_wait.store(false, Ordering::Release);
622 self.0.notifier.notify_waiters();
623 }
624}
625
626pub trait DisplayExt {
629 fn display(self) -> impl Display;
630}
631
632impl<T: Display> DisplayExt for Option<T> {
633 fn display(self) -> impl Display {
634 struct Adapter<T>(Option<T>);
635 impl<T: Display> Display for Adapter<T> {
636 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
637 match &self.0 {
638 Some(value) => value.fmt(f),
639 None => Ok(()),
640 }
641 }
642 }
643 Adapter(self)
644 }
645}
646
647impl<T: Display> DisplayExt for &T {
648 fn display(self) -> impl Display {
649 self as &dyn Display
650 }
651}
652
653impl DisplayExt for std::fmt::Arguments<'_> {
654 fn display(self) -> impl Display {
655 #[repr(transparent)]
656 struct Adapter<'a>(std::fmt::Arguments<'a>);
657 impl Display for Adapter<'_> {
658 #[inline]
659 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
660 f.write_fmt(self.0)
661 }
662 }
663 Adapter(self)
664 }
665}
666
667pub fn path_contains(path: impl AsRef<Path>, needle: impl AsRef<OsStr>) -> bool {
668 path.as_ref().components().any(|c| c.as_os_str() == needle.as_ref())
669}
670
671static WSL: std::sync::LazyLock<bool> = std::sync::LazyLock::new(|| {
672 #[cfg(not(unix))]
673 return false;
674
675 #[cfg(unix)]
676 return rustix::system::uname()
677 .release()
678 .to_str()
679 .is_ok_and(|release| release.contains("WSL"));
680});
681
682#[inline]
683fn wsl_to_windows_path(path: impl AsRef<OsStr>) -> Option<String> {
684 fn impl_(path: &OsStr) -> Result<String, String> {
685 let mut out = std::process::Command::new("wslpath")
686 .arg("-w")
687 .arg(path)
688 .output()
689 .map_err(|err| format_loc!("wslpath failed: {}", err))?;
690 let code = out.status.code().unwrap_or(-1);
691 if code != 0 {
692 return Err(format_loc!("wslpath failed with code={}", code));
693 }
694
695 Ok(String::from_utf8(core::mem::take(&mut out.stdout))
696 .map_err(|err| format_loc!("wslpath returned non-utf8 path: {}", err))?
697 .trim()
698 .to_string())
699 }
700 impl_(path.as_ref()).map_err(|err| tracing::error!("{err}")).ok()
701}
702
703pub fn to_display_path(path: impl AsRef<Path>) -> String {
707 if *WSL {
708 return wsl_to_windows_path(path.as_ref()).unwrap_or_else(|| path.as_ref().to_string_lossy().into_owned());
709 }
710
711 path.as_ref().to_string_lossy().into_owned()
712}
713
714pub struct Defer<T>(pub Option<T>)
715where
716 T: FnOnce();
717
718impl<T> Drop for Defer<T>
719where
720 T: FnOnce(),
721{
722 fn drop(&mut self) {
723 let func = self.0.take().unwrap();
724 func()
725 }
726}
727
728#[inline]
731#[cfg(windows)]
732pub fn strict_canonicalize<P: AsRef<Path>>(path: P) -> anyhow::Result<std::path::PathBuf> {
733 use anyhow::Context;
734 use std::path::PathBuf;
735
736 fn impl_(path: PathBuf) -> anyhow::Result<PathBuf> {
737 let head = path.components().next().context("empty path")?;
738 let disk_;
739 let head = if let std::path::Component::Prefix(prefix) = head {
740 if let std::path::Prefix::VerbatimDisk(disk) = prefix.kind() {
741 disk_ = format!("{}:", disk as char);
742 Path::new(&disk_)
743 .components()
744 .next()
745 .context("failed to parse disk component")?
746 } else {
747 head
748 }
749 } else {
750 head
751 };
752 Ok(std::iter::once(head).chain(path.components().skip(1)).collect())
753 }
754 let canon = std::fs::canonicalize(path)?;
755 impl_(canon)
756}
757
758pub fn acc_vec<T>(mut acc: Vec<T>, item: &mut T) -> Vec<T>
760where
761 T: Default,
762{
763 acc.push(core::mem::take(item));
764 acc
765}
766
767#[cfg(test)]
768mod tests {
769 use super::{WSL, to_display_path};
770 use pretty_assertions::assert_eq;
771
772 #[test]
773 fn test_to_display_path() {
774 if *WSL {
775 assert_eq!(to_display_path("/mnt/c"), r"C:\");
776 let unix_path = to_display_path("/usr");
777 assert!(unix_path.starts_with(r"\\wsl"));
778 assert!(unix_path.ends_with(r"\usr"));
779 }
780 }
781
782 #[test]
783 #[cfg(windows)]
784 fn test_idempotent_canonicalization() {
785 use super::strict_canonicalize;
786 use std::path::Path;
787
788 let lhs = strict_canonicalize(Path::new(".")).unwrap();
789 let rhs = strict_canonicalize(&lhs).unwrap();
790 assert_eq!(lhs, rhs);
791 }
792
793 #[test]
794 fn test_python_first_nth_child_matching() {
795 use tree_sitter::Parser;
796 use tree_sitter_python::LANGUAGE;
797
798 let contents = r#"[
799 # A comment
800 1,
801 # Another comment
802 2,
803 3,
804 }
805 }"#;
806
807 let mut parser = Parser::new();
808 parser.set_language(&LANGUAGE.into()).unwrap();
809 let tree = parser.parse(contents, None).unwrap();
810 let root = tree.root_node();
811 let list_node = root.named_child(0).unwrap();
812 let first_element = list_node.named_child(1).unwrap();
813 let second_element = super::python_nth_named_child_matching::<0>(list_node, "integer").unwrap();
814 pretty_assertions::assert_eq!(first_element, second_element);
815 }
816}