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