-
Notifications
You must be signed in to change notification settings - Fork 52
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 theretortname!)
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 theSpantype. - I'm not particularly attached to any of the organization of exports, things can move around.
Span::newis only used forimpl SpanResolver<impl Span> for &str; making that impl more specific can get rid of that trait method.SpanResolvertakes&mutfor its methods primarily because it can, in order to allow use of a single-threaded DB that requires&mutaccess for caching as a span resolver.Spanresolution is passed throughSpanResolverat the last moment such that aSpanResolvercan supply syntax highlighting for errors.SpanResolver::write_originonly getsio::Writebecause styling is done ahead of time byStylesheet. BecauseWriteColordoes not have an upcast method, this means we can't usedyn WriteColoranywhere that will end up callingSpanResolver::write_origin. This can be changed to takeWriteColorif desired.Diagnostic's layout is tuned to have similar layout to the language server protocol'sDiagnostic.Diagnosticis set up so thatDiagnostic<'_, Sp>can be borrowed but also an ownedDiagnostic<'static, Sp>can be produced by usingCows. This eases use with constructed diagnostics.- Potential style improvement: extend
Style::Codeto be an enum of general code token types (e.g. the list from pygments),SpanResolver::write_spanjust gets the ability to set the style to one of those, which goes through theStyleSheetfor styling.