Skip to main content

charon_error/utils/
source_location.rs

1// pub fn print_path_terminal_link<S: Into<String>>(path: S) {}
2
3use std::{fmt::Display, panic::Location};
4use tracing::Metadata;
5
6use crate::ResultExt;
7
8/// Format for source location links in terminal output.
9///
10/// Controls whether file paths are plain text or clickable terminal hyperlinks
11/// (using the OSC 8 escape sequence).
12#[derive(Debug, Clone, Default, Copy)]
13pub enum LinkDebugIde {
14    /// Plain text path (no hyperlink).
15    #[default]
16    NoLink,
17    /// `file://` protocol link.
18    Browser,
19    /// VSCode `vscode://file/` protocol link for click-to-open.
20    Vscode,
21}
22
23/// Captures a source code location (file, line, column).
24///
25/// Automatically captured via `#[track_caller]` when creating error frames.
26/// Can format the location as a clickable terminal hyperlink for supported IDEs.
27#[derive(Debug, Clone)]
28pub struct SourceLocation {
29    /// Name of the crate where the location was captured.
30    pub crate_name: &'static str,
31    /// Version of the crate where the location was captured.
32    pub crate_version: &'static str,
33    /// File path (can be absolute or relative).
34    pub filename: String,
35    /// Line number in the source file.
36    pub line_number: u32,
37    /// Column number in the source file.
38    pub column_number: u32,
39}
40
41impl SourceLocation {
42    fn check_if_abs_path(path: &str) -> bool {
43        path.starts_with('/')
44    }
45
46    /// Capture the current caller's source location via `#[track_caller]`.
47    #[must_use]
48    #[track_caller]
49    pub fn from_caller_location() -> Self {
50        let caller = Location::caller();
51        Self {
52            crate_name: env!("CARGO_PKG_NAME"),
53            crate_version: env!("CARGO_PKG_VERSION"),
54            filename: caller.file().to_owned(),
55            line_number: caller.line(),
56            column_number: caller.column(),
57        }
58    }
59
60    /// Create from a panic hook's `Location`.
61    #[must_use]
62    pub fn from_panic_location(location: &Location) -> Self {
63        Self {
64            crate_name: env!("CARGO_PKG_NAME"),
65            crate_version: env!("CARGO_PKG_VERSION"),
66            filename: location.file().to_owned(),
67            line_number: location.line(),
68            column_number: location.column(),
69        }
70    }
71
72    /// Create from `tracing` span metadata.
73    #[must_use]
74    pub fn from_metadata(metadata: &Metadata) -> Self {
75        Self {
76            crate_name: env!("CARGO_PKG_NAME"),
77            crate_version: env!("CARGO_PKG_VERSION"),
78            filename: metadata.file().unwrap_or("Unknown").to_owned(),
79            line_number: metadata.line().unwrap_or(0),
80            column_number: 0,
81        }
82    }
83
84    /// Format the location as a string, optionally with a clickable terminal hyperlink.
85    #[must_use]
86    pub fn display_location(&self, overwrite_link_fmt: Option<LinkDebugIde>) -> String {
87        let current_dir = std::env::current_dir().unwrap_error().display().to_string();
88
89        let relative_path = if Self::check_if_abs_path(&self.filename) {
90            // Also remove the `/` after that string
91            let current_dir = format!("{current_dir}/");
92            if self.filename.starts_with(&current_dir) {
93                self.filename.split_at(current_dir.len()).1.to_owned()
94            } else {
95                self.filename.clone()
96            }
97        } else {
98            self.filename.clone()
99        };
100
101        let absolute_path = if Self::check_if_abs_path(&self.filename) {
102            self.filename.clone()
103        } else {
104            format!("{}/{}", current_dir, self.filename)
105        };
106        match overwrite_link_fmt.or(Some(DEBUG_IDE)).unwrap_or_default() {
107            LinkDebugIde::NoLink => format!(
108                "`{}:{}:{}`",
109                // self.crate_name,
110                // self.crate_version,
111                self.filename,
112                self.line_number,
113                self.column_number
114            ),
115            LinkDebugIde::Browser => format!(
116                "\x1b]8;;file://{absolute_path}:{line}:{column}\x1b\\{relative_path}:{line}:{column}\x1b]8;;\x1b\\",
117                // self.crate_name,
118                // self.crate_version,
119                line = self.line_number,
120                column = self.column_number,
121            ),
122            LinkDebugIde::Vscode => format!(
123                "\x1b]8;;vscode://file/{absolute_path}:{line}:{column}\x1b\\{relative_path}:{line}:{column}\x1b]8;;\x1b\\",
124                // self.crate_name,
125                // self.crate_version,
126                line = self.line_number,
127                column = self.column_number,
128            ),
129        }
130    }
131}
132
133// Running in Debug mode
134#[cfg(debug_assertions)]
135static DEBUG_IDE: LinkDebugIde = LinkDebugIde::Vscode;
136// Running in Release mode
137#[cfg(not(debug_assertions))]
138static DEBUG_IDE: LinkDebugIde = LinkDebugIde::NoLink;
139
140impl Display for SourceLocation {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        write!(f, "{}", self.display_location(None))
143    }
144}