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