feat(lsp): hover and goto definition
This commit is contained in:
parent
39ea803fe6
commit
815d7d80c6
|
@ -60,7 +60,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"hex",
|
"hex",
|
||||||
"ignore",
|
"ignore",
|
||||||
"indoc",
|
"indoc 1.0.8",
|
||||||
"miette",
|
"miette",
|
||||||
"owo-colors",
|
"owo-colors",
|
||||||
"pallas-addresses",
|
"pallas-addresses",
|
||||||
|
@ -81,7 +81,7 @@ dependencies = [
|
||||||
"chumsky",
|
"chumsky",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"indoc",
|
"indoc 1.0.8",
|
||||||
"itertools",
|
"itertools",
|
||||||
"miette",
|
"miette",
|
||||||
"ordinal",
|
"ordinal",
|
||||||
|
@ -100,6 +100,8 @@ dependencies = [
|
||||||
"aiken-lang",
|
"aiken-lang",
|
||||||
"aiken-project",
|
"aiken-project",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
|
"indoc 2.0.0",
|
||||||
|
"itertools",
|
||||||
"lsp-server",
|
"lsp-server",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
"miette",
|
"miette",
|
||||||
|
@ -108,6 +110,7 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
|
"urlencoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1082,6 +1085,12 @@ version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da2d6f23ffea9d7e76c53eee25dfb67bcd8fde7f1198b0855350698c9f07c780"
|
checksum = "da2d6f23ffea9d7e76c53eee25dfb67bcd8fde7f1198b0855350698c9f07c780"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indoc"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6fe2b9d82064e8a0226fddb3547f37f28eaa46d0fc210e275d835f08cf3b76a7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
|
@ -2670,6 +2679,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
|
@ -66,6 +66,14 @@ impl UntypedModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TypedModule {
|
||||||
|
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
|
||||||
|
self.definitions
|
||||||
|
.iter()
|
||||||
|
.find_map(|definition| definition.find_node(byte_index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type TypedFunction = Function<Arc<Type>, TypedExpr>;
|
pub type TypedFunction = Function<Arc<Type>, TypedExpr>;
|
||||||
pub type UntypedFunction = Function<(), UntypedExpr>;
|
pub type UntypedFunction = Function<(), UntypedExpr>;
|
||||||
|
|
||||||
|
@ -318,6 +326,48 @@ impl<A, B, C> Definition<A, B, C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TypedDefinition {
|
||||||
|
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
|
||||||
|
// Note that the fn span covers the function head, not
|
||||||
|
// the entire statement.
|
||||||
|
if let Definition::Fn(Function { body, .. })
|
||||||
|
| Definition::Validator(Validator {
|
||||||
|
fun: Function { body, .. },
|
||||||
|
..
|
||||||
|
})
|
||||||
|
| Definition::Test(Function { body, .. }) = self
|
||||||
|
{
|
||||||
|
if let Some(expression) = body.find_node(byte_index) {
|
||||||
|
return Some(Located::Expression(expression));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.location().contains(byte_index) {
|
||||||
|
Some(Located::Definition(self))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Located<'a> {
|
||||||
|
Expression(&'a TypedExpr),
|
||||||
|
Definition(&'a TypedDefinition),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Located<'a> {
|
||||||
|
pub fn definition_location(&self) -> Option<DefinitionLocation<'_>> {
|
||||||
|
match self {
|
||||||
|
Self::Expression(expression) => expression.definition_location(),
|
||||||
|
Self::Definition(definition) => Some(DefinitionLocation {
|
||||||
|
module: None,
|
||||||
|
span: definition.location(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct DefinitionLocation<'module> {
|
pub struct DefinitionLocation<'module> {
|
||||||
pub module: Option<&'module str>,
|
pub module: Option<&'module str>,
|
||||||
|
@ -379,6 +429,12 @@ impl CallArg<UntypedExpr> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TypedCallArg {
|
||||||
|
pub fn find_node(&self, byte_index: usize) -> Option<&TypedExpr> {
|
||||||
|
self.value.find_node(byte_index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct RecordConstructor<T> {
|
pub struct RecordConstructor<T> {
|
||||||
pub location: Span,
|
pub location: Span,
|
||||||
|
@ -816,6 +872,10 @@ impl TypedClause {
|
||||||
end: self.then.location().end,
|
end: self.then.location().end,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_node(&self, byte_index: usize) -> Option<&TypedExpr> {
|
||||||
|
self.then.find_node(byte_index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type UntypedClauseGuard = ClauseGuard<()>;
|
pub type UntypedClauseGuard = ClauseGuard<()>;
|
||||||
|
@ -946,7 +1006,7 @@ pub struct IfBranch<Expr> {
|
||||||
pub location: Span,
|
pub location: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct TypedRecordUpdateArg {
|
pub struct TypedRecordUpdateArg {
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub location: Span,
|
pub location: Span,
|
||||||
|
@ -954,6 +1014,12 @@ pub struct TypedRecordUpdateArg {
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TypedRecordUpdateArg {
|
||||||
|
pub fn find_node(&self, byte_index: usize) -> Option<&TypedExpr> {
|
||||||
|
self.value.find_node(byte_index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct UntypedRecordUpdateArg {
|
pub struct UntypedRecordUpdateArg {
|
||||||
pub label: String,
|
pub label: String,
|
||||||
|
@ -1023,6 +1089,10 @@ impl Span {
|
||||||
end: self.end().max(other.end()),
|
end: self.end().max(other.end()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, byte_index: usize) -> bool {
|
||||||
|
byte_index >= self.start && byte_index < self.end
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Span {
|
impl fmt::Debug for Span {
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor},
|
tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum TypedExpr {
|
pub enum TypedExpr {
|
||||||
Int {
|
Int {
|
||||||
location: Span,
|
location: Span,
|
||||||
|
@ -307,6 +307,96 @@ impl TypedExpr {
|
||||||
| Self::RecordUpdate { location, .. } => *location,
|
| Self::RecordUpdate { location, .. } => *location,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This could be optimised in places to exit early if the first of a series
|
||||||
|
// of expressions is after the byte index.
|
||||||
|
pub fn find_node(&self, byte_index: usize) -> Option<&Self> {
|
||||||
|
if !self.location().contains(byte_index) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self {
|
||||||
|
TypedExpr::ErrorTerm { .. }
|
||||||
|
| TypedExpr::Var { .. }
|
||||||
|
| TypedExpr::Int { .. }
|
||||||
|
| TypedExpr::String { .. }
|
||||||
|
| TypedExpr::ByteArray { .. }
|
||||||
|
| TypedExpr::ModuleSelect { .. } => Some(self),
|
||||||
|
|
||||||
|
TypedExpr::Trace { text, then, .. } => text
|
||||||
|
.find_node(byte_index)
|
||||||
|
.or_else(|| then.find_node(byte_index))
|
||||||
|
.or(Some(self)),
|
||||||
|
|
||||||
|
TypedExpr::Pipeline { expressions, .. } | TypedExpr::Sequence { expressions, .. } => {
|
||||||
|
expressions.iter().find_map(|e| e.find_node(byte_index))
|
||||||
|
}
|
||||||
|
|
||||||
|
TypedExpr::Fn { body, .. } => body.find_node(byte_index).or(Some(self)),
|
||||||
|
|
||||||
|
TypedExpr::Tuple {
|
||||||
|
elems: elements, ..
|
||||||
|
}
|
||||||
|
| TypedExpr::List { elements, .. } => elements
|
||||||
|
.iter()
|
||||||
|
.find_map(|e| e.find_node(byte_index))
|
||||||
|
.or(Some(self)),
|
||||||
|
|
||||||
|
TypedExpr::Call { fun, args, .. } => args
|
||||||
|
.iter()
|
||||||
|
.find_map(|arg| arg.find_node(byte_index))
|
||||||
|
.or_else(|| fun.find_node(byte_index))
|
||||||
|
.or(Some(self)),
|
||||||
|
|
||||||
|
TypedExpr::BinOp { left, right, .. } => left
|
||||||
|
.find_node(byte_index)
|
||||||
|
.or_else(|| right.find_node(byte_index)),
|
||||||
|
|
||||||
|
TypedExpr::Assignment { value, .. } => value.find_node(byte_index),
|
||||||
|
|
||||||
|
TypedExpr::When {
|
||||||
|
subjects, clauses, ..
|
||||||
|
} => subjects
|
||||||
|
.iter()
|
||||||
|
.find_map(|subject| subject.find_node(byte_index))
|
||||||
|
.or_else(|| {
|
||||||
|
clauses
|
||||||
|
.iter()
|
||||||
|
.find_map(|clause| clause.find_node(byte_index))
|
||||||
|
})
|
||||||
|
.or(Some(self)),
|
||||||
|
|
||||||
|
TypedExpr::RecordAccess {
|
||||||
|
record: expression, ..
|
||||||
|
}
|
||||||
|
| TypedExpr::TupleIndex {
|
||||||
|
tuple: expression, ..
|
||||||
|
} => expression.find_node(byte_index).or(Some(self)),
|
||||||
|
|
||||||
|
TypedExpr::RecordUpdate { spread, args, .. } => args
|
||||||
|
.iter()
|
||||||
|
.find_map(|arg| arg.find_node(byte_index))
|
||||||
|
.or_else(|| spread.find_node(byte_index))
|
||||||
|
.or(Some(self)),
|
||||||
|
|
||||||
|
TypedExpr::If {
|
||||||
|
branches,
|
||||||
|
final_else,
|
||||||
|
..
|
||||||
|
} => branches
|
||||||
|
.iter()
|
||||||
|
.find_map(|branch| {
|
||||||
|
branch
|
||||||
|
.condition
|
||||||
|
.find_node(byte_index)
|
||||||
|
.or_else(|| branch.body.find_node(byte_index))
|
||||||
|
})
|
||||||
|
.or_else(|| final_else.find_node(byte_index))
|
||||||
|
.or(Some(self)),
|
||||||
|
|
||||||
|
TypedExpr::UnOp { value, .. } => value.find_node(byte_index).or(Some(self)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
|
@ -715,7 +715,7 @@ pub enum PatternConstructor {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum ModuleValueConstructor {
|
pub enum ModuleValueConstructor {
|
||||||
Record {
|
Record {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
|
@ -13,6 +13,8 @@ authors = ["Lucas Rosa <x@rvcas.dev>"]
|
||||||
aiken-lang = { path = '../aiken-lang', version = "0.0.28" }
|
aiken-lang = { path = '../aiken-lang', version = "0.0.28" }
|
||||||
aiken-project = { path = '../aiken-project', version = "0.0.28" }
|
aiken-project = { path = '../aiken-project', version = "0.0.28" }
|
||||||
crossbeam-channel = "0.5.6"
|
crossbeam-channel = "0.5.6"
|
||||||
|
indoc = "2.0.0"
|
||||||
|
itertools = "0.10.5"
|
||||||
lsp-server = "0.6.0"
|
lsp-server = "0.6.0"
|
||||||
lsp-types = "0.93.2"
|
lsp-types = "0.93.2"
|
||||||
miette = "5.4.1"
|
miette = "5.4.1"
|
||||||
|
@ -21,3 +23,4 @@ serde_json = "1.0.87"
|
||||||
thiserror = "1.0.37"
|
thiserror = "1.0.37"
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
url = "2.3.1"
|
url = "2.3.1"
|
||||||
|
urlencoding = "2.1.2"
|
||||||
|
|
|
@ -2,10 +2,6 @@ use std::env;
|
||||||
|
|
||||||
use aiken_project::{config::Config, paths};
|
use aiken_project::{config::Config, paths};
|
||||||
use lsp_server::Connection;
|
use lsp_server::Connection;
|
||||||
use lsp_types::{
|
|
||||||
OneOf, SaveOptions, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind,
|
|
||||||
TextDocumentSyncOptions, TextDocumentSyncSaveOptions,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod cast;
|
mod cast;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
@ -53,21 +49,32 @@ pub fn start() -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capabilities() -> ServerCapabilities {
|
fn capabilities() -> lsp_types::ServerCapabilities {
|
||||||
ServerCapabilities {
|
lsp_types::ServerCapabilities {
|
||||||
text_document_sync: Some(TextDocumentSyncCapability::Options(
|
completion_provider: Some(lsp_types::CompletionOptions {
|
||||||
TextDocumentSyncOptions {
|
resolve_provider: None,
|
||||||
|
trigger_characters: Some(vec![".".into(), " ".into()]),
|
||||||
|
all_commit_characters: None,
|
||||||
|
work_done_progress_options: lsp_types::WorkDoneProgressOptions {
|
||||||
|
work_done_progress: None,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
document_formatting_provider: Some(lsp_types::OneOf::Left(true)),
|
||||||
|
definition_provider: Some(lsp_types::OneOf::Left(true)),
|
||||||
|
hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)),
|
||||||
|
text_document_sync: Some(lsp_types::TextDocumentSyncCapability::Options(
|
||||||
|
lsp_types::TextDocumentSyncOptions {
|
||||||
open_close: None,
|
open_close: None,
|
||||||
change: Some(TextDocumentSyncKind::FULL),
|
change: Some(lsp_types::TextDocumentSyncKind::FULL),
|
||||||
will_save: None,
|
will_save: None,
|
||||||
will_save_wait_until: None,
|
will_save_wait_until: None,
|
||||||
save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions {
|
save: Some(lsp_types::TextDocumentSyncSaveOptions::SaveOptions(
|
||||||
include_text: Some(false),
|
lsp_types::SaveOptions {
|
||||||
})),
|
include_text: Some(false),
|
||||||
|
},
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
// definition_provider: Some(OneOf::Left(true)),
|
|
||||||
document_formatting_provider: Some(OneOf::Left(true)),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,18 +4,24 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use aiken_lang::{ast::ModuleKind, parser};
|
use aiken_lang::{
|
||||||
|
ast::{Located, ModuleKind, Span},
|
||||||
|
parser,
|
||||||
|
tipo::pretty::Printer,
|
||||||
|
};
|
||||||
use aiken_project::{
|
use aiken_project::{
|
||||||
config,
|
config,
|
||||||
error::{Error as ProjectError, GetSource},
|
error::{Error as ProjectError, GetSource},
|
||||||
|
module::CheckedModule,
|
||||||
};
|
};
|
||||||
|
use indoc::formatdoc;
|
||||||
use lsp_server::{Connection, Message};
|
use lsp_server::{Connection, Message};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
notification::{
|
notification::{
|
||||||
DidChangeTextDocument, DidSaveTextDocument, Notification, Progress, PublishDiagnostics,
|
DidChangeTextDocument, DidSaveTextDocument, Notification, Progress, PublishDiagnostics,
|
||||||
ShowMessage,
|
ShowMessage,
|
||||||
},
|
},
|
||||||
request::{Formatting, Request, WorkDoneProgressCreate},
|
request::{Formatting, GotoDefinition, HoverRequest, Request, WorkDoneProgressCreate},
|
||||||
DocumentFormattingParams, InitializeParams, TextEdit,
|
DocumentFormattingParams, InitializeParams, TextEdit,
|
||||||
};
|
};
|
||||||
use miette::Diagnostic;
|
use miette::Diagnostic;
|
||||||
|
@ -25,7 +31,8 @@ use crate::{
|
||||||
error::Error as ServerError,
|
error::Error as ServerError,
|
||||||
line_numbers::LineNumbers,
|
line_numbers::LineNumbers,
|
||||||
utils::{
|
utils::{
|
||||||
path_to_uri, text_edit_replace, COMPILING_PROGRESS_TOKEN, CREATE_COMPILING_PROGRESS_TOKEN,
|
path_to_uri, span_to_lsp_range, text_edit_replace, uri_to_module_name,
|
||||||
|
COMPILING_PROGRESS_TOKEN, CREATE_COMPILING_PROGRESS_TOKEN,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -230,12 +237,138 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
HoverRequest::METHOD => {
|
||||||
|
let params = cast_request::<HoverRequest>(request)?;
|
||||||
|
|
||||||
|
let opt_hover = self.hover(params)?;
|
||||||
|
|
||||||
|
Ok(lsp_server::Response {
|
||||||
|
id,
|
||||||
|
error: None,
|
||||||
|
result: Some(serde_json::to_value(opt_hover)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
GotoDefinition::METHOD => {
|
||||||
|
let params = cast_request::<GotoDefinition>(request)?;
|
||||||
|
|
||||||
|
let location = self.goto_definition(params)?;
|
||||||
|
|
||||||
|
Ok(lsp_server::Response {
|
||||||
|
id,
|
||||||
|
error: None,
|
||||||
|
result: Some(serde_json::to_value(location)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
unsupported => Err(ServerError::UnsupportedLspRequest {
|
unsupported => Err(ServerError::UnsupportedLspRequest {
|
||||||
request: unsupported.to_string(),
|
request: unsupported.to_string(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn goto_definition(
|
||||||
|
&self,
|
||||||
|
params: lsp_types::GotoDefinitionParams,
|
||||||
|
) -> Result<Option<lsp_types::Location>, ServerError> {
|
||||||
|
let params = params.text_document_position_params;
|
||||||
|
|
||||||
|
let (line_numbers, node) = match self.node_at_position(¶ms) {
|
||||||
|
Some(location) => location,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let location = match node.definition_location() {
|
||||||
|
Some(location) => location,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (uri, line_numbers) = match location.module {
|
||||||
|
None => (params.text_document.uri, &line_numbers),
|
||||||
|
Some(name) => {
|
||||||
|
let module = match self
|
||||||
|
.compiler
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|compiler| compiler.sources.get(name))
|
||||||
|
{
|
||||||
|
Some(module) => module,
|
||||||
|
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let url = url::Url::parse(&format!("file:///{}", &module.path))
|
||||||
|
.expect("goto definition URL parse");
|
||||||
|
|
||||||
|
(url, &module.line_numbers)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let range = span_to_lsp_range(location.span, line_numbers);
|
||||||
|
|
||||||
|
Ok(Some(lsp_types::Location { uri, range }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_at_position(
|
||||||
|
&self,
|
||||||
|
params: &lsp_types::TextDocumentPositionParams,
|
||||||
|
) -> Option<(LineNumbers, Located<'_>)> {
|
||||||
|
let module = self.module_for_uri(¶ms.text_document.uri);
|
||||||
|
|
||||||
|
let module = module?;
|
||||||
|
|
||||||
|
let line_numbers = LineNumbers::new(&module.code);
|
||||||
|
|
||||||
|
let byte_index = line_numbers.byte_index(
|
||||||
|
params.position.line as usize,
|
||||||
|
params.position.character as usize,
|
||||||
|
);
|
||||||
|
|
||||||
|
let node = module.find_node(byte_index);
|
||||||
|
|
||||||
|
let node = node?;
|
||||||
|
|
||||||
|
Some((line_numbers, node))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module_for_uri(&self, uri: &url::Url) -> Option<&CheckedModule> {
|
||||||
|
self.compiler.as_ref().and_then(|compiler| {
|
||||||
|
let module_name = uri_to_module_name(uri, &self.root).expect("uri to module name");
|
||||||
|
|
||||||
|
compiler.modules.get(&module_name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hover(
|
||||||
|
&self,
|
||||||
|
params: lsp_types::HoverParams,
|
||||||
|
) -> Result<Option<lsp_types::Hover>, ServerError> {
|
||||||
|
let params = params.text_document_position_params;
|
||||||
|
|
||||||
|
let (line_numbers, found) = match self.node_at_position(¶ms) {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let expression = match found {
|
||||||
|
Located::Expression(expression) => expression,
|
||||||
|
Located::Definition(_) => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show the type of the hovered node to the user
|
||||||
|
let type_ = Printer::new().pretty_print(expression.tipo().as_ref(), 0);
|
||||||
|
|
||||||
|
let contents = formatdoc! {r#"
|
||||||
|
```aiken
|
||||||
|
{type_}
|
||||||
|
```
|
||||||
|
"#};
|
||||||
|
|
||||||
|
Ok(Some(lsp_types::Hover {
|
||||||
|
contents: lsp_types::HoverContents::Scalar(lsp_types::MarkedString::String(contents)),
|
||||||
|
range: Some(span_to_lsp_range(expression.location(), &line_numbers)),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn listen(&mut self, connection: Connection) -> Result<(), ServerError> {
|
pub fn listen(&mut self, connection: Connection) -> Result<(), ServerError> {
|
||||||
self.create_compilation_progress_token(&connection)?;
|
self.create_compilation_progress_token(&connection)?;
|
||||||
self.start_watching_aiken_toml(&connection)?;
|
self.start_watching_aiken_toml(&connection)?;
|
||||||
|
@ -366,21 +499,13 @@ impl Server {
|
||||||
|
|
||||||
let line_numbers = LineNumbers::new(&src);
|
let line_numbers = LineNumbers::new(&src);
|
||||||
|
|
||||||
let start = line_numbers.line_and_column_number(labeled_span.inner().offset());
|
|
||||||
let end = line_numbers.line_and_column_number(
|
|
||||||
labeled_span.inner().offset() + labeled_span.inner().len(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let lsp_diagnostic = lsp_types::Diagnostic {
|
let lsp_diagnostic = lsp_types::Diagnostic {
|
||||||
range: lsp_types::Range::new(
|
range: span_to_lsp_range(
|
||||||
lsp_types::Position {
|
Span {
|
||||||
line: start.line as u32 - 1,
|
start: labeled_span.inner().offset(),
|
||||||
character: start.column as u32 - 1,
|
end: labeled_span.inner().offset() + labeled_span.inner().len(),
|
||||||
},
|
|
||||||
lsp_types::Position {
|
|
||||||
line: end.line as u32 - 1,
|
|
||||||
character: end.column as u32 - 1,
|
|
||||||
},
|
},
|
||||||
|
&line_numbers,
|
||||||
),
|
),
|
||||||
severity: Some(severity),
|
severity: Some(severity),
|
||||||
code: error
|
code: error
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use aiken_lang::ast::Span;
|
||||||
|
use itertools::Itertools;
|
||||||
use lsp_types::TextEdit;
|
use lsp_types::TextEdit;
|
||||||
|
use urlencoding::decode;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::{error::Error, line_numbers::LineNumbers};
|
||||||
|
|
||||||
pub const COMPILING_PROGRESS_TOKEN: &str = "compiling-gleam";
|
pub const COMPILING_PROGRESS_TOKEN: &str = "compiling-gleam";
|
||||||
pub const CREATE_COMPILING_PROGRESS_TOKEN: &str = "create-compiling-progress-token";
|
pub const CREATE_COMPILING_PROGRESS_TOKEN: &str = "create-compiling-progress-token";
|
||||||
|
@ -32,3 +35,52 @@ pub fn path_to_uri(path: PathBuf) -> Result<lsp_types::Url, Error> {
|
||||||
|
|
||||||
Ok(uri)
|
Ok(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn span_to_lsp_range(location: Span, line_numbers: &LineNumbers) -> lsp_types::Range {
|
||||||
|
let start = line_numbers.line_and_column_number(location.start);
|
||||||
|
let end = line_numbers.line_and_column_number(location.end);
|
||||||
|
|
||||||
|
lsp_types::Range {
|
||||||
|
start: lsp_types::Position {
|
||||||
|
line: start.line as u32 - 1,
|
||||||
|
character: start.column as u32 - 1,
|
||||||
|
},
|
||||||
|
end: lsp_types::Position {
|
||||||
|
line: end.line as u32 - 1,
|
||||||
|
character: end.column as u32 - 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uri_to_module_name(uri: &url::Url, root: &Path) -> Option<String> {
|
||||||
|
let path = if cfg!(target_os = "windows") {
|
||||||
|
let mut uri_path = decode(&uri.path().replace('/', "\\"))
|
||||||
|
.expect("Invalid formatting")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if uri_path.starts_with('\\') {
|
||||||
|
uri_path = uri_path
|
||||||
|
.strip_prefix('\\')
|
||||||
|
.expect("Failed to remove \"\\\" prefix")
|
||||||
|
.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
PathBuf::from(uri_path)
|
||||||
|
} else {
|
||||||
|
PathBuf::from(uri.path())
|
||||||
|
};
|
||||||
|
|
||||||
|
let components = path
|
||||||
|
.strip_prefix(root)
|
||||||
|
.ok()?
|
||||||
|
.components()
|
||||||
|
.skip(1)
|
||||||
|
.map(|c| c.as_os_str().to_string_lossy());
|
||||||
|
|
||||||
|
let module_name = Itertools::intersperse(components, "/".into())
|
||||||
|
.collect::<String>()
|
||||||
|
.strip_suffix(".ak")?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Some(module_name)
|
||||||
|
}
|
||||||
|
|
|
@ -230,7 +230,8 @@ mod test {
|
||||||
let (mut ast, extra) =
|
let (mut ast, extra) =
|
||||||
parser::module(source_code, kind).expect("Failed to parse module");
|
parser::module(source_code, kind).expect("Failed to parse module");
|
||||||
ast.name = name.clone();
|
ast.name = name.clone();
|
||||||
let mut module = ParsedModule {
|
|
||||||
|
ParsedModule {
|
||||||
kind,
|
kind,
|
||||||
ast,
|
ast,
|
||||||
code: source_code.to_string(),
|
code: source_code.to_string(),
|
||||||
|
@ -238,9 +239,7 @@ mod test {
|
||||||
path: PathBuf::new(),
|
path: PathBuf::new(),
|
||||||
extra,
|
extra,
|
||||||
package: self.package.to_string(),
|
package: self.package.to_string(),
|
||||||
};
|
}
|
||||||
module.attach_doc_and_module_comments();
|
|
||||||
module
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(&mut self, module: ParsedModule) -> CheckedModule {
|
fn check(&mut self, module: ParsedModule) -> CheckedModule {
|
||||||
|
@ -261,7 +260,7 @@ mod test {
|
||||||
self.module_types
|
self.module_types
|
||||||
.insert(module.name.clone(), ast.type_info.clone());
|
.insert(module.name.clone(), ast.type_info.clone());
|
||||||
|
|
||||||
CheckedModule {
|
let mut checked_module = CheckedModule {
|
||||||
kind: module.kind,
|
kind: module.kind,
|
||||||
extra: module.extra,
|
extra: module.extra,
|
||||||
name: module.name,
|
name: module.name,
|
||||||
|
@ -269,7 +268,11 @@ mod test {
|
||||||
package: module.package,
|
package: module.package,
|
||||||
input_path: module.path,
|
input_path: module.path,
|
||||||
ast,
|
ast,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
checked_module.attach_doc_and_module_comments();
|
||||||
|
|
||||||
|
checked_module
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -467,7 +467,7 @@ where
|
||||||
// Store the name
|
// Store the name
|
||||||
ast.name = name.clone();
|
ast.name = name.clone();
|
||||||
|
|
||||||
let mut module = ParsedModule {
|
let module = ParsedModule {
|
||||||
kind,
|
kind,
|
||||||
ast,
|
ast,
|
||||||
code,
|
code,
|
||||||
|
@ -489,8 +489,6 @@ where
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
module.attach_doc_and_module_comments();
|
|
||||||
|
|
||||||
parsed_modules.insert(module.name.clone(), module);
|
parsed_modules.insert(module.name.clone(), module);
|
||||||
}
|
}
|
||||||
Err(errs) => {
|
Err(errs) => {
|
||||||
|
@ -561,18 +559,19 @@ where
|
||||||
self.module_types
|
self.module_types
|
||||||
.insert(name.clone(), ast.type_info.clone());
|
.insert(name.clone(), ast.type_info.clone());
|
||||||
|
|
||||||
self.checked_modules.insert(
|
let mut checked_module = CheckedModule {
|
||||||
name.clone(),
|
kind,
|
||||||
CheckedModule {
|
extra,
|
||||||
kind,
|
name: name.clone(),
|
||||||
extra,
|
code,
|
||||||
name,
|
ast,
|
||||||
code,
|
package,
|
||||||
ast,
|
input_path: path,
|
||||||
package,
|
};
|
||||||
input_path: path,
|
|
||||||
},
|
checked_module.attach_doc_and_module_comments();
|
||||||
);
|
|
||||||
|
self.checked_modules.insert(name, checked_module);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use aiken_lang::{
|
use aiken_lang::{
|
||||||
ast::{
|
ast::{
|
||||||
DataType, Definition, ModuleKind, TypedDataType, TypedFunction, TypedModule,
|
DataType, Definition, Located, ModuleKind, TypedDataType, TypedFunction, TypedModule,
|
||||||
TypedValidator, UntypedModule,
|
TypedValidator, UntypedModule,
|
||||||
},
|
},
|
||||||
builder::{DataTypeKey, FunctionAccessKey},
|
builder::{DataTypeKey, FunctionAccessKey},
|
||||||
|
@ -41,55 +41,6 @@ impl ParsedModule {
|
||||||
|
|
||||||
(name, deps)
|
(name, deps)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attach_doc_and_module_comments(&mut self) {
|
|
||||||
// Module Comments
|
|
||||||
self.ast.docs = self
|
|
||||||
.extra
|
|
||||||
.module_comments
|
|
||||||
.iter()
|
|
||||||
.map(|span| {
|
|
||||||
Comment::from((span, self.code.as_str()))
|
|
||||||
.content
|
|
||||||
.to_string()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Order definitions to avoid dissociating doc comments from them
|
|
||||||
let mut definitions: Vec<_> = self.ast.definitions.iter_mut().collect();
|
|
||||||
definitions.sort_by(|a, b| a.location().start.cmp(&b.location().start));
|
|
||||||
|
|
||||||
// Doc Comments
|
|
||||||
let mut doc_comments = self.extra.doc_comments.iter().peekable();
|
|
||||||
for def in &mut definitions {
|
|
||||||
let docs: Vec<&str> =
|
|
||||||
comments_before(&mut doc_comments, def.location().start, &self.code);
|
|
||||||
if !docs.is_empty() {
|
|
||||||
let doc = docs.join("\n");
|
|
||||||
def.put_doc(doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Definition::DataType(DataType { constructors, .. }) = def {
|
|
||||||
for constructor in constructors {
|
|
||||||
let docs: Vec<&str> =
|
|
||||||
comments_before(&mut doc_comments, constructor.location.start, &self.code);
|
|
||||||
if !docs.is_empty() {
|
|
||||||
let doc = docs.join("\n");
|
|
||||||
constructor.put_doc(doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
for argument in constructor.arguments.iter_mut() {
|
|
||||||
let docs: Vec<&str> =
|
|
||||||
comments_before(&mut doc_comments, argument.location.start, &self.code);
|
|
||||||
if !docs.is_empty() {
|
|
||||||
let doc = docs.join("\n");
|
|
||||||
argument.put_doc(doc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ParsedModules(HashMap<String, ParsedModule>);
|
pub struct ParsedModules(HashMap<String, ParsedModule>);
|
||||||
|
@ -223,6 +174,61 @@ pub struct CheckedModule {
|
||||||
pub extra: ModuleExtra,
|
pub extra: ModuleExtra,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CheckedModule {
|
||||||
|
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
|
||||||
|
self.ast.find_node(byte_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn attach_doc_and_module_comments(&mut self) {
|
||||||
|
// Module Comments
|
||||||
|
self.ast.docs = self
|
||||||
|
.extra
|
||||||
|
.module_comments
|
||||||
|
.iter()
|
||||||
|
.map(|span| {
|
||||||
|
Comment::from((span, self.code.as_str()))
|
||||||
|
.content
|
||||||
|
.to_string()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Order definitions to avoid dissociating doc comments from them
|
||||||
|
let mut definitions: Vec<_> = self.ast.definitions.iter_mut().collect();
|
||||||
|
definitions.sort_by(|a, b| a.location().start.cmp(&b.location().start));
|
||||||
|
|
||||||
|
// Doc Comments
|
||||||
|
let mut doc_comments = self.extra.doc_comments.iter().peekable();
|
||||||
|
for def in &mut definitions {
|
||||||
|
let docs: Vec<&str> =
|
||||||
|
comments_before(&mut doc_comments, def.location().start, &self.code);
|
||||||
|
if !docs.is_empty() {
|
||||||
|
let doc = docs.join("\n");
|
||||||
|
def.put_doc(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Definition::DataType(DataType { constructors, .. }) = def {
|
||||||
|
for constructor in constructors {
|
||||||
|
let docs: Vec<&str> =
|
||||||
|
comments_before(&mut doc_comments, constructor.location.start, &self.code);
|
||||||
|
if !docs.is_empty() {
|
||||||
|
let doc = docs.join("\n");
|
||||||
|
constructor.put_doc(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
for argument in constructor.arguments.iter_mut() {
|
||||||
|
let docs: Vec<&str> =
|
||||||
|
comments_before(&mut doc_comments, argument.location.start, &self.code);
|
||||||
|
if !docs.is_empty() {
|
||||||
|
let doc = docs.join("\n");
|
||||||
|
argument.put_doc(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct CheckedModules(HashMap<String, CheckedModule>);
|
pub struct CheckedModules(HashMap<String, CheckedModule>);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue