on master help sign in
Negotiate LSP Position Encoding (#5894)

So far LSP always required that `PositionEncoding.characters` is an UTF-16 offset. Now that LSP 3.17 is available in `lsp-types` request the server to send char offsets (UTF-32) or byte offsets (UTF-8) instead. For compatability with old servers, UTF-16 remains as the fallback as required by the standard.

commit93c7afc4ed2b8d848fa2779f43202ba7f837263b authorPascal Kuthe <pascal.kuthe@semimod.de> dateSat 11 Feb 2023, 07:50 (4 years ago) parent6929a12
5 files changed+49−14
Cargo.lock+2−2 helix-lsp/Cargo.toml+1−1 helix-lsp/src/client.rs+24−5 helix-lsp/src/lib.rs+17−5 helix-term/src/commands/lsp.rs+5−1
Cargo.lock+2−2
@@ -1481,9 +1481,9 @@ dependencies = [
14811481
14821482[[package]]
14831483name = "lsp-types"
1484−version = "0.93.2"
1484+version = "0.94.0"
14851485source = "registry+https://github.com/rust-lang/crates.io-index"
1486−checksum = "9be6e9c7e2d18f651974370d7aff703f9513e0df6e464fd795660edc77e6ca51"
1486+checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237"
14871487dependencies = [
14881488 "bitflags",
14891489 "serde",
helix-lsp/Cargo.toml+1−1
@@ -19,7 +19,7 @@ anyhow = "1.0"
1919futures-executor = "0.3"
2020futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
2121log = "0.4"
22−lsp-types = { version = "0.93", features = ["proposed"] }
22+lsp-types = { version = "0.94" }
2323serde = { version = "1.0", features = ["derive"] }
2424serde_json = "1.0"
2525thiserror = "1.0"
helix-lsp/src/client.rs+24−5
@@ -6,6 +6,7 @@ use crate::{
66
77use helix_core::{find_root, ChangeSet, Rope};
88use helix_loader::{self, VERSION_AND_GIT_HASH};
9+use lsp::PositionEncodingKind;
910use lsp_types as lsp;
1011use serde::Deserialize;
1112use serde_json::Value;
@@ -32,7 +33,6 @@ pub struct Client {
3233 server_tx: UnboundedSender<Payload>,
3334 request_counter: AtomicU64,
3435 pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
35− offset_encoding: OffsetEncoding,
3636 config: Option<Value>,
3737 root_path: std::path::PathBuf,
3838 root_uri: Option<lsp::Url>,
@@ -104,7 +104,6 @@ impl Client {
104104 server_tx,
105105 request_counter: AtomicU64::new(0),
106106 capabilities: OnceCell::new(),
107− offset_encoding: OffsetEncoding::Utf16,
108107 config,
109108 req_timeout,
110109
@@ -147,7 +146,19 @@ impl Client {
147146 }
148147
149148 pub fn offset_encoding(&self) -> OffsetEncoding {
150− self.offset_encoding
149+ self.capabilities()
150+ .position_encoding
151+ .as_ref()
152+ .and_then(|encoding| match encoding.as_str() {
153+ "utf-8" => Some(OffsetEncoding::Utf8),
154+ "utf-16" => Some(OffsetEncoding::Utf16),
155+ "utf-32" => Some(OffsetEncoding::Utf32),
156+ encoding => {
157+ log::error!("Server provided invalid position encording {encoding}, defaulting to utf-16");
158+ None
159+ },
160+ })
161+ .unwrap_or_default()
151162 }
152163
153164 pub fn config(&self) -> Option<&Value> {
@@ -377,6 +388,14 @@ impl Client {
377388 work_done_progress: Some(true),
378389 ..Default::default()
379390 }),
391+ general: Some(lsp::GeneralClientCapabilities {
392+ position_encodings: Some(vec![
393+ PositionEncodingKind::UTF32,
394+ PositionEncodingKind::UTF8,
395+ PositionEncodingKind::UTF16,
396+ ]),
397+ ..Default::default()
398+ }),
380399 ..Default::default()
381400 },
382401 trace: None,
@@ -577,7 +596,7 @@ impl Client {
577596 }]
578597 }
579598 lsp::TextDocumentSyncKind::INCREMENTAL => {
580− Self::changeset_to_changes(old_text, new_text, changes, self.offset_encoding)
599+ Self::changeset_to_changes(old_text, new_text, changes, self.offset_encoding())
581600 }
582601 lsp::TextDocumentSyncKind::NONE => return None,
583602 kind => unimplemented!("{:?}", kind),
@@ -1027,7 +1046,7 @@ impl Client {
10271046 partial_result_params: lsp::PartialResultParams::default(),
10281047 };
10291048
1030− Some(self.call::<lsp::request::WorkspaceSymbol>(params))
1049+ Some(self.call::<lsp::request::WorkspaceSymbolRequest>(params))
10311050 }
10321051
10331052 pub fn code_actions(
helix-lsp/src/lib.rs+17−5
@@ -20,7 +20,6 @@ use std::{
2020 },
2121};
2222
23−use serde::{Deserialize, Serialize};
2423use thiserror::Error;
2524use tokio_stream::wrappers::UnboundedReceiverStream;
2625
@@ -45,13 +44,14 @@ pub enum Error {
4544 Other(#[from] anyhow::Error),
4645}
4746
48−#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
47+#[derive(Clone, Copy, Debug, Default)]
4948pub enum OffsetEncoding {
5049 /// UTF-8 code units aka bytes
51− #[serde(rename = "utf-8")]
5250 Utf8,
51+ /// UTF-32 code units aka chars
52+ Utf32,
5353 /// UTF-16 code units
54− #[serde(rename = "utf-16")]
54+ #[default]
5555 Utf16,
5656}
5757
@@ -168,6 +168,11 @@ pub mod util {
168168 let line_end = line_end_char_index(&doc.slice(..), pos_line);
169169 doc.char_to_utf16_cu(line_start)..doc.char_to_utf16_cu(line_end)
170170 }
171+ OffsetEncoding::Utf32 => {
172+ let line_start = doc.line_to_char(pos_line);
173+ let line_end = line_end_char_index(&doc.slice(..), pos_line);
174+ line_start..line_end
175+ }
171176 };
172177
173178 // The LSP spec demands that the offset is capped to the end of the line
@@ -177,10 +182,10 @@ pub mod util {
177182 .unwrap_or(line.end)
178183 .min(line.end);
179184
180− // TODO prefer UTF32/char indices to avoid this step
181185 match offset_encoding {
182186 OffsetEncoding::Utf8 => doc.try_byte_to_char(pos).ok(),
183187 OffsetEncoding::Utf16 => doc.try_utf16_cu_to_char(pos).ok(),
188+ OffsetEncoding::Utf32 => Some(pos),
184189 }
185190 }
186191
@@ -205,6 +210,13 @@ pub mod util {
205210 let line_start = doc.char_to_utf16_cu(doc.line_to_char(line));
206211 let col = doc.char_to_utf16_cu(pos) - line_start;
207212
213+ lsp::Position::new(line as u32, col as u32)
214+ }
215+ OffsetEncoding::Utf32 => {
216+ let line = doc.char_to_line(pos);
217+ let line_start = doc.line_to_char(line);
218+ let col = pos - line_start;
219+
208220 lsp::Position::new(line as u32, col as u32)
209221 }
210222 }
helix-term/src/commands/lsp.rs+5−1
@@ -1,7 +1,10 @@
11use futures_util::FutureExt;
22use helix_lsp::{
33 block_on,
4− lsp::{self, CodeAction, CodeActionOrCommand, DiagnosticSeverity, NumberOrString},
4+ lsp::{
5+ self, CodeAction, CodeActionOrCommand, CodeActionTriggerKind, DiagnosticSeverity,
6+ NumberOrString,
7+ },
58 util::{diagnostic_to_lsp_diagnostic, lsp_pos_to_pos, lsp_range_to_range, range_to_lsp_range},
69 OffsetEncoding,
710};
@@ -561,6 +564,7 @@ pub fn code_action(cx: &mut Context) {
561564 .map(|diag| diagnostic_to_lsp_diagnostic(doc.text(), diag, offset_encoding))
562565 .collect(),
563566 only: None,
567+ trigger_kind: Some(CodeActionTriggerKind::INVOKED),
564568 },
565569 ) {
566570 Some(future) => future,