Description
In order to address concerns in #11, #12, wycats/language-reporting#6, and probably others.
I've been experimenting with merging the APIs of codespan
/language-reporting
/annotate-snippets
, and the below API surface is what that I think makes the most sense.
NOTE: the suggested API has changed multiple times from feedback, see conversation starting at this comment for the most recent API and discussion.
Original Proposal
An experimental implementation of the API based on #12 is at CAD97/retort#1 (being pushed within 24 hours of posting, I've got one last bit to "port" but I've got to get to bed now but I wanted to get this posted first).
EDIT: I've reconsidered this API, though the linked PR does implement most of it. I'm sketching a new slightly lower-level design from this one, and the diagnostic layout of this current API will probably be a wrapper library around
annotate-snippets
. (I get to use theretort
name!)
API
use termcolor::WriteColor;
trait Span: fmt::Debug + Copy {
type Origin: ?Sized + fmt::Debug + Eq;
fn start(&self) -> usize;
fn end(&self) -> usize;
fn new(&self, start: usize, end: usize) -> Self;
fn origin(&self) -> &Self::Origin;
}
trait SpanResolver<Sp> {
fn first_line_of(&mut self, span: Sp) -> Option<SpannedLine<Sp>>;
fn next_line_of(&mut self, span: Sp, line: SpannedLine<Sp>) -> Option<SpannedLine<Sp>>;
fn write_span(&mut self, w: &mut dyn WriteColor, span: Sp) -> io::Result<()>;
fn write_origin(&mut self, w: &mut dyn WriteColor, origin: Sp) -> io::Result<()>;
}
#[derive(Debug, Copy, Clone)]
pub struct SpannedLine<Sp> {
line_num: usize,
char_count: usize,
span: Sp,
}
impl Span for (usize, usize) {
type Origin = ();
}
impl<Sp: Span<Origin=()>> Span for (&'_ str, Sp) {
type Origin = str;
}
impl<Sp: Span> SpanResolver<Sp> for &str
where Sp::Origin: fmt::Display;
mod diagnostic {
#[derive(Debug, Clone)]
struct Diagnostic<'a, Sp: Span> {
pub primary: Annotation<'a, Sp>,
pub code: Option<Cow<'a, str>>,
pub secondary: Cow<'a, [Annotation<'a, Sp>]>,
}
#[derive(Debug, Clone)]
struct Annotation<'a, Sp: Span> {
pub span: Sp,
pub level: Level,
pub message: Cow<'a, str>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
enum Level {
Err,
Warn,
Info,
Hint,
}
impl<Sp: Span> Diagnostic<'_, Sp> {
pub fn borrow(&self) -> Diagnostic<'_, Sp>;
pub fn into_owned(self) -> Diagnostic<'static, Sp>;
}
impl<Sp: Span> Annotation<'_, Sp> {
pub fn borrow(&self) -> Annotation<'_, Sp>;
pub fn into_owned(self) -> Annotation<'static, Sp>;
}
impl fmt::Display for Level;
}
mod style {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
enum Mark {
None,
Start,
Continue,
End,
}
#[non_exhaustive]
#[derive(Debug, Copy, Clone)]
pub enum Style {
Base,
Code,
Diagnostic(Level),
LineNum,
TitleLine,
OriginLine,
}
trait Stylesheet {
fn set_style(&mut self, w: &mut dyn WriteColor, style: Style) -> io::Result<()>;
fn write_marks(&mut self, w: &mut dyn WriteColor, marks: &[Mark]) -> io::Result<()>;
fn write_divider(&mut self, w: &mut dyn WriteColor) -> io::Result<()>;
fn write_underline(
&mut self,
w: &mut dyn WriteColor,
level: Level,
len: usize,
) -> io::Result<()>;
}
struct Rustc; impl Stylesheet for Rustc;
// other styles in the future
}
mod renderer {
fn render<'a, Sp: Span>(
w: &mut dyn WriteColor,
stylesheet: &dyn Stylesheet,
span_resolver: &mut dyn SpanResolver<Sp>,
diagnostic: &'a Diagnostic<'a, Sp>,
) -> io::Result<()>;
fn lsp<'a, Sp: Span + 'a>(
diagnostics: impl IntoIterator<Item = Diagnostic<'a, Sp>>,
source: Option<&'_ str>,
span_resolver: impl FnMut(Sp) -> lsp_types::Location,
) -> Vec<lsp_types::PublishDiagnosticsParams>;
}
Notes:
- I've skipped imports and implementation bodies for clarity. All definitions are exported where I've written them.
- I've liberally used
dyn Trait
, so the only monomorphization should be over theSpan
type. - I'm not particularly attached to any of the organization of exports, things can move around.
Span::new
is only used forimpl SpanResolver<impl Span> for &str
; making that impl more specific can get rid of that trait method.SpanResolver
takes&mut
for its methods primarily because it can, in order to allow use of a single-threaded DB that requires&mut
access for caching as a span resolver.Span
resolution is passed throughSpanResolver
at the last moment such that aSpanResolver
can supply syntax highlighting for errors.SpanResolver::write_origin
only getsio::Write
because styling is done ahead of time byStylesheet
. BecauseWriteColor
does not have an upcast method, this means we can't usedyn WriteColor
anywhere that will end up callingSpanResolver::write_origin
. This can be changed to takeWriteColor
if desired.Diagnostic
's layout is tuned to have similar layout to the language server protocol'sDiagnostic
.Diagnostic
is set up so thatDiagnostic<'_, Sp>
can be borrowed but also an ownedDiagnostic<'static, Sp>
can be produced by usingCow
s. This eases use with constructed diagnostics.- Potential style improvement: extend
Style::Code
to be an enum of general code token types (e.g. the list from pygments),SpanResolver::write_span
just gets the ability to set the style to one of those, which goes through theStyleSheet
for styling.