on master help sign in
Negotiate LSP Position Encoding (#5894)
93c7afcPascal Kuthe
Cargo.lock+2−2
@@ -1481,9 +1481,9 @@ dependencies = [
1481
1481
1482[[package]]
1482[[package]]
1483name = "lsp-types"
1483name = "lsp-types"
1484−version = "0.93.2"
1484+version = "0.94.0"
1485source = "registry+https://github.com/rust-lang/crates.io-index"
1485source = "registry+https://github.com/rust-lang/crates.io-index"
1486−checksum = "9be6e9c7e2d18f651974370d7aff703f9513e0df6e464fd795660edc77e6ca51"
1486+checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237"
1487dependencies = [
1487dependencies = [
1488 "bitflags",
1488 "bitflags",
1489 "serde",
1489 "serde",
helix-lsp/Cargo.toml+1−1
@@ -19,7 +19,7 @@ anyhow = "1.0"
19futures-executor = "0.3"
19futures-executor = "0.3"
20futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
20futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
21log = "0.4"
21log = "0.4"
22−lsp-types = { version = "0.93", features = ["proposed"] }
22+lsp-types = { version = "0.94" }
23serde = { version = "1.0", features = ["derive"] }
23serde = { version = "1.0", features = ["derive"] }
24serde_json = "1.0"
24serde_json = "1.0"
25thiserror = "1.0"
25thiserror = "1.0"
helix-lsp/src/client.rs+24−5
@@ -6,6 +6,7 @@ use crate::{
6
6
7use helix_core::{find_root, ChangeSet, Rope};
7use helix_core::{find_root, ChangeSet, Rope};
8use helix_loader::{self, VERSION_AND_GIT_HASH};
8use helix_loader::{self, VERSION_AND_GIT_HASH};
9+use lsp::PositionEncodingKind;
9use lsp_types as lsp;
10use lsp_types as lsp;
10use serde::Deserialize;
11use serde::Deserialize;
11use serde_json::Value;
12use serde_json::Value;
@@ -32,7 +33,6 @@ pub struct Client {
32 server_tx: UnboundedSender<Payload>,
33 server_tx: UnboundedSender<Payload>,
33 request_counter: AtomicU64,
34 request_counter: AtomicU64,
34 pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
35 pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
35− offset_encoding: OffsetEncoding,
36 config: Option<Value>,
36 config: Option<Value>,
37 root_path: std::path::PathBuf,
37 root_path: std::path::PathBuf,
38 root_uri: Option<lsp::Url>,
38 root_uri: Option<lsp::Url>,
@@ -104,7 +104,6 @@ impl Client {
104 server_tx,
104 server_tx,
105 request_counter: AtomicU64::new(0),
105 request_counter: AtomicU64::new(0),
106 capabilities: OnceCell::new(),
106 capabilities: OnceCell::new(),
107− offset_encoding: OffsetEncoding::Utf16,
108 config,
107 config,
109 req_timeout,
108 req_timeout,
110
109
@@ -147,7 +146,19 @@ impl Client {
147 }
146 }
148
147
149 pub fn offset_encoding(&self) -> OffsetEncoding {
148 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()
151 }
162 }
152
163
153 pub fn config(&self) -> Option<&Value> {
164 pub fn config(&self) -> Option<&Value> {
@@ -377,6 +388,14 @@ impl Client {
377 work_done_progress: Some(true),
388 work_done_progress: Some(true),
378 ..Default::default()
389 ..Default::default()
379 }),
390 }),
391+ general: Some(lsp::GeneralClientCapabilities {
392+ position_encodings: Some(vec![
393+ PositionEncodingKind::UTF32,
394+ PositionEncodingKind::UTF8,
395+ PositionEncodingKind::UTF16,
396+ ]),
397+ ..Default::default()
398+ }),
380 ..Default::default()
399 ..Default::default()
381 },
400 },
382 trace: None,
401 trace: None,
@@ -577,7 +596,7 @@ impl Client {
577 }]
596 }]
578 }
597 }
579 lsp::TextDocumentSyncKind::INCREMENTAL => {
598 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())
581 }
600 }
582 lsp::TextDocumentSyncKind::NONE => return None,
601 lsp::TextDocumentSyncKind::NONE => return None,
583 kind => unimplemented!("{:?}", kind),
602 kind => unimplemented!("{:?}", kind),
@@ -1027,7 +1046,7 @@ impl Client {
1027 partial_result_params: lsp::PartialResultParams::default(),
1046 partial_result_params: lsp::PartialResultParams::default(),
1028 };
1047 };
1029
1048
1030− Some(self.call::<lsp::request::WorkspaceSymbol>(params))
1049+ Some(self.call::<lsp::request::WorkspaceSymbolRequest>(params))
1031 }
1050 }
1032
1051
1033 pub fn code_actions(
1052 pub fn code_actions(
helix-lsp/src/lib.rs+17−5
@@ -20,7 +20,6 @@ use std::{
20 },
20 },
21};
21};
22
22
23−use serde::{Deserialize, Serialize};
24use thiserror::Error;
23use thiserror::Error;
25use tokio_stream::wrappers::UnboundedReceiverStream;
24use tokio_stream::wrappers::UnboundedReceiverStream;
26
25
@@ -45,13 +44,14 @@ pub enum Error {
45 Other(#[from] anyhow::Error),
44 Other(#[from] anyhow::Error),
46}
45}
47
46
48−#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
47+#[derive(Clone, Copy, Debug, Default)]
49pub enum OffsetEncoding {
48pub enum OffsetEncoding {
50 /// UTF-8 code units aka bytes
49 /// UTF-8 code units aka bytes
51− #[serde(rename = "utf-8")]
52 Utf8,
50 Utf8,
51+ /// UTF-32 code units aka chars
52+ Utf32,
53 /// UTF-16 code units
53 /// UTF-16 code units
54− #[serde(rename = "utf-16")]
54+ #[default]
55 Utf16,
55 Utf16,
56}
56}
57
57
@@ -168,6 +168,11 @@ pub mod util {
168 let line_end = line_end_char_index(&doc.slice(..), pos_line);
168 let line_end = line_end_char_index(&doc.slice(..), pos_line);
169 doc.char_to_utf16_cu(line_start)..doc.char_to_utf16_cu(line_end)
169 doc.char_to_utf16_cu(line_start)..doc.char_to_utf16_cu(line_end)
170 }
170 }
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+ }
171 };
176 };
172
177
173 // The LSP spec demands that the offset is capped to the end of the line
178 // The LSP spec demands that the offset is capped to the end of the line
@@ -177,10 +182,10 @@ pub mod util {
177 .unwrap_or(line.end)
182 .unwrap_or(line.end)
178 .min(line.end);
183 .min(line.end);
179
184
180− // TODO prefer UTF32/char indices to avoid this step
181 match offset_encoding {
185 match offset_encoding {
182 OffsetEncoding::Utf8 => doc.try_byte_to_char(pos).ok(),
186 OffsetEncoding::Utf8 => doc.try_byte_to_char(pos).ok(),
183 OffsetEncoding::Utf16 => doc.try_utf16_cu_to_char(pos).ok(),
187 OffsetEncoding::Utf16 => doc.try_utf16_cu_to_char(pos).ok(),
188+ OffsetEncoding::Utf32 => Some(pos),
184 }
189 }
185 }
190 }
186
191
@@ -205,6 +210,13 @@ pub mod util {
205 let line_start = doc.char_to_utf16_cu(doc.line_to_char(line));
210 let line_start = doc.char_to_utf16_cu(doc.line_to_char(line));
206 let col = doc.char_to_utf16_cu(pos) - line_start;
211 let col = doc.char_to_utf16_cu(pos) - line_start;
207
212
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+
208 lsp::Position::new(line as u32, col as u32)
220 lsp::Position::new(line as u32, col as u32)
209 }
221 }
210 }
222 }
helix-term/src/commands/lsp.rs+5−1
@@ -1,7 +1,10 @@
1use futures_util::FutureExt;
1use futures_util::FutureExt;
2use helix_lsp::{
2use helix_lsp::{
3 block_on,
3 block_on,
4+ lsp::{
4− lsp::{self, CodeAction, CodeActionOrCommand, DiagnosticSeverity, NumberOrString},
5+ self, CodeAction, CodeActionOrCommand, CodeActionTriggerKind, DiagnosticSeverity,
6+ NumberOrString,
7+ },
5 util::{diagnostic_to_lsp_diagnostic, lsp_pos_to_pos, lsp_range_to_range, range_to_lsp_range},
8 util::{diagnostic_to_lsp_diagnostic, lsp_pos_to_pos, lsp_range_to_range, range_to_lsp_range},
6 OffsetEncoding,
9 OffsetEncoding,
7};
10};
@@ -561,6 +564,7 @@ pub fn code_action(cx: &mut Context) {
561 .map(|diag| diagnostic_to_lsp_diagnostic(doc.text(), diag, offset_encoding))
564 .map(|diag| diagnostic_to_lsp_diagnostic(doc.text(), diag, offset_encoding))
562 .collect(),
565 .collect(),
563 only: None,
566 only: None,
567+ trigger_kind: Some(CodeActionTriggerKind::INVOKED),
564 },
568 },
565 ) {
569 ) {
566 Some(future) => future,
570 Some(future) => future,