feat: add a basic lsp

This commit is contained in:
rvcas 2022-11-10 01:03:41 -05:00 committed by Lucas
parent e421b49ab2
commit e90a210537
7 changed files with 377 additions and 16 deletions

188
Cargo.lock generated
View File

@ -51,6 +51,7 @@ name = "aiken"
version = "0.0.24"
dependencies = [
"aiken-lang",
"aiken-lsp",
"aiken-project",
"anyhow",
"clap",
@ -80,6 +81,22 @@ dependencies = [
"vec1",
]
[[package]]
name = "aiken-lsp"
version = "0.0.0"
dependencies = [
"aiken-lang",
"aiken-project",
"crossbeam-channel",
"lsp-server",
"lsp-types",
"miette",
"serde",
"serde_json",
"thiserror",
"tracing",
]
[[package]]
name = "aiken-project"
version = "0.0.24"
@ -274,6 +291,16 @@ dependencies = [
"tiny-keccak",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.12"
@ -347,6 +374,15 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
"percent-encoding",
]
[[package]]
name = "getrandom"
version = "0.2.7"
@ -413,6 +449,16 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "idna"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "ignore"
version = "0.4.18"
@ -492,6 +538,31 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "lsp-server"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f70570c1c29cf6654029b8fe201a5507c153f0d85be6f234d471d756bc36775a"
dependencies = [
"crossbeam-channel",
"log",
"serde",
"serde_json",
]
[[package]]
name = "lsp-types"
version = "0.93.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9be6e9c7e2d18f651974370d7aff703f9513e0df6e464fd795660edc77e6ca51"
dependencies = [
"bitflags",
"serde",
"serde_json",
"serde_repr",
"url",
]
[[package]]
name = "memchr"
version = "2.5.0"
@ -500,9 +571,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "miette"
version = "5.3.0"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28d6092d7e94a90bb9ea8e6c26c99d5d112d49dda2afdb4f7ea8cf09e1a5a6d"
checksum = "7a24c4b4063c21e037dffb4de388ee85e400bff299803aba9513d9c52de8116b"
dependencies = [
"atty",
"backtrace",
@ -520,9 +591,9 @@ dependencies = [
[[package]]
name = "miette-derive"
version = "5.3.0"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2485ed7d1fe80704928e3eb86387439609bd0c6bb96db8208daa364cfd1e09"
checksum = "827d18edee5d43dc309eb0ac565f2b8e2fdc89b986b2d929e924a0f6e7f23835"
dependencies = [
"proc-macro2",
"quote",
@ -743,6 +814,12 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9b0efd3ba03c3a409d44d60425f279ec442bcf0b9e63ff4e410da31c8b0f69f"
[[package]]
name = "percent-encoding"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "petgraph"
version = "0.6.2"
@ -753,6 +830,12 @@ dependencies = [
"indexmap",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "ppv-lite86"
version = "0.2.16"
@ -978,18 +1061,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.145"
version = "1.0.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.145"
version = "1.0.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
dependencies = [
"proc-macro2",
"quote",
@ -998,9 +1081,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.85"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
dependencies = [
"indexmap",
"itoa",
@ -1008,6 +1091,17 @@ dependencies = [
"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]]
name = "smawk"
version = "0.3.1"
@ -1160,6 +1254,21 @@ dependencies = [
"crunchy",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "toml"
version = "0.5.9"
@ -1169,12 +1278,50 @@ dependencies = [
"serde",
]
[[package]]
name = "tracing"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
dependencies = [
"once_cell",
]
[[package]]
name = "typed-arena"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
[[package]]
name = "unicode-bidi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]]
name = "unicode-ident"
version = "1.0.4"
@ -1191,6 +1338,15 @@ dependencies = [
"regex",
]
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.10.0"
@ -1227,6 +1383,18 @@ dependencies = [
"thiserror",
]
[[package]]
name = "url"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
]
[[package]]
name = "vec1"
version = "1.8.0"

View File

@ -14,12 +14,13 @@ clap = { version = "3.1.14", features = ["derive"] }
hex = "0.4.3"
ignore = "0.4.18"
miette = { version = "5.3.0", features = ["fancy"] }
pallas-addresses = "0.14.0-alpha.3"
pallas-codec = "0.14.0-alpha.3"
pallas-crypto = "0.14.0-alpha.3"
pallas-primitives = "0.14.0-alpha.3"
pallas-traverse = "0.14.0-alpha.3"
pallas-addresses = "0.14.0"
pallas-codec = "0.14.0"
pallas-crypto = "0.14.0"
pallas-primitives = "0.14.0"
pallas-traverse = "0.14.0"
aiken-lang = { path = "../lang", version = "0.0.24" }
aiken-lsp = { path = "../lsp", version = "0.0.0" }
aiken-project = { path = '../project', version = "0.0.24" }
uplc = { path = '../uplc', version = "0.0.24" }

13
crates/cli/src/cmd/lsp.rs Normal file
View File

@ -0,0 +1,13 @@
use miette::IntoDiagnostic;
#[derive(clap::Args)]
/// Start the Aiken language server
pub struct Args {
/// Run on stdio
#[clap(long)]
stdio: bool,
}
pub fn exec(_args: Args) -> miette::Result<()> {
aiken_lsp::start().into_diagnostic()
}

View File

@ -1,6 +1,7 @@
pub mod build;
pub mod check;
pub mod fmt;
pub mod lsp;
pub mod new;
pub mod tx;
pub mod uplc;

View File

@ -1,4 +1,4 @@
use aiken::cmd::{build, check, fmt, new, tx, uplc};
use aiken::cmd::{build, check, fmt, lsp, new, tx, uplc};
use clap::Parser;
/// Aiken: a smart-contract language and toolchain for Cardano
@ -12,6 +12,9 @@ pub enum Cmd {
Build(build::Args),
Check(check::Args),
#[clap(hide = true)]
Lsp(lsp::Args),
#[clap(subcommand)]
Tx(tx::Cmd),
@ -32,6 +35,7 @@ fn main() -> miette::Result<()> {
Cmd::Fmt(args) => fmt::exec(args),
Cmd::Build(args) => build::exec(args),
Cmd::Check(args) => check::exec(args),
Cmd::Lsp(args) => lsp::exec(args),
Cmd::Tx(sub_cmd) => tx::exec(sub_cmd),
Cmd::Uplc(sub_cmd) => uplc::exec(sub_cmd),
}

22
crates/lsp/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "aiken-lsp"
version = "0.0.0"
edition = "2021"
description = "Cardano smart contract language and toolchain"
repository = "https://github.com/txpipe/aiken"
homepage = "https://github.com/txpipe/aiken"
license = "Apache-2.0"
authors = ["Lucas Rosa <x@rvcas.dev>"]
[dependencies]
aiken-lang = { path = '../lang', version = "0.0.24" }
aiken-project = { path = '../project', version = "0.0.24" }
crossbeam-channel = "0.5.6"
lsp-server = "0.6.0"
lsp-types = "0.93.2"
miette = "5.4.1"
serde = "1.0.147"
serde_json = "1.0.87"
thiserror = "1.0.37"
tracing = "0.1.37"

152
crates/lsp/src/lib.rs Normal file
View File

@ -0,0 +1,152 @@
use std::{fs, io, path::Path};
use aiken_lang::{ast::ModuleKind, parser};
use crossbeam_channel::SendError;
use lsp_server::{Connection, ExtractError, Message};
use lsp_types::{
request::{Formatting, Request},
DocumentFormattingParams, InitializeParams, OneOf, ServerCapabilities, TextEdit,
};
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum Error {
#[error(transparent)]
#[diagnostic(code(aiken::lsp::server_capabilities))]
ServerCapabilities(#[from] serde_json::Error),
#[error(transparent)]
#[diagnostic(code(aiken::lsp::server_init))]
ServerInit(#[from] lsp_server::ProtocolError),
#[error(transparent)]
#[diagnostic(code(aiken::lsp::io))]
Io(#[from] io::Error),
#[error("Unsupported LSP request: {request}")]
#[diagnostic(code(aiken::lsp::unsupported_lsp_request))]
UnsupportedLspRequest { request: String },
#[error(transparent)]
#[diagnostic(code(aiken::lsp::cast_request))]
CastRequest(#[from] ExtractError<lsp_server::Request>),
#[error(transparent)]
#[diagnostic(code(aiken::lsp::send))]
Send(#[from] SendError<Message>),
}
pub fn start() -> Result<(), Error> {
tracing::info!("Aiken language server starting");
// Create the transport. Includes the stdio (stdin and stdout) versions but this could
// also be implemented to use sockets or HTTP.
let (connection, io_threads) = Connection::stdio();
// Run the server and wait for the two threads to end (typically by trigger LSP Exit event).
let server_capabilities = serde_json::to_value(&ServerCapabilities {
definition_provider: Some(OneOf::Left(true)),
document_formatting_provider: Some(OneOf::Left(true)),
..Default::default()
})?;
let initialization_params = connection.initialize(server_capabilities)?;
let initialization_params = serde_json::from_value(initialization_params)?;
main_loop(connection, initialization_params)?;
io_threads.join()?;
tracing::info!("Aiken language server shutting down");
Ok(())
}
fn main_loop(connection: Connection, _params: InitializeParams) -> Result<(), Error> {
for msg in &connection.receiver {
tracing::debug!("Got message: {:#?}", msg);
match msg {
Message::Request(req) => {
if connection.handle_shutdown(&req)? {
return Ok(());
}
tracing::debug!("Get request: {:#?}", req);
let response = handle_request(req)?;
connection.sender.send(Message::Response(response))?;
}
Message::Response(_) => todo!(),
Message::Notification(_) => todo!(),
}
}
Ok(())
}
fn handle_request(request: lsp_server::Request) -> Result<lsp_server::Response, Error> {
let id = request.id.clone();
match request.method.as_str() {
Formatting::METHOD => {
let params = cast_request::<Formatting>(request)?;
let result = format(params);
match result {
Ok(text_edit) => {
let result = serde_json::to_value(text_edit)?;
Ok(lsp_server::Response {
id,
error: None,
result: Some(result),
})
}
Err(_) => {
todo!("transform project errors in lsp diagnostic")
}
}
}
unsupported => Err(Error::UnsupportedLspRequest {
request: unsupported.to_string(),
}),
}
}
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)
}
fn format(params: DocumentFormattingParams) -> Result<Vec<TextEdit>, aiken_project::error::Error> {
let path = params.text_document.uri.path();
let mut new_text = String::new();
let src = fs::read_to_string(path)?;
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 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,
}
}