Skip to content

Commit 6134d03

Browse files
authored
Merge pull request #2 from JasperDeSutter/completions
wasm demo completions support
2 parents 93043a7 + 7d8ed7b commit 6134d03

File tree

7 files changed

+267
-39
lines changed

7 files changed

+267
-39
lines changed

wasm-demo/Cargo.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wasm-demo/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ console_log = "0.1"
1313
log = "0.4"
1414
wasm-bindgen = "0.2.50"
1515
serde = { version = "1.0", features = ["derive"] }
16+
serde_repr = "0.1"
1617
serde-wasm-bindgen = "0.1"
1718

1819
ra_ide_api = { git = "https://github.com/rust-analyzer/rust-analyzer", features = ["wasm"] }
1920
ra_syntax = { git = "https://github.com/rust-analyzer/rust-analyzer" }
21+
ra_text_edit = { git = "https://github.com/rust-analyzer/rust-analyzer" }

wasm-demo/rustfmt.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
reorder_modules = false
2+
use_small_heuristics = "Max"
3+
newline_style = "Unix"

wasm-demo/src/conv.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use super::return_types;
2+
use ra_ide_api::{
3+
CompletionItem, CompletionItemKind, FileId, FilePosition, InsertTextFormat, LineCol, LineIndex,
4+
};
5+
use ra_syntax::TextRange;
6+
use ra_text_edit::AtomTextEdit;
7+
8+
pub trait Conv {
9+
type Output;
10+
fn conv(self) -> Self::Output;
11+
}
12+
13+
pub trait ConvWith<CTX> {
14+
type Output;
15+
fn conv_with(self, ctx: CTX) -> Self::Output;
16+
}
17+
18+
#[derive(Clone, Copy)]
19+
pub struct Position {
20+
pub line_number: u32,
21+
pub column: u32,
22+
}
23+
24+
impl ConvWith<(&LineIndex, FileId)> for Position {
25+
type Output = FilePosition;
26+
27+
fn conv_with(self, (line_index, file_id): (&LineIndex, FileId)) -> Self::Output {
28+
let line_col = LineCol { line: self.line_number - 1, col_utf16: self.column - 1 };
29+
let offset = line_index.offset(line_col);
30+
FilePosition { file_id, offset }
31+
}
32+
}
33+
34+
impl ConvWith<&LineIndex> for TextRange {
35+
type Output = return_types::Range;
36+
37+
fn conv_with(self, line_index: &LineIndex) -> Self::Output {
38+
let start = line_index.line_col(self.start());
39+
let end = line_index.line_col(self.end());
40+
41+
return_types::Range {
42+
startLineNumber: start.line + 1,
43+
startColumn: start.col_utf16 + 1,
44+
endLineNumber: end.line + 1,
45+
endColumn: end.col_utf16 + 1,
46+
}
47+
}
48+
}
49+
50+
impl Conv for CompletionItemKind {
51+
type Output = return_types::CompletionItemKind;
52+
53+
fn conv(self) -> <Self as Conv>::Output {
54+
use return_types::CompletionItemKind::*;
55+
match self {
56+
CompletionItemKind::Keyword => Keyword,
57+
CompletionItemKind::Snippet => Snippet,
58+
CompletionItemKind::Module => Module,
59+
CompletionItemKind::Function => Function,
60+
CompletionItemKind::Struct => Struct,
61+
CompletionItemKind::Enum => Enum,
62+
CompletionItemKind::EnumVariant => EnumMember,
63+
CompletionItemKind::BuiltinType => Struct,
64+
CompletionItemKind::Binding => Variable,
65+
CompletionItemKind::Field => Field,
66+
CompletionItemKind::Trait => Interface,
67+
CompletionItemKind::TypeAlias => Struct,
68+
CompletionItemKind::Const => Constant,
69+
CompletionItemKind::Static => Value,
70+
CompletionItemKind::Method => Method,
71+
CompletionItemKind::TypeParam => TypeParameter,
72+
CompletionItemKind::Macro => Method,
73+
}
74+
}
75+
}
76+
77+
impl ConvWith<&LineIndex> for &AtomTextEdit {
78+
type Output = return_types::TextEdit;
79+
80+
fn conv_with(self, line_index: &LineIndex) -> Self::Output {
81+
let text = self.insert.clone();
82+
return_types::TextEdit { range: self.delete.conv_with(line_index), text }
83+
}
84+
}
85+
86+
impl ConvWith<&LineIndex> for CompletionItem {
87+
type Output = return_types::CompletionItem;
88+
89+
fn conv_with(self, line_index: &LineIndex) -> Self::Output {
90+
let mut additional_text_edits = Vec::new();
91+
let mut text_edit = None;
92+
// LSP does not allow arbitrary edits in completion, so we have to do a
93+
// non-trivial mapping here.
94+
for atom_edit in self.text_edit().as_atoms() {
95+
if self.source_range().is_subrange(&atom_edit.delete) {
96+
text_edit = Some(if atom_edit.delete == self.source_range() {
97+
atom_edit.conv_with(line_index)
98+
} else {
99+
assert!(self.source_range().end() == atom_edit.delete.end());
100+
let range1 =
101+
TextRange::from_to(atom_edit.delete.start(), self.source_range().start());
102+
let range2 = self.source_range();
103+
let edit1 = AtomTextEdit::replace(range1, String::new());
104+
let edit2 = AtomTextEdit::replace(range2, atom_edit.insert.clone());
105+
additional_text_edits.push(edit1.conv_with(line_index));
106+
edit2.conv_with(line_index)
107+
})
108+
} else {
109+
assert!(self.source_range().intersection(&atom_edit.delete).is_none());
110+
additional_text_edits.push(atom_edit.conv_with(line_index));
111+
}
112+
}
113+
let return_types::TextEdit { range, text } = text_edit.unwrap();
114+
115+
return_types::CompletionItem {
116+
kind: self.kind().unwrap_or(CompletionItemKind::Struct).conv(),
117+
label: self.label().to_string(),
118+
range,
119+
detail: self.detail().map(|it| it.to_string()),
120+
insertText: text,
121+
insertTextRules: match self.insert_text_format() {
122+
InsertTextFormat::PlainText => return_types::CompletionItemInsertTextRule::None,
123+
InsertTextFormat::Snippet => {
124+
return_types::CompletionItemInsertTextRule::InsertAsSnippet
125+
}
126+
},
127+
documentation: self
128+
.documentation()
129+
.map(|doc| return_types::MarkdownString { value: doc.as_str().to_string() }),
130+
filterText: self.lookup().to_string(),
131+
additionalTextEdits: additional_text_edits,
132+
}
133+
}
134+
}

wasm-demo/src/lib.rs

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#![cfg(target_arch = "wasm32")]
22
#![allow(non_snake_case)]
33

4-
use ra_ide_api::{Analysis, FileId, FilePosition, LineCol, Severity};
5-
use ra_syntax::{SyntaxKind, TextRange};
4+
use ra_ide_api::{Analysis, FileId, FilePosition, Severity};
5+
use ra_syntax::SyntaxKind;
66
use wasm_bindgen::prelude::*;
77

8+
mod conv;
9+
use conv::*;
810
mod return_types;
911
use return_types::*;
1012

@@ -30,16 +32,19 @@ impl WorldState {
3032
}
3133

3234
pub fn update(&mut self, code: String) -> JsValue {
35+
log::warn!("update");
3336
let (analysis, file_id) = Analysis::from_single_file(code);
3437
self.analysis = analysis;
3538
self.file_id = file_id;
3639

40+
let line_index = self.analysis.file_line_index(self.file_id).unwrap();
41+
3742
let highlights: Vec<_> = self
3843
.analysis
3944
.highlight(file_id)
4045
.unwrap()
4146
.into_iter()
42-
.map(|hl| Highlight { tag: Some(hl.tag), range: self.range(hl.range) })
47+
.map(|hl| Highlight { tag: Some(hl.tag), range: hl.range.conv_with(&line_index) })
4348
.collect();
4449

4550
let diagnostics: Vec<_> = self
@@ -49,7 +54,7 @@ impl WorldState {
4954
.into_iter()
5055
.map(|d| {
5156
let Range { startLineNumber, startColumn, endLineNumber, endColumn } =
52-
self.range(d.range);
57+
d.range.conv_with(&line_index);
5358
Diagnostic {
5459
message: d.message,
5560
severity: match d.severity {
@@ -67,51 +72,42 @@ impl WorldState {
6772
serde_wasm_bindgen::to_value(&UpdateResult { diagnostics, highlights }).unwrap()
6873
}
6974

70-
fn file_pos(&self, line: u32, col_utf16: u32) -> FilePosition {
71-
// monaco doesn't work zero-based
72-
let line_col = LineCol { line: line - 1, col_utf16: col_utf16 - 1 };
73-
let offset = self.analysis.file_line_index(self.file_id).unwrap().offset(line_col);
74-
FilePosition { file_id: self.file_id, offset }
75-
}
76-
77-
fn range(&self, text_range: TextRange) -> Range {
75+
pub fn completions(&self, line_number: u32, column: u32) -> JsValue {
76+
log::warn!("completions");
7877
let line_index = self.analysis.file_line_index(self.file_id).unwrap();
79-
let start = line_index.line_col(text_range.start());
80-
let end = line_index.line_col(text_range.end());
81-
82-
Range {
83-
startLineNumber: start.line + 1,
84-
startColumn: start.col_utf16 + 1,
85-
endLineNumber: end.line + 1,
86-
endColumn: end.col_utf16 + 1,
87-
}
88-
}
8978

90-
pub fn on_dot_typed(&self, line_number: u32, column: u32) {
91-
let pos = self.file_pos(line_number, column);
92-
log::warn!("on_dot_typed");
93-
let res = self.analysis.on_dot_typed(pos).unwrap();
79+
let pos = Position { line_number, column }.conv_with((&line_index, self.file_id));
80+
let res = match self.analysis.completions(pos).unwrap() {
81+
Some(items) => items,
82+
None => return JsValue::NULL,
83+
};
9484

95-
log::debug!("{:?}", res);
85+
let items: Vec<_> = res.into_iter().map(|item| item.conv_with(&line_index)).collect();
86+
serde_wasm_bindgen::to_value(&items).unwrap()
9687
}
9788

9889
pub fn hover(&self, line_number: u32, column: u32) -> JsValue {
99-
let pos = self.file_pos(line_number, column);
10090
log::warn!("hover");
91+
let line_index = self.analysis.file_line_index(self.file_id).unwrap();
92+
93+
let pos = Position { line_number, column }.conv_with((&line_index, self.file_id));
10194
let info = match self.analysis.hover(pos).unwrap() {
10295
Some(info) => info,
10396
_ => return JsValue::NULL,
10497
};
10598

10699
let value = info.info.to_markup();
107-
let hover =
108-
Hover { contents: vec![MarkdownString { value }], range: self.range(info.range) };
100+
let hover = Hover {
101+
contents: vec![MarkdownString { value }],
102+
range: info.range.conv_with(&line_index),
103+
};
109104

110105
serde_wasm_bindgen::to_value(&hover).unwrap()
111106
}
112107

113108
pub fn code_lenses(&self) -> JsValue {
114109
log::warn!("code_lenses");
110+
let line_index = self.analysis.file_line_index(self.file_id).unwrap();
115111

116112
let results: Vec<_> = self
117113
.analysis
@@ -137,11 +133,11 @@ impl WorldState {
137133
.info
138134
.iter()
139135
.map(|target| target.focus_range().unwrap_or(target.full_range()))
140-
.map(|range| self.range(range))
136+
.map(|range| range.conv_with(&line_index))
141137
.collect();
142138

143139
Some(CodeLensSymbol {
144-
range: self.range(it.node_range),
140+
range: it.node_range.conv_with(&line_index),
145141
command: Some(Command {
146142
id: "editor.action.showReferences".into(),
147143
title,
@@ -155,36 +151,44 @@ impl WorldState {
155151
}
156152

157153
pub fn references(&self, line_number: u32, column: u32) -> JsValue {
158-
let pos = self.file_pos(line_number, column);
159154
log::warn!("references");
155+
let line_index = self.analysis.file_line_index(self.file_id).unwrap();
156+
157+
let pos = Position { line_number, column }.conv_with((&line_index, self.file_id));
160158
let info = match self.analysis.find_all_refs(pos).unwrap() {
161159
Some(info) => info,
162160
_ => return JsValue::NULL,
163161
};
164162

165-
let res: Vec<_> =
166-
info.into_iter().map(|r| Highlight { tag: None, range: self.range(r.range) }).collect();
163+
let res: Vec<_> = info
164+
.into_iter()
165+
.map(|r| Highlight { tag: None, range: r.range.conv_with(&line_index) })
166+
.collect();
167167
serde_wasm_bindgen::to_value(&res).unwrap()
168168
}
169169

170170
pub fn prepare_rename(&self, line_number: u32, column: u32) -> JsValue {
171-
let pos = self.file_pos(line_number, column);
172171
log::warn!("prepare_rename");
172+
let line_index = self.analysis.file_line_index(self.file_id).unwrap();
173+
174+
let pos = Position { line_number, column }.conv_with((&line_index, self.file_id));
173175
let refs = match self.analysis.find_all_refs(pos).unwrap() {
174176
None => return JsValue::NULL,
175177
Some(refs) => refs,
176178
};
177179

178180
let declaration = refs.declaration();
179-
let range = self.range(declaration.range());
181+
let range = declaration.range().conv_with(&line_index);
180182
let text = declaration.name().to_string();
181183

182184
serde_wasm_bindgen::to_value(&RenameLocation { range, text }).unwrap()
183185
}
184186

185187
pub fn rename(&self, line_number: u32, column: u32, new_name: &str) -> JsValue {
186-
let pos = self.file_pos(line_number, column);
187188
log::warn!("rename");
189+
let line_index = self.analysis.file_line_index(self.file_id).unwrap();
190+
191+
let pos = Position { line_number, column }.conv_with((&line_index, self.file_id));
188192
let change = match self.analysis.rename(pos, new_name) {
189193
Ok(Some(change)) => change,
190194
_ => return JsValue::NULL,
@@ -195,7 +199,10 @@ impl WorldState {
195199
.source_file_edits
196200
.iter()
197201
.flat_map(|sfe| sfe.edit.as_atoms())
198-
.map(|atom| TextEdit { range: self.range(atom.delete), text: atom.insert.clone() })
202+
.map(|atom| TextEdit {
203+
range: atom.delete.conv_with(&line_index),
204+
text: atom.insert.clone(),
205+
})
199206
.collect();
200207

201208
serde_wasm_bindgen::to_value(&result).unwrap()

0 commit comments

Comments
 (0)