1 mod client; 2 mod transport; 3 4 pub use client::Client; 5 pub use futures_executor::block_on; 6 pub use jsonrpc::Call; 7 pub use jsonrpc_core as jsonrpc; 8 pub use lsp::{Position, Url}; 9 pub use lsp_types as lsp; 10 11 use futures_util::stream::select_all::SelectAll; 12 use helix_core::syntax::LanguageConfiguration; 13 14 use std::{ 15 collections::{hash_map::Entry, HashMap}, 16 sync::{ 17 atomic::{AtomicUsize, Ordering}, 18 Arc, 19 }, 20 }; 21 22 use serde::{Deserialize, Serialize}; 23 use thiserror::Error; 24 use tokio_stream::wrappers::UnboundedReceiverStream; 25 26 pub type Result<T> = core::result::Result<T, Error>; 27 type LanguageId = String; 28 29 #[derive(Error, Debug)] 30 pub enum Error { 31 #[error("protocol error: {0}")] 32 Rpc(#[from] jsonrpc::Error), 33 #[error("failed to parse: {0}")] 34 Parse(#[from] serde_json::Error), 35 #[error("IO Error: {0}")] 36 IO(#[from] std::io::Error), 37 #[error("request timed out")] 38 Timeout, 39 #[error("server closed the stream")] 40 StreamClosed, 41 #[error("LSP not defined")] 42 LspNotDefined, 43 #[error(transparent)] 44 Other(#[from] anyhow::Error), 45 } 46 47 #[derive(Clone, Copy, Debug, Serialize, Deserialize)] 48 pub enum OffsetEncoding { 49 /// UTF-8 code units aka bytes 50 #[serde(rename = "utf-8")] 51 Utf8, 52 /// UTF-16 code units 53 #[serde(rename = "utf-16")] 54 Utf16, 55 } 56 57 pub mod util { 58 use super::*; 59 use helix_core::{Range, Rope, Transaction}; 60 61 /// Converts [`lsp::Position`] to a position in the document. 62 /// 63 /// Returns `None` if position exceeds document length or an operation overflows. lsp_pos_to_pos( doc: &Rope, pos: lsp::Position, offset_encoding: OffsetEncoding, ) -> Option<usize>64 pub fn lsp_pos_to_pos( 65 doc: &Rope, 66 pos: lsp::Position, 67 offset_encoding: OffsetEncoding, 68 ) -> Option<usize> { 69 let max_line = doc.lines().count().saturating_sub(1); 70 let pos_line = pos.line as usize; 71 let pos_line = if pos_line > max_line { 72 return None; 73 } else { 74 pos_line 75 }; 76 match offset_encoding { 77 OffsetEncoding::Utf8 => { 78 let max_char = doc 79 .line_to_char(max_line) 80 .checked_add(doc.line(max_line).len_chars())?; 81 let line = doc.line_to_char(pos_line); 82 let pos = line.checked_add(pos.character as usize)?; 83 if pos <= max_char { 84 Some(pos) 85 } else { 86 None 87 } 88 } 89 OffsetEncoding::Utf16 => { 90 let max_char = doc 91 .line_to_char(max_line) 92 .checked_add(doc.line(max_line).len_chars())?; 93 let max_cu = doc.char_to_utf16_cu(max_char); 94 let line = doc.line_to_char(pos_line); 95 let line_start = doc.char_to_utf16_cu(line); 96 let pos = line_start.checked_add(pos.character as usize)?; 97 if pos <= max_cu { 98 Some(doc.utf16_cu_to_char(pos)) 99 } else { 100 None 101 } 102 } 103 } 104 } 105 106 /// Converts position in the document to [`lsp::Position`]. 107 /// 108 /// Panics when `pos` is out of `doc` bounds or operation overflows. pos_to_lsp_pos( doc: &Rope, pos: usize, offset_encoding: OffsetEncoding, ) -> lsp::Position109 pub fn pos_to_lsp_pos( 110 doc: &Rope, 111 pos: usize, 112 offset_encoding: OffsetEncoding, 113 ) -> lsp::Position { 114 match offset_encoding { 115 OffsetEncoding::Utf8 => { 116 let line = doc.char_to_line(pos); 117 let line_start = doc.line_to_char(line); 118 let col = pos - line_start; 119 120 lsp::Position::new(line as u32, col as u32) 121 } 122 OffsetEncoding::Utf16 => { 123 let line = doc.char_to_line(pos); 124 let line_start = doc.char_to_utf16_cu(doc.line_to_char(line)); 125 let col = doc.char_to_utf16_cu(pos) - line_start; 126 127 lsp::Position::new(line as u32, col as u32) 128 } 129 } 130 } 131 132 /// Converts a range in the document to [`lsp::Range`]. range_to_lsp_range( doc: &Rope, range: Range, offset_encoding: OffsetEncoding, ) -> lsp::Range133 pub fn range_to_lsp_range( 134 doc: &Rope, 135 range: Range, 136 offset_encoding: OffsetEncoding, 137 ) -> lsp::Range { 138 let start = pos_to_lsp_pos(doc, range.from(), offset_encoding); 139 let end = pos_to_lsp_pos(doc, range.to(), offset_encoding); 140 141 lsp::Range::new(start, end) 142 } 143 lsp_range_to_range( doc: &Rope, range: lsp::Range, offset_encoding: OffsetEncoding, ) -> Option<Range>144 pub fn lsp_range_to_range( 145 doc: &Rope, 146 range: lsp::Range, 147 offset_encoding: OffsetEncoding, 148 ) -> Option<Range> { 149 let start = lsp_pos_to_pos(doc, range.start, offset_encoding)?; 150 let end = lsp_pos_to_pos(doc, range.end, offset_encoding)?; 151 152 Some(Range::new(start, end)) 153 } 154 generate_transaction_from_edits( doc: &Rope, edits: Vec<lsp::TextEdit>, offset_encoding: OffsetEncoding, ) -> Transaction155 pub fn generate_transaction_from_edits( 156 doc: &Rope, 157 edits: Vec<lsp::TextEdit>, 158 offset_encoding: OffsetEncoding, 159 ) -> Transaction { 160 Transaction::change( 161 doc, 162 edits.into_iter().map(|edit| { 163 // simplify "" into None for cleaner changesets 164 let replacement = if !edit.new_text.is_empty() { 165 Some(edit.new_text.into()) 166 } else { 167 None 168 }; 169 170 let start = 171 if let Some(start) = lsp_pos_to_pos(doc, edit.range.start, offset_encoding) { 172 start 173 } else { 174 return (0, 0, None); 175 }; 176 let end = if let Some(end) = lsp_pos_to_pos(doc, edit.range.end, offset_encoding) { 177 end 178 } else { 179 return (0, 0, None); 180 }; 181 (start, end, replacement) 182 }), 183 ) 184 } 185 186 /// The result of asking the language server to format the document. This can be turned into a 187 /// `Transaction`, but the advantage of not doing that straight away is that this one is 188 /// `Send` and `Sync`. 189 #[derive(Clone, Debug)] 190 pub struct LspFormatting { 191 pub doc: Rope, 192 pub edits: Vec<lsp::TextEdit>, 193 pub offset_encoding: OffsetEncoding, 194 } 195 196 impl From<LspFormatting> for Transaction { from(fmt: LspFormatting) -> Transaction197 fn from(fmt: LspFormatting) -> Transaction { 198 generate_transaction_from_edits(&fmt.doc, fmt.edits, fmt.offset_encoding) 199 } 200 } 201 } 202 203 #[derive(Debug, PartialEq, Clone)] 204 pub enum MethodCall { 205 WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams), 206 } 207 208 impl MethodCall { parse(method: &str, params: jsonrpc::Params) -> Option<MethodCall>209 pub fn parse(method: &str, params: jsonrpc::Params) -> Option<MethodCall> { 210 use lsp::request::Request; 211 let request = match method { 212 lsp::request::WorkDoneProgressCreate::METHOD => { 213 let params: lsp::WorkDoneProgressCreateParams = params 214 .parse() 215 .expect("Failed to parse WorkDoneCreate params"); 216 Self::WorkDoneProgressCreate(params) 217 } 218 _ => { 219 log::warn!("unhandled lsp request: {}", method); 220 return None; 221 } 222 }; 223 Some(request) 224 } 225 } 226 227 #[derive(Debug, PartialEq, Clone)] 228 pub enum Notification { 229 // we inject this notification to signal the LSP is ready 230 Initialized, 231 PublishDiagnostics(lsp::PublishDiagnosticsParams), 232 ShowMessage(lsp::ShowMessageParams), 233 LogMessage(lsp::LogMessageParams), 234 ProgressMessage(lsp::ProgressParams), 235 } 236 237 impl Notification { parse(method: &str, params: jsonrpc::Params) -> Option<Notification>238 pub fn parse(method: &str, params: jsonrpc::Params) -> Option<Notification> { 239 use lsp::notification::Notification as _; 240 241 let notification = match method { 242 lsp::notification::Initialized::METHOD => Self::Initialized, 243 lsp::notification::PublishDiagnostics::METHOD => { 244 let params: lsp::PublishDiagnosticsParams = params 245 .parse() 246 .expect("Failed to parse PublishDiagnostics params"); 247 248 // TODO: need to loop over diagnostics and distinguish them by URI 249 Self::PublishDiagnostics(params) 250 } 251 252 lsp::notification::ShowMessage::METHOD => { 253 let params: lsp::ShowMessageParams = params.parse().ok()?; 254 255 Self::ShowMessage(params) 256 } 257 lsp::notification::LogMessage::METHOD => { 258 let params: lsp::LogMessageParams = params.parse().ok()?; 259 260 Self::LogMessage(params) 261 } 262 lsp::notification::Progress::METHOD => { 263 let params: lsp::ProgressParams = params.parse().ok()?; 264 265 Self::ProgressMessage(params) 266 } 267 _ => { 268 log::error!("unhandled LSP notification: {}", method); 269 return None; 270 } 271 }; 272 273 Some(notification) 274 } 275 } 276 277 #[derive(Debug)] 278 pub struct Registry { 279 inner: HashMap<LanguageId, (usize, Arc<Client>)>, 280 281 counter: AtomicUsize, 282 pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>, 283 } 284 285 impl Default for Registry { default() -> Self286 fn default() -> Self { 287 Self::new() 288 } 289 } 290 291 impl Registry { new() -> Self292 pub fn new() -> Self { 293 Self { 294 inner: HashMap::new(), 295 counter: AtomicUsize::new(0), 296 incoming: SelectAll::new(), 297 } 298 } 299 get_by_id(&self, id: usize) -> Option<&Client>300 pub fn get_by_id(&self, id: usize) -> Option<&Client> { 301 self.inner 302 .values() 303 .find(|(client_id, _)| client_id == &id) 304 .map(|(_, client)| client.as_ref()) 305 } 306 get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>>307 pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> { 308 let config = match &language_config.language_server { 309 Some(config) => config, 310 None => return Err(Error::LspNotDefined), 311 }; 312 313 match self.inner.entry(language_config.scope.clone()) { 314 Entry::Occupied(entry) => Ok(entry.get().1.clone()), 315 Entry::Vacant(entry) => { 316 // initialize a new client 317 let id = self.counter.fetch_add(1, Ordering::Relaxed); 318 let (client, incoming, initialize_notify) = Client::start( 319 &config.command, 320 &config.args, 321 language_config.config.clone(), 322 id, 323 )?; 324 self.incoming.push(UnboundedReceiverStream::new(incoming)); 325 let client = Arc::new(client); 326 327 // Initialize the client asynchronously 328 let _client = client.clone(); 329 tokio::spawn(async move { 330 use futures_util::TryFutureExt; 331 let value = _client 332 .capabilities 333 .get_or_try_init(|| { 334 _client 335 .initialize() 336 .map_ok(|response| response.capabilities) 337 }) 338 .await; 339 340 value.expect("failed to initialize capabilities"); 341 342 // next up, notify<initialized> 343 _client 344 .notify::<lsp::notification::Initialized>(lsp::InitializedParams {}) 345 .await 346 .unwrap(); 347 348 initialize_notify.notify_one(); 349 }); 350 351 entry.insert((id, client.clone())); 352 Ok(client) 353 } 354 } 355 } 356 iter_clients(&self) -> impl Iterator<Item = &Arc<Client>>357 pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> { 358 self.inner.values().map(|(_, client)| client) 359 } 360 } 361 362 #[derive(Debug)] 363 pub enum ProgressStatus { 364 Created, 365 Started(lsp::WorkDoneProgress), 366 } 367 368 impl ProgressStatus { progress(&self) -> Option<&lsp::WorkDoneProgress>369 pub fn progress(&self) -> Option<&lsp::WorkDoneProgress> { 370 match &self { 371 ProgressStatus::Created => None, 372 ProgressStatus::Started(progress) => Some(progress), 373 } 374 } 375 } 376 377 #[derive(Default, Debug)] 378 /// Acts as a container for progress reported by language servers. Each server 379 /// has a unique id assigned at creation through [`Registry`]. This id is then used 380 /// to store the progress in this map. 381 pub struct LspProgressMap(HashMap<usize, HashMap<lsp::ProgressToken, ProgressStatus>>); 382 383 impl LspProgressMap { new() -> Self384 pub fn new() -> Self { 385 Self::default() 386 } 387 388 /// Returns a map of all tokens coresponding to the lanaguage server with `id`. progress_map(&self, id: usize) -> Option<&HashMap<lsp::ProgressToken, ProgressStatus>>389 pub fn progress_map(&self, id: usize) -> Option<&HashMap<lsp::ProgressToken, ProgressStatus>> { 390 self.0.get(&id) 391 } 392 is_progressing(&self, id: usize) -> bool393 pub fn is_progressing(&self, id: usize) -> bool { 394 self.0.get(&id).map(|it| !it.is_empty()).unwrap_or_default() 395 } 396 397 /// Returns last progress status for a given server with `id` and `token`. progress(&self, id: usize, token: &lsp::ProgressToken) -> Option<&ProgressStatus>398 pub fn progress(&self, id: usize, token: &lsp::ProgressToken) -> Option<&ProgressStatus> { 399 self.0.get(&id).and_then(|values| values.get(token)) 400 } 401 402 /// Checks if progress `token` for server with `id` is created. is_created(&mut self, id: usize, token: &lsp::ProgressToken) -> bool403 pub fn is_created(&mut self, id: usize, token: &lsp::ProgressToken) -> bool { 404 self.0 405 .get(&id) 406 .map(|values| values.get(token).is_some()) 407 .unwrap_or_default() 408 } 409 create(&mut self, id: usize, token: lsp::ProgressToken)410 pub fn create(&mut self, id: usize, token: lsp::ProgressToken) { 411 self.0 412 .entry(id) 413 .or_default() 414 .insert(token, ProgressStatus::Created); 415 } 416 417 /// Ends the progress by removing the `token` from server with `id`, if removed returns the value. end_progress( &mut self, id: usize, token: &lsp::ProgressToken, ) -> Option<ProgressStatus>418 pub fn end_progress( 419 &mut self, 420 id: usize, 421 token: &lsp::ProgressToken, 422 ) -> Option<ProgressStatus> { 423 self.0.get_mut(&id).and_then(|vals| vals.remove(token)) 424 } 425 426 /// Updates the progess of `token` for server with `id` to `status`, returns the value replaced or `None`. update( &mut self, id: usize, token: lsp::ProgressToken, status: lsp::WorkDoneProgress, ) -> Option<ProgressStatus>427 pub fn update( 428 &mut self, 429 id: usize, 430 token: lsp::ProgressToken, 431 status: lsp::WorkDoneProgress, 432 ) -> Option<ProgressStatus> { 433 self.0 434 .entry(id) 435 .or_default() 436 .insert(token, ProgressStatus::Started(status)) 437 } 438 } 439 440 #[cfg(test)] 441 mod tests { 442 use super::{lsp, util::*, OffsetEncoding}; 443 use helix_core::Rope; 444 445 #[test] converts_lsp_pos_to_pos()446 fn converts_lsp_pos_to_pos() { 447 macro_rules! test_case { 448 ($doc:expr, ($x:expr, $y:expr) => $want:expr) => { 449 let doc = Rope::from($doc); 450 let pos = lsp::Position::new($x, $y); 451 assert_eq!($want, lsp_pos_to_pos(&doc, pos, OffsetEncoding::Utf16)); 452 assert_eq!($want, lsp_pos_to_pos(&doc, pos, OffsetEncoding::Utf8)) 453 }; 454 } 455 456 test_case!("", (0, 0) => Some(0)); 457 test_case!("", (0, 1) => None); 458 test_case!("", (1, 0) => None); 459 test_case!("\n\n", (0, 0) => Some(0)); 460 test_case!("\n\n", (1, 0) => Some(1)); 461 test_case!("\n\n", (1, 1) => Some(2)); 462 test_case!("\n\n", (2, 0) => Some(2)); 463 test_case!("\n\n", (3, 0) => None); 464 test_case!("test\n\n\n\ncase", (4, 3) => Some(11)); 465 test_case!("test\n\n\n\ncase", (4, 4) => Some(12)); 466 test_case!("test\n\n\n\ncase", (4, 5) => None); 467 test_case!("", (u32::MAX, u32::MAX) => None); 468 } 469 } 470