diff options
-rw-r--r-- | code/src/extension.ts | 16 | ||||
-rw-r--r-- | crates/libanalysis/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/libeditor/src/code_actions.rs | 10 | ||||
-rw-r--r-- | crates/libeditor/tests/test.rs | 8 | ||||
-rw-r--r-- | crates/server/src/conv.rs | 44 | ||||
-rw-r--r-- | crates/server/src/main.rs | 5 | ||||
-rw-r--r-- | crates/server/src/main_loop/handlers.rs | 102 | ||||
-rw-r--r-- | crates/server/src/main_loop/mod.rs | 125 | ||||
-rw-r--r-- | crates/server/src/server_world.rs | 117 |
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. | ||
71 | function afterLs(f) { | ||
72 | setTimeout(f, 10) | ||
73 | } | ||
74 | |||
71 | export function deactivate(): Thenable<void> { | 75 | export 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 | ||
41 | pub type FileResolver = dyn Fn(FileId, &Path) -> Option<FileId> + Send + Sync; | 41 | pub type FileResolver = dyn Fn(FileId, &Path) -> Option<FileId> + Send + Sync; |
42 | 42 | ||
43 | #[derive(Debug)] | ||
43 | pub struct WorldState { | 44 | pub 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 | ||
74 | fn 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 | |||
79 | pub fn find_node<'a, N: AstNode<&'a SyntaxRoot>>(syntax: SyntaxNodeRef<'a>, offset: TextUnit) -> Option<N> { | 74 | pub 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 @@ | |||
1 | extern crate libeditor; | 1 | extern crate libeditor; |
2 | extern crate libsyntax2; | 2 | extern crate libsyntax2; |
3 | extern crate itertools; | ||
4 | #[macro_use] | 3 | #[macro_use] |
5 | extern crate assert_eq_text; | 4 | extern crate assert_eq_text; |
6 | 5 | ||
7 | use std::fmt; | ||
8 | use itertools::Itertools; | ||
9 | use assert_eq_text::{assert_eq_dbg}; | 6 | use assert_eq_text::{assert_eq_dbg}; |
10 | use libeditor::{ | 7 | use 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}; | |||
6 | use libsyntax2::{SyntaxKind, TextUnit, TextRange}; | 6 | use libsyntax2::{SyntaxKind, TextUnit, TextRange}; |
7 | use libanalysis::FileId; | 7 | use libanalysis::FileId; |
8 | 8 | ||
9 | use {Result, PathMap}; | 9 | use { |
10 | Result, | ||
11 | server_world::ServerWorld, | ||
12 | }; | ||
10 | 13 | ||
11 | pub trait Conv { | 14 | pub trait Conv { |
12 | type Output; | 15 | type Output; |
@@ -126,57 +129,52 @@ impl<T: ConvWith> ConvWith for Option<T> { | |||
126 | } | 129 | } |
127 | 130 | ||
128 | impl<'a> TryConvWith for &'a Url { | 131 | impl<'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 | ||
138 | impl TryConvWith for FileId { | 139 | impl 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 | ||
149 | impl<'a> TryConvWith for &'a TextDocumentItem { | 147 | impl<'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 | ||
157 | impl<'a> TryConvWith for &'a VersionedTextDocumentIdentifier { | 155 | impl<'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 | ||
165 | impl<'a> TryConvWith for &'a TextDocumentIdentifier { | 163 | impl<'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 | ||
173 | pub fn to_location( | 171 | pub 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; | |||
27 | mod main_loop; | 27 | mod main_loop; |
28 | mod vfs; | 28 | mod vfs; |
29 | mod path_map; | 29 | mod path_map; |
30 | mod server_world; | ||
30 | 31 | ||
31 | use threadpool::ThreadPool; | 32 | use threadpool::ThreadPool; |
32 | use crossbeam_channel::bounded; | 33 | use crossbeam_channel::bounded; |
33 | use flexi_logger::{Logger, Duplicate}; | 34 | use flexi_logger::{Logger, Duplicate}; |
34 | use libanalysis::WorldState; | ||
35 | 35 | ||
36 | use ::{ | 36 | use ::{ |
37 | io::{Io, RawMsg, RawResponse, RawRequest, RawNotification}, | 37 | io::{Io, RawMsg, RawResponse, RawRequest, RawNotification}, |
38 | path_map::PathMap, | ||
39 | }; | 38 | }; |
40 | 39 | ||
41 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; | 40 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; |
@@ -116,7 +115,6 @@ enum Task { | |||
116 | 115 | ||
117 | fn initialized(io: &mut Io) -> Result<()> { | 116 | fn 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 | }; |
8 | use libanalysis::{World, Query}; | 8 | use libanalysis::{Query}; |
9 | use libeditor::{self, CursorPosition}; | 9 | use libeditor::{self, CursorPosition}; |
10 | use libsyntax2::TextUnit; | 10 | use libsyntax2::TextUnit; |
11 | use serde_json::{to_value, from_value}; | 11 | use serde_json::{to_value, from_value}; |
12 | 12 | ||
13 | use ::{ | 13 | use ::{ |
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 | ||
19 | pub fn handle_syntax_tree( | 19 | pub 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 | ||
29 | pub fn handle_extend_selection( | 28 | pub 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 | ||
45 | pub fn handle_find_matching_brace( | 43 | pub 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 | ||
64 | pub fn handle_document_symbol( | 61 | pub 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 | ||
104 | pub fn handle_code_action( | 100 | pub 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 | ||
129 | pub fn handle_workspace_symbol( | 124 | pub 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 | ||
174 | pub fn handle_goto_definition( | 168 | pub 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 | ||
194 | pub fn handle_execute_command( | 187 | pub 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 | ||
267 | pub fn publish_diagnostics( | 259 | pub 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 | ||
288 | pub fn publish_decorations( | 279 | pub 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 @@ | |||
1 | mod handlers; | 1 | mod handlers; |
2 | 2 | ||
3 | use std::{ | 3 | use std::{ |
4 | collections::{HashSet, HashMap}, | 4 | collections::{HashSet}, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use threadpool::ThreadPool; | 7 | use threadpool::ThreadPool; |
8 | use crossbeam_channel::{Sender, Receiver}; | 8 | use crossbeam_channel::{Sender, Receiver}; |
9 | use languageserver_types::Url; | 9 | use languageserver_types::Url; |
10 | use libanalysis::{World, WorldState, FileId}; | ||
11 | use serde_json::to_value; | 10 | use serde_json::to_value; |
12 | 11 | ||
13 | use { | 12 | use { |
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 | ||
33 | pub(super) fn main_loop( | 32 | pub(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 | ||
137 | fn on_request( | 115 | fn 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 | ||
222 | fn on_notification( | 195 | fn 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( | |||
291 | fn handle_request_on_threadpool<R: req::ClientRequest>( | 252 | fn 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 | ||
319 | fn update_file_notifications_on_threadpool( | 275 | fn 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 @@ | |||
1 | use std::{ | ||
2 | path::{PathBuf, Path}, | ||
3 | collections::HashMap, | ||
4 | }; | ||
5 | |||
6 | use languageserver_types::Url; | ||
7 | use libanalysis::{FileId, WorldState, World}; | ||
8 | |||
9 | use { | ||
10 | Result, | ||
11 | path_map::PathMap, | ||
12 | vfs::{FileEvent, FileEventKind}, | ||
13 | }; | ||
14 | |||
15 | #[derive(Debug)] | ||
16 | pub struct ServerWorldState { | ||
17 | pub analysis: WorldState, | ||
18 | pub path_map: PathMap, | ||
19 | pub mem_map: HashMap<FileId, Option<String>>, | ||
20 | } | ||
21 | |||
22 | #[derive(Clone)] | ||
23 | pub struct ServerWorld { | ||
24 | pub analysis: World, | ||
25 | pub path_map: PathMap, | ||
26 | } | ||
27 | |||
28 | impl 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 | |||
100 | impl 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 | } | ||