aboutsummaryrefslogtreecommitdiff
path: root/crates/server
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2018-08-29 16:03:14 +0100
committerAleksey Kladov <[email protected]>2018-08-29 16:03:14 +0100
commit8abf5363433e977c5393bb569e2a5d559cb0a602 (patch)
tree8bb7bc3097cb9e22af9e3be8605cb4745c2fae5f /crates/server
parent2007ccfcfe0bf01c934589dd3c87fda83b06b272 (diff)
Grand refactoring
Diffstat (limited to 'crates/server')
-rw-r--r--crates/server/src/conv.rs80
-rw-r--r--crates/server/src/main.rs3
-rw-r--r--crates/server/src/main_loop/handlers.rs384
-rw-r--r--crates/server/src/main_loop/mod.rs169
-rw-r--r--crates/server/src/req.rs35
-rw-r--r--crates/server/src/server_world.rs8
6 files changed, 289 insertions, 390 deletions
diff --git a/crates/server/src/conv.rs b/crates/server/src/conv.rs
index fc9056914..a59308c3f 100644
--- a/crates/server/src/conv.rs
+++ b/crates/server/src/conv.rs
@@ -1,14 +1,16 @@
1use languageserver_types::{ 1use languageserver_types::{
2 Range, SymbolKind, Position, TextEdit, Location, Url, 2 Range, SymbolKind, Position, TextEdit, Location, Url,
3 TextDocumentIdentifier, VersionedTextDocumentIdentifier, TextDocumentItem, 3 TextDocumentIdentifier, VersionedTextDocumentIdentifier, TextDocumentItem,
4 TextDocumentPositionParams, TextDocumentEdit,
4}; 5};
5use libeditor::{LineIndex, LineCol, Edit, AtomEdit}; 6use libeditor::{LineIndex, LineCol, Edit, AtomEdit};
6use libsyntax2::{SyntaxKind, TextUnit, TextRange}; 7use libsyntax2::{SyntaxKind, TextUnit, TextRange};
7use libanalysis::FileId; 8use libanalysis::{FileId, SourceChange, SourceFileEdit, FileSystemEdit};
8 9
9use { 10use {
10 Result, 11 Result,
11 server_world::ServerWorld, 12 server_world::ServerWorld,
13 req,
12}; 14};
13 15
14pub trait Conv { 16pub trait Conv {
@@ -168,6 +170,82 @@ impl<'a> TryConvWith for &'a TextDocumentIdentifier {
168 } 170 }
169} 171}
170 172
173impl<T: TryConvWith> TryConvWith for Vec<T> {
174 type Ctx = <T as TryConvWith>::Ctx;
175 type Output = Vec<<T as TryConvWith>::Output>;
176 fn try_conv_with(self, ctx: &Self::Ctx) -> Result<Self::Output> {
177 let mut res = Vec::with_capacity(self.len());
178 for item in self {
179 res.push(item.try_conv_with(ctx)?);
180 }
181 Ok(res)
182 }
183}
184
185impl TryConvWith for SourceChange {
186 type Ctx = ServerWorld;
187 type Output = req::SourceChange;
188 fn try_conv_with(self, world: &ServerWorld) -> Result<req::SourceChange> {
189 let cursor_position = match self.cursor_position {
190 None => None,
191 Some(pos) => {
192 let line_index = world.analysis().file_line_index(pos.file_id);
193 Some(TextDocumentPositionParams {
194 text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?),
195 position: pos.offset.conv_with(&line_index),
196 })
197 }
198 };
199 let source_file_edits = self.source_file_edits.try_conv_with(world)?;
200 let file_system_edits = self.file_system_edits.try_conv_with(world)?;
201 Ok(req::SourceChange {
202 label: self.label,
203 source_file_edits,
204 file_system_edits,
205 cursor_position,
206 })
207 }
208}
209
210impl TryConvWith for SourceFileEdit {
211 type Ctx = ServerWorld;
212 type Output = TextDocumentEdit;
213 fn try_conv_with(self, world: &ServerWorld) -> Result<TextDocumentEdit> {
214 let text_document = VersionedTextDocumentIdentifier {
215 uri: self.file_id.try_conv_with(world)?,
216 version: None,
217 };
218 let line_index = world.analysis().file_line_index(self.file_id);
219 let edits = self.edits
220 .into_iter()
221 .map_conv_with(&line_index)
222 .collect();
223 Ok(TextDocumentEdit { text_document, edits })
224 }
225}
226
227impl TryConvWith for FileSystemEdit {
228 type Ctx = ServerWorld;
229 type Output = req::FileSystemEdit;
230 fn try_conv_with(self, world: &ServerWorld) -> Result<req::FileSystemEdit> {
231 let res = match self {
232 FileSystemEdit::CreateFile { anchor, path } => {
233 let uri = world.file_id_to_uri(anchor)?;
234 let path = &path.as_str()[3..]; // strip `../` b/c url is weird
235 let uri = uri.join(path)?;
236 req::FileSystemEdit::CreateFile { uri }
237 },
238 FileSystemEdit::MoveFile { file, path } => {
239 let src = world.file_id_to_uri(file)?;
240 let path = &path.as_str()[3..]; // strip `../` b/c url is weird
241 let dst = src.join(path)?;
242 req::FileSystemEdit::MoveFile { src, dst }
243 },
244 };
245 Ok(res)
246 }
247}
248
171pub fn to_location( 249pub fn to_location(
172 file_id: FileId, 250 file_id: FileId,
173 range: TextRange, 251 range: TextRange,
diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs
index 1a93af65b..6af8bf81b 100644
--- a/crates/server/src/main.rs
+++ b/crates/server/src/main.rs
@@ -35,7 +35,7 @@ use crossbeam_channel::bounded;
35use flexi_logger::{Logger, Duplicate}; 35use flexi_logger::{Logger, Duplicate};
36 36
37use ::{ 37use ::{
38 io::{Io, RawMsg, RawResponse, RawRequest, RawNotification}, 38 io::{Io, RawMsg, RawResponse, RawNotification},
39}; 39};
40 40
41pub type Result<T> = ::std::result::Result<T, ::failure::Error>; 41pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
@@ -109,7 +109,6 @@ fn initialize(io: &mut Io) -> Result<()> {
109 109
110enum Task { 110enum Task {
111 Respond(RawResponse), 111 Respond(RawResponse),
112 Request(RawRequest),
113 Notify(RawNotification), 112 Notify(RawNotification),
114 Die(::failure::Error), 113 Die(::failure::Error),
115} 114}
diff --git a/crates/server/src/main_loop/handlers.rs b/crates/server/src/main_loop/handlers.rs
index 3ee0873f4..45083b084 100644
--- a/crates/server/src/main_loop/handlers.rs
+++ b/crates/server/src/main_loop/handlers.rs
@@ -2,16 +2,13 @@ use std::collections::HashMap;
2 2
3use languageserver_types::{ 3use languageserver_types::{
4 Diagnostic, DiagnosticSeverity, Url, DocumentSymbol, 4 Diagnostic, DiagnosticSeverity, Url, DocumentSymbol,
5 Command, TextDocumentIdentifier, WorkspaceEdit, 5 Command, TextDocumentIdentifier,
6 SymbolInformation, Position, Location, TextEdit, 6 SymbolInformation, Position, Location, TextEdit,
7 CompletionItem, InsertTextFormat, CompletionItemKind, 7 CompletionItem, InsertTextFormat, CompletionItemKind,
8}; 8};
9use serde_json::{to_value, from_value}; 9use serde_json::to_value;
10use url_serde; 10use libanalysis::{Query, FileId, RunnableKind};
11use libanalysis::{self, Query, FileId};
12use libeditor;
13use libsyntax2::{ 11use libsyntax2::{
14 TextUnit,
15 text_utils::contains_offset_nonstrict, 12 text_utils::contains_offset_nonstrict,
16}; 13};
17 14
@@ -26,8 +23,8 @@ pub fn handle_syntax_tree(
26 params: req::SyntaxTreeParams, 23 params: req::SyntaxTreeParams,
27) -> Result<String> { 24) -> Result<String> {
28 let id = params.text_document.try_conv_with(&world)?; 25 let id = params.text_document.try_conv_with(&world)?;
29 let file = world.analysis().file_syntax(id)?; 26 let res = world.analysis().syntax_tree(id);
30 Ok(libeditor::syntax_tree(&file)) 27 Ok(res)
31} 28}
32 29
33pub fn handle_extend_selection( 30pub fn handle_extend_selection(
@@ -35,11 +32,11 @@ pub fn handle_extend_selection(
35 params: req::ExtendSelectionParams, 32 params: req::ExtendSelectionParams,
36) -> Result<req::ExtendSelectionResult> { 33) -> Result<req::ExtendSelectionResult> {
37 let file_id = params.text_document.try_conv_with(&world)?; 34 let file_id = params.text_document.try_conv_with(&world)?;
38 let file = world.analysis().file_syntax(file_id)?; 35 let file = world.analysis().file_syntax(file_id);
39 let line_index = world.analysis().file_line_index(file_id)?; 36 let line_index = world.analysis().file_line_index(file_id);
40 let selections = params.selections.into_iter() 37 let selections = params.selections.into_iter()
41 .map_conv_with(&line_index) 38 .map_conv_with(&line_index)
42 .map(|r| libeditor::extend_selection(&file, r).unwrap_or(r)) 39 .map(|r| world.analysis().extend_selection(&file, r))
43 .map_conv_with(&line_index) 40 .map_conv_with(&line_index)
44 .collect(); 41 .collect();
45 Ok(req::ExtendSelectionResult { selections }) 42 Ok(req::ExtendSelectionResult { selections })
@@ -50,13 +47,13 @@ pub fn handle_find_matching_brace(
50 params: req::FindMatchingBraceParams, 47 params: req::FindMatchingBraceParams,
51) -> Result<Vec<Position>> { 48) -> Result<Vec<Position>> {
52 let file_id = params.text_document.try_conv_with(&world)?; 49 let file_id = params.text_document.try_conv_with(&world)?;
53 let file = world.analysis().file_syntax(file_id)?; 50 let file = world.analysis().file_syntax(file_id);
54 let line_index = world.analysis().file_line_index(file_id)?; 51 let line_index = world.analysis().file_line_index(file_id);
55 let res = params.offsets 52 let res = params.offsets
56 .into_iter() 53 .into_iter()
57 .map_conv_with(&line_index) 54 .map_conv_with(&line_index)
58 .map(|offset| { 55 .map(|offset| {
59 libeditor::matching_brace(&file, offset).unwrap_or(offset) 56 world.analysis().matching_brace(&file, offset).unwrap_or(offset)
60 }) 57 })
61 .map_conv_with(&line_index) 58 .map_conv_with(&line_index)
62 .collect(); 59 .collect();
@@ -66,13 +63,31 @@ pub fn handle_find_matching_brace(
66pub fn handle_join_lines( 63pub fn handle_join_lines(
67 world: ServerWorld, 64 world: ServerWorld,
68 params: req::JoinLinesParams, 65 params: req::JoinLinesParams,
69) -> Result<Vec<TextEdit>> { 66) -> Result<req::SourceChange> {
70 let file_id = params.text_document.try_conv_with(&world)?; 67 let file_id = params.text_document.try_conv_with(&world)?;
71 let file = world.analysis().file_syntax(file_id)?; 68 let line_index = world.analysis().file_line_index(file_id);
72 let line_index = world.analysis().file_line_index(file_id)?;
73 let range = params.range.conv_with(&line_index); 69 let range = params.range.conv_with(&line_index);
74 let res = libeditor::join_lines(&file, range); 70 world.analysis().join_lines(file_id, range)
75 Ok(res.edit.conv_with(&line_index)) 71 .try_conv_with(&world)
72}
73
74pub fn handle_on_type_formatting(
75 world: ServerWorld,
76 params: req::DocumentOnTypeFormattingParams,
77) -> Result<Option<Vec<TextEdit>>> {
78 if params.ch != "=" {
79 return Ok(None);
80 }
81
82 let file_id = params.text_document.try_conv_with(&world)?;
83 let line_index = world.analysis().file_line_index(file_id);
84 let offset = params.position.conv_with(&line_index);
85 let edits = match world.analysis().on_eq_typed(file_id, offset) {
86 None => return Ok(None),
87 Some(mut action) => action.source_file_edits.pop().unwrap().edits,
88 };
89 let edits = edits.into_iter().map_conv_with(&line_index).collect();
90 Ok(Some(edits))
76} 91}
77 92
78pub fn handle_document_symbol( 93pub fn handle_document_symbol(
@@ -80,12 +95,11 @@ pub fn handle_document_symbol(
80 params: req::DocumentSymbolParams, 95 params: req::DocumentSymbolParams,
81) -> Result<Option<req::DocumentSymbolResponse>> { 96) -> Result<Option<req::DocumentSymbolResponse>> {
82 let file_id = params.text_document.try_conv_with(&world)?; 97 let file_id = params.text_document.try_conv_with(&world)?;
83 let file = world.analysis().file_syntax(file_id)?; 98 let line_index = world.analysis().file_line_index(file_id);
84 let line_index = world.analysis().file_line_index(file_id)?;
85 99
86 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new(); 100 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
87 101
88 for symbol in libeditor::file_structure(&file) { 102 for symbol in world.analysis().file_structure(file_id) {
89 let doc_symbol = DocumentSymbol { 103 let doc_symbol = DocumentSymbol {
90 name: symbol.label, 104 name: symbol.label,
91 detail: Some("".to_string()), 105 detail: Some("".to_string()),
@@ -114,130 +128,6 @@ pub fn handle_document_symbol(
114 Ok(Some(req::DocumentSymbolResponse::Nested(res))) 128 Ok(Some(req::DocumentSymbolResponse::Nested(res)))
115} 129}
116 130
117pub fn handle_code_action(
118 world: ServerWorld,
119 params: req::CodeActionParams,
120) -> Result<Option<Vec<Command>>> {
121 let file_id = params.text_document.try_conv_with(&world)?;
122 let file = world.analysis().file_syntax(file_id)?;
123 let line_index = world.analysis().file_line_index(file_id)?;
124 let offset = params.range.conv_with(&line_index).start();
125 let mut res = Vec::new();
126
127 let actions = &[
128 (ActionId::FlipComma, libeditor::flip_comma(&file, offset).is_some()),
129 (ActionId::AddDerive, libeditor::add_derive(&file, offset).is_some()),
130 (ActionId::AddImpl, libeditor::add_impl(&file, offset).is_some()),
131 ];
132
133 for (id, edit) in actions {
134 if *edit {
135 let cmd = apply_code_action_cmd(*id, params.text_document.clone(), offset);
136 res.push(cmd);
137 }
138 }
139
140 for (diag, quick_fix) in world.analysis().diagnostics(file_id)? {
141 let quick_fix = match quick_fix {
142 Some(quick_fix) => quick_fix,
143 None => continue,
144 };
145 if !contains_offset_nonstrict(diag.range, offset) {
146 continue;
147 }
148 let mut ops = Vec::new();
149 for op in quick_fix.fs_ops {
150 let op = match op {
151 libanalysis::FsOp::CreateFile { anchor, path } => {
152 let uri = world.file_id_to_uri(anchor)?;
153 let path = &path.as_str()[3..]; // strip `../` b/c url is weird
154 let uri = uri.join(path)?;
155 FsOp::CreateFile { uri }
156 },
157 libanalysis::FsOp::MoveFile { file, path } => {
158 let src = world.file_id_to_uri(file)?;
159 let path = &path.as_str()[3..]; // strip `../` b/c url is weird
160 let dst = src.join(path)?;
161 FsOp::MoveFile { src, dst }
162 },
163 };
164 ops.push(op)
165 }
166 let cmd = Command {
167 title: "Create module".to_string(),
168 command: "libsyntax-rust.fsEdit".to_string(),
169 arguments: Some(vec![to_value(ops).unwrap()]),
170 };
171 res.push(cmd)
172 }
173 return Ok(Some(res));
174}
175
176#[derive(Serialize)]
177#[serde(tag = "type", rename_all = "camelCase")]
178enum FsOp {
179 CreateFile {
180 #[serde(with = "url_serde")]
181 uri: Url
182 },
183 MoveFile {
184 #[serde(with = "url_serde")]
185 src: Url,
186 #[serde(with = "url_serde")]
187 dst: Url,
188 }
189}
190
191pub fn handle_runnables(
192 world: ServerWorld,
193 params: req::RunnablesParams,
194) -> Result<Vec<req::Runnable>> {
195 let file_id = params.text_document.try_conv_with(&world)?;
196 let file = world.analysis().file_syntax(file_id)?;
197 let line_index = world.analysis().file_line_index(file_id)?;
198 let offset = params.position.map(|it| it.conv_with(&line_index));
199 let mut res = Vec::new();
200 for runnable in libeditor::runnables(&file) {
201 if let Some(offset) = offset {
202 if !contains_offset_nonstrict(runnable.range, offset) {
203 continue;
204 }
205 }
206
207 let r = req::Runnable {
208 range: runnable.range.conv_with(&line_index),
209 label: match &runnable.kind {
210 libeditor::RunnableKind::Test { name } =>
211 format!("test {}", name),
212 libeditor::RunnableKind::Bin =>
213 "run binary".to_string(),
214 },
215 bin: "cargo".to_string(),
216 args: match runnable.kind {
217 libeditor::RunnableKind::Test { name } => {
218 vec![
219 "test".to_string(),
220 "--".to_string(),
221 name,
222 "--nocapture".to_string(),
223 ]
224 }
225 libeditor::RunnableKind::Bin => vec!["run".to_string()]
226 },
227 env: {
228 let mut m = HashMap::new();
229 m.insert(
230 "RUST_BACKTRACE".to_string(),
231 "short".to_string(),
232 );
233 m
234 }
235 };
236 res.push(r);
237 }
238 return Ok(res);
239}
240
241pub fn handle_workspace_symbol( 131pub fn handle_workspace_symbol(
242 world: ServerWorld, 132 world: ServerWorld,
243 params: req::WorkspaceSymbolParams, 133 params: req::WorkspaceSymbolParams,
@@ -265,8 +155,8 @@ pub fn handle_workspace_symbol(
265 155
266 fn exec_query(world: &ServerWorld, query: Query) -> Result<Vec<SymbolInformation>> { 156 fn exec_query(world: &ServerWorld, query: Query) -> Result<Vec<SymbolInformation>> {
267 let mut res = Vec::new(); 157 let mut res = Vec::new();
268 for (file_id, symbol) in world.analysis().world_symbols(query) { 158 for (file_id, symbol) in world.analysis().symbol_search(query) {
269 let line_index = world.analysis().file_line_index(file_id)?; 159 let line_index = world.analysis().file_line_index(file_id);
270 let info = SymbolInformation { 160 let info = SymbolInformation {
271 name: symbol.name.to_string(), 161 name: symbol.name.to_string(),
272 kind: symbol.kind.conv(), 162 kind: symbol.kind.conv(),
@@ -287,11 +177,11 @@ pub fn handle_goto_definition(
287 params: req::TextDocumentPositionParams, 177 params: req::TextDocumentPositionParams,
288) -> Result<Option<req::GotoDefinitionResponse>> { 178) -> Result<Option<req::GotoDefinitionResponse>> {
289 let file_id = params.text_document.try_conv_with(&world)?; 179 let file_id = params.text_document.try_conv_with(&world)?;
290 let line_index = world.analysis().file_line_index(file_id)?; 180 let line_index = world.analysis().file_line_index(file_id);
291 let offset = params.position.conv_with(&line_index); 181 let offset = params.position.conv_with(&line_index);
292 let mut res = Vec::new(); 182 let mut res = Vec::new();
293 for (file_id, symbol) in world.analysis().approximately_resolve_symbol(file_id, offset)? { 183 for (file_id, symbol) in world.analysis().approximately_resolve_symbol(file_id, offset) {
294 let line_index = world.analysis().file_line_index(file_id)?; 184 let line_index = world.analysis().file_line_index(file_id);
295 let location = to_location( 185 let location = to_location(
296 file_id, symbol.node_range, 186 file_id, symbol.node_range,
297 &world, &line_index, 187 &world, &line_index,
@@ -308,7 +198,7 @@ pub fn handle_parent_module(
308 let file_id = params.try_conv_with(&world)?; 198 let file_id = params.try_conv_with(&world)?;
309 let mut res = Vec::new(); 199 let mut res = Vec::new();
310 for (file_id, symbol) in world.analysis().parent_module(file_id) { 200 for (file_id, symbol) in world.analysis().parent_module(file_id) {
311 let line_index = world.analysis().file_line_index(file_id)?; 201 let line_index = world.analysis().file_line_index(file_id);
312 let location = to_location( 202 let location = to_location(
313 file_id, symbol.node_range, 203 file_id, symbol.node_range,
314 &world, &line_index 204 &world, &line_index
@@ -318,15 +208,71 @@ pub fn handle_parent_module(
318 Ok(res) 208 Ok(res)
319} 209}
320 210
211pub fn handle_runnables(
212 world: ServerWorld,
213 params: req::RunnablesParams,
214) -> Result<Vec<req::Runnable>> {
215 let file_id = params.text_document.try_conv_with(&world)?;
216 let line_index = world.analysis().file_line_index(file_id);
217 let offset = params.position.map(|it| it.conv_with(&line_index));
218 let mut res = Vec::new();
219 for runnable in world.analysis().runnables(file_id) {
220 if let Some(offset) = offset {
221 if !contains_offset_nonstrict(runnable.range, offset) {
222 continue;
223 }
224 }
225
226 let r = req::Runnable {
227 range: runnable.range.conv_with(&line_index),
228 label: match &runnable.kind {
229 RunnableKind::Test { name } =>
230 format!("test {}", name),
231 RunnableKind::Bin =>
232 "run binary".to_string(),
233 },
234 bin: "cargo".to_string(),
235 args: match runnable.kind {
236 RunnableKind::Test { name } => {
237 vec![
238 "test".to_string(),
239 "--".to_string(),
240 name,
241 "--nocapture".to_string(),
242 ]
243 }
244 RunnableKind::Bin => vec!["run".to_string()]
245 },
246 env: {
247 let mut m = HashMap::new();
248 m.insert(
249 "RUST_BACKTRACE".to_string(),
250 "short".to_string(),
251 );
252 m
253 }
254 };
255 res.push(r);
256 }
257 return Ok(res);
258}
259
260pub fn handle_decorations(
261 world: ServerWorld,
262 params: TextDocumentIdentifier,
263) -> Result<Vec<Decoration>> {
264 let file_id = params.try_conv_with(&world)?;
265 Ok(highlight(&world, file_id))
266}
267
321pub fn handle_completion( 268pub fn handle_completion(
322 world: ServerWorld, 269 world: ServerWorld,
323 params: req::CompletionParams, 270 params: req::CompletionParams,
324) -> Result<Option<req::CompletionResponse>> { 271) -> Result<Option<req::CompletionResponse>> {
325 let file_id = params.text_document.try_conv_with(&world)?; 272 let file_id = params.text_document.try_conv_with(&world)?;
326 let file = world.analysis().file_syntax(file_id)?; 273 let line_index = world.analysis().file_line_index(file_id);
327 let line_index = world.analysis().file_line_index(file_id)?;
328 let offset = params.position.conv_with(&line_index); 274 let offset = params.position.conv_with(&line_index);
329 let items = match libeditor::scope_completion(&file, offset) { 275 let items = match world.analysis().completions(file_id, offset) {
330 None => return Ok(None), 276 None => return Ok(None),
331 Some(items) => items, 277 Some(items) => items,
332 }; 278 };
@@ -348,91 +294,33 @@ pub fn handle_completion(
348 Ok(Some(req::CompletionResponse::Array(items))) 294 Ok(Some(req::CompletionResponse::Array(items)))
349} 295}
350 296
351pub fn handle_on_type_formatting( 297pub fn handle_code_action(
352 world: ServerWorld, 298 world: ServerWorld,
353 params: req::DocumentOnTypeFormattingParams, 299 params: req::CodeActionParams,
354) -> Result<Option<Vec<TextEdit>>> { 300) -> Result<Option<Vec<Command>>> {
355 if params.ch != "=" {
356 return Ok(None);
357 }
358
359 let file_id = params.text_document.try_conv_with(&world)?; 301 let file_id = params.text_document.try_conv_with(&world)?;
360 let line_index = world.analysis().file_line_index(file_id)?; 302 let line_index = world.analysis().file_line_index(file_id);
361 let offset = params.position.conv_with(&line_index); 303 let offset = params.range.conv_with(&line_index).start();
362 let file = world.analysis().file_syntax(file_id)?;
363 let action = match libeditor::on_eq_typed(&file, offset) {
364 None => return Ok(None),
365 Some(action) => action,
366 };
367 Ok(Some(action.edit.conv_with(&line_index)))
368}
369
370pub fn handle_execute_command(
371 world: ServerWorld,
372 mut params: req::ExecuteCommandParams,
373) -> Result<(req::ApplyWorkspaceEditParams, Option<Position>)> {
374 if params.command.as_str() != "apply_code_action" {
375 bail!("unknown cmd: {:?}", params.command);
376 }
377 if params.arguments.len() != 1 {
378 bail!("expected single arg, got {}", params.arguments.len());
379 }
380 let arg = params.arguments.pop().unwrap();
381 let arg: ActionRequest = from_value(arg)?;
382 let file_id = arg.text_document.try_conv_with(&world)?;
383 let file = world.analysis().file_syntax(file_id)?;
384 let action_result = match arg.id {
385 ActionId::FlipComma => libeditor::flip_comma(&file, arg.offset).map(|f| f()),
386 ActionId::AddDerive => libeditor::add_derive(&file, arg.offset).map(|f| f()),
387 ActionId::AddImpl => libeditor::add_impl(&file, arg.offset).map(|f| f()),
388 }.ok_or_else(|| format_err!("command not applicable"))?;
389 let line_index = world.analysis().file_line_index(file_id)?;
390 let mut changes = HashMap::new();
391 changes.insert(
392 arg.text_document.uri,
393 action_result.edit.conv_with(&line_index),
394 );
395 let edit = WorkspaceEdit {
396 changes: Some(changes),
397 document_changes: None,
398 };
399 let edit = req::ApplyWorkspaceEditParams { edit };
400 let cursor_pos = action_result.cursor_position
401 .map(|off| off.conv_with(&line_index));
402 Ok((edit, cursor_pos))
403}
404 304
405#[derive(Serialize, Deserialize)] 305 let assists = world.analysis().assists(file_id, offset).into_iter();
406struct ActionRequest { 306 let fixes = world.analysis().diagnostics(file_id).into_iter()
407 id: ActionId, 307 .filter_map(|d| Some((d.range, d.fix?)))
408 text_document: TextDocumentIdentifier, 308 .filter(|(range, _fix)| contains_offset_nonstrict(*range, offset))
409 offset: TextUnit, 309 .map(|(_range, fix)| fix);
410}
411 310
412fn apply_code_action_cmd(id: ActionId, doc: TextDocumentIdentifier, offset: TextUnit) -> Command { 311 let mut res = Vec::new();
413 let action_request = ActionRequest { id, text_document: doc, offset }; 312 for source_edit in assists.chain(fixes) {
414 Command { 313 let title = source_edit.label.clone();
415 title: id.title().to_string(), 314 let edit = source_edit.try_conv_with(&world)?;
416 command: "apply_code_action".to_string(), 315 let cmd = Command {
417 arguments: Some(vec![to_value(action_request).unwrap()]), 316 title,
317 command: "libsyntax-rust.applySourceChange".to_string(),
318 arguments: Some(vec![to_value(edit).unwrap()]),
319 };
320 res.push(cmd);
418 } 321 }
419}
420 322
421#[derive(Serialize, Deserialize, Clone, Copy)] 323 Ok(Some(res))
422enum ActionId {
423 FlipComma,
424 AddDerive,
425 AddImpl,
426}
427
428impl ActionId {
429 fn title(&self) -> &'static str {
430 match *self {
431 ActionId::FlipComma => "Flip `,`",
432 ActionId::AddDerive => "Add `#[derive]`",
433 ActionId::AddImpl => "Add impl",
434 }
435 }
436} 324}
437 325
438pub fn publish_diagnostics( 326pub fn publish_diagnostics(
@@ -440,28 +328,20 @@ pub fn publish_diagnostics(
440 uri: Url 328 uri: Url
441) -> Result<req::PublishDiagnosticsParams> { 329) -> Result<req::PublishDiagnosticsParams> {
442 let file_id = world.uri_to_file_id(&uri)?; 330 let file_id = world.uri_to_file_id(&uri)?;
443 let line_index = world.analysis().file_line_index(file_id)?; 331 let line_index = world.analysis().file_line_index(file_id);
444 let diagnostics = world.analysis().diagnostics(file_id)? 332 let diagnostics = world.analysis().diagnostics(file_id)
445 .into_iter() 333 .into_iter()
446 .map(|(d, _quick_fix)| Diagnostic { 334 .map(|d| Diagnostic {
447 range: d.range.conv_with(&line_index), 335 range: d.range.conv_with(&line_index),
448 severity: Some(DiagnosticSeverity::Error), 336 severity: Some(DiagnosticSeverity::Error),
449 code: None, 337 code: None,
450 source: Some("libsyntax2".to_string()), 338 source: Some("libsyntax2".to_string()),
451 message: d.msg, 339 message: d.message,
452 related_information: None, 340 related_information: None,
453 }).collect(); 341 }).collect();
454 Ok(req::PublishDiagnosticsParams { uri, diagnostics }) 342 Ok(req::PublishDiagnosticsParams { uri, diagnostics })
455} 343}
456 344
457pub fn handle_decorations(
458 world: ServerWorld,
459 params: TextDocumentIdentifier,
460) -> Result<Vec<Decoration>> {
461 let file_id = params.try_conv_with(&world)?;
462 highlight(&world, file_id)
463}
464
465pub fn publish_decorations( 345pub fn publish_decorations(
466 world: ServerWorld, 346 world: ServerWorld,
467 uri: Url 347 uri: Url
@@ -469,18 +349,16 @@ pub fn publish_decorations(
469 let file_id = world.uri_to_file_id(&uri)?; 349 let file_id = world.uri_to_file_id(&uri)?;
470 Ok(req::PublishDecorationsParams { 350 Ok(req::PublishDecorationsParams {
471 uri, 351 uri,
472 decorations: highlight(&world, file_id)? 352 decorations: highlight(&world, file_id),
473 }) 353 })
474} 354}
475 355
476fn highlight(world: &ServerWorld, file_id: FileId) -> Result<Vec<Decoration>> { 356fn highlight(world: &ServerWorld, file_id: FileId) -> Vec<Decoration> {
477 let file = world.analysis().file_syntax(file_id)?; 357 let line_index = world.analysis().file_line_index(file_id);
478 let line_index = world.analysis().file_line_index(file_id)?; 358 world.analysis().highlight(file_id)
479 let res = libeditor::highlight(&file)
480 .into_iter() 359 .into_iter()
481 .map(|h| Decoration { 360 .map(|h| Decoration {
482 range: h.range.conv_with(&line_index), 361 range: h.range.conv_with(&line_index),
483 tag: h.tag, 362 tag: h.tag,
484 }).collect(); 363 }).collect()
485 Ok(res)
486} 364}
diff --git a/crates/server/src/main_loop/mod.rs b/crates/server/src/main_loop/mod.rs
index accb13878..0f66248a5 100644
--- a/crates/server/src/main_loop/mod.rs
+++ b/crates/server/src/main_loop/mod.rs
@@ -7,7 +7,6 @@ use std::{
7use threadpool::ThreadPool; 7use threadpool::ThreadPool;
8use crossbeam_channel::{Sender, Receiver}; 8use crossbeam_channel::{Sender, Receiver};
9use languageserver_types::Url; 9use languageserver_types::Url;
10use serde_json::to_value;
11 10
12use { 11use {
13 req, dispatch, 12 req, dispatch,
@@ -15,24 +14,6 @@ use {
15 io::{Io, RawMsg, RawRequest, RawNotification}, 14 io::{Io, RawMsg, RawRequest, RawNotification},
16 vfs::FileEvent, 15 vfs::FileEvent,
17 server_world::{ServerWorldState, ServerWorld}, 16 server_world::{ServerWorldState, ServerWorld},
18 main_loop::handlers::{
19 handle_syntax_tree,
20 handle_extend_selection,
21 publish_diagnostics,
22 publish_decorations,
23 handle_document_symbol,
24 handle_code_action,
25 handle_execute_command,
26 handle_workspace_symbol,
27 handle_goto_definition,
28 handle_find_matching_brace,
29 handle_parent_module,
30 handle_join_lines,
31 handle_completion,
32 handle_runnables,
33 handle_decorations,
34 handle_on_type_formatting,
35 },
36}; 17};
37 18
38pub(super) fn main_loop( 19pub(super) fn main_loop(
@@ -45,7 +26,6 @@ pub(super) fn main_loop(
45 info!("server initialized, serving requests"); 26 info!("server initialized, serving requests");
46 let mut state = ServerWorldState::new(); 27 let mut state = ServerWorldState::new();
47 28
48 let mut next_request_id = 0;
49 let mut pending_requests: HashSet<u64> = HashSet::new(); 29 let mut pending_requests: HashSet<u64> = HashSet::new();
50 let mut fs_events_receiver = Some(&fs_events_receiver); 30 let mut fs_events_receiver = Some(&fs_events_receiver);
51 loop { 31 loop {
@@ -78,12 +58,6 @@ pub(super) fn main_loop(
78 } 58 }
79 Event::Task(task) => { 59 Event::Task(task) => {
80 match task { 60 match task {
81 Task::Request(mut request) => {
82 request.id = next_request_id;
83 pending_requests.insert(next_request_id);
84 next_request_id += 1;
85 io.send(RawMsg::Request(request));
86 }
87 Task::Respond(response) => 61 Task::Respond(response) =>
88 io.send(RawMsg::Response(response)), 62 io.send(RawMsg::Response(response)),
89 Task::Notify(n) => 63 Task::Notify(n) =>
@@ -125,79 +99,26 @@ fn on_request(
125 sender: &Sender<Task>, 99 sender: &Sender<Task>,
126 req: RawRequest, 100 req: RawRequest,
127) -> Result<bool> { 101) -> Result<bool> {
128 let mut req = Some(req); 102 let mut pool_dispatcher = PoolDispatcher {
129 handle_request_on_threadpool::<req::SyntaxTree>( 103 req: Some(req),
130 &mut req, pool, world, sender, handle_syntax_tree, 104 pool, world, sender
131 )?; 105 };
132 handle_request_on_threadpool::<req::ExtendSelection>( 106 pool_dispatcher
133 &mut req, pool, world, sender, handle_extend_selection, 107 .on::<req::SyntaxTree>(handlers::handle_syntax_tree)?
134 )?; 108 .on::<req::ExtendSelection>(handlers::handle_extend_selection)?
135 handle_request_on_threadpool::<req::FindMatchingBrace>( 109 .on::<req::FindMatchingBrace>(handlers::handle_find_matching_brace)?
136 &mut req, pool, world, sender, handle_find_matching_brace, 110 .on::<req::JoinLines>(handlers::handle_join_lines)?
137 )?; 111 .on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)?
138 handle_request_on_threadpool::<req::DocumentSymbolRequest>( 112 .on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)?
139 &mut req, pool, world, sender, handle_document_symbol, 113 .on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
140 )?; 114 .on::<req::GotoDefinition>(handlers::handle_goto_definition)?
141 handle_request_on_threadpool::<req::CodeActionRequest>( 115 .on::<req::ParentModule>(handlers::handle_parent_module)?
142 &mut req, pool, world, sender, handle_code_action, 116 .on::<req::Runnables>(handlers::handle_runnables)?
143 )?; 117 .on::<req::DecorationsRequest>(handlers::handle_decorations)?
144 handle_request_on_threadpool::<req::Runnables>( 118 .on::<req::Completion>(handlers::handle_completion)?
145 &mut req, pool, world, sender, handle_runnables, 119 .on::<req::CodeActionRequest>(handlers::handle_code_action)?;
146 )?;
147 handle_request_on_threadpool::<req::WorkspaceSymbol>(
148 &mut req, pool, world, sender, handle_workspace_symbol,
149 )?;
150 handle_request_on_threadpool::<req::GotoDefinition>(
151 &mut req, pool, world, sender, handle_goto_definition,
152 )?;
153 handle_request_on_threadpool::<req::Completion>(
154 &mut req, pool, world, sender, handle_completion,
155 )?;
156 handle_request_on_threadpool::<req::ParentModule>(
157 &mut req, pool, world, sender, handle_parent_module,
158 )?;
159 handle_request_on_threadpool::<req::JoinLines>(
160 &mut req, pool, world, sender, handle_join_lines,
161 )?;
162 handle_request_on_threadpool::<req::DecorationsRequest>(
163 &mut req, pool, world, sender, handle_decorations,
164 )?;
165 handle_request_on_threadpool::<req::OnTypeFormatting>(
166 &mut req, pool, world, sender, handle_on_type_formatting,
167 )?;
168 dispatch::handle_request::<req::ExecuteCommand, _>(&mut req, |params, resp| {
169 io.send(RawMsg::Response(resp.into_response(Ok(None))?));
170
171 let world = world.snapshot();
172 let sender = sender.clone();
173 pool.execute(move || {
174 let (edit, cursor) = match handle_execute_command(world, params) {
175 Ok(res) => res,
176 Err(e) => return sender.send(Task::Die(e)),
177 };
178 match to_value(edit) {
179 Err(e) => return sender.send(Task::Die(e.into())),
180 Ok(params) => {
181 let request = RawRequest {
182 id: 0,
183 method: <req::ApplyWorkspaceEdit as req::ClientRequest>::METHOD.to_string(),
184 params,
185 };
186 sender.send(Task::Request(request))
187 }
188 }
189 if let Some(cursor) = cursor {
190 let request = RawRequest {
191 id: 0,
192 method: <req::MoveCursor as req::ClientRequest>::METHOD.to_string(),
193 params: to_value(cursor).unwrap(),
194 };
195 sender.send(Task::Request(request))
196 }
197 });
198 Ok(())
199 })?;
200 120
121 let mut req = pool_dispatcher.req;
201 let mut shutdown = false; 122 let mut shutdown = false;
202 dispatch::handle_request::<req::Shutdown, _>(&mut req, |(), resp| { 123 dispatch::handle_request::<req::Shutdown, _>(&mut req, |(), resp| {
203 let resp = resp.into_response(Ok(()))?; 124 let resp = resp.into_response(Ok(()))?;
@@ -273,27 +194,33 @@ fn on_notification(
273 Ok(()) 194 Ok(())
274} 195}
275 196
276fn handle_request_on_threadpool<R: req::ClientRequest>( 197struct PoolDispatcher<'a> {
277 req: &mut Option<RawRequest>, 198 req: Option<RawRequest>,
278 pool: &ThreadPool, 199 pool: &'a ThreadPool,
279 world: &ServerWorldState, 200 world: &'a ServerWorldState,
280 sender: &Sender<Task>, 201 sender: &'a Sender<Task>,
281 f: fn(ServerWorld, R::Params) -> Result<R::Result>, 202}
282) -> Result<()> 203
283{ 204impl<'a> PoolDispatcher<'a> {
284 dispatch::handle_request::<R, _>(req, |params, resp| { 205 fn on<'b, R: req::ClientRequest>(&'b mut self, f: fn(ServerWorld, R::Params) -> Result<R::Result>) -> Result<&'b mut Self> {
285 let world = world.snapshot(); 206 let world = self.world;
286 let sender = sender.clone(); 207 let sender = self.sender;
287 pool.execute(move || { 208 let pool = self.pool;
288 let res = f(world, params); 209 dispatch::handle_request::<R, _>(&mut self.req, |params, resp| {
289 let task = match resp.into_response(res) { 210 let world = world.snapshot();
290 Ok(resp) => Task::Respond(resp), 211 let sender = sender.clone();
291 Err(e) => Task::Die(e), 212 pool.execute(move || {
292 }; 213 let res = f(world, params);
293 sender.send(task); 214 let task = match resp.into_response(res) {
294 }); 215 Ok(resp) => Task::Respond(resp),
295 Ok(()) 216 Err(e) => Task::Die(e),
296 }) 217 };
218 sender.send(task);
219 });
220 Ok(())
221 })?;
222 Ok(self)
223 }
297} 224}
298 225
299fn update_file_notifications_on_threadpool( 226fn update_file_notifications_on_threadpool(
@@ -303,7 +230,7 @@ fn update_file_notifications_on_threadpool(
303 uri: Url, 230 uri: Url,
304) { 231) {
305 pool.execute(move || { 232 pool.execute(move || {
306 match publish_diagnostics(world.clone(), uri.clone()) { 233 match handlers::publish_diagnostics(world.clone(), uri.clone()) {
307 Err(e) => { 234 Err(e) => {
308 error!("failed to compute diagnostics: {:?}", e) 235 error!("failed to compute diagnostics: {:?}", e)
309 } 236 }
@@ -312,7 +239,7 @@ fn update_file_notifications_on_threadpool(
312 sender.send(Task::Notify(not)); 239 sender.send(Task::Notify(not));
313 } 240 }
314 } 241 }
315 match publish_decorations(world, uri) { 242 match handlers::publish_decorations(world, uri) {
316 Err(e) => { 243 Err(e) => {
317 error!("failed to compute decorations: {:?}", e) 244 error!("failed to compute decorations: {:?}", e)
318 } 245 }
diff --git a/crates/server/src/req.rs b/crates/server/src/req.rs
index 881069b1f..c6d2f2efb 100644
--- a/crates/server/src/req.rs
+++ b/crates/server/src/req.rs
@@ -15,6 +15,7 @@ pub use languageserver_types::{
15 TextEdit, 15 TextEdit,
16 CompletionParams, CompletionResponse, 16 CompletionParams, CompletionResponse,
17 DocumentOnTypeFormattingParams, 17 DocumentOnTypeFormattingParams,
18 TextDocumentEdit,
18}; 19};
19 20
20 21
@@ -115,14 +116,6 @@ pub struct Decoration {
115 pub tag: &'static str 116 pub tag: &'static str
116} 117}
117 118
118pub enum MoveCursor {}
119
120impl Request for MoveCursor {
121 type Params = Position;
122 type Result = ();
123 const METHOD: &'static str = "m/moveCursor";
124}
125
126pub enum ParentModule {} 119pub enum ParentModule {}
127 120
128impl Request for ParentModule { 121impl Request for ParentModule {
@@ -135,7 +128,7 @@ pub enum JoinLines {}
135 128
136impl Request for JoinLines { 129impl Request for JoinLines {
137 type Params = JoinLinesParams; 130 type Params = JoinLinesParams;
138 type Result = Vec<TextEdit>; 131 type Result = SourceChange;
139 const METHOD: &'static str = "m/joinLines"; 132 const METHOD: &'static str = "m/joinLines";
140} 133}
141 134
@@ -170,3 +163,27 @@ pub struct Runnable {
170 pub args: Vec<String>, 163 pub args: Vec<String>,
171 pub env: HashMap<String, String>, 164 pub env: HashMap<String, String>,
172} 165}
166
167#[derive(Serialize, Debug)]
168#[serde(rename_all = "camelCase")]
169pub struct SourceChange {
170 pub label: String,
171 pub source_file_edits: Vec<TextDocumentEdit>,
172 pub file_system_edits: Vec<FileSystemEdit>,
173 pub cursor_position: Option<TextDocumentPositionParams>,
174}
175
176#[derive(Serialize, Debug)]
177#[serde(tag = "type", rename_all = "camelCase")]
178pub enum FileSystemEdit {
179 CreateFile {
180 #[serde(with = "url_serde")]
181 uri: Url
182 },
183 MoveFile {
184 #[serde(with = "url_serde")]
185 src: Url,
186 #[serde(with = "url_serde")]
187 dst: Url,
188 }
189}
diff --git a/crates/server/src/server_world.rs b/crates/server/src/server_world.rs
index 1982e727f..6c85914ba 100644
--- a/crates/server/src/server_world.rs
+++ b/crates/server/src/server_world.rs
@@ -5,7 +5,7 @@ use std::{
5}; 5};
6 6
7use languageserver_types::Url; 7use languageserver_types::Url;
8use libanalysis::{FileId, WorldState, World}; 8use libanalysis::{FileId, WorldState, Analysis};
9 9
10use { 10use {
11 Result, 11 Result,
@@ -22,7 +22,7 @@ pub struct ServerWorldState {
22 22
23#[derive(Clone)] 23#[derive(Clone)]
24pub struct ServerWorld { 24pub struct ServerWorld {
25 pub analysis: World, 25 pub analysis: Analysis,
26 pub path_map: PathMap, 26 pub path_map: PathMap,
27} 27}
28 28
@@ -91,14 +91,14 @@ impl ServerWorldState {
91 91
92 pub fn snapshot(&self) -> ServerWorld { 92 pub fn snapshot(&self) -> ServerWorld {
93 ServerWorld { 93 ServerWorld {
94 analysis: self.analysis.snapshot(self.path_map.clone()), 94 analysis: self.analysis.analysis(self.path_map.clone()),
95 path_map: self.path_map.clone() 95 path_map: self.path_map.clone()
96 } 96 }
97 } 97 }
98} 98}
99 99
100impl ServerWorld { 100impl ServerWorld {
101 pub fn analysis(&self) -> &World { 101 pub fn analysis(&self) -> &Analysis {
102 &self.analysis 102 &self.analysis
103 } 103 }
104 104