1use std::hash::{DefaultHasher, Hash, Hasher};
5use std::io::Write;
6use std::sync::atomic::{AtomicBool, Ordering};
7
8use crate::{
9 CompileError, Config, ErrorStyle, ParseError, ParseWarning, ShellError, ShellWarning,
10 ShortReportHandler,
11 engine::{EngineState, Stack, StateWorkingSet},
12};
13use miette::{
14 LabeledSpan, MietteHandlerOpts, NarratableReportHandler, ReportHandler, RgbColors, Severity,
15 SourceCode,
16};
17use serde::{Deserialize, Serialize};
18use thiserror::Error;
19
20pub static SUPPRESS_REPORTING: AtomicBool = AtomicBool::new(false);
24
25#[derive(Error)]
28#[error("{diagnostic}")]
29struct CliError<'src> {
30 stack: Option<&'src Stack>,
31 diagnostic: &'src dyn miette::Diagnostic,
32 working_set: &'src StateWorkingSet<'src>,
33 default_code: Option<&'static str>,
35}
36
37impl<'src> CliError<'src> {
38 pub fn new(
39 stack: Option<&'src Stack>,
40 diagnostic: &'src dyn miette::Diagnostic,
41 working_set: &'src StateWorkingSet<'src>,
42 default_code: Option<&'static str>,
43 ) -> Self {
44 CliError {
45 stack,
46 diagnostic,
47 working_set,
48 default_code,
49 }
50 }
51}
52
53#[derive(Default, derive_more::Debug)]
57#[debug("ReportLog([...])")]
58pub struct ReportLog(Vec<u64>);
59
60#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
62pub enum ReportMode {
63 FirstUse,
64 EveryUse,
65}
66
67pub trait Reportable {
69 fn report_mode(&self) -> ReportMode;
70}
71
72fn should_show_reportable<R>(engine_state: &EngineState, reportable: &R) -> bool
74where
75 R: Reportable + Hash,
76{
77 match reportable.report_mode() {
78 ReportMode::EveryUse => true,
79 ReportMode::FirstUse => {
80 let mut hasher = DefaultHasher::new();
81 reportable.hash(&mut hasher);
82 let hash = hasher.finish();
83
84 let mut report_log = engine_state
85 .report_log
86 .lock()
87 .expect("report log lock is poisoned");
88
89 match report_log.0.contains(&hash) {
90 true => false,
91 false => {
92 report_log.0.push(hash);
93 true
94 }
95 }
96 }
97 }
98}
99
100pub fn format_cli_error(
101 stack: Option<&Stack>,
102 working_set: &StateWorkingSet,
103 error: &dyn miette::Diagnostic,
104 default_code: Option<&'static str>,
105) -> String {
106 format!(
107 "Error: {:?}",
108 CliError::new(stack, error, working_set, default_code)
109 )
110}
111
112pub fn report_shell_error(stack: Option<&Stack>, engine_state: &EngineState, error: &ShellError) {
113 if get_config(stack, engine_state)
114 .display_errors
115 .should_show(error)
116 {
117 let working_set = StateWorkingSet::new(engine_state);
118 report_error(stack, &working_set, error, "nu::shell::error")
119 }
120}
121
122pub fn report_shell_warning(
123 stack: Option<&Stack>,
124 engine_state: &EngineState,
125 warning: &ShellWarning,
126) {
127 if should_show_reportable(engine_state, warning) {
128 report_warning(
129 stack,
130 &StateWorkingSet::new(engine_state),
131 warning,
132 "nu::shell::warning",
133 );
134 }
135}
136
137pub fn report_parse_error(
138 stack: Option<&Stack>,
139 working_set: &StateWorkingSet,
140 error: &ParseError,
141) {
142 report_error(stack, working_set, error, "nu::parser::error");
143}
144
145pub fn report_parse_warning(
146 stack: Option<&Stack>,
147 working_set: &StateWorkingSet,
148 warning: &ParseWarning,
149) {
150 if should_show_reportable(working_set.permanent(), warning) {
151 report_warning(stack, working_set, warning, "nu::parser::warning");
152 }
153}
154
155pub fn report_compile_error(
156 stack: Option<&Stack>,
157 working_set: &StateWorkingSet,
158 error: &CompileError,
159) {
160 report_error(stack, working_set, error, "nu::compile::error");
161}
162
163pub fn report_experimental_option_warning(
164 stack: Option<&Stack>,
165 working_set: &StateWorkingSet,
166 warning: &dyn miette::Diagnostic,
167) {
168 report_warning(
169 stack,
170 working_set,
171 warning,
172 "nu::experimental_option::warning",
173 );
174}
175
176fn report_error(
177 stack: Option<&Stack>,
178 working_set: &StateWorkingSet,
179 error: &dyn miette::Diagnostic,
180 default_code: &'static str,
181) {
182 let report = format!(
183 "Error: {:?}",
184 CliError::new(stack, error, working_set, Some(default_code))
185 );
186
187 if !SUPPRESS_REPORTING.load(Ordering::Relaxed) {
188 if writeln!(std::io::stderr(), "{report}").is_err() {
191 let _ = writeln!(std::io::stdout(), "{report}");
192 }
193 }
194
195 #[cfg(windows)]
197 {
198 let _ = nu_utils::enable_vt_processing();
199 }
200}
201
202fn report_warning(
203 stack: Option<&Stack>,
204 working_set: &StateWorkingSet,
205 warning: &dyn miette::Diagnostic,
206 default_code: &'static str,
207) {
208 let report = format!(
209 "Warning: {:?}",
210 CliError::new(stack, warning, working_set, Some(default_code))
211 );
212
213 if !SUPPRESS_REPORTING.load(Ordering::Relaxed)
214 && writeln!(std::io::stderr(), "{report}").is_err()
215 {
216 let _ = writeln!(std::io::stdout(), "{report}");
217 }
218
219 #[cfg(windows)]
221 {
222 let _ = nu_utils::enable_vt_processing();
223 }
224}
225
226fn get_config<'a>(stack: Option<&'a Stack>, engine_state: &'a EngineState) -> &'a Config {
227 stack
228 .and_then(|s| s.config.as_deref())
229 .unwrap_or(engine_state.get_config())
230}
231
232impl std::fmt::Debug for CliError<'_> {
233 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234 let engine_state = self.working_set.permanent();
235 let config = get_config(self.stack, engine_state);
236
237 let ansi_support = config.use_ansi_coloring.get(engine_state);
238
239 let error_style = config.error_style;
240
241 let error_lines = config.error_lines;
242
243 let miette_handler: Box<dyn ReportHandler> = match error_style {
244 ErrorStyle::Short => Box::new(ShortReportHandler::new()),
245 ErrorStyle::Plain => Box::new(NarratableReportHandler::new()),
246 style => {
247 let handler = MietteHandlerOpts::new()
248 .rgb_colors(RgbColors::Never)
250 .color(ansi_support)
252 .unicode(ansi_support)
253 .terminal_links(ansi_support)
254 .context_lines(error_lines as usize)
255 .with_cause_chain();
256 match style {
257 ErrorStyle::Nested => Box::new(handler.show_related_errors_as_nested().build()),
258 _ => Box::new(handler.build()),
259 }
260 }
261 };
262
263 let _ = miette_handler.debug(self, f);
266
267 Ok(())
268 }
269}
270
271impl miette::Diagnostic for CliError<'_> {
272 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
273 self.diagnostic.code().or_else(|| {
274 self.default_code
275 .map(|code| Box::new(code) as Box<dyn std::fmt::Display>)
276 })
277 }
278
279 fn severity(&self) -> Option<Severity> {
280 self.diagnostic.severity()
281 }
282
283 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
284 self.diagnostic.help()
285 }
286
287 fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
288 self.diagnostic.url()
289 }
290
291 fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
292 self.diagnostic.labels()
293 }
294
295 fn source_code(&self) -> Option<&dyn SourceCode> {
297 if let Some(source_code) = self.diagnostic.source_code() {
298 Some(source_code)
299 } else {
300 Some(&self.working_set)
301 }
302 }
303
304 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
305 self.diagnostic.related()
306 }
307
308 fn diagnostic_source(&self) -> Option<&dyn miette::Diagnostic> {
309 self.diagnostic.diagnostic_source()
310 }
311}