diff --git a/src/code_action_providers/lua_jit.rs b/src/code_action_providers/lua/bindings.rs similarity index 93% rename from src/code_action_providers/lua_jit.rs rename to src/code_action_providers/lua/bindings.rs index 56a9d40..f298280 100644 --- a/src/code_action_providers/lua_jit.rs +++ b/src/code_action_providers/lua/bindings.rs @@ -12,9 +12,7 @@ use tree_sitter::{ Node, }; -use crate::code_action_providers::parsed_document::ParsedDocument; - -use super::helper; +use crate::code_action_providers::{helper::ts_node_to_lsp_range, parsed_document::ParsedDocument}; #[derive(Copy, Clone, Debug)] struct LuaNode(TSNode); @@ -91,7 +89,7 @@ impl UserData for LuaNode { methods.add_method("range", |_, node: &LuaNode, ()| { let node: TSNode = node.0; let node: Node = unsafe { Node::from_raw(node) }; - let r: LuaRange = helper::ts_node_to_lsp_range(&node).into(); + let r: LuaRange = ts_node_to_lsp_range(&node).into(); Result::Ok(r) }); methods.add_method("next_sibling", |_, node: &LuaNode, ()| { @@ -102,22 +100,25 @@ impl UserData for LuaNode { false => Result::Ok(Some(LuaNode(pnode))), } }); - methods.add_method("child_by_field_name", |_, node: &LuaNode, field_name: String| { - let node: TSNode = node.0; - let c_field_name = std::ffi::CString::new(field_name).unwrap(); - let child_node = unsafe { - ts_node_child_by_field_name( - node, - c_field_name.as_ptr(), - c_field_name.as_bytes().len() as u32, - ) - }; + methods.add_method( + "child_by_field_name", + |_, node: &LuaNode, field_name: String| { + let node: TSNode = node.0; + let c_field_name = std::ffi::CString::new(field_name).unwrap(); + let child_node = unsafe { + ts_node_child_by_field_name( + node, + c_field_name.as_ptr(), + c_field_name.as_bytes().len() as u32, + ) + }; - match unsafe { ts_node_is_null(child_node) } { - true => Result::Ok(None), - false => Result::Ok(Some(LuaNode(child_node))), - } - }); + match unsafe { ts_node_is_null(child_node) } { + true => Result::Ok(None), + false => Result::Ok(Some(LuaNode(child_node))), + } + }, + ); } } diff --git a/src/code_action_providers/lua/mod.rs b/src/code_action_providers/lua/mod.rs new file mode 100644 index 0000000..1a2fea8 --- /dev/null +++ b/src/code_action_providers/lua/mod.rs @@ -0,0 +1,2 @@ +pub mod bindings; +pub mod provider; diff --git a/src/code_action_providers/lua_provider.rs b/src/code_action_providers/lua/provider.rs similarity index 96% rename from src/code_action_providers/lua_provider.rs rename to src/code_action_providers/lua/provider.rs index 40d07f2..6bf282a 100644 --- a/src/code_action_providers/lua_provider.rs +++ b/src/code_action_providers/lua/provider.rs @@ -10,13 +10,14 @@ use tower_lsp::lsp_types::{CodeAction, CodeActionKind, TextEdit, WorkspaceEdit}; use crate::code_action_providers::parsed_document::ParsedDocument; use crate::code_action_providers::traits::ActionContext; use crate::code_action_providers::traits::ActionProvider; -use crate::prompt_handlers::traits::LLM; -use crate::ResolveAction; +use crate::llm_handlers::traits::Llm; +use crate::server::ResolveAction; + +use super::bindings::LuaInterface; -use super::lua_jit::LuaInterface; pub struct LuaProvider { - prompt_handler: Arc, + prompt_handler: Arc, lua_source: String, id: String, } @@ -32,7 +33,7 @@ pub enum LuaProviderError { impl LuaProvider { pub fn try_new( file_name: &str, - prompt_handler: Arc, + prompt_handler: Arc, ) -> anyhow::Result { Ok(Self { prompt_handler, diff --git a/src/code_action_providers/mod.rs b/src/code_action_providers/mod.rs index 9775355..2d6eabe 100644 --- a/src/code_action_providers/mod.rs +++ b/src/code_action_providers/mod.rs @@ -1,7 +1,114 @@ -pub mod config; +use std::{collections::HashMap, path::PathBuf, sync::Arc}; + +use lua::provider::LuaProvider; +use tower_lsp::lsp_types::CodeAction; +use traits::ActionProvider; +use yaml::{config, provider::YamlProvider}; + +use crate::{ + llm_handlers::traits::Llm, + nonsense::{self, IndexedText, TextAdapter}, + read_language_config_files, +}; + pub mod helper; -pub mod lua_jit; -pub mod lua_provider; +pub mod lua; pub mod parsed_document; pub mod traits; -pub mod yaml_provider; +pub mod yaml; + +const SUPPORTED_LANGUAGES: [&str; 7] = [ + "gitcommit", + "go", + "markdown", + "python", + "rust", + "text", + "__all__", +]; + +pub fn load_providers( + code_actions_config_dir: PathBuf, + prompt_handler: Arc, +) -> HashMap>> { + let mut providers: HashMap>> = Default::default(); + + //log::info!("Processing config-dir: {:?}", config_dir); + for language in SUPPORTED_LANGUAGES { + let config_dir = code_actions_config_dir.join(language); + for config_path in read_language_config_files(&config_dir, "yaml") { + //log::info!("Processing language config: {:?}", config_path); + match config::CodeActionConfig::from_yaml(&config_path) { + Ok(language_config) => { + for (k, config) in language_config.code_actions.into_iter().enumerate() { + //log::info!("Register action {} for {:?}", config.name, config_path); + providers + .entry(language.to_owned()) + .or_default() + .push(Box::new(YamlProvider::from_config( + config, + &format!("{}.{k}", config_path.to_string_lossy()), + prompt_handler.clone(), + ))); + } + } + Err(_e) => { + //log::warn!("Cannot read {:?} because of {}", &config_path, e); + } + }; + } + for config_path in read_language_config_files(&config_dir, "lua") { + //log::info!("Processing language config: {:?}", config_path); + providers + .entry(language.to_owned()) + .or_default() + .push(Box::new( + LuaProvider::try_new(&config_path.to_string_lossy(), prompt_handler.clone()) + .unwrap(), + )); + } + } + providers +} + +pub fn find_resolver<'a>( + providers: &'a HashMap>>, + code_action_id: &str, + lang: &str, +) -> Option<&'a Box> { + for target_lang in [lang, "__all__"] { + if let Some(language_specific_providers) = providers.get(target_lang) { + for provider in language_specific_providers.iter() { + if provider.can_handle(code_action_id) { + return Some(provider); + } + } + } + } + None +} + +pub fn map_to_lsp(r: &mut CodeAction, index: &IndexedText) { + // if let Ok(r) = r.as_mut() { + if let Some(e) = r.edit.as_mut() { + if let Some(c) = e.changes.as_mut() { + for value in c.values_mut() { + for text_edit in value.iter_mut() { + let fake = std::ops::Range:: { + start: nonsense::Pos { + line: text_edit.range.start.line, + col: text_edit.range.start.character, + }, + end: nonsense::Pos { + line: text_edit.range.end.line, + col: text_edit.range.end.character, + }, + }; + let rs = index.range_to_lsp_range(&fake).unwrap(); + text_edit.range = rs; + } + } + } + } + // } +} diff --git a/src/code_action_providers/config.rs b/src/code_action_providers/yaml/config.rs similarity index 100% rename from src/code_action_providers/config.rs rename to src/code_action_providers/yaml/config.rs diff --git a/src/code_action_providers/yaml/mod.rs b/src/code_action_providers/yaml/mod.rs new file mode 100644 index 0000000..84c6e89 --- /dev/null +++ b/src/code_action_providers/yaml/mod.rs @@ -0,0 +1,2 @@ +pub mod config; +pub mod provider; diff --git a/src/code_action_providers/yaml_provider.rs b/src/code_action_providers/yaml/provider.rs similarity index 97% rename from src/code_action_providers/yaml_provider.rs rename to src/code_action_providers/yaml/provider.rs index 8e185e6..845e04d 100644 --- a/src/code_action_providers/yaml_provider.rs +++ b/src/code_action_providers/yaml/provider.rs @@ -10,8 +10,8 @@ use tower_lsp::lsp_types::{CodeAction, CodeActionKind, TextEdit, WorkspaceEdit}; use crate::code_action_providers::traits::ActionContext; use crate::code_action_providers::traits::ActionProvider; use crate::code_action_providers::{helper, parsed_document::ParsedDocument}; -use crate::prompt_handlers::traits::LLM; -use crate::ResolveAction; +use crate::llm_handlers::traits::Llm; +use crate::server::ResolveAction; use super::config; @@ -24,14 +24,14 @@ fn build_prompt(template: &str, hints: &HashMap) -> String { } pub struct YamlProvider { - prompt_handler: Arc, + prompt_handler: Arc, config: config::CodeAction, id: String, } impl YamlProvider { - pub fn from_config(config: config::CodeAction, id: &str, prompt_handler: Arc) -> Self { + pub fn from_config(config: config::CodeAction, id: &str, prompt_handler: Arc) -> Self { Self { prompt_handler, config, diff --git a/src/prompt_handlers/bedrock.rs b/src/llm_handlers/bedrock.rs similarity index 96% rename from src/prompt_handlers/bedrock.rs rename to src/llm_handlers/bedrock.rs index 1c5a97b..c047032 100644 --- a/src/prompt_handlers/bedrock.rs +++ b/src/llm_handlers/bedrock.rs @@ -8,7 +8,7 @@ use aws_sdk_bedrockruntime::{ use crate::configuration::BedrockConfig; -use super::traits::PromptHandler; +use super::traits::LlmHandler; #[derive(Debug)] @@ -34,7 +34,7 @@ impl BedrockConverse { } } -impl PromptHandler for BedrockConverse { +impl LlmHandler for BedrockConverse { async fn answer(&self, prompt: &str) -> anyhow::Result { let response = self .client diff --git a/src/prompt_handlers/mock.rs b/src/llm_handlers/mock.rs similarity index 74% rename from src/prompt_handlers/mock.rs rename to src/llm_handlers/mock.rs index 6e42a32..6b9ebc1 100644 --- a/src/prompt_handlers/mock.rs +++ b/src/llm_handlers/mock.rs @@ -1,8 +1,8 @@ -use super::traits::PromptHandler; +use super::traits::LlmHandler; #[derive(Debug)] pub struct MockLLM { - answer: String, + pub answer: String, } impl MockLLM { @@ -11,7 +11,7 @@ impl MockLLM { } } -impl PromptHandler for MockLLM { +impl LlmHandler for MockLLM { async fn answer(&self, _: &str) -> anyhow::Result { Ok(self.answer.clone()) } diff --git a/src/prompt_handlers/mod.rs b/src/llm_handlers/mod.rs similarity index 100% rename from src/prompt_handlers/mod.rs rename to src/llm_handlers/mod.rs diff --git a/src/prompt_handlers/traits.rs b/src/llm_handlers/traits.rs similarity index 69% rename from src/prompt_handlers/traits.rs rename to src/llm_handlers/traits.rs index 4fb8e5f..5fa16a1 100644 --- a/src/prompt_handlers/traits.rs +++ b/src/llm_handlers/traits.rs @@ -1,23 +1,23 @@ use super::bedrock::BedrockConverse; use super::mock::MockLLM; -pub trait PromptHandler { +pub trait LlmHandler { fn answer( &self, prompt: &str, ) -> impl std::future::Future> + Send; } -pub enum LLM { +pub enum Llm { Bedrock(BedrockConverse), Mock(MockLLM), } -impl LLM { +impl Llm { pub async fn answer<'a>(&'a self, prompt: &'a str) -> anyhow::Result { match self { - LLM::Bedrock(b) => b.answer(prompt).await, - LLM::Mock(b) => b.answer(prompt).await, + Llm::Bedrock(b) => b.answer(prompt).await, + Llm::Mock(b) => b.answer(prompt).await, } } } diff --git a/src/main.rs b/src/main.rs index 34c298d..6bbefaa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,304 +1,22 @@ use clap::{ArgGroup, Parser}; -use code_action_providers::config; -use code_action_providers::lua_provider::LuaProvider; +use code_action_providers::load_providers; use code_action_providers::parsed_document::ParsedDocument; -use code_action_providers::traits::ActionProvider; -use code_action_providers::yaml_provider::YamlProvider; -use nonsense::TextAdapter; -use prompt_handlers::bedrock::BedrockConverse; -use prompt_handlers::traits::LLM; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use llm_handlers::bedrock::BedrockConverse; +use llm_handlers::mock::MockLLM; +use llm_handlers::traits::Llm; use std::env; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; pub mod code_action_providers; pub mod configuration; +pub mod llm_handlers; pub mod nonsense; -pub mod prompt_handlers; +pub mod server; use tokio::net::{TcpListener, TcpStream}; -use tower_lsp::jsonrpc::Result; use tower_lsp::lsp_types::*; -use tower_lsp::{Client, LanguageServer, LspService, Server}; - -const SUPPORTED_LANGUAGES: [&str; 7] = [ - "rust", - "python", - "text", - "go", - "__all__", - "markdown", - "gitcommit", -]; - -#[derive(Debug, Serialize, Deserialize)] -pub struct ResolveActionKind { - pub id: String, -} -#[derive(Debug, Serialize, Deserialize)] -pub struct ResolveAction { - /// The data to be resolved. - pub data: T, - /// The unique identifier for this resolve action. - pub id: String, -} - -struct Backend { - /// The client used for communicating with the backend. - pub client: Client, - /// The current text being processed. - pub current_text: Arc>, - /// The current language being processed. - pub current_language: Arc>, - /// A map of action providers, keyed by the name of the provider. - pub providers: HashMap>>, - /// The parsed document being processed. - pub parsed_doc: ParsedDocument, - - indexed_text: Arc>>, - // translation: Translation, -} - -impl std::fmt::Debug for Backend { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Backend") - .field("client", &self.client) - .field("current_text", &self.current_text) - .finish() - } -} - -#[tower_lsp::async_trait] -impl LanguageServer for Backend { - async fn initialize(&self, _: InitializeParams) -> Result { - Ok(InitializeResult { - server_info: Some(ServerInfo { - name: "PolyglotLS".to_string(), - version: None, - }), - capabilities: ServerCapabilities { - text_document_sync: Some(TextDocumentSyncCapability::Kind( - TextDocumentSyncKind::FULL, - )), - code_action_provider: Some(CodeActionProviderCapability::Options( - CodeActionOptions { - code_action_kinds: Some(vec![ - CodeActionKind::QUICKFIX, - CodeActionKind::REFACTOR_INLINE, - CodeActionKind::REFACTOR_REWRITE, - CodeActionKind::REFACTOR, - ]), - resolve_provider: Some(true), - work_done_progress_options: WorkDoneProgressOptions { - work_done_progress: None, - }, - }, - )), - ..ServerCapabilities::default() - }, - }) - } - - /// Initialize the language server. - /// - /// # Arguments - /// - /// * `_: InitializedParams` - The initialization parameters. - /// - /// # Returns - /// - /// None. - async fn initialized(&self, _: InitializedParams) { - self.client - .log_message(MessageType::INFO, "initialized!") - .await; - } - - async fn shutdown(&self) -> Result<()> { - Ok(()) - } - - /// Resolve a code action by delegating to language-specific providers. - /// - /// # Arguments - /// - /// * `action` - The code action to resolve. - /// - /// # Returns - /// - /// The resolved code action. - async fn code_action_resolve(&self, action: CodeAction) -> Result { - //log::info!("code_action_resolve {:?}", action); - let json_args = action.data.clone().unwrap(); - let args = serde_json::from_value::(json_args.clone()).unwrap(); - - let source = self.current_text.read().unwrap().clone(); - let lang = self.current_language.read().unwrap().clone(); - let parsed_doc = ParsedDocument::new(&source, &self.parsed_doc.uri, &lang); - let index = self.indexed_text.read().unwrap().clone(); - - for target_lang in [lang.as_str(), "__all__"] { - if let Some(language_specific_providers) = self.providers.get(target_lang) { - for provider in language_specific_providers.iter() { - if provider.can_handle(args.id.as_str()) { - let mut r = provider.on_resolve(&parsed_doc, action.clone()).await; - if let Ok(r) = r.as_mut() { - if let Some(e) = r.edit.as_mut() { - if let Some(c) = e.changes.as_mut() { - for value in c.values_mut() { - for text_edit in value.iter_mut() { - let fake = std::ops::Range:: { - start: nonsense::Pos { - line: text_edit.range.start.line, - col: text_edit.range.start.character, - }, - end: nonsense::Pos { - line: text_edit.range.end.line, - col: text_edit.range.end.character, - }, - }; - let rs = index.range_to_lsp_range(&fake).unwrap(); - text_edit.range = rs; - } - } - } - } - } - return r; - } - } - } - } - - todo!(); - } - - /// Provide code actions for the current document. - /// - /// # Arguments - /// - /// * `params` - The code action parameters. - /// - /// # Returns - /// - /// A `Result>` containing the code actions or an error. - async fn code_action(&self, params: CodeActionParams) -> Result> { - self.client - .log_message(MessageType::INFO, "code action") - .await; - - let uri = ¶ms.text_document.uri; - let source = self.current_text.read().unwrap().clone(); - let lang = self.current_language.read().unwrap().clone(); - let index = self.indexed_text.read().unwrap().clone(); - let doc = ParsedDocument::new(&source, uri, &lang); - - // LSP is UTF16, our abckend is UTF8 - let lsp_range = params.range; - let rs = index.lsp_range_to_range(&lsp_range).unwrap(); - let fake_lsp_range = Range { - start: Position { - line: rs.start.line, - character: rs.start.col, - }, - end: Position { - line: rs.end.line, - character: rs.end.col, - }, - }; - - let mut actions = vec![]; - if let Some(language_specific_providers) = self.providers.get(&lang) { - for provider in language_specific_providers.iter() { - if let Some(action) = provider.create_code_action(&doc, &fake_lsp_range) { - actions.push(CodeActionOrCommand::CodeAction(action)); - } - } - } - if let Some(language_specific_providers) = self.providers.get("__all__") { - for provider in language_specific_providers.iter() { - if let Some(action) = provider.create_code_action(&doc, &fake_lsp_range) { - actions.push(CodeActionOrCommand::CodeAction(action)); - } - } - } - - Ok(Some(actions)) - } - - 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; - } - - /// Handle a text document open notification. - /// - /// # Arguments - /// - /// * `params` - The parameters for the text document open notification. - /// - /// # Returns - /// - /// This function does not return a value. - async fn did_open(&self, params: DidOpenTextDocumentParams) { - //log::info!("did_open"); - self.client - .log_message(MessageType::INFO, "file opened!") - .await; - let mut src = self.current_text.write().unwrap(); - *src = params.text_document.text.clone(); - let mut src = self.current_language.write().unwrap(); - *src = params.text_document.language_id.clone(); - - let mut src = self.indexed_text.write().unwrap(); - *src = nonsense::IndexedText::new(params.text_document.text.to_owned()); - //log::info!("set language to {}", ¶ms.text_document.language_id); - } - - async fn did_change(&self, params: DidChangeTextDocumentParams) { - self.client - .log_message(MessageType::INFO, "file changed!") - .await; - let mut src = self.current_text.write().unwrap(); - *src = params.content_changes[0].text.clone(); - let mut src = self.indexed_text.write().unwrap(); - *src = nonsense::IndexedText::new(params.content_changes[0].text.to_owned()); - } - // - async fn did_save(&self, params: DidSaveTextDocumentParams) { - self.client - .log_message(MessageType::INFO, "file saved!") - .await; - if let Some(new_text) = params.text { - let mut src = self.current_text.write().unwrap(); - *src = new_text.clone(); - let mut src = self.indexed_text.write().unwrap(); - *src = nonsense::IndexedText::new(new_text); - } - } - // - // async fn did_close(&self, _: DidCloseTextDocumentParams) { - // self.client - // .log_message(MessageType::INFO, "file closed!") - // .await; - // } - // - // async fn completion(&self, _: CompletionParams) -> Result> { - // Ok(Some(CompletionResponse::Array(vec![ - // CompletionItem::new_simple("Hello".to_string(), "Some detail".to_string()), - // CompletionItem::new_simple("Bye".to_string(), "More detail".to_string()), - // ]))) - // } -} +use tower_lsp::{LspService, Server}; /// Reads all language configuration files in the specified directory that /// match the given filter. @@ -350,6 +68,11 @@ struct Args { #[arg(long)] stdio: bool, // Just a flag, no value needed + /// Will consume text from stdin, code_action id and cursor position from the arg + /// and return the alternated text to stdout. + #[arg(long)] + use_mock: bool, + /// Path to polyglot configuration YAML file #[arg(long)] polyglot_config_path: Option, @@ -375,50 +98,23 @@ async fn main() { tracing_subscriber::fmt().init(); //log::info!("Start"); - let prompt_handler = Arc::new(LLM::Bedrock( - BedrockConverse::new(&polyglot_config.model.bedrock) - .await - .unwrap(), - )); - let mut providers: HashMap>> = Default::default(); + let prompt_handler; - //log::info!("Processing config-dir: {:?}", config_dir); - for language in SUPPORTED_LANGUAGES { - let config_dir = config_base_dir.join("code_actions").join(language); - for config_path in read_language_config_files(&config_dir, "yaml") { - //log::info!("Processing language config: {:?}", config_path); - match config::CodeActionConfig::from_yaml(&config_path) { - Ok(language_config) => { - for (k, config) in language_config.code_actions.into_iter().enumerate() { - //log::info!("Register action {} for {:?}", config.name, config_path); - providers - .entry(language.to_owned()) - .or_default() - .push(Box::new(YamlProvider::from_config( - config, - &format!("{}.{k}", config_path.to_string_lossy()), - prompt_handler.clone(), - ))); - } - } - Err(_e) => { - //log::warn!("Cannot read {:?} because of {}", &config_path, e); - } - }; - } - for config_path in read_language_config_files(&config_dir, "lua") { - //log::info!("Processing language config: {:?}", config_path); - providers - .entry(language.to_owned()) - .or_default() - .push(Box::new( - LuaProvider::try_new(&config_path.to_string_lossy(), prompt_handler.clone()) - .unwrap(), - )); - } + if args.use_mock { + prompt_handler = Arc::new(Llm::Mock(MockLLM { + answer: "MOCK".to_string(), + })); + } else { + prompt_handler = Arc::new(Llm::Bedrock( + BedrockConverse::new(&polyglot_config.model.bedrock) + .await + .unwrap(), + )); } - let (service, socket) = LspService::new(|client| Backend { + let providers = load_providers(config_base_dir.join("code_actions"), prompt_handler); + + let (service, socket) = LspService::new(|client| server::Backend { client, current_text: Arc::new("".to_string().into()), current_language: Arc::new("".to_string().into()), diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..89a978b --- /dev/null +++ b/src/server.rs @@ -0,0 +1,262 @@ +use crate::code_action_providers::{find_resolver, map_to_lsp}; +use crate::nonsense::{self, TextAdapter}; + +use super::code_action_providers::parsed_document::ParsedDocument; +use super::code_action_providers::traits::ActionProvider; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; + +use tower_lsp::jsonrpc::{self, Result}; +use tower_lsp::lsp_types::*; +use tower_lsp::{Client, LanguageServer}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct ResolveActionKind { + pub id: String, +} +#[derive(Debug, Serialize, Deserialize)] +pub struct ResolveAction { + /// The data to be resolved. + pub data: T, + /// The unique identifier for this resolve action. + pub id: String, +} + +pub(crate) struct Backend { + /// The client used for communicating with the backend. + pub client: Client, + /// The current text being processed. + pub current_text: Arc>, + /// The current language being processed. + pub current_language: Arc>, + /// A map of action providers, keyed by the name of the provider. + pub providers: HashMap>>, + /// The parsed document being processed. + pub parsed_doc: ParsedDocument, + + pub indexed_text: Arc>>, + // translation: Translation, +} + +impl std::fmt::Debug for Backend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Backend") + .field("client", &self.client) + .field("current_text", &self.current_text) + .finish() + } +} + +#[tower_lsp::async_trait] +impl LanguageServer for Backend { + async fn initialize(&self, _: InitializeParams) -> Result { + Ok(InitializeResult { + server_info: Some(ServerInfo { + name: "PolyglotLS".to_string(), + version: None, + }), + capabilities: ServerCapabilities { + text_document_sync: Some(TextDocumentSyncCapability::Kind( + TextDocumentSyncKind::FULL, + )), + code_action_provider: Some(CodeActionProviderCapability::Options( + CodeActionOptions { + code_action_kinds: Some(vec![ + CodeActionKind::QUICKFIX, + CodeActionKind::REFACTOR_INLINE, + CodeActionKind::REFACTOR_REWRITE, + CodeActionKind::REFACTOR, + ]), + resolve_provider: Some(true), + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + }, + )), + ..ServerCapabilities::default() + }, + }) + } + + /// Initialize the language server. + /// + /// # Arguments + /// + /// * `_: InitializedParams` - The initialization parameters. + /// + /// # Returns + /// + /// None. + async fn initialized(&self, _: InitializedParams) { + self.client + .log_message(MessageType::INFO, "initialized!") + .await; + } + + async fn shutdown(&self) -> Result<()> { + Ok(()) + } + + /// Resolve a code action by delegating to language-specific providers. + /// + /// # Arguments + /// + /// * `action` - The code action to resolve. + /// + /// # Returns + /// + /// The resolved code action. + async fn code_action_resolve(&self, action: CodeAction) -> Result { + //log::info!("code_action_resolve {:?}", action); + let json_args = action.data.clone().unwrap(); + let args = serde_json::from_value::(json_args.clone()).unwrap(); + + let source = self.current_text.read().unwrap().clone(); + let lang = self.current_language.read().unwrap().clone(); + let parsed_doc = ParsedDocument::new(&source, &self.parsed_doc.uri, &lang); + let index = self.indexed_text.read().unwrap().clone(); + + let provider = find_resolver(&self.providers, &args.id, &lang); + if provider.is_none() { + return Err(jsonrpc::Error::new(jsonrpc::ErrorCode::ServerError(1))); + } + + let code_action = provider + .unwrap() + .on_resolve(&parsed_doc, action.clone()) + .await; + + match code_action { + Ok(mut c) => { + map_to_lsp(&mut c, &index); + Ok(c) + } + Err(_) => Err(jsonrpc::Error::new(jsonrpc::ErrorCode::ServerError(1))), + } + } + + /// Provide code actions for the current document. + /// + /// # Arguments + /// + /// * `params` - The code action parameters. + /// + /// # Returns + /// + /// A `Result>` containing the code actions or an error. + async fn code_action(&self, params: CodeActionParams) -> Result> { + self.client + .log_message(MessageType::INFO, "code action") + .await; + + let uri = ¶ms.text_document.uri; + let source = self.current_text.read().unwrap().clone(); + let lang = self.current_language.read().unwrap().clone(); + let index = self.indexed_text.read().unwrap().clone(); + let doc = ParsedDocument::new(&source, uri, &lang); + + // LSP is UTF16, our abckend is UTF8 + let lsp_range = params.range; + let rs = index.lsp_range_to_range(&lsp_range).unwrap(); + let fake_lsp_range = Range { + start: Position { + line: rs.start.line, + character: rs.start.col, + }, + end: Position { + line: rs.end.line, + character: rs.end.col, + }, + }; + + let mut actions = vec![]; + if let Some(language_specific_providers) = self.providers.get(&lang) { + for provider in language_specific_providers.iter() { + if let Some(action) = provider.create_code_action(&doc, &fake_lsp_range) { + actions.push(CodeActionOrCommand::CodeAction(action)); + } + } + } + if let Some(language_specific_providers) = self.providers.get("__all__") { + for provider in language_specific_providers.iter() { + if let Some(action) = provider.create_code_action(&doc, &fake_lsp_range) { + actions.push(CodeActionOrCommand::CodeAction(action)); + } + } + } + + Ok(Some(actions)) + } + + 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; + } + + /// Handle a text document open notification. + /// + /// # Arguments + /// + /// * `params` - The parameters for the text document open notification. + /// + /// # Returns + /// + /// This function does not return a value. + async fn did_open(&self, params: DidOpenTextDocumentParams) { + //log::info!("did_open"); + self.client + .log_message(MessageType::INFO, "file opened!") + .await; + let mut src = self.current_text.write().unwrap(); + *src = params.text_document.text.clone(); + let mut src = self.current_language.write().unwrap(); + *src = params.text_document.language_id.clone(); + + let mut src = self.indexed_text.write().unwrap(); + *src = nonsense::IndexedText::new(params.text_document.text.to_owned()); + //log::info!("set language to {}", ¶ms.text_document.language_id); + } + + async fn did_change(&self, params: DidChangeTextDocumentParams) { + self.client + .log_message(MessageType::INFO, "file changed!") + .await; + let mut src = self.current_text.write().unwrap(); + *src = params.content_changes[0].text.clone(); + let mut src = self.indexed_text.write().unwrap(); + *src = nonsense::IndexedText::new(params.content_changes[0].text.to_owned()); + } + // + async fn did_save(&self, params: DidSaveTextDocumentParams) { + self.client + .log_message(MessageType::INFO, "file saved!") + .await; + if let Some(new_text) = params.text { + let mut src = self.current_text.write().unwrap(); + *src = new_text.clone(); + let mut src = self.indexed_text.write().unwrap(); + *src = nonsense::IndexedText::new(new_text); + } + } + // + // async fn did_close(&self, _: DidCloseTextDocumentParams) { + // self.client + // .log_message(MessageType::INFO, "file closed!") + // .await; + // } + // + // async fn completion(&self, _: CompletionParams) -> Result> { + // Ok(Some(CompletionResponse::Array(vec![ + // CompletionItem::new_simple("Hello".to_string(), "Some detail".to_string()), + // CompletionItem::new_simple("Bye".to_string(), "More detail".to_string()), + // ]))) + // } +}