1use miette::{Diagnostic, GraphicalReportHandler, Report, SourceCode, SourceSpan, SpanContents};
2use nu_protocol::{ShellError, Span};
3use vfs::{VfsError, error::VfsErrorKind};
4
5pub struct CommandError {
6 pub error: Report,
7 pub start_offset: usize,
8 pub input: String,
9}
10
11impl CommandError {
12 pub fn new<E>(error: E, input: impl Into<String>) -> Self
13 where
14 E: Diagnostic + Clone + Send + Sync + 'static,
15 {
16 Self {
17 error: Report::new(error),
18 start_offset: 0,
19 input: input.into(),
20 }
21 }
22
23 pub fn with_start_offset(mut self, start_offset: usize) -> Self {
24 self.start_offset = start_offset;
25 self
26 }
27}
28
29impl From<ShellError> for CommandError {
30 fn from(value: ShellError) -> Self {
31 CommandError::new(value, String::new())
32 }
33}
34
35impl From<CommandError> for String {
36 fn from(value: CommandError) -> Self {
37 let handler = GraphicalReportHandler::new()
38 .with_theme(miette::GraphicalTheme::unicode())
39 .with_cause_chain();
40
41 if value.input.is_empty() {
42 let mut msg = String::new();
43 handler
44 .render_report(&mut msg, value.error.as_ref())
45 .unwrap();
46 return msg;
47 }
48
49 let source = OffsetSource {
50 source: value.input,
51 start_offset: value.start_offset,
52 };
53
54 let report_with_source = value.error.with_source_code(source);
55 let mut msg = String::new();
56 handler
57 .render_report(&mut msg, report_with_source.as_ref())
58 .unwrap();
59 msg
60 }
61}
62
63pub struct OffsetSource {
64 pub source: String,
65 pub start_offset: usize,
66}
67
68pub struct OffsetSpanContents<'a> {
69 inner: Box<dyn SpanContents<'a> + 'a>,
70 global_span: SourceSpan,
71}
72
73impl<'a> SpanContents<'a> for OffsetSpanContents<'a> {
74 fn data(&self) -> &'a [u8] {
75 self.inner.data()
76 }
77 fn span(&self) -> &SourceSpan {
78 &self.global_span
79 }
80 fn line(&self) -> usize {
81 self.inner.line()
82 }
83 fn column(&self) -> usize {
84 self.inner.column()
85 }
86 fn line_count(&self) -> usize {
87 self.inner.line_count()
88 }
89}
90
91impl SourceCode for OffsetSource {
92 fn read_span<'b>(
93 &'b self,
94 span: &SourceSpan,
95 context_lines_before: usize,
96 context_lines_after: usize,
97 ) -> Result<Box<dyn miette::SpanContents<'b> + 'b>, miette::MietteError> {
98 let local_start = span.offset().saturating_sub(self.start_offset);
99 let local_len = std::cmp::min(span.len(), self.source.len().saturating_sub(local_start));
100 let local_span = SourceSpan::new(local_start.into(), local_len);
101
102 let local_contents =
103 self.source
104 .read_span(&local_span, context_lines_before, context_lines_after)?;
105
106 let content_local_span = local_contents.span();
107 let global_start = content_local_span.offset() + self.start_offset;
108 let global_span = SourceSpan::new(global_start.into(), content_local_span.len());
109
110 Ok(Box::new(OffsetSpanContents {
111 inner: local_contents,
112 global_span,
113 }))
114 }
115}
116
117pub fn to_shell_err(span: Span) -> impl Fn(VfsError) -> ShellError {
118 move |vfs_error: VfsError| ShellError::GenericError {
119 error: (match vfs_error.kind() {
120 VfsErrorKind::DirectoryExists
121 | VfsErrorKind::FileExists
122 | VfsErrorKind::FileNotFound
123 | VfsErrorKind::InvalidPath => "path error",
124 _ => "io error",
125 })
126 .to_string(),
127 msg: vfs_error.to_string(),
128 span: Some(span),
129 help: None,
130 inner: vec![],
131 }
132}