Skip to content

Commit a013839

Browse files
committed
LSP: Negotiate position encoding based on client capabilities
Negotiate the position encoding during startup to use UTF-8 if supported by the client. Also support UTF32 for completeness, although I don't think it will be used in practice.
1 parent e08abd0 commit a013839

7 files changed

Lines changed: 205 additions & 56 deletions

File tree

examples/edge_cases.bazelrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,4 @@ build:myconfig \
5252
--disk_cache=
5353

5454
# Unicode flags
55-
build:❄️🔥 --❄️=🔥 --🔥=❄️ --❄️🔥
55+
build:❄️🔥 --❄️=a --🔥=❄️ --❄️🔥

src/completion.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
bazel_flags::{BazelFlags, COMMAND_DOCS},
99
bazel_flags_proto::FlagInfo,
1010
line_index::{IndexEntryKind, IndexedLines},
11-
lsp_utils::range_to_lsp,
11+
lsp_utils::{encode_lsp_range, LspPositionEncoding},
1212
tokenizer::Span,
1313
};
1414

@@ -88,6 +88,7 @@ pub fn get_completion_items(
8888
rope: &Rope,
8989
index: &IndexedLines,
9090
pos: usize,
91+
encoding: LspPositionEncoding,
9192
) -> Vec<CompletionItem> {
9293
// For completion, the indices point between characters and not
9394
// at characters. We are generally interested in the token so far
@@ -104,7 +105,7 @@ pub fn get_completion_items(
104105
complete_bazel_flag(
105106
bazel_flags,
106107
&cmd.0,
107-
range_to_lsp(rope, &entry.span).unwrap(),
108+
encode_lsp_range(rope, &entry.span, encoding).unwrap(),
108109
)
109110
} else {
110111
// A flag should never be on a line without a command
@@ -121,12 +122,13 @@ pub fn get_completion_items(
121122
complete_bazel_flag(
122123
bazel_flags,
123124
&cmd.0,
124-
range_to_lsp(
125+
encode_lsp_range(
125126
rope,
126127
&Span {
127128
start: pos,
128129
end: pos,
129130
},
131+
encoding,
130132
)
131133
.unwrap(),
132134
)

src/diagnostic.rs

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ use crate::tokenizer::Span;
1010
use crate::{
1111
bazel_flags::{combine_key_value_flags, BazelFlags, FlagLookupType},
1212
file_utils::resolve_bazelrc_path,
13-
lsp_utils::range_to_lsp,
13+
lsp_utils::{encode_lsp_range, LspPositionEncoding},
1414
parser::{parse_from_str, Line, ParserResult},
1515
};
1616

1717
pub fn diagnostics_from_parser<'a>(
1818
rope: &'a Rope,
1919
errors: &'a [Rich<'a, char>],
20+
encoding: LspPositionEncoding,
2021
) -> impl Iterator<Item = Diagnostic> + 'a {
2122
errors.iter().filter_map(move |item| {
2223
let (message, err_span) = match item.reason() {
@@ -54,14 +55,22 @@ pub fn diagnostics_from_parser<'a>(
5455
end: err_span.end,
5556
};
5657
|| -> Option<Diagnostic> {
57-
Some(Diagnostic::new_simple(range_to_lsp(rope, span)?, message))
58+
Some(Diagnostic::new_simple(
59+
encode_lsp_range(rope, span, encoding)?,
60+
message,
61+
))
5862
}()
5963
})
6064
}
6165

6266
const SKIPPED_PREFIXES: [&str; 4] = ["--//", "--no//", "--@", "--no@"];
6367

64-
fn diagnostics_for_flags(rope: &Rope, line: &Line, bazel_flags: &BazelFlags) -> Vec<Diagnostic> {
68+
fn diagnostics_for_flags(
69+
rope: &Rope,
70+
line: &Line,
71+
bazel_flags: &BazelFlags,
72+
encoding: LspPositionEncoding,
73+
) -> Vec<Diagnostic> {
6574
let mut diagnostics: Vec<Diagnostic> = Vec::<Diagnostic>::new();
6675
let command = &line.command.as_ref().unwrap().0;
6776
for flag in &line.flags {
@@ -77,29 +86,29 @@ fn diagnostics_for_flags(rope: &Rope, line: &Line, bazel_flags: &BazelFlags) ->
7786
// Diagnose flags used on the wrong command
7887
if !flag_description.supports_command(command) {
7988
diagnostics.push(Diagnostic::new_simple(
80-
range_to_lsp(rope, &name.1).unwrap(),
89+
encode_lsp_range(rope, &name.1, encoding).unwrap(),
8190
format!("The flag {:?} is not supported for {:?}. It is supported for {:?} commands, though.", name.0, command, flag_description.commands),
8291
))
8392
}
8493
// Diagnose deprecated options
8594
if flag_description.is_deprecated() {
8695
diagnostics.push(Diagnostic {
87-
range: range_to_lsp(rope, &name.1).unwrap(),
96+
range: encode_lsp_range(rope, &name.1, encoding).unwrap(),
8897
message: format!("The flag {:?} is deprecated.", name.0),
8998
severity: Some(DiagnosticSeverity::WARNING),
9099
tags: Some(vec![DiagnosticTag::DEPRECATED]),
91100
..Default::default()
92101
});
93102
} else if flag_description.is_noop() {
94103
diagnostics.push(Diagnostic {
95-
range: range_to_lsp(rope, &name.1).unwrap(),
104+
range: encode_lsp_range(rope, &name.1, encoding).unwrap(),
96105
message: format!("The flag {:?} is a no-op.", name.0),
97106
severity: Some(DiagnosticSeverity::WARNING),
98107
..Default::default()
99108
});
100109
} else if lookup_type == FlagLookupType::OldName {
101110
diagnostics.push(Diagnostic {
102-
range: range_to_lsp(rope, &name.1).unwrap(),
111+
range: encode_lsp_range(rope, &name.1, encoding).unwrap(),
103112
message: format!(
104113
"The flag {:?} was renamed to \"--{}\".",
105114
name.0, flag_description.name
@@ -110,7 +119,7 @@ fn diagnostics_for_flags(rope: &Rope, line: &Line, bazel_flags: &BazelFlags) ->
110119
});
111120
} else if lookup_type == FlagLookupType::Abbreviation {
112121
diagnostics.push(Diagnostic {
113-
range: range_to_lsp(rope, &name.1).unwrap(),
122+
range: encode_lsp_range(rope, &name.1, encoding).unwrap(),
114123
message: format!(
115124
"Use the full name {:?} instead of its abbreviation.",
116125
flag_description.name
@@ -122,7 +131,7 @@ fn diagnostics_for_flags(rope: &Rope, line: &Line, bazel_flags: &BazelFlags) ->
122131
} else {
123132
// Diagnose unknown flags
124133
diagnostics.push(Diagnostic::new_simple(
125-
range_to_lsp(rope, &name.1).unwrap(),
134+
encode_lsp_range(rope, &name.1, encoding).unwrap(),
126135
format!("Unknown flag {:?}", name.0),
127136
))
128137
}
@@ -131,17 +140,22 @@ fn diagnostics_for_flags(rope: &Rope, line: &Line, bazel_flags: &BazelFlags) ->
131140
diagnostics
132141
}
133142

134-
fn diagnostics_for_imports(rope: &Rope, line: &Line, base_path: Option<&Path>) -> Vec<Diagnostic> {
143+
fn diagnostics_for_imports(
144+
rope: &Rope,
145+
line: &Line,
146+
base_path: Option<&Path>,
147+
encoding: LspPositionEncoding,
148+
) -> Vec<Diagnostic> {
135149
let mut diagnostics: Vec<Diagnostic> = Vec::<Diagnostic>::new();
136150
let command = line.command.as_ref().unwrap();
137151
if line.flags.is_empty() {
138152
diagnostics.push(Diagnostic::new_simple(
139-
range_to_lsp(rope, &command.1).unwrap(),
153+
encode_lsp_range(rope, &command.1, encoding).unwrap(),
140154
"Missing file path".to_string(),
141155
))
142156
} else if line.flags.len() > 1 {
143157
diagnostics.push(Diagnostic::new_simple(
144-
range_to_lsp(rope, &command.1).unwrap(),
158+
encode_lsp_range(rope, &command.1, encoding).unwrap(),
145159
format!(
146160
"`{}` expects a single file name, but received multiple arguments",
147161
command.0
@@ -151,7 +165,7 @@ fn diagnostics_for_imports(rope: &Rope, line: &Line, base_path: Option<&Path>) -
151165
let flag = &line.flags[0];
152166
if flag.name.is_some() {
153167
diagnostics.push(Diagnostic::new_simple(
154-
range_to_lsp(rope, &command.1).unwrap(),
168+
encode_lsp_range(rope, &command.1, encoding).unwrap(),
155169
format!("`{}` expects a file name, not a flag name", command.0),
156170
))
157171
}
@@ -166,22 +180,22 @@ fn diagnostics_for_imports(rope: &Rope, line: &Line, base_path: Option<&Path>) -
166180
if let Some(path) = opt_path {
167181
if !path.exists() {
168182
diagnostics.push(Diagnostic {
169-
range: range_to_lsp(rope, &value.1).unwrap(),
183+
range: encode_lsp_range(rope, &value.1, encoding).unwrap(),
170184
message: "Imported file does not exist".to_string(),
171185
severity: Some(severity),
172186
..Default::default()
173187
})
174188
} else if !path.is_file() {
175189
diagnostics.push(Diagnostic {
176-
range: range_to_lsp(rope, &value.1).unwrap(),
190+
range: encode_lsp_range(rope, &value.1, encoding).unwrap(),
177191
message: "Imported path exists, but is not a file".to_string(),
178192
severity: Some(severity),
179193
..Default::default()
180194
})
181195
}
182196
} else {
183197
diagnostics.push(Diagnostic {
184-
range: range_to_lsp(rope, &value.1).unwrap(),
198+
range: encode_lsp_range(rope, &value.1, encoding).unwrap(),
185199
message: "Unable to resolve file name".to_string(),
186200
severity: Some(severity),
187201
..Default::default()
@@ -198,6 +212,7 @@ pub fn diagnostics_from_rcconfig(
198212
lines: &[Line],
199213
bazel_flags: &BazelFlags,
200214
file_path: Option<&Path>,
215+
encoding: LspPositionEncoding,
201216
) -> Vec<Diagnostic> {
202217
let config_regex = Regex::new(r"^[a-z_][a-z0-9]*(?:[-_][a-z0-9]+)*$").unwrap();
203218
let mut diagnostics: Vec<Diagnostic> = Vec::<Diagnostic>::new();
@@ -206,18 +221,18 @@ pub fn diagnostics_from_rcconfig(
206221
// Command-specific diagnostics
207222
if let Some((command, span)) = &l.command {
208223
if command == "import" || command == "try-import" {
209-
diagnostics.extend(diagnostics_for_imports(rope, l, file_path))
224+
diagnostics.extend(diagnostics_for_imports(rope, l, file_path, encoding))
210225
} else if bazel_flags.flags_by_commands.contains_key(command) {
211-
diagnostics.extend(diagnostics_for_flags(rope, l, bazel_flags))
226+
diagnostics.extend(diagnostics_for_flags(rope, l, bazel_flags, encoding))
212227
} else {
213228
diagnostics.push(Diagnostic::new_simple(
214-
range_to_lsp(rope, span).unwrap(),
229+
encode_lsp_range(rope, span, encoding).unwrap(),
215230
format!("Unknown command {:?}", command),
216231
));
217232
}
218233
} else if !l.flags.is_empty() {
219234
diagnostics.push(Diagnostic::new_simple(
220-
range_to_lsp(rope, &l.span).unwrap(),
235+
encode_lsp_range(rope, &l.span, encoding).unwrap(),
221236
"Missing command".to_string(),
222237
));
223238
}
@@ -227,20 +242,20 @@ pub fn diagnostics_from_rcconfig(
227242
if config_name.is_empty() {
228243
// Empty config names make no sense
229244
diagnostics.push(Diagnostic::new_simple(
230-
range_to_lsp(rope, span).unwrap(),
245+
encode_lsp_range(rope, span, encoding).unwrap(),
231246
"Empty configuration names are pointless".to_string(),
232247
));
233248
} else if !config_regex.is_match(config_name) {
234249
// Overly complex config names
235250
diagnostics.push(Diagnostic::new_simple(
236-
range_to_lsp(rope, span).unwrap(),
251+
encode_lsp_range(rope, span, encoding).unwrap(),
237252
"Overly complicated config name. Config names should consist only of lower-case ASCII characters.".to_string(),
238253
));
239254
}
240255
if let Some((command, _)) = &l.command {
241256
if ["startup", "import", "try-import"].contains(&command.as_str()) {
242257
diagnostics.push(Diagnostic::new_simple(
243-
range_to_lsp(rope, span).unwrap(),
258+
encode_lsp_range(rope, span, encoding).unwrap(),
244259
format!(
245260
"Configuration names not supported on {:?} commands",
246261
command
@@ -257,6 +272,7 @@ pub fn diagnostics_from_string(
257272
str: &str,
258273
bazel_flags: &BazelFlags,
259274
file_path: Option<&Path>,
275+
encoding: LspPositionEncoding,
260276
) -> Vec<Diagnostic> {
261277
let rope = Rope::from_str(str);
262278
let ParserResult {
@@ -267,12 +283,13 @@ pub fn diagnostics_from_string(
267283
combine_key_value_flags(&mut lines, bazel_flags);
268284

269285
let mut diagnostics: Vec<Diagnostic> = Vec::<Diagnostic>::new();
270-
diagnostics.extend(diagnostics_from_parser(&rope, &errors));
286+
diagnostics.extend(diagnostics_from_parser(&rope, &errors, encoding));
271287
diagnostics.extend(diagnostics_from_rcconfig(
272288
&rope,
273289
&lines,
274290
bazel_flags,
275291
file_path,
292+
encoding,
276293
));
277294
diagnostics
278295
}
@@ -282,7 +299,7 @@ fn test_diagnose_string(str: &str) -> Vec<String> {
282299
use crate::bazel_flags::load_packaged_bazel_flags;
283300

284301
let bazel_flags = load_packaged_bazel_flags("8.0.0");
285-
return diagnostics_from_string(str, &bazel_flags, None)
302+
return diagnostics_from_string(str, &bazel_flags, None, LspPositionEncoding::UTF32)
286303
.iter_mut()
287304
.map(|d| std::mem::take(&mut d.message))
288305
.collect::<Vec<_>>();

src/formatting.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use tower_lsp::lsp_types::TextEdit;
44

55
use crate::{
66
bazel_flags::BazelFlags,
7-
lsp_utils::range_to_lsp,
7+
lsp_utils::{encode_lsp_range, LspPositionEncoding},
88
parser::{parse_from_str, Line, ParserResult},
99
tokenizer::Span,
1010
};
@@ -204,6 +204,7 @@ pub fn get_text_edits_for_lines(
204204
lines: &[Line],
205205
rope: &Rope,
206206
line_flow: FormatLineFlow,
207+
encoding: LspPositionEncoding,
207208
) -> Vec<TextEdit> {
208209
reflow_lines(lines, line_flow)
209210
.iter()
@@ -212,7 +213,7 @@ pub fn get_text_edits_for_lines(
212213
let formatted = format_line(line, use_line_continuations);
213214
if formatted != rope.slice(line.span.clone()) {
214215
Some(TextEdit {
215-
range: range_to_lsp(rope, &line.span)?,
216+
range: encode_lsp_range(rope, &line.span, encoding)?,
216217
new_text: formatted,
217218
})
218219
} else {

0 commit comments

Comments
 (0)