feat(lsp): enable compiling a project

This commit is contained in:
rvcas 2023-02-17 21:57:01 -05:00 committed by Lucas
parent b55726c90f
commit 38bcbaf701
8 changed files with 520 additions and 250 deletions

View File

@ -0,0 +1,21 @@
use crate::error::Error;
pub fn cast_request<R>(request: lsp_server::Request) -> Result<R::Params, Error>
where
R: lsp_types::request::Request,
R::Params: serde::de::DeserializeOwned,
{
let (_, params) = request.extract(R::METHOD)?;
Ok(params)
}
pub fn cast_notification<N>(notification: lsp_server::Notification) -> Result<N::Params, Error>
where
N: lsp_types::notification::Notification,
N::Params: serde::de::DeserializeOwned,
{
let params = notification.extract::<N::Params>(N::METHOD)?;
Ok(params)
}

View File

@ -1,12 +1,17 @@
use std::env;
use aiken_project::{config::Config, paths};
use lsp_server::Connection; use lsp_server::Connection;
use lsp_types::{ use lsp_types::{
OneOf, SaveOptions, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, OneOf, SaveOptions, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind,
TextDocumentSyncOptions, TextDocumentSyncSaveOptions, TextDocumentSyncOptions, TextDocumentSyncSaveOptions,
}; };
mod cast;
pub mod error; pub mod error;
mod line_numbers; mod line_numbers;
pub mod server; pub mod server;
mod utils;
use error::Error; use error::Error;
@ -15,6 +20,18 @@ use crate::server::Server;
pub fn start() -> Result<(), Error> { pub fn start() -> Result<(), Error> {
tracing::info!("Aiken language server starting"); tracing::info!("Aiken language server starting");
let root = env::current_dir()?;
let config = if paths::project_config().exists() {
tracing::info!("Aiken project detected");
Some(Config::load(&root).expect("failed to load aiken.toml"))
} else {
tracing::info!("Aiken project config not found");
None
};
// Create the transport. Includes the stdio (stdin and stdout) versions but this could // Create the transport. Includes the stdio (stdin and stdout) versions but this could
// also be implemented to use sockets or HTTP. // also be implemented to use sockets or HTTP.
let (connection, io_threads) = Connection::stdio(); let (connection, io_threads) = Connection::stdio();
@ -25,7 +42,7 @@ pub fn start() -> Result<(), Error> {
let initialization_params = connection.initialize(server_capabilities)?; let initialization_params = connection.initialize(server_capabilities)?;
let initialize_params = serde_json::from_value(initialization_params)?; let initialize_params = serde_json::from_value(initialization_params)?;
let mut server = Server::new(initialize_params, None); let mut server = Server::new(initialize_params, config, root);
server.listen(connection)?; server.listen(connection)?;

View File

@ -1,26 +1,44 @@
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
error::Error,
fs, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use aiken_lang::{ast::ModuleKind, parser}; use aiken_lang::{ast::ModuleKind, parser};
use aiken_project::{config, error::Error as ProjectError}; use aiken_project::{
config,
error::{Error as ProjectError, GetSource},
};
use lsp_server::{Connection, Message}; use lsp_server::{Connection, Message};
use lsp_types::{ use lsp_types::{
notification::{ notification::{
DidChangeTextDocument, DidSaveTextDocument, Notification, PublishDiagnostics, ShowMessage, DidChangeTextDocument, DidSaveTextDocument, Notification, Progress, PublishDiagnostics,
ShowMessage,
}, },
request::{Formatting, Request}, request::{Formatting, Request, WorkDoneProgressCreate},
DocumentFormattingParams, InitializeParams, TextEdit, DocumentFormattingParams, InitializeParams, TextEdit,
}; };
use miette::Diagnostic; use miette::Diagnostic;
use crate::{error::Error as ServerError, line_numbers::LineNumbers}; use crate::{
cast::{cast_notification, cast_request},
error::Error as ServerError,
line_numbers::LineNumbers,
utils::{
path_to_uri, text_edit_replace, COMPILING_PROGRESS_TOKEN, CREATE_COMPILING_PROGRESS_TOKEN,
},
};
use self::lsp_project::LspProject;
mod lsp_project;
pub mod telemetry;
#[allow(dead_code)] #[allow(dead_code)]
pub struct Server { pub struct Server {
// Project root directory
root: PathBuf,
config: Option<config::Config>, config: Option<config::Config>,
/// Files that have been edited in memory /// Files that have been edited in memory
@ -39,114 +57,114 @@ pub struct Server {
/// to the client. These are likely locationless Aiken diagnostics, as LSP /// to the client. These are likely locationless Aiken diagnostics, as LSP
/// diagnostics always need a location. /// diagnostics always need a location.
stored_messages: Vec<lsp_types::ShowMessageParams>, stored_messages: Vec<lsp_types::ShowMessageParams>,
/// An instance of a LspProject
compiler: Option<LspProject>,
} }
impl Server { impl Server {
pub fn new(initialize_params: InitializeParams, config: Option<config::Config>) -> Self { /// Clear all diagnostics that have been previously published to the client
Self { fn clear_all_diagnostics(&mut self, connection: &Connection) -> Result<(), ServerError> {
config, for file in self.published_diagnostics.drain() {
edited: HashMap::new(), let params = lsp_types::PublishDiagnosticsParams {
initialize_params, uri: file,
published_diagnostics: HashSet::new(), diagnostics: vec![],
stored_diagnostics: HashMap::new(), version: None,
stored_messages: Vec::new(), };
}
}
pub fn listen(&mut self, connection: Connection) -> Result<(), ServerError> { let notification = lsp_server::Notification {
self.publish_stored_diagnostics(&connection)?; method: PublishDiagnostics::METHOD.to_string(),
params: serde_json::to_value(params)?,
};
for msg in &connection.receiver { connection
tracing::debug!("Got message: {:#?}", msg); .sender
.send(lsp_server::Message::Notification(notification))?;
match msg {
Message::Request(req) => {
if connection.handle_shutdown(&req)? {
return Ok(());
}
tracing::debug!("Get request: {:#?}", req);
let response = self.handle_request(req)?;
self.publish_stored_diagnostics(&connection)?;
connection.sender.send(Message::Response(response))?;
}
Message::Response(_) => todo!(),
Message::Notification(notification) => {
self.handle_notification(&connection, notification)?
}
}
} }
Ok(()) Ok(())
} }
fn handle_request( /// Compile the project if we are in one. Otherwise do nothing.
&mut self, fn compile(&mut self, connection: &Connection) -> Result<(), ServerError> {
request: lsp_server::Request, self.notify_client_of_compilation_start(connection)?;
) -> Result<lsp_server::Response, ServerError> {
let id = request.id.clone();
match request.method.as_str() { eprintln!("{:?}", self.compiler.is_some());
Formatting::METHOD => { if let Some(compiler) = self.compiler.as_mut() {
let params = cast_request::<Formatting>(request)?; let result = compiler.compile();
let result = self.format(params); for warning in compiler.project.warnings() {
self.process_diagnostic(warning)?;
}
match result { if let Err(errs) = result {
Ok(text_edit) => { for err in errs {
let result = serde_json::to_value(text_edit)?; self.process_diagnostic(err)?;
Ok(lsp_server::Response {
id,
error: None,
result: Some(result),
})
}
Err(err) => match err {
ProjectError::List(errors) => {
for error in errors {
if error.source_code().is_some() {
self.process_diagnostic(error)?;
}
}
Ok(lsp_server::Response {
id,
error: None,
result: Some(serde_json::json!(null)),
})
}
error => {
if error.source_code().is_some() {
self.process_diagnostic(error)?;
Ok(lsp_server::Response {
id,
error: None,
result: Some(serde_json::json!(null)),
})
} else {
Ok(lsp_server::Response {
id,
error: Some(lsp_server::ResponseError {
code: 1, // We should assign a code to each error.
message: format!("{error:?}"),
data: None,
}),
result: None,
})
}
}
},
} }
} }
unsupported => Err(ServerError::UnsupportedLspRequest {
request: unsupported.to_string(),
}),
} }
self.notify_client_of_compilation_end(connection)?;
Ok(())
}
fn create_compilation_progress_token(
&mut self,
connection: &lsp_server::Connection,
) -> Result<(), ServerError> {
let params = lsp_types::WorkDoneProgressCreateParams {
token: lsp_types::NumberOrString::String(COMPILING_PROGRESS_TOKEN.into()),
};
let request = lsp_server::Request {
id: CREATE_COMPILING_PROGRESS_TOKEN.to_string().into(),
method: WorkDoneProgressCreate::METHOD.into(),
params: serde_json::to_value(&params)?,
};
connection
.sender
.send(lsp_server::Message::Request(request))?;
Ok(())
}
fn create_new_compiler(&mut self) {
if let Some(config) = self.config.as_ref() {
let compiler = LspProject::new(config.clone(), self.root.clone(), telemetry::Lsp);
self.compiler = Some(compiler);
}
}
fn format(
&mut self,
params: DocumentFormattingParams,
) -> Result<Vec<TextEdit>, Vec<ProjectError>> {
let path = params.text_document.uri.path();
let mut new_text = String::new();
match self.edited.get(path) {
Some(src) => {
let (module, extra) = parser::module(src, ModuleKind::Lib).map_err(|errs| {
aiken_project::error::Error::from_parse_errors(errs, Path::new(path), src)
})?;
aiken_lang::format::pretty(&mut new_text, module, extra, src);
}
None => {
let src = fs::read_to_string(path).map_err(ProjectError::from)?;
let (module, extra) = parser::module(&src, ModuleKind::Lib).map_err(|errs| {
aiken_project::error::Error::from_parse_errors(errs, Path::new(path), &src)
})?;
aiken_lang::format::pretty(&mut new_text, module, extra, &src);
}
}
Ok(vec![text_edit_replace(new_text)])
} }
fn handle_notification( fn handle_notification(
@ -178,96 +196,123 @@ impl Server {
} }
} }
fn format(&mut self, params: DocumentFormattingParams) -> Result<Vec<TextEdit>, ProjectError> { fn handle_request(
let path = params.text_document.uri.path(); &mut self,
let mut new_text = String::new(); request: lsp_server::Request,
) -> Result<lsp_server::Response, ServerError> {
let id = request.id.clone();
match self.edited.get(path) { match request.method.as_str() {
Some(src) => { Formatting::METHOD => {
let (module, extra) = parser::module(src, ModuleKind::Lib).map_err(|errs| { let params = cast_request::<Formatting>(request)?;
aiken_project::error::Error::from_parse_errors(errs, Path::new(path), src)
})?;
aiken_lang::format::pretty(&mut new_text, module, extra, src); let result = self.format(params);
}
None => { match result {
let src = fs::read_to_string(path)?; Ok(text_edit) => {
let result = serde_json::to_value(text_edit)?;
let (module, extra) = parser::module(&src, ModuleKind::Lib).map_err(|errs| {
aiken_project::error::Error::from_parse_errors(errs, Path::new(path), &src) Ok(lsp_server::Response {
})?; id,
error: None,
aiken_lang::format::pretty(&mut new_text, module, extra, &src); result: Some(result),
})
}
Err(errors) => {
for error in errors {
self.process_diagnostic(error)?;
}
Ok(lsp_server::Response {
id,
error: None,
result: Some(serde_json::json!(null)),
})
}
}
} }
unsupported => Err(ServerError::UnsupportedLspRequest {
request: unsupported.to_string(),
}),
} }
Ok(vec![text_edit_replace(new_text)])
} }
/// Publish all stored diagnostics to the client. pub fn listen(&mut self, connection: Connection) -> Result<(), ServerError> {
/// Any previously publish diagnostics are cleared before the new set are self.create_compilation_progress_token(&connection)?;
/// published to the client. self.start_watching_aiken_toml(&connection)?;
fn publish_stored_diagnostics(&mut self, connection: &Connection) -> Result<(), ServerError> {
self.clear_all_diagnostics(connection)?;
for (path, diagnostics) in self.stored_diagnostics.drain() { // Compile the project once so we have all the state and any initial errors
let uri = path_to_uri(path)?; self.compile(&connection)?;
self.publish_stored_diagnostics(&connection)?;
// Record that we have published diagnostics to this file so we can for msg in &connection.receiver {
// clear it later when they are outdated. tracing::debug!("Got message: {:#?}", msg);
self.published_diagnostics.insert(uri.clone());
// Publish the diagnostics match msg {
let params = lsp_types::PublishDiagnosticsParams { Message::Request(req) => {
uri, if connection.handle_shutdown(&req)? {
diagnostics, return Ok(());
version: None, }
};
let notification = lsp_server::Notification { tracing::debug!("Get request: {:#?}", req);
method: PublishDiagnostics::METHOD.to_string(),
params: serde_json::to_value(params)?,
};
connection let response = self.handle_request(req)?;
.sender
.send(lsp_server::Message::Notification(notification))?;
}
for message in self.stored_messages.drain(..) { self.publish_stored_diagnostics(&connection)?;
let notification = lsp_server::Notification {
method: ShowMessage::METHOD.to_string(),
params: serde_json::to_value(message)?,
};
connection connection.sender.send(Message::Response(response))?;
.sender }
.send(lsp_server::Message::Notification(notification))?; Message::Response(_) => (),
Message::Notification(notification) => {
self.handle_notification(&connection, notification)?
}
}
} }
Ok(()) Ok(())
} }
/// Clear all diagnostics that have been previously published to the client pub fn new(
fn clear_all_diagnostics(&mut self, connection: &Connection) -> Result<(), ServerError> { initialize_params: InitializeParams,
for file in self.published_diagnostics.drain() { config: Option<config::Config>,
let params = lsp_types::PublishDiagnosticsParams { root: PathBuf,
uri: file, ) -> Self {
diagnostics: vec![], let mut server = Server {
version: None, root,
}; config,
edited: HashMap::new(),
initialize_params,
published_diagnostics: HashSet::new(),
stored_diagnostics: HashMap::new(),
stored_messages: Vec::new(),
compiler: None,
};
let notification = lsp_server::Notification { server.create_new_compiler();
method: PublishDiagnostics::METHOD.to_string(),
params: serde_json::to_value(params)?,
};
connection server
.sender }
.send(lsp_server::Message::Notification(notification))?;
}
Ok(()) fn notify_client_of_compilation_end(&self, connection: &Connection) -> Result<(), ServerError> {
self.send_work_done_notification(
connection,
lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message: None }),
)
}
fn notify_client_of_compilation_start(
&self,
connection: &Connection,
) -> Result<(), ServerError> {
self.send_work_done_notification(
connection,
lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
title: "Compiling Aiken".into(),
cancellable: Some(false),
message: None,
percentage: None,
}),
)
} }
/// Convert Aiken diagnostics into 1 or more LSP diagnostics and store them /// Convert Aiken diagnostics into 1 or more LSP diagnostics and store them
@ -277,7 +322,10 @@ impl Server {
/// If the Aiken diagnostic cannot be converted to LSP diagnostic (due to it /// If the Aiken diagnostic cannot be converted to LSP diagnostic (due to it
/// not having a location) it is stored as a message suitable for use with /// not having a location) it is stored as a message suitable for use with
/// the `showMessage` notification instead. /// the `showMessage` notification instead.
fn process_diagnostic(&mut self, error: ProjectError) -> Result<(), ServerError> { fn process_diagnostic<E>(&mut self, error: E) -> Result<(), ServerError>
where
E: Diagnostic + GetSource,
{
let (severity, typ) = match error.severity() { let (severity, typ) = match error.severity() {
Some(severity) => match severity { Some(severity) => match severity {
miette::Severity::Error => ( miette::Severity::Error => (
@ -369,56 +417,126 @@ impl Server {
Ok(()) Ok(())
} }
/// Publish all stored diagnostics to the client.
/// Any previously publish diagnostics are cleared before the new set are
/// published to the client.
fn publish_stored_diagnostics(&mut self, connection: &Connection) -> Result<(), ServerError> {
self.clear_all_diagnostics(connection)?;
for (path, diagnostics) in self.stored_diagnostics.drain() {
let uri = path_to_uri(path)?;
// Record that we have published diagnostics to this file so we can
// clear it later when they are outdated.
self.published_diagnostics.insert(uri.clone());
// Publish the diagnostics
let params = lsp_types::PublishDiagnosticsParams {
uri,
diagnostics,
version: None,
};
let notification = lsp_server::Notification {
method: PublishDiagnostics::METHOD.to_string(),
params: serde_json::to_value(params)?,
};
connection
.sender
.send(lsp_server::Message::Notification(notification))?;
}
for message in self.stored_messages.drain(..) {
let notification = lsp_server::Notification {
method: ShowMessage::METHOD.to_string(),
params: serde_json::to_value(message)?,
};
connection
.sender
.send(lsp_server::Message::Notification(notification))?;
}
Ok(())
}
fn push_diagnostic(&mut self, path: PathBuf, diagnostic: lsp_types::Diagnostic) { fn push_diagnostic(&mut self, path: PathBuf, diagnostic: lsp_types::Diagnostic) {
self.stored_diagnostics self.stored_diagnostics
.entry(path) .entry(path)
.or_default() .or_default()
.push(diagnostic); .push(diagnostic);
} }
}
fn cast_request<R>(request: lsp_server::Request) -> Result<R::Params, ServerError> fn send_work_done_notification(
where &self,
R: lsp_types::request::Request, connection: &Connection,
R::Params: serde::de::DeserializeOwned, work_done: lsp_types::WorkDoneProgress,
{ ) -> Result<(), ServerError> {
let (_, params) = request.extract(R::METHOD)?; tracing::info!("sending {:?}", work_done);
Ok(params) let params = lsp_types::ProgressParams {
} token: lsp_types::NumberOrString::String(COMPILING_PROGRESS_TOKEN.to_string()),
value: lsp_types::ProgressParamsValue::WorkDone(work_done),
};
fn cast_notification<N>(notification: lsp_server::Notification) -> Result<N::Params, ServerError> let notification = lsp_server::Notification {
where method: Progress::METHOD.into(),
N: lsp_types::notification::Notification, params: serde_json::to_value(&params)?,
N::Params: serde::de::DeserializeOwned, };
{
let params = notification.extract::<N::Params>(N::METHOD)?;
Ok(params) connection
} .sender
.send(lsp_server::Message::Notification(notification))?;
fn text_edit_replace(new_text: String) -> TextEdit { Ok(())
TextEdit { }
range: lsp_types::Range {
start: lsp_types::Position { fn start_watching_aiken_toml(&mut self, connection: &Connection) -> Result<(), ServerError> {
line: 0, let supports_watch_files = self
character: 0, .initialize_params
}, .capabilities
end: lsp_types::Position { .workspace
line: u32::MAX, .as_ref()
character: 0, .and_then(|w| w.did_change_watched_files)
}, .map(|wf| wf.dynamic_registration == Some(true))
}, .unwrap_or(false);
new_text,
if !supports_watch_files {
tracing::warn!("lsp_client_cannot_watch_gleam_toml");
return Ok(());
}
// Register gleam.toml as a watched file so we get a notification when
// it changes and thus know that we need to rebuild the entire project.
let register_options =
serde_json::value::to_value(lsp_types::DidChangeWatchedFilesRegistrationOptions {
watchers: vec![lsp_types::FileSystemWatcher {
glob_pattern: "aiken.toml".into(),
kind: Some(lsp_types::WatchKind::Change),
}],
})?;
let watch_config = lsp_types::Registration {
id: "watch-aiken-toml".into(),
method: "workspace/didChangeWatchedFiles".into(),
register_options: Some(register_options),
};
let request = lsp_server::Request {
id: 1.into(),
method: "client/registerCapability".into(),
params: serde_json::value::to_value(lsp_types::RegistrationParams {
registrations: vec![watch_config],
})
.expect("client/registerCapability to json"),
};
connection
.sender
.send(lsp_server::Message::Request(request))?;
Ok(())
} }
} }
fn path_to_uri(path: PathBuf) -> Result<lsp_types::Url, ServerError> {
let mut file: String = "file://".into();
file.push_str(&path.as_os_str().to_string_lossy());
let uri = lsp_types::Url::parse(&file)?;
Ok(uri)
}

View File

@ -0,0 +1,64 @@
use std::{collections::HashMap, path::PathBuf};
use aiken_lang::ast::Tracing;
use aiken_project::{config::Config, error::Error as ProjectError, module::CheckedModule, Project};
use crate::line_numbers::LineNumbers;
#[derive(Debug)]
pub struct SourceInfo {
/// The path to the source file from within the project root
pub path: String,
/// Useful for converting from Aiken's byte index offsets to the LSP line
/// and column number positions.
pub line_numbers: LineNumbers,
}
pub struct LspProject {
pub project: Project<super::telemetry::Lsp>,
pub modules: HashMap<String, CheckedModule>,
pub sources: HashMap<String, SourceInfo>,
}
impl LspProject {
pub fn new(config: Config, root: PathBuf, telemetry: super::telemetry::Lsp) -> Self {
Self {
project: Project::new_with_config(config, root, telemetry),
modules: HashMap::new(),
sources: HashMap::new(),
}
}
pub fn compile(&mut self) -> Result<(), Vec<ProjectError>> {
let checkpoint = self.project.checkpoint();
let result = self
.project
.check(true, None, false, false, Tracing::NoTraces);
self.project.restore(checkpoint);
result?;
let modules = self.project.modules();
for module in modules.into_iter() {
let path = module
.input_path
.canonicalize()
.expect("Canonicalize")
.as_os_str()
.to_string_lossy()
.to_string();
let line_numbers = LineNumbers::new(&module.code);
let source = SourceInfo { path, line_numbers };
self.sources.insert(module.name.to_string(), source);
self.modules.insert(module.name.to_string(), module);
}
Ok(())
}
}

View File

@ -0,0 +1,7 @@
use aiken_project::telemetry::EventListener;
pub struct Lsp;
impl EventListener for Lsp {}
impl Lsp {}

View File

@ -0,0 +1,34 @@
use std::path::PathBuf;
use lsp_types::TextEdit;
use crate::error::Error;
pub const COMPILING_PROGRESS_TOKEN: &str = "compiling-gleam";
pub const CREATE_COMPILING_PROGRESS_TOKEN: &str = "create-compiling-progress-token";
pub fn text_edit_replace(new_text: String) -> TextEdit {
TextEdit {
range: lsp_types::Range {
start: lsp_types::Position {
line: 0,
character: 0,
},
end: lsp_types::Position {
line: u32::MAX,
character: 0,
},
},
new_text,
}
}
pub fn path_to_uri(path: PathBuf) -> Result<lsp_types::Url, Error> {
let mut file: String = "file://".into();
file.push_str(&path.as_os_str().to_string_lossy());
let uri = lsp_types::Url::parse(&file)?;
Ok(uri)
}

View File

@ -151,37 +151,42 @@ impl Error {
}); });
} }
Error::List(errors) errors
} }
}
pub fn append(self, next: Self) -> Self { impl Debug for Error {
match (self, next) { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
(Error::List(mut errors), Error::List(mut next_errors)) => { let miette_handler = MietteHandlerOpts::new()
errors.append(&mut next_errors); // For better support of terminal themes use the ANSI coloring
.rgb_colors(RgbColors::Never)
// If ansi support is disabled in the config disable the eye-candy
.color(true)
.unicode(true)
.terminal_links(true)
.build();
Error::List(errors) // Ignore error to prevent format! panics. This can happen if span points at some
} // inaccessible location, for example by calling `report_error()` with wrong working set.
(Error::List(mut errors), rest) => { let _ = miette_handler.debug(self, f);
errors.push(rest);
Error::List(errors) Ok(())
}
(rest, Error::List(mut next_errors)) => {
let mut errors = vec![rest];
errors.append(&mut next_errors);
Error::List(errors)
}
(error, next_error) => Error::List(vec![error, next_error]),
}
} }
}
pub fn is_empty(&self) -> bool { impl From<Error> for Vec<Error> {
matches!(self, Error::List(errors) if errors.is_empty()) fn from(value: Error) -> Self {
vec![value]
} }
}
pub fn path(&self) -> Option<PathBuf> { pub trait GetSource {
fn path(&self) -> Option<PathBuf>;
fn src(&self) -> Option<String>;
}
impl GetSource for Error {
fn path(&self) -> Option<PathBuf> {
match self { match self {
Error::DuplicateModule { second, .. } => Some(second.to_path_buf()), Error::DuplicateModule { second, .. } => Some(second.to_path_buf()),
Error::FileIo { .. } => None, Error::FileIo { .. } => None,
@ -234,25 +239,6 @@ impl Error {
} }
} }
impl Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let miette_handler = MietteHandlerOpts::new()
// For better support of terminal themes use the ANSI coloring
.rgb_colors(RgbColors::Never)
// If ansi support is disabled in the config disable the eye-candy
.color(true)
.unicode(true)
.terminal_links(true)
.build();
// Ignore error to prevent format! panics. This can happen if span points at some
// inaccessible location, for example by calling `report_error()` with wrong working set.
let _ = miette_handler.debug(self, f);
Ok(())
}
}
impl Diagnostic for Error { impl Diagnostic for Error {
fn severity(&self) -> Option<miette::Severity> { fn severity(&self) -> Option<miette::Severity> {
Some(miette::Severity::Error) Some(miette::Severity::Error)
@ -493,6 +479,24 @@ pub enum Warning {
DependencyAlreadyExists { name: PackageName }, DependencyAlreadyExists { name: PackageName },
} }
impl GetSource for Warning {
fn path(&self) -> Option<PathBuf> {
match self {
Warning::NoValidators => None,
Warning::Type { path, .. } => Some(path.clone()),
Warning::DependencyAlreadyExists { .. } => None,
}
}
fn src(&self) -> Option<String> {
match self {
Warning::NoValidators => None,
Warning::Type { src, .. } => Some(src.clone()),
Warning::DependencyAlreadyExists { .. } => None,
}
}
}
impl Diagnostic for Warning { impl Diagnostic for Warning {
fn severity(&self) -> Option<miette::Severity> { fn severity(&self) -> Option<miette::Severity> {
Some(miette::Severity::Warning) Some(miette::Severity::Warning)

View File

@ -56,6 +56,11 @@ pub struct Source {
pub kind: ModuleKind, pub kind: ModuleKind,
} }
pub struct Checkpoint {
module_types: HashMap<String, TypeInfo>,
defined_modules: HashMap<String, PathBuf>,
}
pub struct Project<T> pub struct Project<T>
where where
T: EventListener, T: EventListener,