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