mirror of
https://github.com/roc-lang/roc.git
synced 2024-10-26 18:28:43 +03:00
Support basic diagnostic reporting
This commit is contained in:
parent
c50925240d
commit
9d365a8a57
46
Cargo.lock
generated
46
Cargo.lock
generated
@ -158,6 +158,17 @@ dependencies = [
|
|||||||
"stable_deref_trait",
|
"stable_deref_trait",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.59"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@ -169,6 +180,18 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "auto_impl"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7862e21c893d65a1650125d157eaeec691439379a1cee17ee49031b79236ada4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -3304,6 +3327,17 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_repr"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@ -3847,6 +3881,17 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "1.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.24.1"
|
version = "0.24.1"
|
||||||
@ -4063,6 +4108,7 @@ dependencies = [
|
|||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"idna",
|
"idna",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -26,6 +26,7 @@ members = [
|
|||||||
"crates/wasi-libc-sys",
|
"crates/wasi-libc-sys",
|
||||||
"crates/wasm_module",
|
"crates/wasm_module",
|
||||||
"crates/wasm_interp",
|
"crates/wasm_interp",
|
||||||
|
"crates/lang_srv",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
|
@ -160,6 +160,13 @@ impl Error {
|
|||||||
Error::Unmatchable { .. } => Warning,
|
Error::Unmatchable { .. } => Warning,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn region(&self) -> Region {
|
||||||
|
match self {
|
||||||
|
Error::Incomplete(region, _, _) => *region,
|
||||||
|
Error::Redundant { branch_region, .. } => *branch_region,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
@ -445,6 +445,14 @@ pub enum PrecedenceProblem {
|
|||||||
BothNonAssociative(Region, Loc<BinOp>, Loc<BinOp>),
|
BothNonAssociative(Region, Loc<BinOp>, Loc<BinOp>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PrecedenceProblem {
|
||||||
|
pub fn region(&self) -> Region {
|
||||||
|
match self {
|
||||||
|
PrecedenceProblem::BothNonAssociative(region, _, _) => *region,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Enum to store the various types of errors that can cause parsing an integer to fail.
|
/// Enum to store the various types of errors that can cause parsing an integer to fail.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum IntErrorKind {
|
pub enum IntErrorKind {
|
||||||
@ -619,6 +627,45 @@ impl RuntimeError {
|
|||||||
err => format!("{err:?}"),
|
err => format!("{err:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn region(&self) -> Region {
|
||||||
|
match self {
|
||||||
|
RuntimeError::Shadowing { shadow, .. } => shadow.region,
|
||||||
|
RuntimeError::InvalidOptionalValue { field_region, .. } => *field_region,
|
||||||
|
RuntimeError::UnsupportedPattern(region)
|
||||||
|
| RuntimeError::MalformedPattern(_, region)
|
||||||
|
| RuntimeError::OpaqueOutsideScope {
|
||||||
|
referenced_region: region,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| RuntimeError::OpaqueAppliedToMultipleArgs(region)
|
||||||
|
| RuntimeError::ValueNotExposed { region, .. }
|
||||||
|
| RuntimeError::ModuleNotImported { region, .. }
|
||||||
|
| RuntimeError::InvalidPrecedence(_, region)
|
||||||
|
| RuntimeError::MalformedIdentifier(_, _, region)
|
||||||
|
| RuntimeError::MalformedTypeName(_, region)
|
||||||
|
| RuntimeError::MalformedClosure(region)
|
||||||
|
| RuntimeError::InvalidRecordUpdate { region }
|
||||||
|
| RuntimeError::InvalidFloat(_, region, _)
|
||||||
|
| RuntimeError::InvalidInt(_, _, region, _)
|
||||||
|
| RuntimeError::EmptySingleQuote(region)
|
||||||
|
| RuntimeError::MultipleCharsInSingleQuote(region)
|
||||||
|
| RuntimeError::DegenerateBranch(region)
|
||||||
|
| RuntimeError::InvalidInterpolation(region)
|
||||||
|
| RuntimeError::InvalidHexadecimal(region)
|
||||||
|
| RuntimeError::InvalidUnicodeCodePt(region) => *region,
|
||||||
|
RuntimeError::UnresolvedTypeVar | RuntimeError::ErroneousType => Region::zero(),
|
||||||
|
RuntimeError::LookupNotInScope(ident, _) => ident.region,
|
||||||
|
RuntimeError::OpaqueNotDefined { usage, .. } => usage.region,
|
||||||
|
RuntimeError::OpaqueNotApplied(ident) => ident.region,
|
||||||
|
RuntimeError::CircularDef(cycle) => cycle[0].symbol_region,
|
||||||
|
RuntimeError::NonExhaustivePattern => Region::zero(),
|
||||||
|
RuntimeError::NoImplementationNamed { .. }
|
||||||
|
| RuntimeError::NoImplementation
|
||||||
|
| RuntimeError::VoidValue
|
||||||
|
| RuntimeError::ExposedButNotDefined(_) => Region::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
@ -667,7 +667,7 @@ fn solve(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
problems.push(TypeError::UnexposedLookup(*symbol));
|
problems.push(TypeError::UnexposedLookup(*region, *symbol));
|
||||||
|
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
|
22
crates/lang_srv/Cargo.toml
Normal file
22
crates/lang_srv/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "roc_lang_srv"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "roc_ls"
|
||||||
|
path = "src/server.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
roc_load = { path = "../compiler/load" }
|
||||||
|
roc_problem = { path = "../compiler/problem" }
|
||||||
|
roc_region = { path = "../compiler/region" }
|
||||||
|
roc_reporting = { path = "../reporting" }
|
||||||
|
roc_solve_problem = { path = "../compiler/solve_problem" }
|
||||||
|
roc_target = { path = "../compiler/roc_target" }
|
||||||
|
|
||||||
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
|
|
||||||
|
tower-lsp = "0.17.0"
|
||||||
|
tokio = { version = "1.20.1", features = [ "rt", "rt-multi-thread", "macros", "io-std", ] }
|
||||||
|
parking_lot = "0.12.1"
|
176
crates/lang_srv/src/convert.rs
Normal file
176
crates/lang_srv/src/convert.rs
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
use roc_region::all::{LineColumnRegion, LineInfo, Region};
|
||||||
|
use tower_lsp::lsp_types::{Position, Range};
|
||||||
|
|
||||||
|
fn range_of_region(line_info: &LineInfo, region: Region) -> Range {
|
||||||
|
let LineColumnRegion { start, end } = line_info.convert_region(region);
|
||||||
|
Range {
|
||||||
|
start: Position {
|
||||||
|
line: start.line,
|
||||||
|
character: start.column,
|
||||||
|
},
|
||||||
|
end: Position {
|
||||||
|
line: end.line,
|
||||||
|
character: end.column,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod diag {
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use roc_load::LoadingProblem;
|
||||||
|
use roc_region::all::LineInfo;
|
||||||
|
use roc_solve_problem::TypeError;
|
||||||
|
|
||||||
|
use roc_reporting::report::{RocDocAllocator, Severity};
|
||||||
|
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range};
|
||||||
|
|
||||||
|
use super::range_of_region;
|
||||||
|
|
||||||
|
pub trait IntoLspSeverity {
|
||||||
|
fn into_lsp_severity(self) -> DiagnosticSeverity;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLspSeverity for Severity {
|
||||||
|
fn into_lsp_severity(self) -> DiagnosticSeverity {
|
||||||
|
match self {
|
||||||
|
Severity::RuntimeError => DiagnosticSeverity::ERROR,
|
||||||
|
Severity::Warning => DiagnosticSeverity::WARNING,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoLspDiagnostic<'a> {
|
||||||
|
type Feed;
|
||||||
|
|
||||||
|
fn into_lsp_diagnostic(self, feed: &'a Self::Feed) -> Option<Diagnostic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLspDiagnostic<'_> for LoadingProblem<'_> {
|
||||||
|
type Feed = ();
|
||||||
|
|
||||||
|
fn into_lsp_diagnostic(self, _feed: &()) -> Option<Diagnostic> {
|
||||||
|
let range = Range {
|
||||||
|
start: Position {
|
||||||
|
line: 0,
|
||||||
|
character: 0,
|
||||||
|
},
|
||||||
|
end: Position {
|
||||||
|
line: 0,
|
||||||
|
character: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let msg;
|
||||||
|
match self {
|
||||||
|
LoadingProblem::FileProblem { filename, error } => {
|
||||||
|
msg = format!(
|
||||||
|
"Failed to load {} due to an I/O error: {}",
|
||||||
|
filename.display(),
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
LoadingProblem::ParsingFailed(_) => {
|
||||||
|
unreachable!("should be formatted before sent back")
|
||||||
|
}
|
||||||
|
LoadingProblem::UnexpectedHeader(header) => {
|
||||||
|
msg = format!("Unexpected header: {}", header);
|
||||||
|
}
|
||||||
|
LoadingProblem::MsgChannelDied => {
|
||||||
|
msg = format!("Internal error: message channel died");
|
||||||
|
}
|
||||||
|
LoadingProblem::ErrJoiningWorkerThreads => {
|
||||||
|
msg = format!("Internal error: analysis worker threads died");
|
||||||
|
}
|
||||||
|
LoadingProblem::TriedToImportAppModule => {
|
||||||
|
msg = format!("Attempted to import app module");
|
||||||
|
}
|
||||||
|
LoadingProblem::FormattedReport(report) => {
|
||||||
|
msg = report;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Diagnostic {
|
||||||
|
range,
|
||||||
|
severity: Some(DiagnosticSeverity::ERROR),
|
||||||
|
code: None,
|
||||||
|
code_description: None,
|
||||||
|
source: Some("load".to_owned()),
|
||||||
|
message: msg,
|
||||||
|
related_information: None,
|
||||||
|
tags: None,
|
||||||
|
data: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ProblemFmt<'a> {
|
||||||
|
pub alloc: &'a RocDocAllocator<'a>,
|
||||||
|
pub line_info: &'a LineInfo,
|
||||||
|
pub path: &'a Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoLspDiagnostic<'a> for roc_problem::can::Problem {
|
||||||
|
type Feed = ProblemFmt<'a>;
|
||||||
|
|
||||||
|
fn into_lsp_diagnostic(self, fmt: &'a ProblemFmt<'a>) -> Option<Diagnostic> {
|
||||||
|
let range = range_of_region(fmt.line_info, self.region());
|
||||||
|
|
||||||
|
let report = roc_reporting::report::can_problem(
|
||||||
|
&fmt.alloc,
|
||||||
|
&fmt.line_info,
|
||||||
|
fmt.path.to_path_buf(),
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
|
||||||
|
let severity = report.severity.into_lsp_severity();
|
||||||
|
|
||||||
|
let mut msg = String::new();
|
||||||
|
report.render_ci(&mut msg, fmt.alloc);
|
||||||
|
|
||||||
|
Some(Diagnostic {
|
||||||
|
range,
|
||||||
|
severity: Some(severity),
|
||||||
|
code: None,
|
||||||
|
code_description: None,
|
||||||
|
source: None,
|
||||||
|
message: msg,
|
||||||
|
related_information: None,
|
||||||
|
tags: None,
|
||||||
|
data: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoLspDiagnostic<'a> for TypeError {
|
||||||
|
type Feed = ProblemFmt<'a>;
|
||||||
|
|
||||||
|
fn into_lsp_diagnostic(self, fmt: &'a ProblemFmt<'a>) -> Option<Diagnostic> {
|
||||||
|
let range = range_of_region(fmt.line_info, self.region());
|
||||||
|
|
||||||
|
let report = roc_reporting::report::type_problem(
|
||||||
|
&fmt.alloc,
|
||||||
|
&fmt.line_info,
|
||||||
|
fmt.path.to_path_buf(),
|
||||||
|
self,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let severity = report.severity.into_lsp_severity();
|
||||||
|
|
||||||
|
let mut msg = String::new();
|
||||||
|
report.render_ci(&mut msg, fmt.alloc);
|
||||||
|
|
||||||
|
Some(Diagnostic {
|
||||||
|
range,
|
||||||
|
severity: Some(severity),
|
||||||
|
code: None,
|
||||||
|
code_description: None,
|
||||||
|
source: None,
|
||||||
|
message: msg,
|
||||||
|
related_information: None,
|
||||||
|
tags: None,
|
||||||
|
data: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
158
crates/lang_srv/src/registry.rs
Normal file
158
crates/lang_srv/src/registry.rs
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use roc_load::{LoadedModule, LoadingProblem};
|
||||||
|
use roc_region::all::LineInfo;
|
||||||
|
use roc_reporting::report::RocDocAllocator;
|
||||||
|
use tower_lsp::lsp_types::{Diagnostic, Url};
|
||||||
|
|
||||||
|
use crate::convert::diag::{IntoLspDiagnostic, ProblemFmt};
|
||||||
|
|
||||||
|
pub(crate) enum DocumentChange {
|
||||||
|
Modified(Url, String),
|
||||||
|
Closed(Url),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Document {
|
||||||
|
url: Url,
|
||||||
|
source: String,
|
||||||
|
|
||||||
|
arena: Bump,
|
||||||
|
|
||||||
|
// Incrementally updated module, diagnostis, etc.
|
||||||
|
module: Option<Result<LoadedModule, ()>>,
|
||||||
|
diagnostics: Option<Vec<Diagnostic>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Document {
|
||||||
|
fn new(url: Url, source: String) -> Self {
|
||||||
|
Self {
|
||||||
|
url,
|
||||||
|
source,
|
||||||
|
arena: Bump::new(),
|
||||||
|
|
||||||
|
module: None,
|
||||||
|
diagnostics: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prime(&mut self, source: String) {
|
||||||
|
self.source = source;
|
||||||
|
self.module = None;
|
||||||
|
self.diagnostics = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module(&mut self) -> Result<&mut LoadedModule, LoadingProblem<'_>> {
|
||||||
|
if let Some(Ok(module)) = &mut self.module {
|
||||||
|
// Safety: returning for time self is alive
|
||||||
|
return Ok(unsafe { std::mem::transmute(module) });
|
||||||
|
}
|
||||||
|
|
||||||
|
let fi = self.url.to_file_path().unwrap();
|
||||||
|
let src_dir = fi.parent().unwrap().to_path_buf();
|
||||||
|
|
||||||
|
let loaded = roc_load::load_and_typecheck_str(
|
||||||
|
&self.arena,
|
||||||
|
fi,
|
||||||
|
&self.source,
|
||||||
|
src_dir,
|
||||||
|
Default::default(),
|
||||||
|
roc_target::TargetInfo::default_x86_64(),
|
||||||
|
roc_reporting::report::RenderTarget::Generic,
|
||||||
|
);
|
||||||
|
|
||||||
|
match loaded {
|
||||||
|
Ok(module) => {
|
||||||
|
self.module = Some(Ok(module));
|
||||||
|
Ok(self.module.as_mut().unwrap().as_mut().unwrap())
|
||||||
|
}
|
||||||
|
Err(problem) => {
|
||||||
|
self.module = Some(Err(()));
|
||||||
|
Err(problem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diagnostics(&mut self) -> Vec<Diagnostic> {
|
||||||
|
if let Some(diagnostics) = &self.diagnostics {
|
||||||
|
return diagnostics.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let loaded: Result<&'static mut LoadedModule, LoadingProblem> =
|
||||||
|
unsafe { std::mem::transmute(self.module()) };
|
||||||
|
|
||||||
|
let diagnostics = match loaded {
|
||||||
|
Ok(module) => {
|
||||||
|
let lines: Vec<_> = self.source.lines().collect();
|
||||||
|
let line_info = LineInfo::new(&self.source);
|
||||||
|
|
||||||
|
let alloc = RocDocAllocator::new(&lines, module.module_id, &module.interns);
|
||||||
|
|
||||||
|
let mut all_problems = Vec::new();
|
||||||
|
let module_path = self.url.to_file_path().unwrap();
|
||||||
|
let fmt = ProblemFmt {
|
||||||
|
alloc: &alloc,
|
||||||
|
line_info: &line_info,
|
||||||
|
path: &module_path,
|
||||||
|
};
|
||||||
|
|
||||||
|
for can_problem in module
|
||||||
|
.can_problems
|
||||||
|
.remove(&module.module_id)
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
if let Some(diag) = can_problem.into_lsp_diagnostic(&fmt) {
|
||||||
|
all_problems.push(diag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for type_problem in module
|
||||||
|
.type_problems
|
||||||
|
.remove(&module.module_id)
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
if let Some(diag) = type_problem.into_lsp_diagnostic(&fmt) {
|
||||||
|
all_problems.push(diag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
all_problems
|
||||||
|
}
|
||||||
|
Err(problem) => {
|
||||||
|
let mut all_problems = vec![];
|
||||||
|
all_problems.extend(problem.into_lsp_diagnostic(&()));
|
||||||
|
all_problems
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.diagnostics = Some(diagnostics);
|
||||||
|
self.diagnostics.as_ref().unwrap().clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct Registry {
|
||||||
|
documents: HashMap<Url, Document>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Registry {
|
||||||
|
pub fn apply_change(&mut self, change: DocumentChange) {
|
||||||
|
match change {
|
||||||
|
DocumentChange::Modified(url, source) => match self.documents.get_mut(&url) {
|
||||||
|
Some(document) => document.prime(source),
|
||||||
|
None => {
|
||||||
|
self.documents
|
||||||
|
.insert(url.clone(), Document::new(url, source));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DocumentChange::Closed(url) => {
|
||||||
|
self.documents.remove(&url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn diagnostics(&mut self, document: &Url) -> Vec<Diagnostic> {
|
||||||
|
self.documents.get_mut(document).unwrap().diagnostics()
|
||||||
|
}
|
||||||
|
}
|
112
crates/lang_srv/src/server.rs
Normal file
112
crates/lang_srv/src/server.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use parking_lot::{Mutex, MutexGuard};
|
||||||
|
use registry::{DocumentChange, Registry};
|
||||||
|
use tower_lsp::jsonrpc::Result;
|
||||||
|
use tower_lsp::lsp_types::*;
|
||||||
|
use tower_lsp::{Client, LanguageServer, LspService, Server};
|
||||||
|
|
||||||
|
mod convert;
|
||||||
|
mod registry;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct RocLs {
|
||||||
|
client: Client,
|
||||||
|
registry: Mutex<Registry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RocLs {
|
||||||
|
pub fn new(client: Client) -> Self {
|
||||||
|
Self {
|
||||||
|
client,
|
||||||
|
registry: Mutex::new(Registry::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn registry(&self) -> MutexGuard<Registry> {
|
||||||
|
self.registry.lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capabilities() -> ServerCapabilities {
|
||||||
|
let text_document_sync = Some(TextDocumentSyncCapability::Options(
|
||||||
|
// TODO: later on make this incremental
|
||||||
|
TextDocumentSyncOptions {
|
||||||
|
open_close: Some(true),
|
||||||
|
change: Some(TextDocumentSyncKind::FULL),
|
||||||
|
..TextDocumentSyncOptions::default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
let hover_provider = Some(HoverProviderCapability::Simple(true));
|
||||||
|
|
||||||
|
ServerCapabilities {
|
||||||
|
text_document_sync,
|
||||||
|
hover_provider,
|
||||||
|
..ServerCapabilities::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records a document content change.
|
||||||
|
async fn change(&self, fi: Url, text: String, version: i32) {
|
||||||
|
self.registry()
|
||||||
|
.apply_change(DocumentChange::Modified(fi.clone(), text));
|
||||||
|
|
||||||
|
let diagnostics = self.registry().diagnostics(&fi);
|
||||||
|
self.client
|
||||||
|
.publish_diagnostics(fi, diagnostics, Some(version))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn close(&self, fi: Url) {
|
||||||
|
self.registry().apply_change(DocumentChange::Closed(fi));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tower_lsp::async_trait]
|
||||||
|
impl LanguageServer for RocLs {
|
||||||
|
async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
|
||||||
|
Ok(InitializeResult {
|
||||||
|
capabilities: Self::capabilities(),
|
||||||
|
..InitializeResult::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn initialized(&self, _: InitializedParams) {
|
||||||
|
self.client
|
||||||
|
.log_message(MessageType::INFO, "Roc language server initialized.")
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn did_open(&self, params: DidOpenTextDocumentParams) {
|
||||||
|
let TextDocumentItem {
|
||||||
|
uri, text, version, ..
|
||||||
|
} = params.text_document;
|
||||||
|
self.change(uri, text, version).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn did_change(&self, params: DidChangeTextDocumentParams) {
|
||||||
|
let VersionedTextDocumentIdentifier { uri, version, .. } = params.text_document;
|
||||||
|
|
||||||
|
// NOTE: We specify that we expect full-content syncs in the server capabilities,
|
||||||
|
// so here we assume the only change passed is a change of the entire document's content.
|
||||||
|
let TextDocumentContentChangeEvent { text, .. } =
|
||||||
|
params.content_changes.into_iter().next().unwrap();
|
||||||
|
|
||||||
|
self.change(uri, text, version).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn did_close(&self, params: DidCloseTextDocumentParams) {
|
||||||
|
let TextDocumentIdentifier { uri } = params.text_document;
|
||||||
|
self.close(uri).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn shutdown(&self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let stdin = tokio::io::stdin();
|
||||||
|
let stdout = tokio::io::stdout();
|
||||||
|
|
||||||
|
let (service, socket) = LspService::new(RocLs::new);
|
||||||
|
Server::new(stdin, stdout, socket).serve(service).await;
|
||||||
|
}
|
@ -71,7 +71,7 @@ pub fn type_problem<'b>(
|
|||||||
symbol,
|
symbol,
|
||||||
overall_type,
|
overall_type,
|
||||||
)),
|
)),
|
||||||
UnexposedLookup(symbol) => {
|
UnexposedLookup(_, symbol) => {
|
||||||
let title = "UNRECOGNIZED NAME".to_string();
|
let title = "UNRECOGNIZED NAME".to_string();
|
||||||
let doc = alloc
|
let doc = alloc
|
||||||
.stack(vec![alloc
|
.stack(vec![alloc
|
||||||
|
@ -134,7 +134,7 @@ impl<'b> Report<'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Render to CI console output, where no colors are available.
|
/// Render to CI console output, where no colors are available.
|
||||||
pub fn render_ci(self, buf: &'b mut String, alloc: &'b RocDocAllocator<'b>) {
|
pub fn render_ci(self, buf: &mut String, alloc: &'b RocDocAllocator<'b>) {
|
||||||
let err_msg = "<buffer is not a utf-8 encoded string>";
|
let err_msg = "<buffer is not a utf-8 encoded string>";
|
||||||
|
|
||||||
self.pretty(alloc)
|
self.pretty(alloc)
|
||||||
|
Loading…
Reference in New Issue
Block a user