diff --git a/Cargo.toml b/Cargo.toml index c5e79d0..4425db9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,4 @@ log = "*" lazy_static = "1.3.0" lsp-types = { version = "0.61.0", features = ["proposed"] } simple-logging = "*" -url = "*" +url = {version = "2.0.0", features = ["serde"]} diff --git a/autoload/lspc.vim b/autoload/lspc.vim index deac750..f35564a 100644 --- a/autoload/lspc.vim +++ b/autoload/lspc.vim @@ -88,6 +88,13 @@ function! lspc#format_doc() call rpcnotify(s:job_id, 'format_doc', l:lang_id, l:cur_path, l:lines) endfunction +function! lspc#completion() + let l:lang_id = 'rust' + let l:cur_path = lspc#buffer#filename() + let l:position = lspc#buffer#position() + call rpcnotify(s:job_id, 'completion', l:lang_id, l:cur_path, l:position, 1) +endfunction + function! lspc#hello_from_the_other_side() call rpcnotify(s:job_id, 'hello') endfunction diff --git a/src/lspc.rs b/src/lspc.rs index a25ef4a..d5b13bb 100644 --- a/src/lspc.rs +++ b/src/lspc.rs @@ -2,7 +2,6 @@ pub mod handler; // Custom LSP types pub mod msg; pub mod types; - use std::{ collections::HashMap, io, @@ -12,11 +11,15 @@ use std::{ use crossbeam::channel::{tick, Receiver, Select}; use lsp_types::{ + notification::ShowMessage, + request::{ + Completion, Formatting, GotoDefinition, GotoDefinitionResponse, HoverRequest, Initialize, + }, + CompletionContext, CompletionItem, CompletionParams, CompletionResponse, CompletionTriggerKind, self as lsp, notification::{self as noti}, - request::{Formatting, GotoDefinition, GotoDefinitionResponse, HoverRequest, Initialize}, DocumentFormattingParams, FormattingOptions, Hover, Location, Position, ShowMessageParams, - TextDocumentIdentifier, TextEdit, + TextDocumentIdentifier, TextDocumentPositionParams, TextEdit, }; use serde::{Deserialize, Serialize}; use url::Url; @@ -67,6 +70,13 @@ pub enum Event { text_document_lines: Vec, text_document: TextDocumentIdentifier, }, + RequestCompletion { + lang_id: String, + text_document: TextDocumentIdentifier, + position: Position, + trigger_kind: CompletionTriggerKind, + trigger_character: Option, + }, DidOpen { buf_id: B, text_document: TextDocumentIdentifier, @@ -176,6 +186,11 @@ pub trait Editor: 'static { fn show_message(&mut self, show_message_params: &ShowMessageParams) -> Result<(), EditorError>; fn goto(&mut self, location: &Location) -> Result<(), EditorError>; fn apply_edits(&self, lines: &Vec, edits: &Vec) -> Result<(), EditorError>; + fn show_completions( + &self, + column: u64, + completion_items: &Vec, + ) -> Result<(), EditorError>; fn watch_file_events( &mut self, text_document: &TextDocumentIdentifier, @@ -522,6 +537,44 @@ impl Lspc { }), )?; } + Event::RequestCompletion { + lang_id, + text_document, + position, + trigger_kind, + trigger_character, + } => { + let handler = self.handler_for(&lang_id).ok_or(LspcError::NotStarted)?; + let text_document_position = TextDocumentPositionParams { + text_document, + position, + }; + let context = Some(CompletionContext { + trigger_kind, + trigger_character, + }); + let params = CompletionParams { + text_document_position, + context, + }; + handler.lsp_request::( + params, + Box::new(move |editor: &mut E, _handler, response| { + if let Some(completion_list) = response { + match completion_list { + CompletionResponse::Array(items) => { + editor.show_completions(position.character, &items)?; + } + CompletionResponse::List(list) => { + editor.show_completions(position.character, &list.items)?; + } + } + } + + Ok(()) + }), + )?; + } Event::DidOpen { buf_id, text_document, diff --git a/src/neovim.rs b/src/neovim.rs index f8efed6..74c42e9 100644 --- a/src/neovim.rs +++ b/src/neovim.rs @@ -11,7 +11,7 @@ use std::{ use crossbeam::channel::{self, Receiver, Sender}; use lsp_types::{ - self as lsp, GotoCapability, Hover, HoverCapability, HoverContents, Location, MarkedString, + self as lsp, CompletionItem, CompletionTriggerKind, GotoCapability, Hover, HoverCapability, HoverContents, Location, MarkedString, MarkupContent, MarkupKind, Position, ShowMessageParams, TextDocumentClientCapabilities, TextDocumentIdentifier, TextEdit, }; @@ -245,6 +245,28 @@ fn to_event(msg: NvimMessage) -> Result, EditorError> { text_document: format_doc_params.1, text_document_lines: format_doc_params.2, }) + } else if method == "completion" { + #[derive(Deserialize)] + struct CompletionParams( + String, + #[serde(deserialize_with = "text_document_from_path_str")] + TextDocumentIdentifier, + Position, + CompletionTriggerKind, + #[serde(default)] Option, + ); + + let completion_params: CompletionParams = + Deserialize::deserialize(Value::from(params)) + .map_err(|_e| EditorError::Parse("failed to parse completion params"))?; + + Ok(Event::RequestCompletion { + lang_id: completion_params.0, + text_document: completion_params.1, + position: completion_params.2, + trigger_kind: completion_params.3, + trigger_character: completion_params.4, + }) } else if method == "did_open" { #[derive(Deserialize)] struct DidOpenParams( @@ -635,6 +657,26 @@ impl Editor for Neovim { Ok(()) } + + fn show_completions( + &self, + column: u64, + completion_items: &Vec, + ) -> Result<(), EditorError> { + let mut vim_complete_items = vec![]; + for item in completion_items { + vim_complete_items.push(Value::Map(vec![ + ("icase".into(), 1.into()), + ("word".into(), item.label.clone().into()), + ("abbr".into(), item.label.clone().into()), + ])); + } + self.call_function( + "complete", + Value::Array(vec![column.into(), Value::Array(vim_complete_items)]), + )?; + Ok(()) + } } impl Message for NvimMessage {