Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lsp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "simplicityhl-lsp"
version = "0.2.0"
version = "0.2.1"
edition = "2021"
rust-version = "1.79"
description = "Language Server Protocol (LSP) server for SimplicityHL."
Expand All @@ -22,6 +22,8 @@ thiserror = "2.0.17"
ropey = "1.6.1"
miniscript = "12"
simplicityhl = { version = "0.3.0" }
nom = "8.0.0"
lazy_static = "1.5.0"

[lints.rust]
unsafe_code = "deny"
Expand Down
30 changes: 6 additions & 24 deletions lsp/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl LanguageServer for Backend {
)),
completion_provider: Some(CompletionOptions {
resolve_provider: Some(false),
trigger_characters: Some(vec![":".to_string()]),
trigger_characters: Some(vec![":".to_string(), "<".to_string()]),
work_done_progress_options: WorkDoneProgressOptions::default(),
all_commit_characters: None,
completion_item: None,
Expand Down Expand Up @@ -174,30 +174,12 @@ impl LanguageServer for Backend {
"RopeSlice to str conversion failed".into(),
))?;

let trimmed_prefix = prefix.trim_end();

if let Some(last) = trimmed_prefix
.rsplit(|c: char| !c.is_alphanumeric() && c != ':')
.next()
{
if last.starts_with("jet:::") {
return Ok(Some(CompletionResponse::Array(vec![])));
} else if last == "jet::" || last.starts_with("jet::") {
return Ok(Some(CompletionResponse::Array(
self.completion_provider.jets().to_vec(),
)));
}
// Completion after a colon is needed only for jets.
} else if trimmed_prefix.ends_with(':') {
return Ok(Some(CompletionResponse::Array(vec![])));
}

let mut completions =
CompletionProvider::get_function_completions(&doc.functions.functions_and_docs());
completions.extend_from_slice(self.completion_provider.builtins());
completions.extend_from_slice(self.completion_provider.modules());
let completions = self
.completion_provider
.process_completions(prefix, &doc.functions.functions_and_docs())
.map(CompletionResponse::Array);

Ok(Some(CompletionResponse::Array(completions)))
Ok(completions)
}

async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
Expand Down
89 changes: 74 additions & 15 deletions lsp/src/completion/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ use simplicityhl::parse::Function;

pub mod builtin;
pub mod jet;
pub mod tokens;
pub mod type_cast;
pub mod types;

use tower_lsp_server::lsp_types::{
CompletionItem, CompletionItemKind, Documentation, InsertTextFormat, MarkupContent, MarkupKind,
};

use tokens::parse;
use tokens::CompletionType;

/// Build and provide [`CompletionItem`] for jets and builtin functions.
#[derive(Debug)]
pub struct CompletionProvider {
Expand All @@ -19,6 +24,9 @@ pub struct CompletionProvider {

/// Modules completions.
modules: Vec<CompletionItem>,

/// Default Type cast completions.
type_casts: Vec<CompletionItem>,
}

impl CompletionProvider {
Expand All @@ -41,28 +49,28 @@ impl CompletionProvider {
.iter()
.map(|(module, detail)| module_to_completion((*module).to_string(), (*detail).to_string()))
.collect();

let type_casts_completion = type_cast::TYPE_CASTS
.iter()
.map(|(&to, &from)| CompletionItem {
label: format!("{to} <- {from}"),
kind: Some(CompletionItemKind::FUNCTION),
detail: Some(format!("Cast into type `{to}`",)),
documentation: None,
insert_text: Some(format!("{from}>::into(${{1:{from}}})")),
insert_text_format: Some(InsertTextFormat::SNIPPET),
..Default::default()
})
.collect::<Vec<_>>();

Self {
jets: jets_completion,
builtin: builtin_completion,
modules: modules_completion,
type_casts: type_casts_completion,
}
}

/// Return jets completions.
pub fn jets(&self) -> &[CompletionItem] {
&self.jets
}

/// Return builtin functions completions.
pub fn builtins(&self) -> &[CompletionItem] {
&self.builtin
}

/// Return builtin functions completions.
pub fn modules(&self) -> &[CompletionItem] {
&self.modules
}

/// Get generic functions completions.
pub fn get_function_completions(functions: &[(&Function, &str)]) -> Vec<CompletionItem> {
functions
Expand All @@ -73,6 +81,57 @@ impl CompletionProvider {
})
.collect()
}

/// Return completions based on line and functions provided.
pub fn process_completions(
&self,
prefix: &str,
functions: &[(&Function, &str)],
) -> Option<Vec<CompletionItem>> {
let completion_type = parse(prefix)?;

match completion_type {
CompletionType::Jet => Some(self.jets.clone()),

CompletionType::Assignment(type_name) => {
let to = type_name.as_str();

if let Some(from) = type_cast::TYPE_CASTS.get(to) {
return Some(vec![CompletionItem {
label: format!("{to} <- {from}"),
kind: Some(CompletionItemKind::FUNCTION),
detail: Some(format!("Cast into type `{to}`",)),
documentation: None,
insert_text: Some(format!("{from}>::into(${{1:{from}}})")),
insert_text_format: Some(InsertTextFormat::SNIPPET),
..Default::default()
}]);
}
Some(self.type_casts.clone())
}

CompletionType::ClosingType => Some(vec![CompletionItem {
label: "into".to_string(),
kind: Some(CompletionItemKind::FUNCTION),
detail: Some("Cast into type".to_string()),
documentation: None,
insert_text: Some("into(${1:type})".to_string()),
insert_text_format: Some(InsertTextFormat::SNIPPET),
..Default::default()
}]),

CompletionType::NonCompletionSymbol => None,

_ => {
let mut completions = CompletionProvider::get_function_completions(functions);

completions.extend_from_slice(&self.builtin);
completions.extend_from_slice(&self.modules);

Some(completions)
}
}
}
}

/// Convert [`simplicityhl::parse::Function`] to [`types::FunctionTemplate`].
Expand Down
Loading