aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide/src/lib.rs2
-rw-r--r--crates/ide_db/src/line_index.rs29
-rw-r--r--crates/ide_db/src/line_index/tests.rs33
-rw-r--r--crates/rust-analyzer/src/bin/main.rs8
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs2
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs8
-rw-r--r--crates/rust-analyzer/src/config.rs12
-rw-r--r--crates/rust-analyzer/src/from_proto.rs32
-rw-r--r--crates/rust-analyzer/src/global_state.rs11
-rw-r--r--crates/rust-analyzer/src/handlers.rs91
-rw-r--r--crates/rust-analyzer/src/lib.rs2
-rw-r--r--crates/rust-analyzer/src/line_index.rs (renamed from crates/rust-analyzer/src/line_endings.rs)16
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs4
-rw-r--r--crates/rust-analyzer/src/lsp_utils.rs19
-rw-r--r--crates/rust-analyzer/src/to_proto.rs90
-rw-r--r--docs/dev/lsp-extensions.md8
16 files changed, 221 insertions, 146 deletions
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 89e7bef7d..a2c8db505 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -95,7 +95,7 @@ pub use ide_db::{
95 }, 95 },
96 call_info::CallInfo, 96 call_info::CallInfo,
97 label::Label, 97 label::Label,
98 line_index::{LineCol, LineIndex}, 98 line_index::{LineCol, LineColUtf16, LineIndex},
99 search::{ReferenceAccess, SearchScope}, 99 search::{ReferenceAccess, SearchScope},
100 source_change::{FileSystemEdit, SourceChange}, 100 source_change::{FileSystemEdit, SourceChange},
101 symbol_index::Query, 101 symbol_index::Query,
diff --git a/crates/ide_db/src/line_index.rs b/crates/ide_db/src/line_index.rs
index 41226305e..8e9d8cca2 100644
--- a/crates/ide_db/src/line_index.rs
+++ b/crates/ide_db/src/line_index.rs
@@ -15,11 +15,19 @@ pub struct LineIndex {
15} 15}
16 16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub struct LineCol { 18pub struct LineColUtf16 {
19 /// Zero-based 19 /// Zero-based
20 pub line: u32, 20 pub line: u32,
21 /// Zero-based 21 /// Zero-based
22 pub col_utf16: u32, 22 pub col: u32,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26pub struct LineCol {
27 /// Zero-based
28 pub line: u32,
29 /// Zero-based utf8 offset
30 pub col: u32,
23} 31}
24 32
25#[derive(Clone, Debug, Hash, PartialEq, Eq)] 33#[derive(Clone, Debug, Hash, PartialEq, Eq)]
@@ -92,14 +100,21 @@ impl LineIndex {
92 let line = partition_point(&self.newlines, |&it| it <= offset) - 1; 100 let line = partition_point(&self.newlines, |&it| it <= offset) - 1;
93 let line_start_offset = self.newlines[line]; 101 let line_start_offset = self.newlines[line];
94 let col = offset - line_start_offset; 102 let col = offset - line_start_offset;
95 103 LineCol { line: line as u32, col: col.into() }
96 LineCol { line: line as u32, col_utf16: self.utf8_to_utf16_col(line as u32, col) as u32 }
97 } 104 }
98 105
99 pub fn offset(&self, line_col: LineCol) -> TextSize { 106 pub fn offset(&self, line_col: LineCol) -> TextSize {
100 //FIXME: return Result 107 self.newlines[line_col.line as usize] + TextSize::from(line_col.col)
101 let col = self.utf16_to_utf8_col(line_col.line, line_col.col_utf16); 108 }
102 self.newlines[line_col.line as usize] + col 109
110 pub fn to_utf16(&self, line_col: LineCol) -> LineColUtf16 {
111 let col = self.utf8_to_utf16_col(line_col.line, line_col.col.into());
112 LineColUtf16 { line: line_col.line, col: col as u32 }
113 }
114
115 pub fn to_utf8(&self, line_col: LineColUtf16) -> LineCol {
116 let col = self.utf16_to_utf8_col(line_col.line, line_col.col);
117 LineCol { line: line_col.line, col: col.into() }
103 } 118 }
104 119
105 pub fn lines(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ { 120 pub fn lines(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ {
diff --git a/crates/ide_db/src/line_index/tests.rs b/crates/ide_db/src/line_index/tests.rs
index 05f7484e8..09f3bca62 100644
--- a/crates/ide_db/src/line_index/tests.rs
+++ b/crates/ide_db/src/line_index/tests.rs
@@ -3,24 +3,29 @@ use super::*;
3#[test] 3#[test]
4fn test_line_index() { 4fn test_line_index() {
5 let text = "hello\nworld"; 5 let text = "hello\nworld";
6 let table = [
7 (00, 0, 0),
8 (01, 0, 1),
9 (05, 0, 5),
10 (06, 1, 0),
11 (07, 1, 1),
12 (08, 1, 2),
13 (10, 1, 4),
14 (11, 1, 5),
15 (12, 1, 6),
16 ];
17
6 let index = LineIndex::new(text); 18 let index = LineIndex::new(text);
7 assert_eq!(index.line_col(0.into()), LineCol { line: 0, col_utf16: 0 }); 19 for &(offset, line, col) in &table {
8 assert_eq!(index.line_col(1.into()), LineCol { line: 0, col_utf16: 1 }); 20 assert_eq!(index.line_col(offset.into()), LineCol { line, col });
9 assert_eq!(index.line_col(5.into()), LineCol { line: 0, col_utf16: 5 }); 21 }
10 assert_eq!(index.line_col(6.into()), LineCol { line: 1, col_utf16: 0 });
11 assert_eq!(index.line_col(7.into()), LineCol { line: 1, col_utf16: 1 });
12 assert_eq!(index.line_col(8.into()), LineCol { line: 1, col_utf16: 2 });
13 assert_eq!(index.line_col(10.into()), LineCol { line: 1, col_utf16: 4 });
14 assert_eq!(index.line_col(11.into()), LineCol { line: 1, col_utf16: 5 });
15 assert_eq!(index.line_col(12.into()), LineCol { line: 1, col_utf16: 6 });
16 22
17 let text = "\nhello\nworld"; 23 let text = "\nhello\nworld";
24 let table = [(0, 0, 0), (1, 1, 0), (2, 1, 1), (6, 1, 5), (7, 2, 0)];
18 let index = LineIndex::new(text); 25 let index = LineIndex::new(text);
19 assert_eq!(index.line_col(0.into()), LineCol { line: 0, col_utf16: 0 }); 26 for &(offset, line, col) in &table {
20 assert_eq!(index.line_col(1.into()), LineCol { line: 1, col_utf16: 0 }); 27 assert_eq!(index.line_col(offset.into()), LineCol { line, col });
21 assert_eq!(index.line_col(2.into()), LineCol { line: 1, col_utf16: 1 }); 28 }
22 assert_eq!(index.line_col(6.into()), LineCol { line: 1, col_utf16: 5 });
23 assert_eq!(index.line_col(7.into()), LineCol { line: 2, col_utf16: 0 });
24} 29}
25 30
26#[test] 31#[test]
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index 93d0ad4ec..89482b952 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -8,7 +8,7 @@ use std::{convert::TryFrom, env, fs, path::PathBuf, process};
8 8
9use lsp_server::Connection; 9use lsp_server::Connection;
10use project_model::ProjectManifest; 10use project_model::ProjectManifest;
11use rust_analyzer::{cli, config::Config, from_json, Result}; 11use rust_analyzer::{cli, config::Config, from_json, lsp_ext::supports_utf8, Result};
12use vfs::AbsPathBuf; 12use vfs::AbsPathBuf;
13 13
14#[cfg(all(feature = "mimalloc"))] 14#[cfg(all(feature = "mimalloc"))]
@@ -127,7 +127,11 @@ fn run_server() -> Result<()> {
127 name: String::from("rust-analyzer"), 127 name: String::from("rust-analyzer"),
128 version: Some(String::from(env!("REV"))), 128 version: Some(String::from(env!("REV"))),
129 }), 129 }),
130 offset_encoding: None, 130 offset_encoding: if supports_utf8(&initialize_params.capabilities) {
131 Some("utf-8".to_string())
132 } else {
133 None
134 },
131 }; 135 };
132 136
133 let initialize_result = serde_json::to_value(initialize_result).unwrap(); 137 let initialize_result = serde_json::to_value(initialize_result).unwrap();
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index 6735b6388..877abd12b 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -97,7 +97,7 @@ impl BenchCmd {
97 let offset = host 97 let offset = host
98 .analysis() 98 .analysis()
99 .file_line_index(file_id)? 99 .file_line_index(file_id)?
100 .offset(LineCol { line: pos.line - 1, col_utf16: pos.column }); 100 .offset(LineCol { line: pos.line - 1, col: pos.column });
101 let file_position = FilePosition { file_id, offset }; 101 let file_position = FilePosition { file_id, offset };
102 102
103 if is_completion { 103 if is_completion {
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 3417af687..6d6f398f4 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -218,9 +218,9 @@ impl AnalysisStatsCmd {
218 bar.println(format!( 218 bar.println(format!(
219 "{}:{}-{}:{}: {}", 219 "{}:{}-{}:{}: {}",
220 start.line + 1, 220 start.line + 1,
221 start.col_utf16, 221 start.col,
222 end.line + 1, 222 end.line + 1,
223 end.col_utf16, 223 end.col,
224 ty.display(db) 224 ty.display(db)
225 )); 225 ));
226 } else { 226 } else {
@@ -250,9 +250,9 @@ impl AnalysisStatsCmd {
250 "{} {}:{}-{}:{}: Expected {}, got {}", 250 "{} {}:{}-{}:{}: Expected {}, got {}",
251 path, 251 path,
252 start.line + 1, 252 start.line + 1,
253 start.col_utf16, 253 start.col,
254 end.line + 1, 254 end.line + 1,
255 end.col_utf16, 255 end.col,
256 mismatch.expected.display(db), 256 mismatch.expected.display(db),
257 mismatch.actual.display(db) 257 mismatch.actual.display(db)
258 )); 258 ));
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 04a77d677..556fc2eeb 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -23,7 +23,10 @@ use rustc_hash::FxHashSet;
23use serde::{de::DeserializeOwned, Deserialize}; 23use serde::{de::DeserializeOwned, Deserialize};
24use vfs::AbsPathBuf; 24use vfs::AbsPathBuf;
25 25
26use crate::{caps::completion_item_edit_resolve, diagnostics::DiagnosticsMapConfig}; 26use crate::{
27 caps::completion_item_edit_resolve, diagnostics::DiagnosticsMapConfig,
28 line_index::OffsetEncoding, lsp_ext::supports_utf8,
29};
27 30
28config_data! { 31config_data! {
29 struct ConfigData { 32 struct ConfigData {
@@ -415,6 +418,13 @@ impl Config {
415 false 418 false
416 ) 419 )
417 } 420 }
421 pub fn offset_encoding(&self) -> OffsetEncoding {
422 if supports_utf8(&self.caps) {
423 OffsetEncoding::Utf8
424 } else {
425 OffsetEncoding::Utf16
426 }
427 }
418 428
419 fn experimental(&self, index: &'static str) -> bool { 429 fn experimental(&self, index: &'static str) -> bool {
420 try_or!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?, false) 430 try_or!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?, false)
diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs
index 6676eebf4..5b02b2598 100644
--- a/crates/rust-analyzer/src/from_proto.rs
+++ b/crates/rust-analyzer/src/from_proto.rs
@@ -1,12 +1,17 @@
1//! Conversion lsp_types types to rust-analyzer specific ones. 1//! Conversion lsp_types types to rust-analyzer specific ones.
2use std::convert::TryFrom; 2use std::convert::TryFrom;
3 3
4use ide::{Annotation, AnnotationKind, AssistKind, LineCol, LineIndex}; 4use ide::{Annotation, AnnotationKind, AssistKind, LineCol, LineColUtf16};
5use ide_db::base_db::{FileId, FilePosition, FileRange}; 5use ide_db::base_db::{FileId, FilePosition, FileRange};
6use syntax::{TextRange, TextSize}; 6use syntax::{TextRange, TextSize};
7use vfs::AbsPathBuf; 7use vfs::AbsPathBuf;
8 8
9use crate::{from_json, global_state::GlobalStateSnapshot, lsp_ext, Result}; 9use crate::{
10 from_json,
11 global_state::GlobalStateSnapshot,
12 line_index::{LineIndex, OffsetEncoding},
13 lsp_ext, Result,
14};
10 15
11pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> { 16pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> {
12 let path = url.to_file_path().map_err(|()| "url is not a file")?; 17 let path = url.to_file_path().map_err(|()| "url is not a file")?;
@@ -18,8 +23,17 @@ pub(crate) fn vfs_path(url: &lsp_types::Url) -> Result<vfs::VfsPath> {
18} 23}
19 24
20pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> TextSize { 25pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> TextSize {
21 let line_col = LineCol { line: position.line as u32, col_utf16: position.character as u32 }; 26 let line_col = match line_index.encoding {
22 line_index.offset(line_col) 27 OffsetEncoding::Utf8 => {
28 LineCol { line: position.line as u32, col: position.character as u32 }
29 }
30 OffsetEncoding::Utf16 => {
31 let line_col =
32 LineColUtf16 { line: position.line as u32, col: position.character as u32 };
33 line_index.index.to_utf8(line_col)
34 }
35 };
36 line_index.index.offset(line_col)
23} 37}
24 38
25pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> TextRange { 39pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> TextRange {
@@ -37,8 +51,8 @@ pub(crate) fn file_position(
37 tdpp: lsp_types::TextDocumentPositionParams, 51 tdpp: lsp_types::TextDocumentPositionParams,
38) -> Result<FilePosition> { 52) -> Result<FilePosition> {
39 let file_id = file_id(world, &tdpp.text_document.uri)?; 53 let file_id = file_id(world, &tdpp.text_document.uri)?;
40 let line_index = world.analysis.file_line_index(file_id)?; 54 let line_index = world.file_line_index(file_id)?;
41 let offset = offset(&*line_index, tdpp.position); 55 let offset = offset(&line_index, tdpp.position);
42 Ok(FilePosition { file_id, offset }) 56 Ok(FilePosition { file_id, offset })
43} 57}
44 58
@@ -48,7 +62,7 @@ pub(crate) fn file_range(
48 range: lsp_types::Range, 62 range: lsp_types::Range,
49) -> Result<FileRange> { 63) -> Result<FileRange> {
50 let file_id = file_id(world, &text_document_identifier.uri)?; 64 let file_id = file_id(world, &text_document_identifier.uri)?;
51 let line_index = world.analysis.file_line_index(file_id)?; 65 let line_index = world.file_line_index(file_id)?;
52 let range = text_range(&line_index, range); 66 let range = text_range(&line_index, range);
53 Ok(FileRange { file_id, range }) 67 Ok(FileRange { file_id, range })
54} 68}
@@ -78,7 +92,7 @@ pub(crate) fn annotation(
78 lsp_ext::CodeLensResolveData::Impls(params) => { 92 lsp_ext::CodeLensResolveData::Impls(params) => {
79 let file_id = 93 let file_id =
80 world.url_to_file_id(&params.text_document_position_params.text_document.uri)?; 94 world.url_to_file_id(&params.text_document_position_params.text_document.uri)?;
81 let line_index = world.analysis.file_line_index(file_id)?; 95 let line_index = world.file_line_index(file_id)?;
82 96
83 Ok(Annotation { 97 Ok(Annotation {
84 range: text_range(&line_index, code_lens.range), 98 range: text_range(&line_index, code_lens.range),
@@ -90,7 +104,7 @@ pub(crate) fn annotation(
90 } 104 }
91 lsp_ext::CodeLensResolveData::References(params) => { 105 lsp_ext::CodeLensResolveData::References(params) => {
92 let file_id = world.url_to_file_id(&params.text_document.uri)?; 106 let file_id = world.url_to_file_id(&params.text_document.uri)?;
93 let line_index = world.analysis.file_line_index(file_id)?; 107 let line_index = world.file_line_index(file_id)?;
94 108
95 Ok(Annotation { 109 Ok(Annotation {
96 range: text_range(&line_index, code_lens.range), 110 range: text_range(&line_index, code_lens.range),
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index c3bc8791d..52c249713 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -7,7 +7,7 @@ use std::{sync::Arc, time::Instant};
7 7
8use crossbeam_channel::{unbounded, Receiver, Sender}; 8use crossbeam_channel::{unbounded, Receiver, Sender};
9use flycheck::FlycheckHandle; 9use flycheck::FlycheckHandle;
10use ide::{Analysis, AnalysisHost, Change, FileId}; 10use ide::{Analysis, AnalysisHost, Cancelable, Change, FileId};
11use ide_db::base_db::{CrateId, VfsPath}; 11use ide_db::base_db::{CrateId, VfsPath};
12use lsp_types::{SemanticTokens, Url}; 12use lsp_types::{SemanticTokens, Url};
13use parking_lot::{Mutex, RwLock}; 13use parking_lot::{Mutex, RwLock};
@@ -22,7 +22,7 @@ use crate::{
22 diagnostics::{CheckFixes, DiagnosticCollection}, 22 diagnostics::{CheckFixes, DiagnosticCollection},
23 document::DocumentData, 23 document::DocumentData,
24 from_proto, 24 from_proto,
25 line_endings::LineEndings, 25 line_index::{LineEndings, LineIndex},
26 main_loop::Task, 26 main_loop::Task,
27 op_queue::OpQueue, 27 op_queue::OpQueue,
28 reload::SourceRootConfig, 28 reload::SourceRootConfig,
@@ -271,8 +271,11 @@ impl GlobalStateSnapshot {
271 file_id_to_url(&self.vfs.read().0, id) 271 file_id_to_url(&self.vfs.read().0, id)
272 } 272 }
273 273
274 pub(crate) fn file_line_endings(&self, id: FileId) -> LineEndings { 274 pub(crate) fn file_line_index(&self, file_id: FileId) -> Cancelable<LineIndex> {
275 self.vfs.read().1[&id] 275 let endings = self.vfs.read().1[&file_id];
276 let index = self.analysis.file_line_index(file_id)?;
277 let res = LineIndex { index, endings, encoding: self.config.offset_encoding() };
278 Ok(res)
276 } 279 }
277 280
278 pub(crate) fn url_file_version(&self, url: &Url) -> Option<i32> { 281 pub(crate) fn url_file_version(&self, url: &Url) -> Option<i32> {
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 2d697c75f..4f6f250d6 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -5,12 +5,11 @@
5use std::{ 5use std::{
6 io::Write as _, 6 io::Write as _,
7 process::{self, Stdio}, 7 process::{self, Stdio},
8 sync::Arc,
9}; 8};
10 9
11use ide::{ 10use ide::{
12 AnnotationConfig, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, LineIndex, 11 AnnotationConfig, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, Query,
13 Query, RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, TextEdit, 12 RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, TextEdit,
14}; 13};
15use ide_db::SymbolKind; 14use ide_db::SymbolKind;
16use itertools::Itertools; 15use itertools::Itertools;
@@ -37,7 +36,7 @@ use crate::{
37 diff::diff, 36 diff::diff,
38 from_proto, 37 from_proto,
39 global_state::{GlobalState, GlobalStateSnapshot}, 38 global_state::{GlobalState, GlobalStateSnapshot},
40 line_endings::LineEndings, 39 line_index::{LineEndings, LineIndex},
41 lsp_ext::{self, InlayHint, InlayHintsParams}, 40 lsp_ext::{self, InlayHint, InlayHintsParams},
42 lsp_utils::all_edits_are_disjoint, 41 lsp_utils::all_edits_are_disjoint,
43 to_proto, LspError, Result, 42 to_proto, LspError, Result,
@@ -100,7 +99,7 @@ pub(crate) fn handle_syntax_tree(
100) -> Result<String> { 99) -> Result<String> {
101 let _p = profile::span("handle_syntax_tree"); 100 let _p = profile::span("handle_syntax_tree");
102 let id = from_proto::file_id(&snap, &params.text_document.uri)?; 101 let id = from_proto::file_id(&snap, &params.text_document.uri)?;
103 let line_index = snap.analysis.file_line_index(id)?; 102 let line_index = snap.file_line_index(id)?;
104 let text_range = params.range.map(|r| from_proto::text_range(&line_index, r)); 103 let text_range = params.range.map(|r| from_proto::text_range(&line_index, r));
105 let res = snap.analysis.syntax_tree(id, text_range)?; 104 let res = snap.analysis.syntax_tree(id, text_range)?;
106 Ok(res) 105 Ok(res)
@@ -122,7 +121,7 @@ pub(crate) fn handle_expand_macro(
122) -> Result<Option<lsp_ext::ExpandedMacro>> { 121) -> Result<Option<lsp_ext::ExpandedMacro>> {
123 let _p = profile::span("handle_expand_macro"); 122 let _p = profile::span("handle_expand_macro");
124 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; 123 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
125 let line_index = snap.analysis.file_line_index(file_id)?; 124 let line_index = snap.file_line_index(file_id)?;
126 let offset = from_proto::offset(&line_index, params.position); 125 let offset = from_proto::offset(&line_index, params.position);
127 126
128 let res = snap.analysis.expand_macro(FilePosition { file_id, offset })?; 127 let res = snap.analysis.expand_macro(FilePosition { file_id, offset })?;
@@ -135,7 +134,7 @@ pub(crate) fn handle_selection_range(
135) -> Result<Option<Vec<lsp_types::SelectionRange>>> { 134) -> Result<Option<Vec<lsp_types::SelectionRange>>> {
136 let _p = profile::span("handle_selection_range"); 135 let _p = profile::span("handle_selection_range");
137 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; 136 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
138 let line_index = snap.analysis.file_line_index(file_id)?; 137 let line_index = snap.file_line_index(file_id)?;
139 let res: Result<Vec<lsp_types::SelectionRange>> = params 138 let res: Result<Vec<lsp_types::SelectionRange>> = params
140 .positions 139 .positions
141 .into_iter() 140 .into_iter()
@@ -178,7 +177,7 @@ pub(crate) fn handle_matching_brace(
178) -> Result<Vec<Position>> { 177) -> Result<Vec<Position>> {
179 let _p = profile::span("handle_matching_brace"); 178 let _p = profile::span("handle_matching_brace");
180 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; 179 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
181 let line_index = snap.analysis.file_line_index(file_id)?; 180 let line_index = snap.file_line_index(file_id)?;
182 let res = params 181 let res = params
183 .positions 182 .positions
184 .into_iter() 183 .into_iter()
@@ -200,8 +199,7 @@ pub(crate) fn handle_join_lines(
200) -> Result<Vec<lsp_types::TextEdit>> { 199) -> Result<Vec<lsp_types::TextEdit>> {
201 let _p = profile::span("handle_join_lines"); 200 let _p = profile::span("handle_join_lines");
202 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; 201 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
203 let line_index = snap.analysis.file_line_index(file_id)?; 202 let line_index = snap.file_line_index(file_id)?;
204 let line_endings = snap.file_line_endings(file_id);
205 let mut res = TextEdit::default(); 203 let mut res = TextEdit::default();
206 for range in params.ranges { 204 for range in params.ranges {
207 let range = from_proto::text_range(&line_index, range); 205 let range = from_proto::text_range(&line_index, range);
@@ -213,7 +211,7 @@ pub(crate) fn handle_join_lines(
213 } 211 }
214 } 212 }
215 } 213 }
216 let res = to_proto::text_edit_vec(&line_index, line_endings, res); 214 let res = to_proto::text_edit_vec(&line_index, res);
217 Ok(res) 215 Ok(res)
218} 216}
219 217
@@ -227,9 +225,8 @@ pub(crate) fn handle_on_enter(
227 None => return Ok(None), 225 None => return Ok(None),
228 Some(it) => it, 226 Some(it) => it,
229 }; 227 };
230 let line_index = snap.analysis.file_line_index(position.file_id)?; 228 let line_index = snap.file_line_index(position.file_id)?;
231 let line_endings = snap.file_line_endings(position.file_id); 229 let edit = to_proto::snippet_text_edit_vec(&line_index, true, edit);
232 let edit = to_proto::snippet_text_edit_vec(&line_index, line_endings, true, edit);
233 Ok(Some(edit)) 230 Ok(Some(edit))
234} 231}
235 232
@@ -240,8 +237,7 @@ pub(crate) fn handle_on_type_formatting(
240) -> Result<Option<Vec<lsp_types::TextEdit>>> { 237) -> Result<Option<Vec<lsp_types::TextEdit>>> {
241 let _p = profile::span("handle_on_type_formatting"); 238 let _p = profile::span("handle_on_type_formatting");
242 let mut position = from_proto::file_position(&snap, params.text_document_position)?; 239 let mut position = from_proto::file_position(&snap, params.text_document_position)?;
243 let line_index = snap.analysis.file_line_index(position.file_id)?; 240 let line_index = snap.file_line_index(position.file_id)?;
244 let line_endings = snap.file_line_endings(position.file_id);
245 241
246 // in `ide`, the `on_type` invariant is that 242 // in `ide`, the `on_type` invariant is that
247 // `text.char_at(position) == typed_char`. 243 // `text.char_at(position) == typed_char`.
@@ -269,7 +265,7 @@ pub(crate) fn handle_on_type_formatting(
269 // This should be a single-file edit 265 // This should be a single-file edit
270 let (_, edit) = edit.source_file_edits.into_iter().next().unwrap(); 266 let (_, edit) = edit.source_file_edits.into_iter().next().unwrap();
271 267
272 let change = to_proto::text_edit_vec(&line_index, line_endings, edit); 268 let change = to_proto::text_edit_vec(&line_index, edit);
273 Ok(Some(change)) 269 Ok(Some(change))
274} 270}
275 271
@@ -279,7 +275,7 @@ pub(crate) fn handle_document_symbol(
279) -> Result<Option<lsp_types::DocumentSymbolResponse>> { 275) -> Result<Option<lsp_types::DocumentSymbolResponse>> {
280 let _p = profile::span("handle_document_symbol"); 276 let _p = profile::span("handle_document_symbol");
281 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; 277 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
282 let line_index = snap.analysis.file_line_index(file_id)?; 278 let line_index = snap.file_line_index(file_id)?;
283 279
284 let mut parents: Vec<(lsp_types::DocumentSymbol, Option<usize>)> = Vec::new(); 280 let mut parents: Vec<(lsp_types::DocumentSymbol, Option<usize>)> = Vec::new();
285 281
@@ -535,7 +531,7 @@ pub(crate) fn handle_runnables(
535) -> Result<Vec<lsp_ext::Runnable>> { 531) -> Result<Vec<lsp_ext::Runnable>> {
536 let _p = profile::span("handle_runnables"); 532 let _p = profile::span("handle_runnables");
537 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; 533 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
538 let line_index = snap.analysis.file_line_index(file_id)?; 534 let line_index = snap.file_line_index(file_id)?;
539 let offset = params.position.map(|it| from_proto::offset(&line_index, it)); 535 let offset = params.position.map(|it| from_proto::offset(&line_index, it));
540 let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; 536 let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
541 537
@@ -645,14 +641,12 @@ pub(crate) fn handle_completion(
645 None => return Ok(None), 641 None => return Ok(None),
646 Some(items) => items, 642 Some(items) => items,
647 }; 643 };
648 let line_index = snap.analysis.file_line_index(position.file_id)?; 644 let line_index = snap.file_line_index(position.file_id)?;
649 let line_endings = snap.file_line_endings(position.file_id);
650 645
651 let items: Vec<CompletionItem> = items 646 let items: Vec<CompletionItem> = items
652 .into_iter() 647 .into_iter()
653 .flat_map(|item| { 648 .flat_map(|item| {
654 let mut new_completion_items = 649 let mut new_completion_items = to_proto::completion_item(&line_index, item.clone());
655 to_proto::completion_item(&line_index, line_endings, item.clone());
656 650
657 if completion_config.enable_imports_on_the_fly { 651 if completion_config.enable_imports_on_the_fly {
658 for new_item in &mut new_completion_items { 652 for new_item in &mut new_completion_items {
@@ -693,8 +687,7 @@ pub(crate) fn handle_completion_resolve(
693 }; 687 };
694 688
695 let file_id = from_proto::file_id(&snap, &resolve_data.position.text_document.uri)?; 689 let file_id = from_proto::file_id(&snap, &resolve_data.position.text_document.uri)?;
696 let line_index = snap.analysis.file_line_index(file_id)?; 690 let line_index = snap.file_line_index(file_id)?;
697 let line_endings = snap.file_line_endings(file_id);
698 let offset = from_proto::offset(&line_index, resolve_data.position.position); 691 let offset = from_proto::offset(&line_index, resolve_data.position.position);
699 692
700 let additional_edits = snap 693 let additional_edits = snap
@@ -707,9 +700,7 @@ pub(crate) fn handle_completion_resolve(
707 resolve_data.import_for_trait_assoc_item, 700 resolve_data.import_for_trait_assoc_item,
708 )? 701 )?
709 .into_iter() 702 .into_iter()
710 .flat_map(|edit| { 703 .flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel)))
711 edit.into_iter().map(|indel| to_proto::text_edit(&line_index, line_endings, indel))
712 })
713 .collect_vec(); 704 .collect_vec();
714 705
715 if !all_edits_are_disjoint(&original_completion, &additional_edits) { 706 if !all_edits_are_disjoint(&original_completion, &additional_edits) {
@@ -738,7 +729,7 @@ pub(crate) fn handle_folding_range(
738 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; 729 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
739 let folds = snap.analysis.folding_ranges(file_id)?; 730 let folds = snap.analysis.folding_ranges(file_id)?;
740 let text = snap.analysis.file_text(file_id)?; 731 let text = snap.analysis.file_text(file_id)?;
741 let line_index = snap.analysis.file_line_index(file_id)?; 732 let line_index = snap.file_line_index(file_id)?;
742 let line_folding_only = snap.config.line_folding_only(); 733 let line_folding_only = snap.config.line_folding_only();
743 let res = folds 734 let res = folds
744 .into_iter() 735 .into_iter()
@@ -775,7 +766,7 @@ pub(crate) fn handle_hover(
775 None => return Ok(None), 766 None => return Ok(None),
776 Some(info) => info, 767 Some(info) => info,
777 }; 768 };
778 let line_index = snap.analysis.file_line_index(position.file_id)?; 769 let line_index = snap.file_line_index(position.file_id)?;
779 let range = to_proto::range(&line_index, info.range); 770 let range = to_proto::range(&line_index, info.range);
780 let hover = lsp_ext::Hover { 771 let hover = lsp_ext::Hover {
781 hover: lsp_types::Hover { 772 hover: lsp_types::Hover {
@@ -797,7 +788,7 @@ pub(crate) fn handle_prepare_rename(
797 788
798 let change = snap.analysis.prepare_rename(position)?.map_err(to_proto::rename_error)?; 789 let change = snap.analysis.prepare_rename(position)?.map_err(to_proto::rename_error)?;
799 790
800 let line_index = snap.analysis.file_line_index(position.file_id)?; 791 let line_index = snap.file_line_index(position.file_id)?;
801 let range = to_proto::range(&line_index, change.range); 792 let range = to_proto::range(&line_index, change.range);
802 Ok(Some(PrepareRenameResponse::Range(range))) 793 Ok(Some(PrepareRenameResponse::Range(range)))
803} 794}
@@ -857,8 +848,7 @@ pub(crate) fn handle_formatting(
857 let file = snap.analysis.file_text(file_id)?; 848 let file = snap.analysis.file_text(file_id)?;
858 let crate_ids = snap.analysis.crate_for(file_id)?; 849 let crate_ids = snap.analysis.crate_for(file_id)?;
859 850
860 let file_line_index = snap.analysis.file_line_index(file_id)?; 851 let line_index = snap.file_line_index(file_id)?;
861 let file_line_endings = snap.file_line_endings(file_id);
862 852
863 let mut rustfmt = match snap.config.rustfmt() { 853 let mut rustfmt = match snap.config.rustfmt() {
864 RustfmtConfig::Rustfmt { extra_args } => { 854 RustfmtConfig::Rustfmt { extra_args } => {
@@ -935,24 +925,19 @@ pub(crate) fn handle_formatting(
935 925
936 let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout); 926 let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout);
937 927
938 if file_line_endings != new_line_endings { 928 if line_index.endings != new_line_endings {
939 // If line endings are different, send the entire file. 929 // If line endings are different, send the entire file.
940 // Diffing would not work here, as the line endings might be the only 930 // Diffing would not work here, as the line endings might be the only
941 // difference. 931 // difference.
942 Ok(Some(to_proto::text_edit_vec( 932 Ok(Some(to_proto::text_edit_vec(
943 &file_line_index, 933 &line_index,
944 new_line_endings,
945 TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text), 934 TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text),
946 ))) 935 )))
947 } else if *file == new_text { 936 } else if *file == new_text {
948 // The document is already formatted correctly -- no edits needed. 937 // The document is already formatted correctly -- no edits needed.
949 Ok(None) 938 Ok(None)
950 } else { 939 } else {
951 Ok(Some(to_proto::text_edit_vec( 940 Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text))))
952 &file_line_index,
953 file_line_endings,
954 diff(&file, &new_text),
955 )))
956 } 941 }
957} 942}
958 943
@@ -969,7 +954,7 @@ pub(crate) fn handle_code_action(
969 } 954 }
970 955
971 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; 956 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
972 let line_index = snap.analysis.file_line_index(file_id)?; 957 let line_index = snap.file_line_index(file_id)?;
973 let range = from_proto::text_range(&line_index, params.range); 958 let range = from_proto::text_range(&line_index, params.range);
974 let frange = FileRange { file_id, range }; 959 let frange = FileRange { file_id, range };
975 960
@@ -1010,7 +995,7 @@ pub(crate) fn handle_code_action(
1010fn add_quick_fixes( 995fn add_quick_fixes(
1011 snap: &GlobalStateSnapshot, 996 snap: &GlobalStateSnapshot,
1012 frange: FileRange, 997 frange: FileRange,
1013 line_index: &Arc<LineIndex>, 998 line_index: &LineIndex,
1014 acc: &mut Vec<lsp_ext::CodeAction>, 999 acc: &mut Vec<lsp_ext::CodeAction>,
1015) -> Result<()> { 1000) -> Result<()> {
1016 let diagnostics = snap.analysis.diagnostics(&snap.config.diagnostics(), frange.file_id)?; 1001 let diagnostics = snap.analysis.diagnostics(&snap.config.diagnostics(), frange.file_id)?;
@@ -1052,7 +1037,7 @@ pub(crate) fn handle_code_action_resolve(
1052 }; 1037 };
1053 1038
1054 let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?; 1039 let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?;
1055 let line_index = snap.analysis.file_line_index(file_id)?; 1040 let line_index = snap.file_line_index(file_id)?;
1056 let range = from_proto::text_range(&line_index, params.code_action_params.range); 1041 let range = from_proto::text_range(&line_index, params.code_action_params.range);
1057 let frange = FileRange { file_id, range }; 1042 let frange = FileRange { file_id, range };
1058 1043
@@ -1131,7 +1116,7 @@ pub(crate) fn handle_document_highlight(
1131) -> Result<Option<Vec<DocumentHighlight>>> { 1116) -> Result<Option<Vec<DocumentHighlight>>> {
1132 let _p = profile::span("handle_document_highlight"); 1117 let _p = profile::span("handle_document_highlight");
1133 let position = from_proto::file_position(&snap, params.text_document_position_params)?; 1118 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
1134 let line_index = snap.analysis.file_line_index(position.file_id)?; 1119 let line_index = snap.file_line_index(position.file_id)?;
1135 1120
1136 let refs = match snap 1121 let refs = match snap
1137 .analysis 1122 .analysis
@@ -1192,7 +1177,7 @@ pub(crate) fn publish_diagnostics(
1192 file_id: FileId, 1177 file_id: FileId,
1193) -> Result<Vec<Diagnostic>> { 1178) -> Result<Vec<Diagnostic>> {
1194 let _p = profile::span("publish_diagnostics"); 1179 let _p = profile::span("publish_diagnostics");
1195 let line_index = snap.analysis.file_line_index(file_id)?; 1180 let line_index = snap.file_line_index(file_id)?;
1196 1181
1197 let diagnostics: Vec<Diagnostic> = snap 1182 let diagnostics: Vec<Diagnostic> = snap
1198 .analysis 1183 .analysis
@@ -1226,7 +1211,7 @@ pub(crate) fn handle_inlay_hints(
1226) -> Result<Vec<InlayHint>> { 1211) -> Result<Vec<InlayHint>> {
1227 let _p = profile::span("handle_inlay_hints"); 1212 let _p = profile::span("handle_inlay_hints");
1228 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; 1213 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1229 let line_index = snap.analysis.file_line_index(file_id)?; 1214 let line_index = snap.file_line_index(file_id)?;
1230 Ok(snap 1215 Ok(snap
1231 .analysis 1216 .analysis
1232 .inlay_hints(file_id, &snap.config.inlay_hints())? 1217 .inlay_hints(file_id, &snap.config.inlay_hints())?
@@ -1277,7 +1262,7 @@ pub(crate) fn handle_call_hierarchy_incoming(
1277 1262
1278 for call_item in call_items.into_iter() { 1263 for call_item in call_items.into_iter() {
1279 let file_id = call_item.target.file_id; 1264 let file_id = call_item.target.file_id;
1280 let line_index = snap.analysis.file_line_index(file_id)?; 1265 let line_index = snap.file_line_index(file_id)?;
1281 let item = to_proto::call_hierarchy_item(&snap, call_item.target)?; 1266 let item = to_proto::call_hierarchy_item(&snap, call_item.target)?;
1282 res.push(CallHierarchyIncomingCall { 1267 res.push(CallHierarchyIncomingCall {
1283 from: item, 1268 from: item,
@@ -1312,7 +1297,7 @@ pub(crate) fn handle_call_hierarchy_outgoing(
1312 1297
1313 for call_item in call_items.into_iter() { 1298 for call_item in call_items.into_iter() {
1314 let file_id = call_item.target.file_id; 1299 let file_id = call_item.target.file_id;
1315 let line_index = snap.analysis.file_line_index(file_id)?; 1300 let line_index = snap.file_line_index(file_id)?;
1316 let item = to_proto::call_hierarchy_item(&snap, call_item.target)?; 1301 let item = to_proto::call_hierarchy_item(&snap, call_item.target)?;
1317 res.push(CallHierarchyOutgoingCall { 1302 res.push(CallHierarchyOutgoingCall {
1318 to: item, 1303 to: item,
@@ -1335,7 +1320,7 @@ pub(crate) fn handle_semantic_tokens_full(
1335 1320
1336 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; 1321 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1337 let text = snap.analysis.file_text(file_id)?; 1322 let text = snap.analysis.file_text(file_id)?;
1338 let line_index = snap.analysis.file_line_index(file_id)?; 1323 let line_index = snap.file_line_index(file_id)?;
1339 1324
1340 let highlights = snap.analysis.highlight(file_id)?; 1325 let highlights = snap.analysis.highlight(file_id)?;
1341 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); 1326 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
@@ -1354,7 +1339,7 @@ pub(crate) fn handle_semantic_tokens_full_delta(
1354 1339
1355 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; 1340 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1356 let text = snap.analysis.file_text(file_id)?; 1341 let text = snap.analysis.file_text(file_id)?;
1357 let line_index = snap.analysis.file_line_index(file_id)?; 1342 let line_index = snap.file_line_index(file_id)?;
1358 1343
1359 let highlights = snap.analysis.highlight(file_id)?; 1344 let highlights = snap.analysis.highlight(file_id)?;
1360 1345
@@ -1384,7 +1369,7 @@ pub(crate) fn handle_semantic_tokens_range(
1384 1369
1385 let frange = from_proto::file_range(&snap, params.text_document, params.range)?; 1370 let frange = from_proto::file_range(&snap, params.text_document, params.range)?;
1386 let text = snap.analysis.file_text(frange.file_id)?; 1371 let text = snap.analysis.file_text(frange.file_id)?;
1387 let line_index = snap.analysis.file_line_index(frange.file_id)?; 1372 let line_index = snap.file_line_index(frange.file_id)?;
1388 1373
1389 let highlights = snap.analysis.highlight_range(frange)?; 1374 let highlights = snap.analysis.highlight_range(frange)?;
1390 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); 1375 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
@@ -1432,7 +1417,7 @@ fn show_impl_command_link(
1432 if snap.config.hover().implementations { 1417 if snap.config.hover().implementations {
1433 if let Some(nav_data) = snap.analysis.goto_implementation(*position).unwrap_or(None) { 1418 if let Some(nav_data) = snap.analysis.goto_implementation(*position).unwrap_or(None) {
1434 let uri = to_proto::url(snap, position.file_id); 1419 let uri = to_proto::url(snap, position.file_id);
1435 let line_index = snap.analysis.file_line_index(position.file_id).ok()?; 1420 let line_index = snap.file_line_index(position.file_id).ok()?;
1436 let position = to_proto::position(&line_index, position.offset); 1421 let position = to_proto::position(&line_index, position.offset);
1437 let locations: Vec<_> = nav_data 1422 let locations: Vec<_> = nav_data
1438 .info 1423 .info
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index 2207b9a87..8b874239c 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -29,7 +29,7 @@ mod from_proto;
29mod semantic_tokens; 29mod semantic_tokens;
30mod markdown; 30mod markdown;
31mod diagnostics; 31mod diagnostics;
32mod line_endings; 32mod line_index;
33mod request_metrics; 33mod request_metrics;
34mod lsp_utils; 34mod lsp_utils;
35mod thread_pool; 35mod thread_pool;
diff --git a/crates/rust-analyzer/src/line_endings.rs b/crates/rust-analyzer/src/line_index.rs
index bf0e255d9..c116414da 100644
--- a/crates/rust-analyzer/src/line_endings.rs
+++ b/crates/rust-analyzer/src/line_index.rs
@@ -1,7 +1,23 @@
1//! Enhances `ide::LineIndex` with additional info required to convert offsets
2//! into lsp positions.
3//!
1//! We maintain invariant that all internal strings use `\n` as line separator. 4//! We maintain invariant that all internal strings use `\n` as line separator.
2//! This module does line ending conversion and detection (so that we can 5//! This module does line ending conversion and detection (so that we can
3//! convert back to `\r\n` on the way out). 6//! convert back to `\r\n` on the way out).
4 7
8use std::sync::Arc;
9
10pub enum OffsetEncoding {
11 Utf8,
12 Utf16,
13}
14
15pub(crate) struct LineIndex {
16 pub(crate) index: Arc<ide::LineIndex>,
17 pub(crate) endings: LineEndings,
18 pub(crate) encoding: OffsetEncoding,
19}
20
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub(crate) enum LineEndings { 22pub(crate) enum LineEndings {
7 Unix, 23 Unix,
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index a1ad855c3..0d2c8f7ff 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -385,3 +385,7 @@ pub(crate) enum CodeLensResolveData {
385 Impls(lsp_types::request::GotoImplementationParams), 385 Impls(lsp_types::request::GotoImplementationParams),
386 References(lsp_types::TextDocumentPositionParams), 386 References(lsp_types::TextDocumentPositionParams),
387} 387}
388
389pub fn supports_utf8(caps: &lsp_types::ClientCapabilities) -> bool {
390 caps.offset_encoding.as_deref().unwrap_or_default().iter().any(|it| it == "utf-8")
391}
diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs
index 2d06fe538..84f78b5b8 100644
--- a/crates/rust-analyzer/src/lsp_utils.rs
+++ b/crates/rust-analyzer/src/lsp_utils.rs
@@ -1,11 +1,14 @@
1//! Utilities for LSP-related boilerplate code. 1//! Utilities for LSP-related boilerplate code.
2use std::{error::Error, ops::Range}; 2use std::{error::Error, ops::Range, sync::Arc};
3 3
4use ide::LineIndex;
5use ide_db::base_db::Canceled; 4use ide_db::base_db::Canceled;
6use lsp_server::Notification; 5use lsp_server::Notification;
7 6
8use crate::{from_proto, global_state::GlobalState}; 7use crate::{
8 from_proto,
9 global_state::GlobalState,
10 line_index::{LineEndings, LineIndex, OffsetEncoding},
11};
9 12
10pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool { 13pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool {
11 e.downcast_ref::<Canceled>().is_some() 14 e.downcast_ref::<Canceled>().is_some()
@@ -90,7 +93,13 @@ pub(crate) fn apply_document_changes(
90 old_text: &mut String, 93 old_text: &mut String,
91 content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>, 94 content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
92) { 95) {
93 let mut line_index = LineIndex::new(old_text); 96 let mut line_index = LineIndex {
97 index: Arc::new(ide::LineIndex::new(old_text)),
98 // We don't care about line endings or offset encoding here.
99 endings: LineEndings::Unix,
100 encoding: OffsetEncoding::Utf16,
101 };
102
94 // The changes we got must be applied sequentially, but can cross lines so we 103 // The changes we got must be applied sequentially, but can cross lines so we
95 // have to keep our line index updated. 104 // have to keep our line index updated.
96 // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we 105 // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
@@ -115,7 +124,7 @@ pub(crate) fn apply_document_changes(
115 match change.range { 124 match change.range {
116 Some(range) => { 125 Some(range) => {
117 if !index_valid.covers(range.end.line) { 126 if !index_valid.covers(range.end.line) {
118 line_index = LineIndex::new(&old_text); 127 line_index.index = Arc::new(ide::LineIndex::new(&old_text));
119 } 128 }
120 index_valid = IndexValid::UpToLineExclusive(range.start.line); 129 index_valid = IndexValid::UpToLineExclusive(range.start.line);
121 let range = from_proto::text_range(&line_index, range); 130 let range = from_proto::text_range(&line_index, range);
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index b0ddb603d..70cb7fbab 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -7,22 +7,29 @@ use std::{
7use ide::{ 7use ide::{
8 Annotation, AnnotationKind, Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, 8 Annotation, AnnotationKind, Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind,
9 Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct, 9 Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct,
10 HlRange, HlTag, Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, 10 HlRange, HlTag, Indel, InlayHint, InlayKind, InsertTextFormat, Markup, NavigationTarget,
11 NavigationTarget, ReferenceAccess, RenameError, Runnable, Severity, SourceChange, TextEdit, 11 ReferenceAccess, RenameError, Runnable, Severity, SourceChange, TextEdit, TextRange, TextSize,
12 TextRange, TextSize,
13}; 12};
14use ide_db::SymbolKind; 13use ide_db::SymbolKind;
15use itertools::Itertools; 14use itertools::Itertools;
16use serde_json::to_value; 15use serde_json::to_value;
17 16
18use crate::{ 17use crate::{
19 cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, 18 cargo_target_spec::CargoTargetSpec,
20 line_endings::LineEndings, lsp_ext, semantic_tokens, Result, 19 global_state::GlobalStateSnapshot,
20 line_index::{LineEndings, LineIndex, OffsetEncoding},
21 lsp_ext, semantic_tokens, Result,
21}; 22};
22 23
23pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position { 24pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
24 let line_col = line_index.line_col(offset); 25 let line_col = line_index.index.line_col(offset);
25 lsp_types::Position::new(line_col.line, line_col.col_utf16) 26 match line_index.encoding {
27 OffsetEncoding::Utf8 => lsp_types::Position::new(line_col.line, line_col.col),
28 OffsetEncoding::Utf16 => {
29 let line_col = line_index.index.to_utf16(line_col);
30 lsp_types::Position::new(line_col.line, line_col.col)
31 }
32 }
26} 33}
27 34
28pub(crate) fn range(line_index: &LineIndex, range: TextRange) -> lsp_types::Range { 35pub(crate) fn range(line_index: &LineIndex, range: TextRange) -> lsp_types::Range {
@@ -122,13 +129,9 @@ pub(crate) fn completion_item_kind(
122 } 129 }
123} 130}
124 131
125pub(crate) fn text_edit( 132pub(crate) fn text_edit(line_index: &LineIndex, indel: Indel) -> lsp_types::TextEdit {
126 line_index: &LineIndex,
127 line_endings: LineEndings,
128 indel: Indel,
129) -> lsp_types::TextEdit {
130 let range = range(line_index, indel.delete); 133 let range = range(line_index, indel.delete);
131 let new_text = match line_endings { 134 let new_text = match line_index.endings {
132 LineEndings::Unix => indel.insert, 135 LineEndings::Unix => indel.insert,
133 LineEndings::Dos => indel.insert.replace('\n', "\r\n"), 136 LineEndings::Dos => indel.insert.replace('\n', "\r\n"),
134 }; 137 };
@@ -137,11 +140,10 @@ pub(crate) fn text_edit(
137 140
138pub(crate) fn snippet_text_edit( 141pub(crate) fn snippet_text_edit(
139 line_index: &LineIndex, 142 line_index: &LineIndex,
140 line_endings: LineEndings,
141 is_snippet: bool, 143 is_snippet: bool,
142 indel: Indel, 144 indel: Indel,
143) -> lsp_ext::SnippetTextEdit { 145) -> lsp_ext::SnippetTextEdit {
144 let text_edit = text_edit(line_index, line_endings, indel); 146 let text_edit = text_edit(line_index, indel);
145 let insert_text_format = 147 let insert_text_format =
146 if is_snippet { Some(lsp_types::InsertTextFormat::Snippet) } else { None }; 148 if is_snippet { Some(lsp_types::InsertTextFormat::Snippet) } else { None };
147 lsp_ext::SnippetTextEdit { 149 lsp_ext::SnippetTextEdit {
@@ -153,27 +155,24 @@ pub(crate) fn snippet_text_edit(
153 155
154pub(crate) fn text_edit_vec( 156pub(crate) fn text_edit_vec(
155 line_index: &LineIndex, 157 line_index: &LineIndex,
156 line_endings: LineEndings,
157 text_edit: TextEdit, 158 text_edit: TextEdit,
158) -> Vec<lsp_types::TextEdit> { 159) -> Vec<lsp_types::TextEdit> {
159 text_edit.into_iter().map(|indel| self::text_edit(line_index, line_endings, indel)).collect() 160 text_edit.into_iter().map(|indel| self::text_edit(line_index, indel)).collect()
160} 161}
161 162
162pub(crate) fn snippet_text_edit_vec( 163pub(crate) fn snippet_text_edit_vec(
163 line_index: &LineIndex, 164 line_index: &LineIndex,
164 line_endings: LineEndings,
165 is_snippet: bool, 165 is_snippet: bool,
166 text_edit: TextEdit, 166 text_edit: TextEdit,
167) -> Vec<lsp_ext::SnippetTextEdit> { 167) -> Vec<lsp_ext::SnippetTextEdit> {
168 text_edit 168 text_edit
169 .into_iter() 169 .into_iter()
170 .map(|indel| self::snippet_text_edit(line_index, line_endings, is_snippet, indel)) 170 .map(|indel| self::snippet_text_edit(line_index, is_snippet, indel))
171 .collect() 171 .collect()
172} 172}
173 173
174pub(crate) fn completion_item( 174pub(crate) fn completion_item(
175 line_index: &LineIndex, 175 line_index: &LineIndex,
176 line_endings: LineEndings,
177 completion_item: CompletionItem, 176 completion_item: CompletionItem,
178) -> Vec<lsp_types::CompletionItem> { 177) -> Vec<lsp_types::CompletionItem> {
179 fn set_score(res: &mut lsp_types::CompletionItem, label: &str) { 178 fn set_score(res: &mut lsp_types::CompletionItem, label: &str) {
@@ -190,19 +189,19 @@ pub(crate) fn completion_item(
190 for indel in completion_item.text_edit().iter() { 189 for indel in completion_item.text_edit().iter() {
191 if indel.delete.contains_range(source_range) { 190 if indel.delete.contains_range(source_range) {
192 text_edit = Some(if indel.delete == source_range { 191 text_edit = Some(if indel.delete == source_range {
193 self::text_edit(line_index, line_endings, indel.clone()) 192 self::text_edit(line_index, indel.clone())
194 } else { 193 } else {
195 assert!(source_range.end() == indel.delete.end()); 194 assert!(source_range.end() == indel.delete.end());
196 let range1 = TextRange::new(indel.delete.start(), source_range.start()); 195 let range1 = TextRange::new(indel.delete.start(), source_range.start());
197 let range2 = source_range; 196 let range2 = source_range;
198 let indel1 = Indel::replace(range1, String::new()); 197 let indel1 = Indel::replace(range1, String::new());
199 let indel2 = Indel::replace(range2, indel.insert.clone()); 198 let indel2 = Indel::replace(range2, indel.insert.clone());
200 additional_text_edits.push(self::text_edit(line_index, line_endings, indel1)); 199 additional_text_edits.push(self::text_edit(line_index, indel1));
201 self::text_edit(line_index, line_endings, indel2) 200 self::text_edit(line_index, indel2)
202 }) 201 })
203 } else { 202 } else {
204 assert!(source_range.intersect(indel.delete).is_none()); 203 assert!(source_range.intersect(indel.delete).is_none());
205 let text_edit = self::text_edit(line_index, line_endings, indel.clone()); 204 let text_edit = self::text_edit(line_index, indel.clone());
206 additional_text_edits.push(text_edit); 205 additional_text_edits.push(text_edit);
207 } 206 }
208 } 207 }
@@ -358,7 +357,7 @@ pub(crate) fn semantic_tokens(
358 let token_index = semantic_tokens::type_index(type_); 357 let token_index = semantic_tokens::type_index(type_);
359 let modifier_bitset = mods.0; 358 let modifier_bitset = mods.0;
360 359
361 for mut text_range in line_index.lines(highlight_range.range) { 360 for mut text_range in line_index.index.lines(highlight_range.range) {
362 if text[text_range].ends_with('\n') { 361 if text[text_range].ends_with('\n') {
363 text_range = 362 text_range =
364 TextRange::new(text_range.start(), text_range.end() - TextSize::of('\n')); 363 TextRange::new(text_range.start(), text_range.end() - TextSize::of('\n'));
@@ -565,7 +564,7 @@ pub(crate) fn location(
565 frange: FileRange, 564 frange: FileRange,
566) -> Result<lsp_types::Location> { 565) -> Result<lsp_types::Location> {
567 let url = url(snap, frange.file_id); 566 let url = url(snap, frange.file_id);
568 let line_index = snap.analysis.file_line_index(frange.file_id)?; 567 let line_index = snap.file_line_index(frange.file_id)?;
569 let range = range(&line_index, frange.range); 568 let range = range(&line_index, frange.range);
570 let loc = lsp_types::Location::new(url, range); 569 let loc = lsp_types::Location::new(url, range);
571 Ok(loc) 570 Ok(loc)
@@ -577,7 +576,7 @@ pub(crate) fn location_from_nav(
577 nav: NavigationTarget, 576 nav: NavigationTarget,
578) -> Result<lsp_types::Location> { 577) -> Result<lsp_types::Location> {
579 let url = url(snap, nav.file_id); 578 let url = url(snap, nav.file_id);
580 let line_index = snap.analysis.file_line_index(nav.file_id)?; 579 let line_index = snap.file_line_index(nav.file_id)?;
581 let range = range(&line_index, nav.full_range); 580 let range = range(&line_index, nav.full_range);
582 let loc = lsp_types::Location::new(url, range); 581 let loc = lsp_types::Location::new(url, range);
583 Ok(loc) 582 Ok(loc)
@@ -590,7 +589,7 @@ pub(crate) fn location_link(
590) -> Result<lsp_types::LocationLink> { 589) -> Result<lsp_types::LocationLink> {
591 let origin_selection_range = match src { 590 let origin_selection_range = match src {
592 Some(src) => { 591 Some(src) => {
593 let line_index = snap.analysis.file_line_index(src.file_id)?; 592 let line_index = snap.file_line_index(src.file_id)?;
594 let range = range(&line_index, src.range); 593 let range = range(&line_index, src.range);
595 Some(range) 594 Some(range)
596 } 595 }
@@ -610,7 +609,7 @@ fn location_info(
610 snap: &GlobalStateSnapshot, 609 snap: &GlobalStateSnapshot,
611 target: NavigationTarget, 610 target: NavigationTarget,
612) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> { 611) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> {
613 let line_index = snap.analysis.file_line_index(target.file_id)?; 612 let line_index = snap.file_line_index(target.file_id)?;
614 613
615 let target_uri = url(snap, target.file_id); 614 let target_uri = url(snap, target.file_id);
616 let target_range = range(&line_index, target.full_range); 615 let target_range = range(&line_index, target.full_range);
@@ -648,12 +647,8 @@ pub(crate) fn snippet_text_document_edit(
648 edit: TextEdit, 647 edit: TextEdit,
649) -> Result<lsp_ext::SnippetTextDocumentEdit> { 648) -> Result<lsp_ext::SnippetTextDocumentEdit> {
650 let text_document = optional_versioned_text_document_identifier(snap, file_id); 649 let text_document = optional_versioned_text_document_identifier(snap, file_id);
651 let line_index = snap.analysis.file_line_index(file_id)?; 650 let line_index = snap.file_line_index(file_id)?;
652 let line_endings = snap.file_line_endings(file_id); 651 let edits = edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect();
653 let edits = edit
654 .into_iter()
655 .map(|it| snippet_text_edit(&line_index, line_endings, is_snippet, it))
656 .collect();
657 Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits }) 652 Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits })
658} 653}
659 654
@@ -674,9 +669,8 @@ pub(crate) fn snippet_text_document_ops(
674 if !initial_contents.is_empty() { 669 if !initial_contents.is_empty() {
675 let text_document = 670 let text_document =
676 lsp_types::OptionalVersionedTextDocumentIdentifier { uri, version: None }; 671 lsp_types::OptionalVersionedTextDocumentIdentifier { uri, version: None };
677 let range = range(&LineIndex::new(""), TextRange::empty(TextSize::from(0)));
678 let text_edit = lsp_ext::SnippetTextEdit { 672 let text_edit = lsp_ext::SnippetTextEdit {
679 range, 673 range: lsp_types::Range::default(),
680 new_text: initial_contents, 674 new_text: initial_contents,
681 insert_text_format: Some(lsp_types::InsertTextFormat::PlainText), 675 insert_text_format: Some(lsp_types::InsertTextFormat::PlainText),
682 }; 676 };
@@ -867,7 +861,7 @@ pub(crate) fn code_lens(
867) -> Result<lsp_types::CodeLens> { 861) -> Result<lsp_types::CodeLens> {
868 match annotation.kind { 862 match annotation.kind {
869 AnnotationKind::Runnable { debug, runnable: run } => { 863 AnnotationKind::Runnable { debug, runnable: run } => {
870 let line_index = snap.analysis.file_line_index(run.nav.file_id)?; 864 let line_index = snap.file_line_index(run.nav.file_id)?;
871 let annotation_range = range(&line_index, annotation.range); 865 let annotation_range = range(&line_index, annotation.range);
872 866
873 let action = run.action(); 867 let action = run.action();
@@ -883,7 +877,7 @@ pub(crate) fn code_lens(
883 Ok(lsp_types::CodeLens { range: annotation_range, command: Some(command), data: None }) 877 Ok(lsp_types::CodeLens { range: annotation_range, command: Some(command), data: None })
884 } 878 }
885 AnnotationKind::HasImpls { position: file_position, data } => { 879 AnnotationKind::HasImpls { position: file_position, data } => {
886 let line_index = snap.analysis.file_line_index(file_position.file_id)?; 880 let line_index = snap.file_line_index(file_position.file_id)?;
887 let annotation_range = range(&line_index, annotation.range); 881 let annotation_range = range(&line_index, annotation.range);
888 let url = url(snap, file_position.file_id); 882 let url = url(snap, file_position.file_id);
889 883
@@ -926,7 +920,7 @@ pub(crate) fn code_lens(
926 }) 920 })
927 } 921 }
928 AnnotationKind::HasReferences { position: file_position, data } => { 922 AnnotationKind::HasReferences { position: file_position, data } => {
929 let line_index = snap.analysis.file_line_index(file_position.file_id)?; 923 let line_index = snap.file_line_index(file_position.file_id)?;
930 let annotation_range = range(&line_index, annotation.range); 924 let annotation_range = range(&line_index, annotation.range);
931 let url = url(snap, file_position.file_id); 925 let url = url(snap, file_position.file_id);
932 926
@@ -1060,6 +1054,8 @@ pub(crate) fn rename_error(err: RenameError) -> crate::LspError {
1060 1054
1061#[cfg(test)] 1055#[cfg(test)]
1062mod tests { 1056mod tests {
1057 use std::sync::Arc;
1058
1063 use hir::PrefixKind; 1059 use hir::PrefixKind;
1064 use ide::Analysis; 1060 use ide::Analysis;
1065 use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap}; 1061 use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
@@ -1077,7 +1073,11 @@ mod tests {
1077 }"#; 1073 }"#;
1078 1074
1079 let (offset, text) = test_utils::extract_offset(fixture); 1075 let (offset, text) = test_utils::extract_offset(fixture);
1080 let line_index = LineIndex::new(&text); 1076 let line_index = LineIndex {
1077 index: Arc::new(ide::LineIndex::new(&text)),
1078 endings: LineEndings::Unix,
1079 encoding: OffsetEncoding::Utf16,
1080 };
1081 let (analysis, file_id) = Analysis::from_single_file(text); 1081 let (analysis, file_id) = Analysis::from_single_file(text);
1082 let completions: Vec<(String, Option<String>)> = analysis 1082 let completions: Vec<(String, Option<String>)> = analysis
1083 .completions( 1083 .completions(
@@ -1095,7 +1095,7 @@ mod tests {
1095 .unwrap() 1095 .unwrap()
1096 .into_iter() 1096 .into_iter()
1097 .filter(|c| c.label().ends_with("arg")) 1097 .filter(|c| c.label().ends_with("arg"))
1098 .map(|c| completion_item(&line_index, LineEndings::Unix, c)) 1098 .map(|c| completion_item(&line_index, c))
1099 .flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text))) 1099 .flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text)))
1100 .collect(); 1100 .collect();
1101 expect_test::expect![[r#" 1101 expect_test::expect![[r#"
@@ -1133,7 +1133,11 @@ fn main() {
1133 let folds = analysis.folding_ranges(file_id).unwrap(); 1133 let folds = analysis.folding_ranges(file_id).unwrap();
1134 assert_eq!(folds.len(), 4); 1134 assert_eq!(folds.len(), 4);
1135 1135
1136 let line_index = LineIndex::new(&text); 1136 let line_index = LineIndex {
1137 index: Arc::new(ide::LineIndex::new(&text)),
1138 endings: LineEndings::Unix,
1139 encoding: OffsetEncoding::Utf16,
1140 };
1137 let converted: Vec<lsp_types::FoldingRange> = 1141 let converted: Vec<lsp_types::FoldingRange> =
1138 folds.into_iter().map(|it| folding_range(&text, &line_index, true, it)).collect(); 1142 folds.into_iter().map(|it| folding_range(&text, &line_index, true, it)).collect();
1139 1143
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index a4e108724..164c8482e 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
1<!--- 1<!---
2lsp_ext.rs hash: 34aec6bfeaeb97a 2lsp_ext.rs hash: d279d971d4f62cd7
3 3
4If you need to change the above hash to make the test pass, please check if you 4If you need to change the above hash to make the test pass, please check if you
5need to adjust this doc as well and ping this issue: 5need to adjust this doc as well and ping this issue:
@@ -19,6 +19,12 @@ Requests, which are likely to always remain specific to `rust-analyzer` are unde
19 19
20If you want to be notified about the changes to this document, subscribe to [#4604](https://github.com/rust-analyzer/rust-analyzer/issues/4604). 20If you want to be notified about the changes to this document, subscribe to [#4604](https://github.com/rust-analyzer/rust-analyzer/issues/4604).
21 21
22## UTF-8 offsets
23
24rust-analyzer supports clangd's extension for opting into UTF-8 as the coordinate space for offsets (by default, LSP uses UTF-16 offsets).
25
26https://clangd.llvm.org/extensions.html#utf-8-offsets
27
22## `initializationOptions` 28## `initializationOptions`
23 29
24For `initializationOptions`, `rust-analyzer` expects `"rust-analyzer"` section of the configuration. 30For `initializationOptions`, `rust-analyzer` expects `"rust-analyzer"` section of the configuration.