From a979e3212762a9ed67a43cee3ef281361cf83ab0 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 17 Aug 2023 21:57:39 -0400 Subject: [PATCH] Utilize LSP completion `itemDefaults` a bit Tailwind likes to throw a lot of completion data at us, this gets it to send less. Previously it would respond to a completion with 2.5 MB JSON blob, now it is more like 0.8 MB. Relies on a local copy of lsp-types with the `itemDefaults` field added. I don't have write perms to push to our fork of the crate atm, sorry :) --- Cargo.lock | 4 +- crates/lsp/Cargo.toml | 2 +- crates/lsp/src/lsp.rs | 8 ++++ crates/project/src/lsp_command.rs | 69 +++++++++++++++++++++++-------- 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ff9981a6a0bd52e9d1ac24169fbc428db1ff0ce..4e1d0b53a6102df67ab2855a38c28c166d8061d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4185,9 +4185,7 @@ dependencies = [ [[package]] name = "lsp-types" -version = "0.94.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237" +version = "0.94.1" dependencies = [ "bitflags 1.3.2", "serde", diff --git a/crates/lsp/Cargo.toml b/crates/lsp/Cargo.toml index 47e0995c852ab49417bdfafe714123c85db3d223..c3ff37dc61bf5ca8f22f842eb7883481e7c65da1 100644 --- a/crates/lsp/Cargo.toml +++ b/crates/lsp/Cargo.toml @@ -20,7 +20,7 @@ anyhow.workspace = true async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553", optional = true } futures.workspace = true log.workspace = true -lsp-types = "0.94" +lsp-types = { path = "../../../lsp-types" } parking_lot.workspace = true postage.workspace = true serde.workspace = true diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 66ef33418b80dccd81fcf661255b84946a161989..f39d97aeb56ae2ceaa919a59d6874a31e9328351 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -423,6 +423,14 @@ impl LanguageServer { }), ..Default::default() }), + completion_list: Some(CompletionListCapability { + item_defaults: Some(vec![ + "commitCharacters".to_owned(), + "editRange".to_owned(), + "insertTextMode".to_owned(), + "data".to_owned(), + ]), + }), ..Default::default() }), rename: Some(RenameClientCapabilities { diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index ad5b63ae2a65809f1e7f5d64e501c9016562406b..c1bb890d496b771b680084d0cc708d27655f2988 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -15,7 +15,10 @@ use language::{ range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, }; -use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, ServerCapabilities}; +use lsp::{ + CompletionListItemDefaultsEditRange, DocumentHighlightKind, LanguageServer, LanguageServerId, + ServerCapabilities, +}; use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions { @@ -1341,10 +1344,16 @@ impl LspCommand for GetCompletions { server_id: LanguageServerId, cx: AsyncAppContext, ) -> Result> { + let mut response_list = None; let completions = if let Some(completions) = completions { match completions { lsp::CompletionResponse::Array(completions) => completions, - lsp::CompletionResponse::List(list) => list.items, + + lsp::CompletionResponse::List(mut list) => { + let items = std::mem::take(&mut list.items); + response_list = Some(list); + items + } } } else { Default::default() @@ -1354,6 +1363,7 @@ impl LspCommand for GetCompletions { let language = buffer.language().cloned(); let snapshot = buffer.snapshot(); let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left); + let mut range_for_token = None; completions .into_iter() @@ -1374,6 +1384,7 @@ impl LspCommand for GetCompletions { edit.new_text.clone(), ) } + // If the language server does not provide a range, then infer // the range based on the syntax tree. None => { @@ -1381,27 +1392,51 @@ impl LspCommand for GetCompletions { log::info!("completion out of expected range"); return None; } - let Range { start, end } = range_for_token - .get_or_insert_with(|| { - let offset = self.position.to_offset(&snapshot); - let (range, kind) = snapshot.surrounding_word(offset); - if kind == Some(CharKind::Word) { - range - } else { - offset..offset - } - }) - .clone(); + + let default_edit_range = response_list + .as_ref() + .and_then(|list| list.item_defaults.as_ref()) + .and_then(|defaults| defaults.edit_range.as_ref()) + .and_then(|range| match range { + CompletionListItemDefaultsEditRange::Range(r) => Some(r), + _ => None, + }); + + let range = if let Some(range) = default_edit_range { + let range = range_from_lsp(range.clone()); + let start = snapshot.clip_point_utf16(range.start, Bias::Left); + let end = snapshot.clip_point_utf16(range.end, Bias::Left); + if start != range.start.0 || end != range.end.0 { + log::info!("completion out of expected range"); + return None; + } + + snapshot.anchor_before(start)..snapshot.anchor_after(end) + } else { + range_for_token + .get_or_insert_with(|| { + let offset = self.position.to_offset(&snapshot); + let (range, kind) = snapshot.surrounding_word(offset); + let range = if kind == Some(CharKind::Word) { + range + } else { + offset..offset + }; + + snapshot.anchor_before(range.start) + ..snapshot.anchor_after(range.end) + }) + .clone() + }; + let text = lsp_completion .insert_text .as_ref() .unwrap_or(&lsp_completion.label) .clone(); - ( - snapshot.anchor_before(start)..snapshot.anchor_after(end), - text, - ) + (range, text) } + Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => { log::info!("unsupported insert/replace completion"); return None;