mirror of
https://github.com/Dummi26/mers.git
synced 2025-03-10 14:13:52 +01:00
add merslsp
This commit is contained in:
parent
0042f1b502
commit
f9b62bcefd
10
merslsp/Cargo.toml
Normal file
10
merslsp/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "merslsp"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
mers_lib = "0.8.0"
|
||||
lspower = "1.5.0"
|
||||
tokio = { version = "1.36.0", features = ["full"] }
|
||||
line-span = "0.1.5"
|
305
merslsp/src/main.rs
Normal file
305
merslsp/src/main.rs
Normal file
@ -0,0 +1,305 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use line_span::LineSpans;
|
||||
use lspower::jsonrpc;
|
||||
use lspower::lsp::*;
|
||||
use lspower::{Client, LanguageServer, LspService, Server};
|
||||
use mers_lib::data::Type;
|
||||
use mers_lib::errors::CheckError;
|
||||
use mers_lib::parsing::SourceFrom;
|
||||
use mers_lib::prelude_extend_config::Config;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Backend {
|
||||
client: Client,
|
||||
documents: Mutex<HashMap<Url, TextDocument>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TextDocument {
|
||||
source: String,
|
||||
file_path: Option<PathBuf>,
|
||||
srca: Option<Arc<mers_lib::prelude_compile::Source>>,
|
||||
parsed: Option<Result<Box<dyn mers_lib::program::parsed::MersStatement>, CheckError>>,
|
||||
compiled: Option<Result<Box<dyn mers_lib::program::run::MersStatement>, CheckError>>,
|
||||
checked: Option<Result<mers_lib::data::Type, CheckError>>,
|
||||
}
|
||||
impl TextDocument {
|
||||
pub fn changed(&mut self) {
|
||||
self.srca = None;
|
||||
self.parsed = None;
|
||||
self.compiled = None;
|
||||
self.checked = None;
|
||||
}
|
||||
pub fn srca(&mut self) -> &Arc<mers_lib::prelude_compile::Source> {
|
||||
if self.srca.is_none() {
|
||||
self.srca = Some(Arc::new(mers_lib::prelude_compile::Source::new(
|
||||
self.file_path
|
||||
.clone()
|
||||
.map(|v| SourceFrom::File(v))
|
||||
.unwrap_or(SourceFrom::Unspecified),
|
||||
self.source.clone(),
|
||||
)));
|
||||
}
|
||||
self.srca.as_ref().unwrap()
|
||||
}
|
||||
pub fn parsed(
|
||||
&mut self,
|
||||
) -> &Result<Box<dyn mers_lib::program::parsed::MersStatement>, CheckError> {
|
||||
if self.parsed.is_none() {
|
||||
self.parsed = Some(mers_lib::prelude_compile::parse(
|
||||
&mut mers_lib::prelude_compile::Source::clone(self.srca()),
|
||||
self.srca(),
|
||||
))
|
||||
}
|
||||
self.parsed.as_ref().unwrap()
|
||||
}
|
||||
pub fn compiled(
|
||||
&mut self,
|
||||
) -> &Result<Box<dyn mers_lib::program::run::MersStatement>, CheckError> {
|
||||
if self.compiled.is_none() {
|
||||
self.compiled =
|
||||
Some(self.parsed().as_ref().map_err(|e| e.clone()).and_then(|v| {
|
||||
mers_lib::prelude_compile::compile(&**v, mers_config().infos().0)
|
||||
}));
|
||||
}
|
||||
self.compiled.as_ref().unwrap()
|
||||
}
|
||||
pub fn checked(&mut self) -> &Result<mers_lib::data::Type, CheckError> {
|
||||
if self.checked.is_none() {
|
||||
self.checked = Some(
|
||||
self.compiled()
|
||||
.as_ref()
|
||||
.map_err(|e| e.clone())
|
||||
.and_then(|v| mers_lib::prelude_compile::check(&**v, mers_config().infos().2)),
|
||||
);
|
||||
}
|
||||
self.checked.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn mers_config() -> Config {
|
||||
Config::new().bundle_std()
|
||||
}
|
||||
|
||||
#[lspower::async_trait]
|
||||
impl LanguageServer for Backend {
|
||||
async fn initialize(&self, _: InitializeParams) -> jsonrpc::Result<InitializeResult> {
|
||||
let mut init = InitializeResult::default();
|
||||
init.capabilities.text_document_sync =
|
||||
Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL));
|
||||
init.capabilities.completion_provider = Some(CompletionOptions::default());
|
||||
init.capabilities.hover_provider = Some(HoverProviderCapability::Simple(true));
|
||||
Ok(init)
|
||||
}
|
||||
|
||||
async fn initialized(&self, _: InitializedParams) {}
|
||||
|
||||
async fn shutdown(&self) -> jsonrpc::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn did_open(&self, params: DidOpenTextDocumentParams) {
|
||||
let fp = params.text_document.uri.to_file_path();
|
||||
self.documents.lock().unwrap().insert(
|
||||
params.text_document.uri,
|
||||
TextDocument {
|
||||
source: params.text_document.text,
|
||||
file_path: fp.ok(),
|
||||
srca: None,
|
||||
parsed: None,
|
||||
compiled: None,
|
||||
checked: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async fn did_change(&self, params: DidChangeTextDocumentParams) {
|
||||
let mut last_text = None;
|
||||
for change in params.content_changes {
|
||||
if change.range.is_none() {
|
||||
last_text = Some(change.text);
|
||||
}
|
||||
}
|
||||
if let Some(new_text) = last_text {
|
||||
if let Some(d) = self
|
||||
.documents
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_mut(¶ms.text_document.uri)
|
||||
{
|
||||
d.source = new_text;
|
||||
d.changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn did_close(&self, params: DidCloseTextDocumentParams) {
|
||||
self.documents
|
||||
.lock()
|
||||
.unwrap()
|
||||
.remove(¶ms.text_document.uri);
|
||||
}
|
||||
|
||||
async fn completion(
|
||||
&self,
|
||||
params: CompletionParams,
|
||||
) -> jsonrpc::Result<Option<CompletionResponse>> {
|
||||
if let Some(doc) = self
|
||||
.documents
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_mut(¶ms.text_document_position.text_document.uri)
|
||||
{
|
||||
let byte_pos = doc_byte_pos(&doc.source, params.text_document_position.position);
|
||||
let byte_pos_in_src = doc.srca().pos_from_og(byte_pos, false);
|
||||
if let Ok(parsed) = doc.parsed() {
|
||||
let (mut i1, _, mut i3) = mers_config().infos();
|
||||
i1.global.enable_hooks = true;
|
||||
let mut save_info_at = i1.global.save_info_at.lock().unwrap();
|
||||
let my_saved_info_index = save_info_at.len();
|
||||
save_info_at.push((vec![], byte_pos_in_src, 0));
|
||||
drop(save_info_at);
|
||||
let variable_types = if let Ok(compiled) =
|
||||
mers_lib::prelude_compile::compile_mut(&**parsed, &mut i1)
|
||||
{
|
||||
i3.global.enable_hooks = true;
|
||||
let mut save_info_at = i3.global.save_info_at.lock().unwrap();
|
||||
let my_saved_info_index = save_info_at.len();
|
||||
save_info_at.push((vec![], byte_pos_in_src, 0));
|
||||
drop(save_info_at);
|
||||
_ = mers_lib::prelude_compile::check_mut(&*compiled, &mut i3);
|
||||
let save_info_at = i3.global.save_info_at.lock().unwrap();
|
||||
let my_saved_info = &save_info_at[my_saved_info_index].0;
|
||||
let mut variable_types: HashMap<(usize, usize), Type> = HashMap::new();
|
||||
for (_range, info, _err) in my_saved_info {
|
||||
for (is, scope) in info.scopes.iter().enumerate() {
|
||||
for (iv, var) in scope.vars.iter().enumerate() {
|
||||
let key = (is, iv);
|
||||
if let Some(t) = variable_types.get_mut(&key) {
|
||||
t.add_all(var);
|
||||
} else {
|
||||
variable_types.insert(key, var.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(save_info_at);
|
||||
Some(variable_types)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let save_info_at = i1.global.save_info_at.lock().unwrap();
|
||||
let result = &save_info_at[my_saved_info_index].0;
|
||||
let mut variables = HashMap::new();
|
||||
for (_range, a, _err) in result {
|
||||
for (depth, scope) in a.scopes.iter().enumerate() {
|
||||
for (var, v) in scope.vars.iter() {
|
||||
variables.insert(var, (depth, *v));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut variables = variables.into_iter().collect::<Vec<_>>();
|
||||
// sort by depth (rev), then name -> local variables first, globals later, within the same scope A->Z
|
||||
variables.sort_unstable_by(|a, b| b.1 .0.cmp(&a.1 .0).then_with(|| a.0.cmp(&b.0)));
|
||||
let items = variables
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
CompletionItem::new_simple(
|
||||
v.0.clone(),
|
||||
if let Some(typ) =
|
||||
variable_types.as_ref().and_then(|types| types.get(&v.1 .1))
|
||||
{
|
||||
format!("var: `{typ}`")
|
||||
} else {
|
||||
format!("variable")
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
drop(save_info_at);
|
||||
return Ok(Some(CompletionResponse::Array(items)));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn hover(&self, params: HoverParams) -> crate::jsonrpc::Result<Option<Hover>> {
|
||||
let this_doc = params.text_document_position_params.text_document.uri;
|
||||
Ok(
|
||||
if let Some(doc) = self.documents.lock().unwrap().get_mut(&this_doc) {
|
||||
let pos = params.text_document_position_params.position;
|
||||
let byte_pos = doc_byte_pos(&doc.source, pos);
|
||||
Some(match doc.compiled() {
|
||||
Ok(compiled) => {
|
||||
let file = Arc::clone(compiled.source_range().in_file());
|
||||
let pos = file.pos_from_og(byte_pos, false);
|
||||
let (_, _, mut i3) = mers_config().infos();
|
||||
i3.global.enable_hooks = true;
|
||||
let save_info_at = Arc::new(Mutex::new(vec![(vec![], pos, 0)]));
|
||||
i3.global.save_info_at = Arc::clone(&save_info_at);
|
||||
_ = mers_lib::prelude_compile::check_mut(&**compiled, &mut i3);
|
||||
let hook_res = &save_info_at.lock().unwrap()[0];
|
||||
Hover {
|
||||
contents: HoverContents::Markup(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
value: format!(
|
||||
"Local Types:{}",
|
||||
hook_res
|
||||
.0
|
||||
.iter()
|
||||
.map(|(_, _, t)| match t {
|
||||
Ok(t) => format!("\n- {t}"),
|
||||
Err(e) => format!("\n- {e}"),
|
||||
})
|
||||
.collect::<String>()
|
||||
),
|
||||
}),
|
||||
range: None,
|
||||
}
|
||||
}
|
||||
Err(e) => Hover {
|
||||
contents: HoverContents::Markup(MarkupContent {
|
||||
kind: MarkupKind::PlainText,
|
||||
value: format!("{e}"),
|
||||
}),
|
||||
range: None,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let stdin = tokio::io::stdin();
|
||||
let stdout = tokio::io::stdout();
|
||||
|
||||
let (service, messages) = LspService::new(|client| Backend {
|
||||
client,
|
||||
documents: Default::default(),
|
||||
});
|
||||
Server::new(stdin, stdout)
|
||||
.interleave(messages)
|
||||
.serve(service)
|
||||
.await;
|
||||
}
|
||||
|
||||
fn doc_byte_pos(src: &str, pos: Position) -> usize {
|
||||
let line_start = src
|
||||
.line_spans()
|
||||
.take(pos.line as _)
|
||||
.map(|l| l.as_str_with_ending().len())
|
||||
.sum::<usize>();
|
||||
let line_pos = src
|
||||
.chars()
|
||||
.take(pos.character as _)
|
||||
.map(|c| c.len_utf8())
|
||||
.sum::<usize>();
|
||||
line_start + line_pos
|
||||
}
|
Loading…
Reference in New Issue
Block a user