Skip to content

Commit

Permalink
Create Files trait
Browse files Browse the repository at this point in the history
  • Loading branch information
brendanzab committed Feb 24, 2020
1 parent 01ec3e4 commit 1a68b22
Show file tree
Hide file tree
Showing 13 changed files with 297 additions and 250 deletions.
1 change: 0 additions & 1 deletion codespan-lsp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@ edition = "2018"

[dependencies]
codespan = { version = "0.8.0", path = "../codespan" } # CODESPAN
codespan-reporting = { version = "0.8.0", path = "../codespan-reporting/" } # CODESPAN
lsp-types = "0.70"
url = "2"
87 changes: 1 addition & 86 deletions codespan-lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ use codespan::{
ByteIndex, ByteOffset, ColumnIndex, FileId, Files, LineIndex, LineIndexOutOfBoundsError,
LocationError, RawIndex, RawOffset, Span, SpanOutOfBoundsError,
};
use codespan_reporting::diagnostic::{Diagnostic, Severity};
use lsp_types as lsp;
use std::{error, fmt};
use std::ffi::OsString;
use std::path::PathBuf;
use url::Url;
use std::{error, fmt};

#[derive(Debug, PartialEq)]
pub enum Error {
Expand Down Expand Up @@ -167,89 +165,6 @@ pub fn range_to_byte_span<Source: AsRef<str>>(
))
}

pub fn make_lsp_severity(severity: Severity) -> lsp::DiagnosticSeverity {
match severity {
Severity::Error | Severity::Bug => lsp::DiagnosticSeverity::Error,
Severity::Warning => lsp::DiagnosticSeverity::Warning,
Severity::Note => lsp::DiagnosticSeverity::Information,
Severity::Help => lsp::DiagnosticSeverity::Hint,
}
}

/// Translates a `codespan_reporting::Diagnostic` to a
/// `languageserver_types::Diagnostic`.
///
/// Since the language client requires `Url`s to locate the diagnostics,
/// `correlate_file_url` is necessary to resolve codespan `FileName`s
///
/// `code` and `file` are left empty by this function
pub fn make_lsp_diagnostic<Source: AsRef<str>>(
files: &Files<Source>,
source: impl Into<Option<String>>,
diagnostic: Diagnostic,
mut correlate_file_url: impl FnMut(FileId) -> Result<Url, ()>,
) -> Result<lsp::Diagnostic, Error> {
// We need a position for the primary error so take the span from the first primary label
let primary_file_id = diagnostic.primary_label.file_id;
let primary_span = diagnostic.primary_label.span;
let primary_label_range = byte_span_to_range(files, primary_file_id, primary_span)?;

// Collect additional context for primary message
let primary_message = {
let mut message = diagnostic.message;

if !diagnostic.notes.is_empty() {
// Spacer between message and notes
message.push_str("\n\n");
// Insert notes as a bulleted list
for note in diagnostic.notes {
for (i, line) in note.lines().enumerate() {
message.push_str(" ");
match i {
0 => message.push_str("•"),
_ => message.push_str(" "),
}
message.push_str(" ");
message.push_str(line.trim_end());
message.push_str("\n");
}
}
}

message
};

let related_information = diagnostic
.secondary_labels
.into_iter()
.map(|label| {
let file_id = label.file_id;
let range = byte_span_to_range(files, file_id, label.span)?;
let uri = correlate_file_url(file_id)
.map_err(|()| Error::UnableToCorrelateFilename(files.name(file_id).to_owned()))?;

Ok(lsp::DiagnosticRelatedInformation {
location: lsp::Location { uri, range },
message: label.message,
})
})
.collect::<Result<Vec<_>, Error>>()?;

Ok(lsp::Diagnostic {
range: primary_label_range,
code: diagnostic.code.map(lsp::NumberOrString::String),
source: source.into(),
severity: Some(make_lsp_severity(diagnostic.severity)),
message: primary_message,
related_information: if related_information.is_empty() {
None
} else {
Some(related_information)
},
tags: None,
})
}

#[cfg(test)]
mod tests {
use codespan::Location;
Expand Down
153 changes: 132 additions & 21 deletions codespan-reporting/src/diagnostic.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! Diagnostic data structures.

use codespan::{FileId, Span};
#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::ops::Range;

/// A severity level for diagnostic messages.
///
Expand Down Expand Up @@ -54,32 +54,36 @@ impl PartialOrd for Severity {
/// A label describing an underlined region of code associated with a diagnostic.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct Label {
pub struct Label<FileId> {
/// The file that we are labelling.
pub file_id: FileId,
/// The span we are going to include in the final snippet.
pub span: Span,
/// The range we are going to include in the final snippet.
pub range: Range<usize>,
/// A message to provide some additional information for the underlined
/// code. These should not include line breaks.
pub message: String,
}

impl Label {
impl<FileId> Label<FileId> {
/// Create a new label.
pub fn new(file_id: FileId, span: impl Into<Span>, message: impl Into<String>) -> Label {
pub fn new(
file_id: FileId,
range: impl Into<Range<usize>>,
message: impl Into<String>,
) -> Label<FileId> {
Label {
file_id,
span: span.into(),
range: range.into(),
message: message.into(),
}
}
}

/// Represents a diagnostic message that can provide information like errors and
/// warnings to the user.
#[derive(Clone, Debug)]
#[derive(Clone)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct Diagnostic {
pub struct Diagnostic<FileId> {
/// The overall severity of the diagnostic
pub severity: Severity,
/// An optional code that identifies this diagnostic.
Expand All @@ -91,17 +95,21 @@ pub struct Diagnostic {
/// `primary_label`.
pub message: String,
/// A label that describes the primary cause of this diagnostic.
pub primary_label: Label,
pub primary_label: Label<FileId>,
/// Notes that are associated with the primary cause of the diagnostic.
/// These can include line breaks for improved formatting.
pub notes: Vec<String>,
/// Secondary labels that provide additional context for the diagnostic.
pub secondary_labels: Vec<Label>,
pub secondary_labels: Vec<Label<FileId>>,
}

impl Diagnostic {
impl<FileId> Diagnostic<FileId> {
/// Create a new diagnostic.
pub fn new(severity: Severity, message: impl Into<String>, primary_label: Label) -> Diagnostic {
pub fn new(
severity: Severity,
message: impl Into<String>,
primary_label: Label<FileId>,
) -> Diagnostic<FileId> {
Diagnostic {
severity,
code: None,
Expand All @@ -113,45 +121,148 @@ impl Diagnostic {
}

/// Create a new diagnostic with a severity of `Severity::Bug`.
pub fn new_bug(message: impl Into<String>, primary_label: Label) -> Diagnostic {
pub fn new_bug(message: impl Into<String>, primary_label: Label<FileId>) -> Diagnostic<FileId> {
Diagnostic::new(Severity::Bug, message, primary_label)
}

/// Create a new diagnostic with a severity of `Severity::Error`.
pub fn new_error(message: impl Into<String>, primary_label: Label) -> Diagnostic {
pub fn new_error(
message: impl Into<String>,
primary_label: Label<FileId>,
) -> Diagnostic<FileId> {
Diagnostic::new(Severity::Error, message, primary_label)
}

/// Create a new diagnostic with a severity of `Severity::Warning`.
pub fn new_warning(message: impl Into<String>, primary_label: Label) -> Diagnostic {
pub fn new_warning(
message: impl Into<String>,
primary_label: Label<FileId>,
) -> Diagnostic<FileId> {
Diagnostic::new(Severity::Warning, message, primary_label)
}

/// Create a new diagnostic with a severity of `Severity::Note`.
pub fn new_note(message: impl Into<String>, primary_label: Label) -> Diagnostic {
pub fn new_note(
message: impl Into<String>,
primary_label: Label<FileId>,
) -> Diagnostic<FileId> {
Diagnostic::new(Severity::Note, message, primary_label)
}

/// Create a new diagnostic with a severity of `Severity::Help`.
pub fn new_help(message: impl Into<String>, primary_label: Label) -> Diagnostic {
pub fn new_help(
message: impl Into<String>,
primary_label: Label<FileId>,
) -> Diagnostic<FileId> {
Diagnostic::new(Severity::Help, message, primary_label)
}

/// Add an error code to the diagnostic.
pub fn with_code(mut self, code: impl Into<String>) -> Diagnostic {
pub fn with_code(mut self, code: impl Into<String>) -> Diagnostic<FileId> {
self.code = Some(code.into());
self
}

/// Add some notes to the diagnostic.
pub fn with_notes(mut self, notes: Vec<String>) -> Diagnostic {
pub fn with_notes(mut self, notes: Vec<String>) -> Diagnostic<FileId> {
self.notes = notes;
self
}

/// Add some secondary labels to the diagnostic.
pub fn with_secondary_labels(mut self, labels: impl IntoIterator<Item = Label>) -> Diagnostic {
pub fn with_secondary_labels(
mut self,
labels: impl IntoIterator<Item = Label<FileId>>,
) -> Diagnostic<FileId> {
self.secondary_labels.extend(labels);
self
}
}

/// A location in a source file.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Location {
/// The line index in the source file.
pub line: usize,
/// The column index in the source file.
pub column: usize,
}

/// A line within a source file.
pub struct Line<Source> {
/// The starting byte index of the line.
pub start: usize,
/// The source of the line.
pub source: Source,
}

impl<Source> Line<Source>
where
Source: AsRef<str>,
{
pub fn column_index(&self, byte_index: usize) -> Option<usize> {
match byte_index.checked_sub(self.start) {
None => Some(0),
Some(relative_index) => {
let source = self.source.as_ref().get(..relative_index)?;
Some(source.chars().count())
},
}
}
}

/// Files that can be used for pretty printing.
pub trait Files {
type FileId: Copy + PartialEq + PartialOrd + Eq + Ord + std::hash::Hash;
type Origin: std::fmt::Display;
type LineSource: AsRef<str>;

/// The origin of a file.
fn origin(&self, id: Self::FileId) -> Option<Self::Origin>;

/// The line at the given index.
fn line(&self, id: Self::FileId, line_index: usize) -> Option<Line<Self::LineSource>>;

/// The index of the line at the given byte index.
fn line_index(&self, id: Self::FileId, byte_index: usize) -> Option<usize>;

/// The location of the given byte index.
fn location(&self, id: Self::FileId, byte_index: usize) -> Option<Location> {
let line_index = self.line_index(id, byte_index)?;
let column_index = self.line(id, line_index)?.column_index(byte_index)?;

Some(Location {
line: line_index,
column: column_index,
})
}
}

impl<Source> Files for codespan::Files<Source>
where
Source: AsRef<str>,
{
type FileId = codespan::FileId;
type Origin = String;
type LineSource = String;

fn origin(&self, id: codespan::FileId) -> Option<String> {
use std::path::PathBuf;

Some(PathBuf::from(self.name(id)).display().to_string())
}

fn line_index(&self, id: Self::FileId, byte_index: usize) -> Option<usize> {
Some(self.line_index(id, byte_index as u32).to_usize())
}

fn line(&self, id: codespan::FileId, line_index: usize) -> Option<Line<String>> {
let span = self.line_span(id, line_index as u32).ok()?;
let source = self.source_slice(id, span).ok()?;

Some(Line {
start: span.start().to_usize(),
source: source.to_owned(),
})
}
}
13 changes: 6 additions & 7 deletions codespan-reporting/src/term.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
//! Terminal back-end for emitting diagnostics.

use codespan::Files;
use std::io;
use std::str::FromStr;
use termcolor::{ColorChoice, WriteColor};

use crate::diagnostic::Diagnostic;
use crate::diagnostic::{Diagnostic, Files};

mod config;
mod views;
Expand All @@ -15,11 +14,11 @@ pub use termcolor;
pub use self::config::{Chars, Config, DisplayStyle, Styles};

/// Emit a diagnostic using the given writer, context, config, and files.
pub fn emit<Source: AsRef<str>>(
pub fn emit<F: Files>(
writer: &mut (impl WriteColor + ?Sized),
config: &Config,
files: &Files<Source>,
diagnostic: &Diagnostic,
files: &F,
diagnostic: &Diagnostic<F::FileId>,
) -> io::Result<()> {
use self::views::{RichDiagnostic, ShortDiagnostic};

Expand Down Expand Up @@ -98,14 +97,14 @@ mod tests {

#[test]
fn unsized_emit() {
let mut files = Files::new();
let mut files = codespan::Files::new();

let id = files.add("test", "");
emit(
&mut termcolor::NoColor::new(Vec::<u8>::new()) as &mut dyn WriteColor,
&Config::default(),
&files,
&Diagnostic::new_bug("", Label::new(id, codespan::Span::default(), "")),
&Diagnostic::new_bug("", Label::new(id, 0..0, "")),
)
.unwrap();
}
Expand Down
Loading

0 comments on commit 1a68b22

Please sign in to comment.