use anyhow::Result;
use clap::Parser;
use dashmap::DashMap;
use schemars::JsonSchema;
use tower_lsp::{jsonrpc::Result as RpcResult, lsp_types::*, Client, LanguageServer};
use crate::abstract_syntax_tree_types::VariableKind;
#[derive(Parser, Clone, Debug)]
pub struct Server {
#[clap(long, default_value = "8080")]
pub socket: i32,
#[clap(short, long, default_value = "false")]
pub stdio: bool,
}
pub struct Backend {
pub client: Client,
pub stdlib_completions: Vec<CompletionItem>,
pub token_map: DashMap<String, Vec<crate::tokeniser::Token>>,
pub ast_map: DashMap<String, crate::abstract_syntax_tree_types::Program>,
}
impl Backend {
async fn on_change(&self, params: TextDocumentItem) {
let tokens = crate::tokeniser::lexer(¶ms.text);
self.token_map.insert(params.uri.to_string(), tokens.clone());
let ast = match crate::parser::abstract_syntax_tree(&tokens) {
Ok(ast) => ast,
Err(e) => {
self.client
.log_message(MessageType::ERROR, format!("Error parsing: {:?}", e))
.await;
return;
}
};
self.ast_map.insert(params.uri.to_string(), ast);
}
async fn completions_get_variables_from_ast(&self, file_name: &str) -> Vec<CompletionItem> {
let mut completions = vec![];
let ast = match self.ast_map.get(file_name) {
Some(ast) => ast,
None => return completions,
};
for item in &ast.body {
match item {
crate::abstract_syntax_tree_types::BodyItem::ExpressionStatement(_) => continue,
crate::abstract_syntax_tree_types::BodyItem::ReturnStatement(_) => continue,
crate::abstract_syntax_tree_types::BodyItem::VariableDeclaration(variable) => {
for declaration in &variable.declarations {
completions.push(CompletionItem {
label: declaration.id.name.to_string(),
label_details: None,
kind: Some(match variable.kind {
crate::abstract_syntax_tree_types::VariableKind::Let => CompletionItemKind::VARIABLE,
crate::abstract_syntax_tree_types::VariableKind::Const => CompletionItemKind::CONSTANT,
crate::abstract_syntax_tree_types::VariableKind::Var => CompletionItemKind::VARIABLE,
crate::abstract_syntax_tree_types::VariableKind::Fn => CompletionItemKind::FUNCTION,
}),
detail: Some(variable.kind.to_string()),
documentation: None,
deprecated: None,
preselect: None,
sort_text: None,
filter_text: None,
insert_text: None,
insert_text_format: None,
insert_text_mode: None,
text_edit: None,
additional_text_edits: None,
command: None,
commit_characters: None,
data: None,
tags: None,
});
}
}
}
}
completions
}
}
#[tower_lsp::async_trait]
impl LanguageServer for Backend {
async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
self.client
.log_message(MessageType::INFO, format!("initialize: {:?}", params))
.await;
Ok(InitializeResult {
capabilities: ServerCapabilities {
hover_provider: Some(HoverProviderCapability::Simple(true)),
completion_provider: Some(CompletionOptions {
resolve_provider: Some(false),
trigger_characters: Some(vec![".".to_string()]),
work_done_progress_options: Default::default(),
all_commit_characters: None,
..Default::default()
}),
text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
open_close: Some(true),
change: Some(TextDocumentSyncKind::FULL),
..Default::default()
})),
workspace: Some(WorkspaceServerCapabilities {
workspace_folders: Some(WorkspaceFoldersServerCapabilities {
supported: Some(true),
change_notifications: Some(OneOf::Left(true)),
}),
file_operations: None,
}),
..Default::default()
},
..Default::default()
})
}
async fn initialized(&self, params: InitializedParams) {
self.client
.log_message(MessageType::INFO, format!("initialized: {:?}", params))
.await;
}
async fn shutdown(&self) -> RpcResult<()> {
self.client.log_message(MessageType::INFO, "shutdown".to_string()).await;
Ok(())
}
async fn did_change_workspace_folders(&self, _: DidChangeWorkspaceFoldersParams) {
self.client
.log_message(MessageType::INFO, "workspace folders changed!")
.await;
}
async fn did_change_configuration(&self, _: DidChangeConfigurationParams) {
self.client
.log_message(MessageType::INFO, "configuration changed!")
.await;
}
async fn did_change_watched_files(&self, _: DidChangeWatchedFilesParams) {
self.client
.log_message(MessageType::INFO, "watched files have changed!")
.await;
}
async fn did_open(&self, params: DidOpenTextDocumentParams) {
self.on_change(TextDocumentItem {
uri: params.text_document.uri,
text: params.text_document.text,
version: params.text_document.version,
language_id: params.text_document.language_id,
})
.await
}
async fn did_change(&self, mut params: DidChangeTextDocumentParams) {
self.on_change(TextDocumentItem {
uri: params.text_document.uri,
text: std::mem::take(&mut params.content_changes[0].text),
version: params.text_document.version,
language_id: Default::default(),
})
.await
}
async fn did_save(&self, _: DidSaveTextDocumentParams) {
self.client.log_message(MessageType::INFO, "file saved!").await;
}
async fn did_close(&self, _: DidCloseTextDocumentParams) {
self.client.log_message(MessageType::INFO, "file closed!").await;
}
async fn hover(&self, params: HoverParams) -> RpcResult<Option<Hover>> {
self.client
.log_message(MessageType::INFO, format!("hover: {:?}", params))
.await;
Ok(Some(Hover {
contents: HoverContents::Scalar(MarkedString::String("You're hovering!".to_string())),
range: None,
}))
}
async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
let mut completions = vec![
CompletionItem {
label: "|>".to_string(),
label_details: None,
kind: Some(CompletionItemKind::OPERATOR),
detail: None,
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: "A pipe operator.".to_string(),
})),
deprecated: Some(false),
preselect: None,
sort_text: None,
filter_text: None,
insert_text: Some("|> ".to_string()),
insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
insert_text_mode: None,
text_edit: None,
additional_text_edits: None,
command: None,
commit_characters: None,
data: None,
tags: None,
},
CompletionItem {
label: "show".to_string(),
label_details: Some(CompletionItemLabelDetails {
detail: Some("(sg: SketchGroup)".to_string()),
description: None,
}),
kind: Some(CompletionItemKind::FUNCTION),
detail: None,
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::PlainText,
value: "Show a model.".to_string(),
})),
deprecated: Some(false),
preselect: None,
sort_text: None,
filter_text: None,
insert_text: None,
insert_text_format: None,
insert_text_mode: None,
text_edit: None,
additional_text_edits: None,
command: None,
commit_characters: None,
data: None,
tags: None,
},
];
completions.extend(self.stdlib_completions.clone());
completions.extend(
self.completions_get_variables_from_ast(params.text_document_position.text_document.uri.as_ref())
.await,
);
Ok(Some(CompletionResponse::Array(completions)))
}
}
pub fn get_completions_from_stdlib(stdlib: &crate::std::StdLib) -> Result<Vec<CompletionItem>> {
let mut completions = Vec::new();
for internal_fn in &stdlib.internal_fn_names {
completions.push(CompletionItem {
label: internal_fn.name(),
label_details: Some(CompletionItemLabelDetails {
detail: Some(internal_fn.fn_signature().replace(&internal_fn.name(), "")),
description: None,
}),
kind: Some(CompletionItemKind::FUNCTION),
detail: None,
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: if !internal_fn.description().is_empty() {
format!("{}\n\n{}", internal_fn.summary(), internal_fn.description())
} else {
internal_fn.summary()
},
})),
deprecated: Some(internal_fn.deprecated()),
preselect: None,
sort_text: None,
filter_text: None,
insert_text: Some(format!(
"{}({})",
internal_fn.name(),
internal_fn
.args()
.iter()
.enumerate()
.map(|(index, item)| {
let format = item.get_autocomplete_string().unwrap();
if item.type_ == "SketchGroup" || item.type_ == "ExtrudeGroup" {
format!("${{{}:{}}}", index + 1, "%")
} else {
format!("${{{}:{}}}", index + 1, format)
}
})
.collect::<Vec<_>>()
.join(",")
)),
insert_text_format: Some(InsertTextFormat::SNIPPET),
insert_text_mode: None,
text_edit: None,
additional_text_edits: None,
command: None,
commit_characters: None,
data: None,
tags: None,
});
}
let variable_kinds = completions_get_variable_kinds()?;
completions.extend(variable_kinds);
Ok(completions)
}
fn completions_get_variable_kinds() -> Result<Vec<CompletionItem>> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
let schema = VariableKind::json_schema(&mut generator);
let schemars::schema::Schema::Object(o) = &schema else {
anyhow::bail!("expected object schema: {:#?}", schema);
};
let Some(subschemas) = &o.subschemas else {
anyhow::bail!("expected subschemas: {:#?}", schema);
};
let Some(one_ofs) = &subschemas.one_of else {
anyhow::bail!("expected one_of: {:#?}", schema);
};
let mut completions = vec![];
for one_of in one_ofs {
completions.push(crate::docs::completion_item_from_enum_schema(
one_of,
CompletionItemKind::KEYWORD,
)?);
}
Ok(completions)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_completions_get_variable_kinds() {
let completions = completions_get_variable_kinds().unwrap();
assert!(!completions.is_empty());
}
}