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 _ = writeln!(
178 std::io::stderr(),
179 "Error: {:?}",
180 CliError::new(stack, error, working_set, Some(default_code))
181 );
182 #[cfg(windows)]
184 {
185 let _ = nu_utils::enable_vt_processing();
186 }
187}
188
189fn report_warning(
190 stack: Option<&Stack>,
191 working_set: &StateWorkingSet,
192 warning: &dyn miette::Diagnostic,
193 default_code: &'static str,
194) {
195 let _ = writeln!(
196 std::io::stderr(),
197 "Warning: {:?}",
198 CliError::new(stack, warning, working_set, Some(default_code))
199 );
200 #[cfg(windows)]
202 {
203 let _ = nu_utils::enable_vt_processing();
204 }
205}
206
207fn get_config<'a>(stack: Option<&'a Stack>, engine_state: &'a EngineState) -> &'a Config {
208 stack
209 .and_then(|s| s.config.as_deref())
210 .unwrap_or(engine_state.get_config())
211}
212
213impl std::fmt::Debug for CliError<'_> {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 let engine_state = self.working_set.permanent();
216 let config = get_config(self.stack, engine_state);
217
218 let ansi_support = config.use_ansi_coloring.get(engine_state);
219
220 let error_style = config.error_style;
221
222 let error_lines = config.error_lines;
223
224 let miette_handler: Box<dyn ReportHandler> = match error_style {
225 ErrorStyle::Short => Box::new(ShortReportHandler::new()),
226 ErrorStyle::Plain => Box::new(NarratableReportHandler::new()),
227 style => {
228 let handler = MietteHandlerOpts::new()
229 .rgb_colors(RgbColors::Never)
231 .color(ansi_support)
233 .unicode(ansi_support)
234 .terminal_links(ansi_support)
235 .context_lines(error_lines as usize);
236 match style {
237 ErrorStyle::Nested => Box::new(
238 handler
239 .show_related_errors_as_nested()
240 .with_cause_chain()
241 .build(),
242 ),
243 _ => Box::new(handler.build()),
244 }
245 }
246 };
247
248 let _ = miette_handler.debug(self, f);
251
252 Ok(())
253 }
254}
255
256impl miette::Diagnostic for CliError<'_> {
257 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
258 self.diagnostic.code().or_else(|| {
259 self.default_code
260 .map(|code| Box::new(code) as Box<dyn std::fmt::Display>)
261 })
262 }
263
264 fn severity(&self) -> Option<Severity> {
265 self.diagnostic.severity()
266 }
267
268 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
269 self.diagnostic.help()
270 }
271
272 fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
273 self.diagnostic.url()
274 }
275
276 fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
277 self.diagnostic.labels()
278 }
279
280 fn source_code(&self) -> Option<&dyn SourceCode> {
282 if let Some(source_code) = self.diagnostic.source_code() {
283 Some(source_code)
284 } else {
285 Some(&self.working_set)
286 }
287 }
288
289 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
290 self.diagnostic.related()
291 }
292
293 fn diagnostic_source(&self) -> Option<&dyn miette::Diagnostic> {
294 self.diagnostic.diagnostic_source()
295 }
296}