aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--code/src/extension.ts16
-rw-r--r--crates/libanalysis/src/lib.rs1
-rw-r--r--crates/libeditor/src/code_actions.rs10
-rw-r--r--crates/libeditor/tests/test.rs8
-rw-r--r--crates/server/src/conv.rs44
-rw-r--r--crates/server/src/main.rs5
-rw-r--r--crates/server/src/main_loop/handlers.rs102
-rw-r--r--crates/server/src/main_loop/mod.rs125
-rw-r--r--crates/server/src/server_world.rs117
9 files changed, 246 insertions, 182 deletions
diff --git a/code/src/extension.ts b/code/src/extension.ts
index 95305db2d..084a9d769 100644
--- a/code/src/extension.ts
+++ b/code/src/extension.ts
@@ -60,14 +60,18 @@ export function activate(context: vscode.ExtensionContext) {
60 vscode.workspace.onDidChangeTextDocument((event: vscode.TextDocumentChangeEvent) => { 60 vscode.workspace.onDidChangeTextDocument((event: vscode.TextDocumentChangeEvent) => {
61 let doc = event.document 61 let doc = event.document
62 if (doc.languageId != "rust") return 62 if (doc.languageId != "rust") return
63 // We need to order this after LS updates, but there's no API for that. 63 afterLs(() => {
64 // Hence, good old setTimeout.
65 setTimeout(() => {
66 textDocumentContentProvider.eventEmitter.fire(uris.syntaxTree) 64 textDocumentContentProvider.eventEmitter.fire(uris.syntaxTree)
67 }, 10) 65 })
68 }, null, context.subscriptions) 66 }, null, context.subscriptions)
69} 67}
70 68
69// We need to order this after LS updates, but there's no API for that.
70// Hence, good old setTimeout.
71function afterLs(f) {
72 setTimeout(f, 10)
73}
74
71export function deactivate(): Thenable<void> { 75export function deactivate(): Thenable<void> {
72 if (!client) { 76 if (!client) {
73 return undefined; 77 return undefined;
@@ -118,7 +122,9 @@ function startServer() {
118 if (editor == null) return 122 if (editor == null) return
119 if (!editor.selection.isEmpty) return 123 if (!editor.selection.isEmpty) return
120 let position = client.protocol2CodeConverter.asPosition(params) 124 let position = client.protocol2CodeConverter.asPosition(params)
121 editor.selection = new vscode.Selection(position, position); 125 afterLs(() => {
126 editor.selection = new vscode.Selection(position, position)
127 })
122 } 128 }
123 ) 129 )
124 }) 130 })
diff --git a/crates/libanalysis/src/lib.rs b/crates/libanalysis/src/lib.rs
index a50a0f32f..b1093fe83 100644
--- a/crates/libanalysis/src/lib.rs
+++ b/crates/libanalysis/src/lib.rs
@@ -40,6 +40,7 @@ const INDEXING_THRESHOLD: usize = 128;
40 40
41pub type FileResolver = dyn Fn(FileId, &Path) -> Option<FileId> + Send + Sync; 41pub type FileResolver = dyn Fn(FileId, &Path) -> Option<FileId> + Send + Sync;
42 42
43#[derive(Debug)]
43pub struct WorldState { 44pub struct WorldState {
44 data: Arc<WorldData> 45 data: Arc<WorldData>
45} 46}
diff --git a/crates/libeditor/src/code_actions.rs b/crates/libeditor/src/code_actions.rs
index 6df64be12..4b2515835 100644
--- a/crates/libeditor/src/code_actions.rs
+++ b/crates/libeditor/src/code_actions.rs
@@ -71,13 +71,11 @@ fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<Synta
71 .find(|node| !node.kind().is_trivia()) 71 .find(|node| !node.kind().is_trivia())
72} 72}
73 73
74fn find_non_trivia_leaf(syntax: SyntaxNodeRef, offset: TextUnit) -> Option<SyntaxNodeRef> {
75 find_leaf_at_offset(syntax, offset)
76 .find(|leaf| !leaf.kind().is_trivia())
77}
78
79pub fn find_node<'a, N: AstNode<&'a SyntaxRoot>>(syntax: SyntaxNodeRef<'a>, offset: TextUnit) -> Option<N> { 74pub fn find_node<'a, N: AstNode<&'a SyntaxRoot>>(syntax: SyntaxNodeRef<'a>, offset: TextUnit) -> Option<N> {
80 let leaf = find_non_trivia_leaf(syntax, offset)?; 75 let leaves = find_leaf_at_offset(syntax, offset);
76 let leaf = leaves.clone()
77 .find(|leaf| !leaf.kind().is_trivia())
78 .or_else(|| leaves.right_biased())?;
81 ancestors(leaf) 79 ancestors(leaf)
82 .filter_map(N::cast) 80 .filter_map(N::cast)
83 .next() 81 .next()
diff --git a/crates/libeditor/tests/test.rs b/crates/libeditor/tests/test.rs
index df4cb65d1..cc4226710 100644
--- a/crates/libeditor/tests/test.rs
+++ b/crates/libeditor/tests/test.rs
@@ -1,11 +1,8 @@
1extern crate libeditor; 1extern crate libeditor;
2extern crate libsyntax2; 2extern crate libsyntax2;
3extern crate itertools;
4#[macro_use] 3#[macro_use]
5extern crate assert_eq_text; 4extern crate assert_eq_text;
6 5
7use std::fmt;
8use itertools::Itertools;
9use assert_eq_text::{assert_eq_dbg}; 6use assert_eq_text::{assert_eq_dbg};
10use libeditor::{ 7use libeditor::{
11 File, TextUnit, TextRange, ActionResult, CursorPosition, 8 File, TextUnit, TextRange, ActionResult, CursorPosition,
@@ -119,6 +116,11 @@ fn test_add_derive() {
119 |file, off| add_derive(file, off).map(|f| f()), 116 |file, off| add_derive(file, off).map(|f| f()),
120 ); 117 );
121 check_action( 118 check_action(
119 "struct Foo { <|> a: i32, }",
120 "#[derive(<|>)]\nstruct Foo { a: i32, }",
121 |file, off| add_derive(file, off).map(|f| f()),
122 );
123 check_action(
122 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", 124 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
123 "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", 125 "#[derive(Clone<|>)]\nstruct Foo { a: i32, }",
124 |file, off| add_derive(file, off).map(|f| f()), 126 |file, off| add_derive(file, off).map(|f| f()),
diff --git a/crates/server/src/conv.rs b/crates/server/src/conv.rs
index b3709ccaf..fc9056914 100644
--- a/crates/server/src/conv.rs
+++ b/crates/server/src/conv.rs
@@ -6,7 +6,10 @@ use libeditor::{LineIndex, LineCol, Edit, AtomEdit};
6use libsyntax2::{SyntaxKind, TextUnit, TextRange}; 6use libsyntax2::{SyntaxKind, TextUnit, TextRange};
7use libanalysis::FileId; 7use libanalysis::FileId;
8 8
9use {Result, PathMap}; 9use {
10 Result,
11 server_world::ServerWorld,
12};
10 13
11pub trait Conv { 14pub trait Conv {
12 type Output; 15 type Output;
@@ -126,57 +129,52 @@ impl<T: ConvWith> ConvWith for Option<T> {
126} 129}
127 130
128impl<'a> TryConvWith for &'a Url { 131impl<'a> TryConvWith for &'a Url {
129 type Ctx = PathMap; 132 type Ctx = ServerWorld;
130 type Output = FileId; 133 type Output = FileId;
131 fn try_conv_with(self, path_map: &PathMap) -> Result<FileId> { 134 fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
132 let path = self.to_file_path() 135 world.uri_to_file_id(self)
133 .map_err(|()| format_err!("invalid uri: {}", self))?;
134 path_map.get_id(&path).ok_or_else(|| format_err!("unknown file: {}", path.display()))
135 } 136 }
136} 137}
137 138
138impl TryConvWith for FileId { 139impl TryConvWith for FileId {
139 type Ctx = PathMap; 140 type Ctx = ServerWorld;
140 type Output = Url; 141 type Output = Url;
141 fn try_conv_with(self, path_map: &PathMap) -> Result<Url> { 142 fn try_conv_with(self, world: &ServerWorld) -> Result<Url> {
142 let path = path_map.get_path(self); 143 world.file_id_to_uri(self)
143 let url = Url::from_file_path(path)
144 .map_err(|()| format_err!("can't convert path to url: {}", path.display()))?;
145 Ok(url)
146 } 144 }
147} 145}
148 146
149impl<'a> TryConvWith for &'a TextDocumentItem { 147impl<'a> TryConvWith for &'a TextDocumentItem {
150 type Ctx = PathMap; 148 type Ctx = ServerWorld;
151 type Output = FileId; 149 type Output = FileId;
152 fn try_conv_with(self, path_map: &PathMap) -> Result<FileId> { 150 fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
153 self.uri.try_conv_with(path_map) 151 self.uri.try_conv_with(world)
154 } 152 }
155} 153}
156 154
157impl<'a> TryConvWith for &'a VersionedTextDocumentIdentifier { 155impl<'a> TryConvWith for &'a VersionedTextDocumentIdentifier {
158 type Ctx = PathMap; 156 type Ctx = ServerWorld;
159 type Output = FileId; 157 type Output = FileId;
160 fn try_conv_with(self, path_map: &PathMap) -> Result<FileId> { 158 fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
161 self.uri.try_conv_with(path_map) 159 self.uri.try_conv_with(world)
162 } 160 }
163} 161}
164 162
165impl<'a> TryConvWith for &'a TextDocumentIdentifier { 163impl<'a> TryConvWith for &'a TextDocumentIdentifier {
166 type Ctx = PathMap; 164 type Ctx = ServerWorld;
167 type Output = FileId; 165 type Output = FileId;
168 fn try_conv_with(self, path_map: &PathMap) -> Result<FileId> { 166 fn try_conv_with(self, world: &ServerWorld) -> Result<FileId> {
169 self.uri.try_conv_with(path_map) 167 world.uri_to_file_id(&self.uri)
170 } 168 }
171} 169}
172 170
173pub fn to_location( 171pub fn to_location(
174 file_id: FileId, 172 file_id: FileId,
175 range: TextRange, 173 range: TextRange,
176 path_map: &PathMap, 174 world: &ServerWorld,
177 line_index: &LineIndex, 175 line_index: &LineIndex,
178) -> Result<Location> { 176) -> Result<Location> {
179 let url = file_id.try_conv_with(path_map)?; 177 let url = file_id.try_conv_with(world)?;
180 let loc = Location::new( 178 let loc = Location::new(
181 url, 179 url,
182 range.conv_with(line_index), 180 range.conv_with(line_index),
diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs
index 71d53199c..5e4c0fe7e 100644
--- a/crates/server/src/main.rs
+++ b/crates/server/src/main.rs
@@ -27,15 +27,14 @@ mod conv;
27mod main_loop; 27mod main_loop;
28mod vfs; 28mod vfs;
29mod path_map; 29mod path_map;
30mod server_world;
30 31
31use threadpool::ThreadPool; 32use threadpool::ThreadPool;
32use crossbeam_channel::bounded; 33use crossbeam_channel::bounded;
33use flexi_logger::{Logger, Duplicate}; 34use flexi_logger::{Logger, Duplicate};
34use libanalysis::WorldState;
35 35
36use ::{ 36use ::{
37 io::{Io, RawMsg, RawResponse, RawRequest, RawNotification}, 37 io::{Io, RawMsg, RawResponse, RawRequest, RawNotification},
38 path_map::PathMap,
39}; 38};
40 39
41pub type Result<T> = ::std::result::Result<T, ::failure::Error>; 40pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
@@ -116,7 +115,6 @@ enum Task {
116 115
117fn initialized(io: &mut Io) -> Result<()> { 116fn initialized(io: &mut Io) -> Result<()> {
118 { 117 {
119 let mut world = WorldState::new();
120 let mut pool = ThreadPool::new(4); 118 let mut pool = ThreadPool::new(4);
121 let (task_sender, task_receiver) = bounded::<Task>(16); 119 let (task_sender, task_receiver) = bounded::<Task>(16);
122 let (fs_events_receiver, watcher) = vfs::watch(vec![ 120 let (fs_events_receiver, watcher) = vfs::watch(vec![
@@ -125,7 +123,6 @@ fn initialized(io: &mut Io) -> Result<()> {
125 info!("lifecycle: handshake finished, server ready to serve requests"); 123 info!("lifecycle: handshake finished, server ready to serve requests");
126 let res = main_loop::main_loop( 124 let res = main_loop::main_loop(
127 io, 125 io,
128 &mut world,
129 &mut pool, 126 &mut pool,
130 task_sender, 127 task_sender,
131 task_receiver.clone(), 128 task_receiver.clone(),
diff --git a/crates/server/src/main_loop/handlers.rs b/crates/server/src/main_loop/handlers.rs
index f25c64c97..675f69bec 100644
--- a/crates/server/src/main_loop/handlers.rs
+++ b/crates/server/src/main_loop/handlers.rs
@@ -5,35 +5,33 @@ use languageserver_types::{
5 Command, TextDocumentIdentifier, WorkspaceEdit, 5 Command, TextDocumentIdentifier, WorkspaceEdit,
6 SymbolInformation, Position, 6 SymbolInformation, Position,
7}; 7};
8use libanalysis::{World, Query}; 8use libanalysis::{Query};
9use libeditor::{self, CursorPosition}; 9use libeditor::{self, CursorPosition};
10use libsyntax2::TextUnit; 10use libsyntax2::TextUnit;
11use serde_json::{to_value, from_value}; 11use serde_json::{to_value, from_value};
12 12
13use ::{ 13use ::{
14 PathMap,
15 req::{self, Decoration}, Result, 14 req::{self, Decoration}, Result,
16 conv::{Conv, ConvWith, TryConvWith, MapConvWith, to_location}, 15 conv::{Conv, ConvWith, TryConvWith, MapConvWith, to_location},
16 server_world::ServerWorld,
17}; 17};
18 18
19pub fn handle_syntax_tree( 19pub fn handle_syntax_tree(
20 world: World, 20 world: ServerWorld,
21 path_map: PathMap,
22 params: req::SyntaxTreeParams, 21 params: req::SyntaxTreeParams,
23) -> Result<String> { 22) -> Result<String> {
24 let id = params.text_document.try_conv_with(&path_map)?; 23 let id = params.text_document.try_conv_with(&world)?;
25 let file = world.file_syntax(id)?; 24 let file = world.analysis().file_syntax(id)?;
26 Ok(libeditor::syntax_tree(&file)) 25 Ok(libeditor::syntax_tree(&file))
27} 26}
28 27
29pub fn handle_extend_selection( 28pub fn handle_extend_selection(
30 world: World, 29 world: ServerWorld,
31 path_map: PathMap,
32 params: req::ExtendSelectionParams, 30 params: req::ExtendSelectionParams,
33) -> Result<req::ExtendSelectionResult> { 31) -> Result<req::ExtendSelectionResult> {
34 let file_id = params.text_document.try_conv_with(&path_map)?; 32 let file_id = params.text_document.try_conv_with(&world)?;
35 let file = world.file_syntax(file_id)?; 33 let file = world.analysis().file_syntax(file_id)?;
36 let line_index = world.file_line_index(file_id)?; 34 let line_index = world.analysis().file_line_index(file_id)?;
37 let selections = params.selections.into_iter() 35 let selections = params.selections.into_iter()
38 .map_conv_with(&line_index) 36 .map_conv_with(&line_index)
39 .map(|r| libeditor::extend_selection(&file, r).unwrap_or(r)) 37 .map(|r| libeditor::extend_selection(&file, r).unwrap_or(r))
@@ -43,13 +41,12 @@ pub fn handle_extend_selection(
43} 41}
44 42
45pub fn handle_find_matching_brace( 43pub fn handle_find_matching_brace(
46 world: World, 44 world: ServerWorld,
47 path_map: PathMap,
48 params: req::FindMatchingBraceParams, 45 params: req::FindMatchingBraceParams,
49) -> Result<Vec<Position>> { 46) -> Result<Vec<Position>> {
50 let file_id = params.text_document.try_conv_with(&path_map)?; 47 let file_id = params.text_document.try_conv_with(&world)?;
51 let file = world.file_syntax(file_id)?; 48 let file = world.analysis().file_syntax(file_id)?;
52 let line_index = world.file_line_index(file_id)?; 49 let line_index = world.analysis().file_line_index(file_id)?;
53 let res = params.offsets 50 let res = params.offsets
54 .into_iter() 51 .into_iter()
55 .map_conv_with(&line_index) 52 .map_conv_with(&line_index)
@@ -62,13 +59,12 @@ pub fn handle_find_matching_brace(
62} 59}
63 60
64pub fn handle_document_symbol( 61pub fn handle_document_symbol(
65 world: World, 62 world: ServerWorld,
66 path_map: PathMap,
67 params: req::DocumentSymbolParams, 63 params: req::DocumentSymbolParams,
68) -> Result<Option<req::DocumentSymbolResponse>> { 64) -> Result<Option<req::DocumentSymbolResponse>> {
69 let file_id = params.text_document.try_conv_with(&path_map)?; 65 let file_id = params.text_document.try_conv_with(&world)?;
70 let file = world.file_syntax(file_id)?; 66 let file = world.analysis().file_syntax(file_id)?;
71 let line_index = world.file_line_index(file_id)?; 67 let line_index = world.analysis().file_line_index(file_id)?;
72 68
73 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new(); 69 let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
74 70
@@ -102,13 +98,12 @@ pub fn handle_document_symbol(
102} 98}
103 99
104pub fn handle_code_action( 100pub fn handle_code_action(
105 world: World, 101 world: ServerWorld,
106 path_map: PathMap,
107 params: req::CodeActionParams, 102 params: req::CodeActionParams,
108) -> Result<Option<Vec<Command>>> { 103) -> Result<Option<Vec<Command>>> {
109 let file_id = params.text_document.try_conv_with(&path_map)?; 104 let file_id = params.text_document.try_conv_with(&world)?;
110 let file = world.file_syntax(file_id)?; 105 let file = world.analysis().file_syntax(file_id)?;
111 let line_index = world.file_line_index(file_id)?; 106 let line_index = world.analysis().file_line_index(file_id)?;
112 let offset = params.range.conv_with(&line_index).start(); 107 let offset = params.range.conv_with(&line_index).start();
113 let mut ret = Vec::new(); 108 let mut ret = Vec::new();
114 109
@@ -127,8 +122,7 @@ pub fn handle_code_action(
127} 122}
128 123
129pub fn handle_workspace_symbol( 124pub fn handle_workspace_symbol(
130 world: World, 125 world: ServerWorld,
131 path_map: PathMap,
132 params: req::WorkspaceSymbolParams, 126 params: req::WorkspaceSymbolParams,
133) -> Result<Option<Vec<SymbolInformation>>> { 127) -> Result<Option<Vec<SymbolInformation>>> {
134 let all_symbols = params.query.contains("#"); 128 let all_symbols = params.query.contains("#");
@@ -143,25 +137,25 @@ pub fn handle_workspace_symbol(
143 q.limit(128); 137 q.limit(128);
144 q 138 q
145 }; 139 };
146 let mut res = exec_query(&world, &path_map, query)?; 140 let mut res = exec_query(&world, query)?;
147 if res.is_empty() && !all_symbols { 141 if res.is_empty() && !all_symbols {
148 let mut query = Query::new(params.query); 142 let mut query = Query::new(params.query);
149 query.limit(128); 143 query.limit(128);
150 res = exec_query(&world, &path_map, query)?; 144 res = exec_query(&world, query)?;
151 } 145 }
152 146
153 return Ok(Some(res)); 147 return Ok(Some(res));
154 148
155 fn exec_query(world: &World, path_map: &PathMap, query: Query) -> Result<Vec<SymbolInformation>> { 149 fn exec_query(world: &ServerWorld, query: Query) -> Result<Vec<SymbolInformation>> {
156 let mut res = Vec::new(); 150 let mut res = Vec::new();
157 for (file_id, symbol) in world.world_symbols(query) { 151 for (file_id, symbol) in world.analysis().world_symbols(query) {
158 let line_index = world.file_line_index(file_id)?; 152 let line_index = world.analysis().file_line_index(file_id)?;
159 let info = SymbolInformation { 153 let info = SymbolInformation {
160 name: symbol.name.to_string(), 154 name: symbol.name.to_string(),
161 kind: symbol.kind.conv(), 155 kind: symbol.kind.conv(),
162 location: to_location( 156 location: to_location(
163 file_id, symbol.node_range, 157 file_id, symbol.node_range,
164 path_map, &line_index 158 world, &line_index
165 )?, 159 )?,
166 container_name: None, 160 container_name: None,
167 }; 161 };
@@ -172,19 +166,18 @@ pub fn handle_workspace_symbol(
172} 166}
173 167
174pub fn handle_goto_definition( 168pub fn handle_goto_definition(
175 world: World, 169 world: ServerWorld,
176 path_map: PathMap,
177 params: req::TextDocumentPositionParams, 170 params: req::TextDocumentPositionParams,
178) -> Result<Option<req::GotoDefinitionResponse>> { 171) -> Result<Option<req::GotoDefinitionResponse>> {
179 let file_id = params.text_document.try_conv_with(&path_map)?; 172 let file_id = params.text_document.try_conv_with(&world)?;
180 let line_index = world.file_line_index(file_id)?; 173 let line_index = world.analysis().file_line_index(file_id)?;
181 let offset = params.position.conv_with(&line_index); 174 let offset = params.position.conv_with(&line_index);
182 let mut res = Vec::new(); 175 let mut res = Vec::new();
183 for (file_id, symbol) in world.approximately_resolve_symbol(file_id, offset)? { 176 for (file_id, symbol) in world.analysis().approximately_resolve_symbol(file_id, offset)? {
184 let line_index = world.file_line_index(file_id)?; 177 let line_index = world.analysis().file_line_index(file_id)?;
185 let location = to_location( 178 let location = to_location(
186 file_id, symbol.node_range, 179 file_id, symbol.node_range,
187 &path_map, &line_index, 180 &world, &line_index,
188 )?; 181 )?;
189 res.push(location) 182 res.push(location)
190 } 183 }
@@ -192,8 +185,7 @@ pub fn handle_goto_definition(
192} 185}
193 186
194pub fn handle_execute_command( 187pub fn handle_execute_command(
195 world: World, 188 world: ServerWorld,
196 path_map: PathMap,
197 mut params: req::ExecuteCommandParams, 189 mut params: req::ExecuteCommandParams,
198) -> Result<(req::ApplyWorkspaceEditParams, Option<Position>)> { 190) -> Result<(req::ApplyWorkspaceEditParams, Option<Position>)> {
199 if params.command.as_str() != "apply_code_action" { 191 if params.command.as_str() != "apply_code_action" {
@@ -204,13 +196,13 @@ pub fn handle_execute_command(
204 } 196 }
205 let arg = params.arguments.pop().unwrap(); 197 let arg = params.arguments.pop().unwrap();
206 let arg: ActionRequest = from_value(arg)?; 198 let arg: ActionRequest = from_value(arg)?;
207 let file_id = arg.text_document.try_conv_with(&path_map)?; 199 let file_id = arg.text_document.try_conv_with(&world)?;
208 let file = world.file_syntax(file_id)?; 200 let file = world.analysis().file_syntax(file_id)?;
209 let action_result = match arg.id { 201 let action_result = match arg.id {
210 ActionId::FlipComma => libeditor::flip_comma(&file, arg.offset).map(|f| f()), 202 ActionId::FlipComma => libeditor::flip_comma(&file, arg.offset).map(|f| f()),
211 ActionId::AddDerive => libeditor::add_derive(&file, arg.offset).map(|f| f()), 203 ActionId::AddDerive => libeditor::add_derive(&file, arg.offset).map(|f| f()),
212 }.ok_or_else(|| format_err!("command not applicable"))?; 204 }.ok_or_else(|| format_err!("command not applicable"))?;
213 let line_index = world.file_line_index(file_id)?; 205 let line_index = world.analysis().file_line_index(file_id)?;
214 let mut changes = HashMap::new(); 206 let mut changes = HashMap::new();
215 changes.insert( 207 changes.insert(
216 arg.text_document.uri, 208 arg.text_document.uri,
@@ -265,13 +257,12 @@ impl ActionId {
265} 257}
266 258
267pub fn publish_diagnostics( 259pub fn publish_diagnostics(
268 world: World, 260 world: ServerWorld,
269 path_map: PathMap,
270 uri: Url 261 uri: Url
271) -> Result<req::PublishDiagnosticsParams> { 262) -> Result<req::PublishDiagnosticsParams> {
272 let file_id = uri.try_conv_with(&path_map)?; 263 let file_id = world.uri_to_file_id(&uri)?;
273 let file = world.file_syntax(file_id)?; 264 let file = world.analysis().file_syntax(file_id)?;
274 let line_index = world.file_line_index(file_id)?; 265 let line_index = world.analysis().file_line_index(file_id)?;
275 let diagnostics = libeditor::diagnostics(&file) 266 let diagnostics = libeditor::diagnostics(&file)
276 .into_iter() 267 .into_iter()
277 .map(|d| Diagnostic { 268 .map(|d| Diagnostic {
@@ -286,13 +277,12 @@ pub fn publish_diagnostics(
286} 277}
287 278
288pub fn publish_decorations( 279pub fn publish_decorations(
289 world: World, 280 world: ServerWorld,
290 path_map: PathMap,
291 uri: Url 281 uri: Url
292) -> Result<req::PublishDecorationsParams> { 282) -> Result<req::PublishDecorationsParams> {
293 let file_id = uri.try_conv_with(&path_map)?; 283 let file_id = world.uri_to_file_id(&uri)?;
294 let file = world.file_syntax(file_id)?; 284 let file = world.analysis().file_syntax(file_id)?;
295 let line_index = world.file_line_index(file_id)?; 285 let line_index = world.analysis().file_line_index(file_id)?;
296 let decorations = libeditor::highlight(&file) 286 let decorations = libeditor::highlight(&file)
297 .into_iter() 287 .into_iter()
298 .map(|h| Decoration { 288 .map(|h| Decoration {
diff --git a/crates/server/src/main_loop/mod.rs b/crates/server/src/main_loop/mod.rs
index a8340df59..ad7c480dc 100644
--- a/crates/server/src/main_loop/mod.rs
+++ b/crates/server/src/main_loop/mod.rs
@@ -1,21 +1,20 @@
1mod handlers; 1mod handlers;
2 2
3use std::{ 3use std::{
4 collections::{HashSet, HashMap}, 4 collections::{HashSet},
5}; 5};
6 6
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 libanalysis::{World, WorldState, FileId};
11use serde_json::to_value; 10use serde_json::to_value;
12 11
13use { 12use {
14 req, dispatch, 13 req, dispatch,
15 Task, Result, PathMap, 14 Task, Result,
16 io::{Io, RawMsg, RawRequest, RawNotification}, 15 io::{Io, RawMsg, RawRequest, RawNotification},
17 vfs::{FileEvent, FileEventKind}, 16 vfs::FileEvent,
18 conv::TryConvWith, 17 server_world::{ServerWorldState, ServerWorld},
19 main_loop::handlers::{ 18 main_loop::handlers::{
20 handle_syntax_tree, 19 handle_syntax_tree,
21 handle_extend_selection, 20 handle_extend_selection,
@@ -32,17 +31,16 @@ use {
32 31
33pub(super) fn main_loop( 32pub(super) fn main_loop(
34 io: &mut Io, 33 io: &mut Io,
35 world: &mut WorldState,
36 pool: &mut ThreadPool, 34 pool: &mut ThreadPool,
37 task_sender: Sender<Task>, 35 task_sender: Sender<Task>,
38 task_receiver: Receiver<Task>, 36 task_receiver: Receiver<Task>,
39 fs_events_receiver: Receiver<Vec<FileEvent>>, 37 fs_events_receiver: Receiver<Vec<FileEvent>>,
40) -> Result<()> { 38) -> Result<()> {
41 info!("server initialized, serving requests"); 39 info!("server initialized, serving requests");
40 let mut state = ServerWorldState::new();
41
42 let mut next_request_id = 0; 42 let mut next_request_id = 0;
43 let mut pending_requests: HashSet<u64> = HashSet::new(); 43 let mut pending_requests: HashSet<u64> = HashSet::new();
44 let mut path_map = PathMap::new();
45 let mut mem_map: HashMap<FileId, Option<String>> = HashMap::new();
46 let mut fs_events_receiver = Some(&fs_events_receiver); 44 let mut fs_events_receiver = Some(&fs_events_receiver);
47 loop { 45 loop {
48 enum Event { 46 enum Event {
@@ -91,37 +89,17 @@ pub(super) fn main_loop(
91 } 89 }
92 Event::Fs(events) => { 90 Event::Fs(events) => {
93 trace!("fs change, {} events", events.len()); 91 trace!("fs change, {} events", events.len());
94 let changes = events.into_iter() 92 state.apply_fs_changes(events);
95 .map(|event| {
96 let text = match event.kind {
97 FileEventKind::Add(text) => Some(text),
98 FileEventKind::Remove => None,
99 };
100 (event.path, text)
101 })
102 .map(|(path, text)| {
103 (path_map.get_or_insert(path), text)
104 })
105 .filter_map(|(id, text)| {
106 if mem_map.contains_key(&id) {
107 mem_map.insert(id, text);
108 None
109 } else {
110 Some((id, text))
111 }
112 });
113
114 world.change_files(changes);
115 } 93 }
116 Event::Msg(msg) => { 94 Event::Msg(msg) => {
117 match msg { 95 match msg {
118 RawMsg::Request(req) => { 96 RawMsg::Request(req) => {
119 if !on_request(io, world, &path_map, pool, &task_sender, req)? { 97 if !on_request(io, &state, pool, &task_sender, req)? {
120 return Ok(()); 98 return Ok(());
121 } 99 }
122 } 100 }
123 RawMsg::Notification(not) => { 101 RawMsg::Notification(not) => {
124 on_notification(io, world, &mut path_map, pool, &task_sender, not, &mut mem_map)? 102 on_notification(io, &mut state, pool, &task_sender, not)?
125 } 103 }
126 RawMsg::Response(resp) => { 104 RawMsg::Response(resp) => {
127 if !pending_requests.remove(&resp.id) { 105 if !pending_requests.remove(&resp.id) {
@@ -136,45 +114,40 @@ pub(super) fn main_loop(
136 114
137fn on_request( 115fn on_request(
138 io: &mut Io, 116 io: &mut Io,
139 world: &WorldState, 117 world: &ServerWorldState,
140 path_map: &PathMap,
141 pool: &ThreadPool, 118 pool: &ThreadPool,
142 sender: &Sender<Task>, 119 sender: &Sender<Task>,
143 req: RawRequest, 120 req: RawRequest,
144) -> Result<bool> { 121) -> Result<bool> {
145 let mut req = Some(req); 122 let mut req = Some(req);
146 handle_request_on_threadpool::<req::SyntaxTree>( 123 handle_request_on_threadpool::<req::SyntaxTree>(
147 &mut req, pool, path_map, world, sender, handle_syntax_tree, 124 &mut req, pool, world, sender, handle_syntax_tree,
148 )?; 125 )?;
149 handle_request_on_threadpool::<req::ExtendSelection>( 126 handle_request_on_threadpool::<req::ExtendSelection>(
150 &mut req, pool, path_map, world, sender, handle_extend_selection, 127 &mut req, pool, world, sender, handle_extend_selection,
151 )?; 128 )?;
152 handle_request_on_threadpool::<req::FindMatchingBrace>( 129 handle_request_on_threadpool::<req::FindMatchingBrace>(
153 &mut req, pool, path_map, world, sender, handle_find_matching_brace, 130 &mut req, pool, world, sender, handle_find_matching_brace,
154 )?; 131 )?;
155 handle_request_on_threadpool::<req::DocumentSymbolRequest>( 132 handle_request_on_threadpool::<req::DocumentSymbolRequest>(
156 &mut req, pool, path_map, world, sender, handle_document_symbol, 133 &mut req, pool, world, sender, handle_document_symbol,
157 )?; 134 )?;
158 handle_request_on_threadpool::<req::CodeActionRequest>( 135 handle_request_on_threadpool::<req::CodeActionRequest>(
159 &mut req, pool, path_map, world, sender, handle_code_action, 136 &mut req, pool, world, sender, handle_code_action,
160 )?; 137 )?;
161 handle_request_on_threadpool::<req::WorkspaceSymbol>( 138 handle_request_on_threadpool::<req::WorkspaceSymbol>(
162 &mut req, pool, path_map, world, sender, handle_workspace_symbol, 139 &mut req, pool, world, sender, handle_workspace_symbol,
163 )?; 140 )?;
164 handle_request_on_threadpool::<req::GotoDefinition>( 141 handle_request_on_threadpool::<req::GotoDefinition>(
165 &mut req, pool, path_map, world, sender, handle_goto_definition, 142 &mut req, pool, world, sender, handle_goto_definition,
166 )?; 143 )?;
167 dispatch::handle_request::<req::ExecuteCommand, _>(&mut req, |params, resp| { 144 dispatch::handle_request::<req::ExecuteCommand, _>(&mut req, |params, resp| {
168 io.send(RawMsg::Response(resp.into_response(Ok(None))?)); 145 io.send(RawMsg::Response(resp.into_response(Ok(None))?));
169 146
170 let world = world.snapshot({ 147 let world = world.snapshot();
171 let pm = path_map.clone();
172 move |id, path| pm.resolve(id, path)
173 });
174 let path_map = path_map.clone();
175 let sender = sender.clone(); 148 let sender = sender.clone();
176 pool.execute(move || { 149 pool.execute(move || {
177 let (edit, cursor) = match handle_execute_command(world, path_map, params) { 150 let (edit, cursor) = match handle_execute_command(world, params) {
178 Ok(res) => res, 151 Ok(res) => res,
179 Err(e) => return sender.send(Task::Die(e)), 152 Err(e) => return sender.send(Task::Die(e)),
180 }; 153 };
@@ -221,60 +194,48 @@ fn on_request(
221 194
222fn on_notification( 195fn on_notification(
223 io: &mut Io, 196 io: &mut Io,
224 world: &mut WorldState, 197 state: &mut ServerWorldState,
225 path_map: &mut PathMap,
226 pool: &ThreadPool, 198 pool: &ThreadPool,
227 sender: &Sender<Task>, 199 sender: &Sender<Task>,
228 not: RawNotification, 200 not: RawNotification,
229 mem_map: &mut HashMap<FileId, Option<String>>,
230) -> Result<()> { 201) -> Result<()> {
231 let mut not = Some(not); 202 let mut not = Some(not);
232 dispatch::handle_notification::<req::DidOpenTextDocument, _>(&mut not, |params| { 203 dispatch::handle_notification::<req::DidOpenTextDocument, _>(&mut not, |params| {
233 let uri = params.text_document.uri; 204 let uri = params.text_document.uri;
234 let path = uri.to_file_path() 205 let path = uri.to_file_path()
235 .map_err(|()| format_err!("invalid uri: {}", uri))?; 206 .map_err(|()| format_err!("invalid uri: {}", uri))?;
236 let file_id = path_map.get_or_insert(path); 207 state.add_mem_file(path, params.text_document.text);
237 mem_map.insert(file_id, None);
238 world.change_file(file_id, Some(params.text_document.text));
239 update_file_notifications_on_threadpool( 208 update_file_notifications_on_threadpool(
240 pool, 209 pool,
241 world.snapshot({ 210 state.snapshot(),
242 let pm = path_map.clone();
243 move |id, path| pm.resolve(id, path)
244 }),
245 path_map.clone(),
246 sender.clone(), 211 sender.clone(),
247 uri, 212 uri,
248 ); 213 );
249 Ok(()) 214 Ok(())
250 })?; 215 })?;
251 dispatch::handle_notification::<req::DidChangeTextDocument, _>(&mut not, |mut params| { 216 dispatch::handle_notification::<req::DidChangeTextDocument, _>(&mut not, |mut params| {
252 let file_id = params.text_document.try_conv_with(path_map)?; 217 let uri = params.text_document.uri;
218 let path = uri.to_file_path()
219 .map_err(|()| format_err!("invalid uri: {}", uri))?;
253 let text = params.content_changes.pop() 220 let text = params.content_changes.pop()
254 .ok_or_else(|| format_err!("empty changes"))? 221 .ok_or_else(|| format_err!("empty changes"))?
255 .text; 222 .text;
256 world.change_file(file_id, Some(text)); 223 state.change_mem_file(path.as_path(), text)?;
257 update_file_notifications_on_threadpool( 224 update_file_notifications_on_threadpool(
258 pool, 225 pool,
259 world.snapshot({ 226 state.snapshot(),
260 let pm = path_map.clone();
261 move |id, path| pm.resolve(id, path)
262 }),
263 path_map.clone(),
264 sender.clone(), 227 sender.clone(),
265 params.text_document.uri, 228 uri,
266 ); 229 );
267 Ok(()) 230 Ok(())
268 })?; 231 })?;
269 dispatch::handle_notification::<req::DidCloseTextDocument, _>(&mut not, |params| { 232 dispatch::handle_notification::<req::DidCloseTextDocument, _>(&mut not, |params| {
270 let file_id = params.text_document.try_conv_with(path_map)?; 233 let uri = params.text_document.uri;
271 let text = match mem_map.remove(&file_id) { 234 let path = uri.to_file_path()
272 Some(text) => text, 235 .map_err(|()| format_err!("invalid uri: {}", uri))?;
273 None => bail!("unmatched close notification"), 236 state.remove_mem_file(path.as_path())?;
274 };
275 world.change_file(file_id, text);
276 let not = req::PublishDiagnosticsParams { 237 let not = req::PublishDiagnosticsParams {
277 uri: params.text_document.uri, 238 uri,
278 diagnostics: Vec::new(), 239 diagnostics: Vec::new(),
279 }; 240 };
280 let not = dispatch::send_notification::<req::PublishDiagnostics>(not); 241 let not = dispatch::send_notification::<req::PublishDiagnostics>(not);
@@ -291,21 +252,16 @@ fn on_notification(
291fn handle_request_on_threadpool<R: req::ClientRequest>( 252fn handle_request_on_threadpool<R: req::ClientRequest>(
292 req: &mut Option<RawRequest>, 253 req: &mut Option<RawRequest>,
293 pool: &ThreadPool, 254 pool: &ThreadPool,
294 path_map: &PathMap, 255 world: &ServerWorldState,
295 world: &WorldState,
296 sender: &Sender<Task>, 256 sender: &Sender<Task>,
297 f: fn(World, PathMap, R::Params) -> Result<R::Result>, 257 f: fn(ServerWorld, R::Params) -> Result<R::Result>,
298) -> Result<()> 258) -> Result<()>
299{ 259{
300 dispatch::handle_request::<R, _>(req, |params, resp| { 260 dispatch::handle_request::<R, _>(req, |params, resp| {
301 let world = world.snapshot({ 261 let world = world.snapshot();
302 let pm = path_map.clone();
303 move |id, path| pm.resolve(id, path)
304 });
305 let path_map = path_map.clone();
306 let sender = sender.clone(); 262 let sender = sender.clone();
307 pool.execute(move || { 263 pool.execute(move || {
308 let res = f(world, path_map, params); 264 let res = f(world, params);
309 let task = match resp.into_response(res) { 265 let task = match resp.into_response(res) {
310 Ok(resp) => Task::Respond(resp), 266 Ok(resp) => Task::Respond(resp),
311 Err(e) => Task::Die(e), 267 Err(e) => Task::Die(e),
@@ -318,13 +274,12 @@ fn handle_request_on_threadpool<R: req::ClientRequest>(
318 274
319fn update_file_notifications_on_threadpool( 275fn update_file_notifications_on_threadpool(
320 pool: &ThreadPool, 276 pool: &ThreadPool,
321 world: World, 277 world: ServerWorld,
322 path_map: PathMap,
323 sender: Sender<Task>, 278 sender: Sender<Task>,
324 uri: Url, 279 uri: Url,
325) { 280) {
326 pool.execute(move || { 281 pool.execute(move || {
327 match publish_diagnostics(world.clone(), path_map.clone(), uri.clone()) { 282 match publish_diagnostics(world.clone(), uri.clone()) {
328 Err(e) => { 283 Err(e) => {
329 error!("failed to compute diagnostics: {:?}", e) 284 error!("failed to compute diagnostics: {:?}", e)
330 } 285 }
@@ -333,7 +288,7 @@ fn update_file_notifications_on_threadpool(
333 sender.send(Task::Notify(not)); 288 sender.send(Task::Notify(not));
334 } 289 }
335 } 290 }
336 match publish_decorations(world, path_map.clone(), uri) { 291 match publish_decorations(world, uri) {
337 Err(e) => { 292 Err(e) => {
338 error!("failed to compute decorations: {:?}", e) 293 error!("failed to compute decorations: {:?}", e)
339 } 294 }
diff --git a/crates/server/src/server_world.rs b/crates/server/src/server_world.rs
new file mode 100644
index 000000000..c0d2efb86
--- /dev/null
+++ b/crates/server/src/server_world.rs
@@ -0,0 +1,117 @@
1use std::{
2 path::{PathBuf, Path},
3 collections::HashMap,
4};
5
6use languageserver_types::Url;
7use libanalysis::{FileId, WorldState, World};
8
9use {
10 Result,
11 path_map::PathMap,
12 vfs::{FileEvent, FileEventKind},
13};
14
15#[derive(Debug)]
16pub struct ServerWorldState {
17 pub analysis: WorldState,
18 pub path_map: PathMap,
19 pub mem_map: HashMap<FileId, Option<String>>,
20}
21
22#[derive(Clone)]
23pub struct ServerWorld {
24 pub analysis: World,
25 pub path_map: PathMap,
26}
27
28impl ServerWorldState {
29 pub fn new() -> ServerWorldState {
30 ServerWorldState {
31 analysis: WorldState::new(),
32 path_map: PathMap::new(),
33 mem_map: HashMap::new(),
34 }
35 }
36
37 pub fn apply_fs_changes(&mut self, events: Vec<FileEvent>) {
38 let pm = &mut self.path_map;
39 let mm = &mut self.mem_map;
40 let changes = events.into_iter()
41 .map(|event| {
42 let text = match event.kind {
43 FileEventKind::Add(text) => Some(text),
44 FileEventKind::Remove => None,
45 };
46 (event.path, text)
47 })
48 .map(|(path, text)| {
49 (pm.get_or_insert(path), text)
50 })
51 .filter_map(|(id, text)| {
52 if mm.contains_key(&id) {
53 mm.insert(id, text);
54 None
55 } else {
56 Some((id, text))
57 }
58 });
59
60 self.analysis.change_files(changes);
61 }
62
63 pub fn add_mem_file(&mut self, path: PathBuf, text: String) {
64 let file_id = self.path_map.get_or_insert(path);
65 self.mem_map.insert(file_id, None);
66 self.analysis.change_file(file_id, Some(text));
67 }
68
69 pub fn change_mem_file(&mut self, path: &Path, text: String) -> Result<()> {
70 let file_id = self.path_map.get_id(path).ok_or_else(|| {
71 format_err!("change to unknown file: {}", path.display())
72 })?;
73 self.analysis.change_file(file_id, Some(text));
74 Ok(())
75 }
76
77 pub fn remove_mem_file(&mut self, path: &Path) -> Result<()> {
78 let file_id = self.path_map.get_id(path).ok_or_else(|| {
79 format_err!("change to unknown file: {}", path.display())
80 })?;
81 let text = match self.mem_map.remove(&file_id) {
82 Some(text) => text,
83 None => bail!("unmatched close notification"),
84 };
85 self.analysis.change_file(file_id, text);
86 Ok(())
87 }
88
89 pub fn snapshot(&self) -> ServerWorld {
90 let pm = self.path_map.clone();
91 ServerWorld {
92 analysis: self.analysis.snapshot(move |id, path| {
93 pm.resolve(id, path)
94 }),
95 path_map: self.path_map.clone()
96 }
97 }
98}
99
100impl ServerWorld {
101 pub fn analysis(&self) -> &World {
102 &self.analysis
103 }
104
105 pub fn uri_to_file_id(&self, uri: &Url) -> Result<FileId> {
106 let path = uri.to_file_path()
107 .map_err(|()| format_err!("invalid uri: {}", uri))?;
108 self.path_map.get_id(&path).ok_or_else(|| format_err!("unknown file: {}", path.display()))
109 }
110
111 pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> {
112 let path = self.path_map.get_path(id);
113 let url = Url::from_file_path(path)
114 .map_err(|()| format_err!("can't convert path to url: {}", path.display()))?;
115 Ok(url)
116 }
117}