2020-10-21 16:42:45 +09:00
use crate ::{
transport ::{ Payload , Transport } ,
2020-10-23 18:48:03 +09:00
Call , Error ,
2020-10-21 16:42:45 +09:00
} ;
type Result < T > = core ::result ::Result < T , Error > ;
2020-12-25 17:20:09 +09:00
use helix_core ::{ ChangeSet , Rope , Transaction } ;
2020-10-21 16:42:45 +09:00
// use std::collections::HashMap;
2020-10-29 14:06:33 +09:00
use std ::sync ::atomic ::{ AtomicU64 , Ordering } ;
2020-10-21 16:42:45 +09:00
use jsonrpc_core as jsonrpc ;
use lsp_types as lsp ;
use serde_json ::Value ;
use smol ::{
channel ::{ Receiver , Sender } ,
io ::{ BufReader , BufWriter } ,
// prelude::*,
process ::{ Child , ChildStderr , Command , Stdio } ,
Executor ,
} ;
pub struct Client {
_process : Child ,
stderr : BufReader < ChildStderr > ,
outgoing : Sender < Payload > ,
2020-12-23 15:50:16 +09:00
// pub incoming: Receiver<Call>,
2020-10-29 14:06:33 +09:00
pub request_counter : AtomicU64 ,
2020-10-21 16:42:45 +09:00
capabilities : Option < lsp ::ServerCapabilities > ,
// TODO: handle PublishDiagnostics Version
// diagnostics: HashMap<lsp::Url, Vec<lsp::Diagnostic>>,
}
impl Client {
2020-12-23 15:50:16 +09:00
pub fn start ( ex : & Executor , cmd : & str , args : & [ String ] ) -> ( Self , Receiver < Call > ) {
2020-10-21 16:42:45 +09:00
let mut process = Command ::new ( cmd )
. args ( args )
. stdin ( Stdio ::piped ( ) )
. stdout ( Stdio ::piped ( ) )
. stderr ( Stdio ::piped ( ) )
. spawn ( )
. expect ( " Failed to start language server " ) ;
// smol makes sure the process is reaped on drop, but using kill_on_drop(true) maybe?
// TODO: do we need bufreader/writer here? or do we use async wrappers on unblock?
let writer = BufWriter ::new ( process . stdin . take ( ) . expect ( " Failed to open stdin " ) ) ;
let reader = BufReader ::new ( process . stdout . take ( ) . expect ( " Failed to open stdout " ) ) ;
let stderr = BufReader ::new ( process . stderr . take ( ) . expect ( " Failed to open stderr " ) ) ;
let ( incoming , outgoing ) = Transport ::start ( ex , reader , writer ) ;
2020-12-23 15:50:16 +09:00
let client = Client {
2020-10-21 16:42:45 +09:00
_process : process ,
stderr ,
outgoing ,
2020-12-23 15:50:16 +09:00
// incoming,
2020-10-29 14:06:33 +09:00
request_counter : AtomicU64 ::new ( 0 ) ,
2020-10-21 16:42:45 +09:00
capabilities : None ,
// diagnostics: HashMap::new(),
2020-12-23 15:50:16 +09:00
} ;
// TODO: async client.initialize()
// maybe use an arc<atomic> flag
( client , incoming )
2020-10-21 16:42:45 +09:00
}
2020-10-29 14:06:33 +09:00
fn next_request_id ( & self ) -> jsonrpc ::Id {
let id = self . request_counter . fetch_add ( 1 , Ordering ::Relaxed ) ;
jsonrpc ::Id ::Num ( id )
2020-10-21 16:42:45 +09:00
}
fn to_params ( value : Value ) -> Result < jsonrpc ::Params > {
use jsonrpc ::Params ;
let params = match value {
Value ::Null = > Params ::None ,
Value ::Bool ( _ ) | Value ::Number ( _ ) | Value ::String ( _ ) = > Params ::Array ( vec! [ value ] ) ,
Value ::Array ( vec ) = > Params ::Array ( vec ) ,
Value ::Object ( map ) = > Params ::Map ( map ) ,
} ;
Ok ( params )
}
2020-10-23 18:48:03 +09:00
/// Execute a RPC request on the language server.
2020-10-29 14:06:33 +09:00
pub async fn request < R : lsp ::request ::Request > ( & self , params : R ::Params ) -> Result < R ::Result >
2020-10-21 16:42:45 +09:00
where
R ::Params : serde ::Serialize ,
R ::Result : core ::fmt ::Debug , // TODO: temporary
{
let params = serde_json ::to_value ( params ) ? ;
let request = jsonrpc ::MethodCall {
jsonrpc : Some ( jsonrpc ::Version ::V2 ) ,
id : self . next_request_id ( ) ,
method : R ::METHOD . to_string ( ) ,
params : Self ::to_params ( params ) ? ,
} ;
let ( tx , rx ) = smol ::channel ::bounded ::< Result < Value > > ( 1 ) ;
self . outgoing
. send ( Payload ::Request {
chan : tx ,
value : request ,
} )
. await
. map_err ( | e | Error ::Other ( e . into ( ) ) ) ? ;
let response = rx . recv ( ) . await . map_err ( | e | Error ::Other ( e . into ( ) ) ) ? ? ;
let response = serde_json ::from_value ( response ) ? ;
// TODO: we should pass request to a sender thread via a channel
// so it can't be interleaved
// TODO: responses can be out of order, we need to register a single shot response channel
Ok ( response )
}
2020-10-23 18:48:03 +09:00
/// Send a RPC notification to the language server.
2020-10-29 14:06:33 +09:00
pub async fn notify < R : lsp ::notification ::Notification > ( & self , params : R ::Params ) -> Result < ( ) >
2020-10-21 16:42:45 +09:00
where
R ::Params : serde ::Serialize ,
{
let params = serde_json ::to_value ( params ) ? ;
let notification = jsonrpc ::Notification {
jsonrpc : Some ( jsonrpc ::Version ::V2 ) ,
method : R ::METHOD . to_string ( ) ,
params : Self ::to_params ( params ) ? ,
} ;
self . outgoing
. send ( Payload ::Notification ( notification ) )
. await
. map_err ( | e | Error ::Other ( e . into ( ) ) ) ? ;
Ok ( ( ) )
}
2020-10-23 18:48:03 +09:00
/// Reply to a language server RPC call.
pub async fn reply (
2020-10-29 14:06:33 +09:00
& self ,
2020-10-23 18:48:03 +09:00
id : jsonrpc ::Id ,
result : core ::result ::Result < Value , jsonrpc ::Error > ,
) -> Result < ( ) > {
use jsonrpc ::{ Failure , Output , Success , Version } ;
let output = match result {
Ok ( result ) = > Output ::Success ( Success {
jsonrpc : Some ( Version ::V2 ) ,
id ,
result ,
} ) ,
Err ( error ) = > Output ::Failure ( Failure {
jsonrpc : Some ( Version ::V2 ) ,
id ,
error ,
} ) ,
} ;
self . outgoing
. send ( Payload ::Response ( output ) )
. await
. map_err ( | e | Error ::Other ( e . into ( ) ) ) ? ;
Ok ( ( ) )
}
2020-10-21 16:42:45 +09:00
// -------------------------------------------------------------------------------------------
// General messages
// -------------------------------------------------------------------------------------------
pub async fn initialize ( & mut self ) -> Result < ( ) > {
// TODO: delay any requests that are triggered prior to initialize
#[ allow(deprecated) ]
let params = lsp ::InitializeParams {
2020-12-01 09:53:17 +09:00
process_id : Some ( std ::process ::id ( ) ) ,
2020-10-21 16:42:45 +09:00
root_path : None ,
// root_uri: Some(lsp_types::Url::parse("file://localhost/")?),
root_uri : None , // set to project root in the future
initialization_options : None ,
2020-10-23 18:48:03 +09:00
capabilities : lsp ::ClientCapabilities {
2020-12-23 16:20:49 +09:00
// text_document:
// { completion: {
// dynamic_registration: bool
// completion_item: { snippet, documentation_format, ... }
// completion_item_kind: { }
// } }
2020-10-23 18:48:03 +09:00
.. Default ::default ( )
} ,
2020-10-21 16:42:45 +09:00
trace : None ,
workspace_folders : None ,
client_info : None ,
2020-12-01 09:53:17 +09:00
locale : None , // TODO
2020-10-21 16:42:45 +09:00
} ;
let response = self . request ::< lsp ::request ::Initialize > ( params ) . await ? ;
self . capabilities = Some ( response . capabilities ) ;
// next up, notify<initialized>
self . notify ::< lsp ::notification ::Initialized > ( lsp ::InitializedParams { } )
. await ? ;
Ok ( ( ) )
}
2020-10-29 14:06:33 +09:00
pub async fn shutdown ( & self ) -> Result < ( ) > {
2020-10-21 16:42:45 +09:00
self . request ::< lsp ::request ::Shutdown > ( ( ) ) . await
}
2020-10-29 14:06:33 +09:00
pub async fn exit ( & self ) -> Result < ( ) > {
2020-10-21 16:42:45 +09:00
self . notify ::< lsp ::notification ::Exit > ( ( ) ) . await
}
// -------------------------------------------------------------------------------------------
// Text document
// -------------------------------------------------------------------------------------------
2020-12-25 17:20:09 +09:00
pub async fn text_document_did_open (
& self ,
uri : lsp ::Url ,
version : i32 ,
doc : & Rope ,
) -> Result < ( ) > {
2020-10-21 16:42:45 +09:00
self . notify ::< lsp ::notification ::DidOpenTextDocument > ( lsp ::DidOpenTextDocumentParams {
text_document : lsp ::TextDocumentItem {
2020-12-25 17:20:09 +09:00
uri ,
2020-10-21 16:42:45 +09:00
language_id : " rust " . to_string ( ) , // TODO: hardcoded for now
2020-12-25 17:20:09 +09:00
version ,
text : String ::from ( doc ) ,
2020-10-21 16:42:45 +09:00
} ,
} )
. await
}
2020-10-23 18:48:03 +09:00
fn to_changes ( changeset : & ChangeSet ) -> Vec < lsp ::TextDocumentContentChangeEvent > {
let mut iter = changeset . changes ( ) . iter ( ) . peekable ( ) ;
let mut old_pos = 0 ;
let mut changes = Vec ::new ( ) ;
use crate ::util ::pos_to_lsp_pos ;
use helix_core ::Operation ::* ;
// TEMP
let rope = helix_core ::Rope ::from ( " " ) ;
let old_text = rope . slice ( .. ) ;
while let Some ( change ) = iter . next ( ) {
let len = match change {
Delete ( i ) | Retain ( i ) = > * i ,
Insert ( _ ) = > 0 ,
} ;
let old_end = old_pos + len ;
match change {
Retain ( _ ) = > { }
Delete ( _ ) = > {
let start = pos_to_lsp_pos ( & old_text , old_pos ) ;
let end = pos_to_lsp_pos ( & old_text , old_end ) ;
// a subsequent ins means a replace, consume it
if let Some ( Insert ( s ) ) = iter . peek ( ) {
iter . next ( ) ;
// replacement
changes . push ( lsp ::TextDocumentContentChangeEvent {
range : Some ( lsp ::Range ::new ( start , end ) ) ,
text : s . into ( ) ,
range_length : None ,
} ) ;
} else {
// deletion
changes . push ( lsp ::TextDocumentContentChangeEvent {
range : Some ( lsp ::Range ::new ( start , end ) ) ,
text : " " . to_string ( ) ,
range_length : None ,
} ) ;
} ;
}
Insert ( s ) = > {
2020-12-25 17:20:09 +09:00
// TODO:
// thread 'main' panicked at 'Attempt to index past end of slice: char index 1211, slice char length 0', /home/speed/.cargo/registry/src/github.com-1ecc6299db9ec823/ropey-1.2.0/src/slice.rs:301:9
// stack backtrace:
// 0: rust_begin_unwind
// at /rustc/b32e6e6ac8921035177256ab6806e6ab0d4b9b94/library/std/src/panicking.rs:493:5
// 1: std::panicking::begin_panic_fmt
// at /rustc/b32e6e6ac8921035177256ab6806e6ab0d4b9b94/library/std/src/panicking.rs:435:5
// 2: ropey::slice::RopeSlice::char_to_line
// at /home/speed/.cargo/registry/src/github.com-1ecc6299db9ec823/ropey-1.2.0/src/slice.rs:301:9
// 3: helix_lsp::util::pos_to_lsp_pos
// at /home/speed/src/helix/helix-lsp/src/lib.rs:39:20
// 4: helix_lsp::client::Client::to_changes
// at /home/speed/src/helix/helix-lsp/src/client.rs:293:33
// 5: helix_lsp::client::Client::text_document_did_change::{{closure}}
// at /home/speed/src/helix/helix-lsp/src/client.rs:338:55
2020-10-23 18:48:03 +09:00
let start = pos_to_lsp_pos ( & old_text , old_pos ) ;
// insert
changes . push ( lsp ::TextDocumentContentChangeEvent {
range : Some ( lsp ::Range ::new ( start , start ) ) ,
text : s . into ( ) ,
range_length : None ,
} ) ;
}
}
old_pos = old_end ;
}
changes
}
2020-10-21 16:42:45 +09:00
// TODO: trigger any time history.commit_revision happens
pub async fn text_document_did_change (
2020-12-23 15:50:16 +09:00
& self ,
2020-12-25 17:20:09 +09:00
text_document : lsp ::VersionedTextDocumentIdentifier ,
changes : & ChangeSet ,
2020-10-21 16:42:45 +09:00
) -> Result < ( ) > {
2020-10-23 18:48:03 +09:00
// figure out what kind of sync the server supports
let capabilities = self . capabilities . as_ref ( ) . unwrap ( ) ; // TODO: needs post init
let sync_capabilities = match capabilities . text_document_sync {
Some ( lsp ::TextDocumentSyncCapability ::Kind ( kind ) ) = > kind ,
Some ( lsp ::TextDocumentSyncCapability ::Options ( lsp ::TextDocumentSyncOptions {
change : Some ( kind ) ,
..
} ) ) = > kind ,
// None | SyncOptions { changes: None }
_ = > return Ok ( ( ) ) ,
} ;
let changes = match sync_capabilities {
lsp ::TextDocumentSyncKind ::Full = > {
vec! [ lsp ::TextDocumentContentChangeEvent {
// range = None -> whole document
range : None , //Some(Range)
range_length : None , // u64 apparently deprecated
text : " " . to_string ( ) ,
} ] // TODO: probably need old_state here too?
}
2020-12-25 17:20:09 +09:00
lsp ::TextDocumentSyncKind ::Incremental = > Self ::to_changes ( changes ) ,
2020-10-23 18:48:03 +09:00
lsp ::TextDocumentSyncKind ::None = > return Ok ( ( ) ) ,
} ;
2020-10-21 16:42:45 +09:00
self . notify ::< lsp ::notification ::DidChangeTextDocument > ( lsp ::DidChangeTextDocumentParams {
2020-12-25 17:20:09 +09:00
text_document ,
2020-10-23 18:48:03 +09:00
content_changes : changes ,
2020-10-21 16:42:45 +09:00
} )
. await
}
2020-10-22 14:35:07 +09:00
// TODO: impl into() TextDocumentIdentifier / VersionedTextDocumentIdentifier for Document.
2020-10-21 16:42:45 +09:00
2020-12-25 17:20:09 +09:00
pub async fn text_document_did_close (
& self ,
text_document : lsp ::TextDocumentIdentifier ,
) -> Result < ( ) > {
2020-10-21 16:42:45 +09:00
self . notify ::< lsp ::notification ::DidCloseTextDocument > ( lsp ::DidCloseTextDocumentParams {
2020-12-25 17:20:09 +09:00
text_document ,
2020-10-21 16:42:45 +09:00
} )
. await
}
// will_save / will_save_wait_until
2020-12-23 15:50:16 +09:00
pub async fn text_document_did_save ( & self ) -> anyhow ::Result < ( ) > {
2020-10-21 16:42:45 +09:00
unimplemented! ( )
}
2020-12-23 16:20:49 +09:00
2020-12-25 17:20:09 +09:00
pub async fn completion (
& self ,
text_document : lsp ::TextDocumentIdentifier ,
position : lsp ::Position ,
) -> anyhow ::Result < Vec < lsp ::CompletionItem > > {
2020-12-23 16:20:49 +09:00
// TODO: figure out what should happen when you complete with multiple cursors
let params = lsp ::CompletionParams {
text_document_position : lsp ::TextDocumentPositionParams {
2020-12-25 17:20:09 +09:00
text_document ,
position ,
2020-12-23 16:20:49 +09:00
} ,
// TODO: support these tokens by async receiving and updating the choice list
work_done_progress_params : lsp ::WorkDoneProgressParams {
work_done_token : None ,
} ,
partial_result_params : lsp ::PartialResultParams {
partial_result_token : None ,
} ,
context : None ,
// lsp::CompletionContext { trigger_kind: , trigger_character: Some(), }
} ;
let response = self . request ::< lsp ::request ::Completion > ( params ) . await ? ;
let items = match response {
Some ( lsp ::CompletionResponse ::Array ( items ) ) = > items ,
// TODO: do something with is_incomplete
Some ( lsp ::CompletionResponse ::List ( lsp ::CompletionList {
is_incomplete : _is_incomplete ,
items ,
} ) ) = > items ,
None = > Vec ::new ( ) ,
} ;
Ok ( items )
}
2020-10-21 16:42:45 +09:00
}