aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--code/package.json32
-rw-r--r--code/src/extension.ts5
-rw-r--r--crates/libanalysis/src/lib.rs78
-rw-r--r--crates/server/Cargo.toml1
-rw-r--r--crates/server/src/main.rs26
-rw-r--r--crates/server/src/main_loop/mod.rs61
-rw-r--r--crates/server/src/vfs.rs79
8 files changed, 223 insertions, 60 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 8b7cd6a6b..9748fd578 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,2 +1,3 @@
1[workspace] 1[workspace]
2members = [ "crates/*" ] 2members = [ "crates/*" ]
3exclude = [ "crates/indxr" ]
diff --git a/code/package.json b/code/package.json
index eef16522f..8563ba73c 100644
--- a/code/package.json
+++ b/code/package.json
@@ -44,6 +44,38 @@
44 "key": "ctrl+w", 44 "key": "ctrl+w",
45 "when": "editorTextFocus && editorLangId == rust" 45 "when": "editorTextFocus && editorLangId == rust"
46 } 46 }
47 ],
48 "problemMatchers": [
49 {
50 "name": "rustc",
51 "fileLocation": [
52 "relative",
53 "${workspaceRoot}"
54 ],
55 "pattern": [
56 {
57 "regexp": "^(warning|warn|error)(\\[(.*)\\])?: (.*)$",
58 "severity": 1,
59 "message": 4,
60 "code": 3
61 },
62 {
63 "regexp": "^([\\s->=]*(.*):(\\d*):(\\d*)|.*)$",
64 "file": 2,
65 "line": 3,
66 "column": 4
67 },
68 {
69 "regexp": "^.*$"
70 },
71 {
72 "regexp": "^([\\s->=]*(.*):(\\d*):(\\d*)|.*)$",
73 "file": 2,
74 "line": 3,
75 "column": 4
76 }
77 ]
78 }
47 ] 79 ]
48 } 80 }
49} 81}
diff --git a/code/src/extension.ts b/code/src/extension.ts
index 0fa092573..dd0c29f14 100644
--- a/code/src/extension.ts
+++ b/code/src/extension.ts
@@ -61,8 +61,9 @@ export function deactivate(): Thenable<void> {
61 61
62function startServer() { 62function startServer() {
63 let run: lc.Executable = { 63 let run: lc.Executable = {
64 command: "m", 64 command: "cargo",
65 // args: ["run", "--package", "m"], 65 args: ["run", "--package", "m"],
66 // command: "m",
66 options: { cwd: "." } 67 options: { cwd: "." }
67 } 68 }
68 let serverOptions: lc.ServerOptions = { 69 let serverOptions: lc.ServerOptions = {
diff --git a/crates/libanalysis/src/lib.rs b/crates/libanalysis/src/lib.rs
index 74f043a9b..e4df3de2e 100644
--- a/crates/libanalysis/src/lib.rs
+++ b/crates/libanalysis/src/lib.rs
@@ -1,3 +1,4 @@
1#[macro_use]
1extern crate failure; 2extern crate failure;
2extern crate parking_lot; 3extern crate parking_lot;
3#[macro_use] 4#[macro_use]
@@ -9,12 +10,10 @@ extern crate libeditor;
9use once_cell::sync::OnceCell; 10use once_cell::sync::OnceCell;
10 11
11use std::{ 12use std::{
12 fs,
13 sync::Arc, 13 sync::Arc,
14 collections::hash_map::HashMap, 14 collections::hash_map::HashMap,
15 path::{PathBuf, Path}, 15 path::{PathBuf, Path},
16}; 16};
17use parking_lot::RwLock;
18use libsyntax2::ast; 17use libsyntax2::ast;
19use libeditor::LineIndex; 18use libeditor::LineIndex;
20 19
@@ -40,22 +39,27 @@ impl WorldState {
40 World { data: self.data.clone() } 39 World { data: self.data.clone() }
41 } 40 }
42 41
43 pub fn change_overlay(&mut self, path: PathBuf, text: Option<String>) { 42 pub fn change_file(&mut self, path: PathBuf, text: Option<String>) {
43 self.change_files(::std::iter::once((path, text)));
44 }
45
46 pub fn change_files(&mut self, changes: impl Iterator<Item=(PathBuf, Option<String>)>) {
44 let data = self.data_mut(); 47 let data = self.data_mut();
45 data.file_map.get_mut().remove(&path); 48 for (path, text) in changes {
46 if let Some(text) = text { 49 data.file_map.remove(&path);
47 data.mem_map.insert(path, Arc::new(text)); 50 if let Some(text) = text {
48 } else { 51 let file_data = FileData::new(text);
49 data.mem_map.remove(&path); 52 data.file_map.insert(path, Arc::new(file_data));
53 } else {
54 data.file_map.remove(&path);
55 }
50 } 56 }
51 } 57 }
52 58
53 fn data_mut(&mut self) -> &mut WorldData { 59 fn data_mut(&mut self) -> &mut WorldData {
54 if Arc::get_mut(&mut self.data).is_none() { 60 if Arc::get_mut(&mut self.data).is_none() {
55 let file_map = self.data.file_map.read().clone();
56 self.data = Arc::new(WorldData { 61 self.data = Arc::new(WorldData {
57 mem_map: self.data.mem_map.clone(), 62 file_map: self.data.file_map.clone(),
58 file_map: RwLock::new(file_map),
59 }); 63 });
60 } 64 }
61 Arc::get_mut(&mut self.data).unwrap() 65 Arc::get_mut(&mut self.data).unwrap()
@@ -69,7 +73,7 @@ impl World {
69 let syntax = data.syntax 73 let syntax = data.syntax
70 .get_or_init(|| { 74 .get_or_init(|| {
71 trace!("parsing: {}", path.display()); 75 trace!("parsing: {}", path.display());
72 ast::File::parse(self.file_text(path, &data)) 76 ast::File::parse(&data.text)
73 }).clone(); 77 }).clone();
74 Ok(syntax) 78 Ok(syntax)
75 } 79 }
@@ -79,56 +83,38 @@ impl World {
79 let index = data.lines 83 let index = data.lines
80 .get_or_init(|| { 84 .get_or_init(|| {
81 trace!("calc line index: {}", path.display()); 85 trace!("calc line index: {}", path.display());
82 LineIndex::new(self.file_text(path, &data)) 86 LineIndex::new(&data.text)
83 }); 87 });
84 Ok(index.clone()) 88 Ok(index.clone())
85 } 89 }
86 90
87 fn file_text<'a>(&'a self, path: &Path, file_data: &'a FileData) -> &'a str {
88 match file_data.text.as_ref() {
89 Some(text) => text.as_str(),
90 None => self.data.mem_map[path].as_str()
91 }
92 }
93
94 fn file_data(&self, path: &Path) -> Result<Arc<FileData>> { 91 fn file_data(&self, path: &Path) -> Result<Arc<FileData>> {
95 { 92 match self.data.file_map.get(path) {
96 let guard = self.data.file_map.read(); 93 Some(data) => Ok(data.clone()),
97 if let Some(data) = guard.get(path) { 94 None => bail!("unknown file: {}", path.display()),
98 return Ok(data.clone());
99 }
100 } 95 }
101
102 let text = if self.data.mem_map.contains_key(path) {
103 None
104 } else {
105 trace!("loading file from disk: {}", path.display());
106 Some(fs::read_to_string(path)?)
107 };
108 let res = {
109 let mut guard = self.data.file_map.write();
110 guard.entry(path.to_owned())
111 .or_insert_with(|| Arc::new(FileData {
112 text,
113 syntax: OnceCell::new(),
114 lines: OnceCell::new(),
115 }))
116 .clone()
117 };
118 Ok(res)
119 } 96 }
120} 97}
121 98
122 99
123#[derive(Default, Debug)] 100#[derive(Default, Debug)]
124struct WorldData { 101struct WorldData {
125 mem_map: HashMap<PathBuf, Arc<String>>, 102 file_map: HashMap<PathBuf, Arc<FileData>>,
126 file_map: RwLock<HashMap<PathBuf, Arc<FileData>>>,
127} 103}
128 104
129#[derive(Debug)] 105#[derive(Debug)]
130struct FileData { 106struct FileData {
131 text: Option<String>, 107 text: String,
132 syntax: OnceCell<ast::File>, 108 syntax: OnceCell<ast::File>,
133 lines: OnceCell<LineIndex>, 109 lines: OnceCell<LineIndex>,
134} 110}
111
112impl FileData {
113 fn new(text: String) -> FileData {
114 FileData {
115 text,
116 syntax: OnceCell::new(),
117 lines: OnceCell::new(),
118 }
119 }
120}
diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml
index 8e077ecf0..0ad193b8a 100644
--- a/crates/server/Cargo.toml
+++ b/crates/server/Cargo.toml
@@ -15,6 +15,7 @@ flexi_logger = "0.9.1"
15log = "0.4.3" 15log = "0.4.3"
16url_serde = "0.2.0" 16url_serde = "0.2.0"
17languageserver-types = "0.49.0" 17languageserver-types = "0.49.0"
18walkdir = "2.2.0"
18text_unit = { version = "0.1.2", features = ["serde"] } 19text_unit = { version = "0.1.2", features = ["serde"] }
19 20
20libsyntax2 = { path = "../libsyntax2" } 21libsyntax2 = { path = "../libsyntax2" }
diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs
index c2952465e..8dca32183 100644
--- a/crates/server/src/main.rs
+++ b/crates/server/src/main.rs
@@ -13,6 +13,7 @@ extern crate threadpool;
13extern crate log; 13extern crate log;
14extern crate url_serde; 14extern crate url_serde;
15extern crate flexi_logger; 15extern crate flexi_logger;
16extern crate walkdir;
16extern crate libeditor; 17extern crate libeditor;
17extern crate libanalysis; 18extern crate libanalysis;
18extern crate libsyntax2; 19extern crate libsyntax2;
@@ -24,6 +25,9 @@ mod dispatch;
24mod util; 25mod util;
25mod conv; 26mod conv;
26mod main_loop; 27mod main_loop;
28mod vfs;
29
30use std::path::PathBuf;
27 31
28use threadpool::ThreadPool; 32use threadpool::ThreadPool;
29use crossbeam_channel::bounded; 33use crossbeam_channel::bounded;
@@ -114,13 +118,29 @@ fn initialized(io: &mut Io) -> Result<()> {
114 { 118 {
115 let mut world = WorldState::new(); 119 let mut world = WorldState::new();
116 let mut pool = ThreadPool::new(4); 120 let mut pool = ThreadPool::new(4);
117 let (sender, receiver) = bounded::<Task>(16); 121 let (task_sender, task_receiver) = bounded::<Task>(16);
122 let (fs_events_receiver, watcher) = vfs::watch(vec![
123 PathBuf::from("./")
124 ]);
118 info!("lifecycle: handshake finished, server ready to serve requests"); 125 info!("lifecycle: handshake finished, server ready to serve requests");
119 let res = main_loop::main_loop(io, &mut world, &mut pool, sender, receiver.clone()); 126 let res = main_loop::main_loop(
127 io,
128 &mut world,
129 &mut pool,
130 task_sender,
131 task_receiver.clone(),
132 fs_events_receiver,
133 );
134
120 info!("waiting for background jobs to finish..."); 135 info!("waiting for background jobs to finish...");
121 receiver.for_each(drop); 136 task_receiver.for_each(drop);
122 pool.join(); 137 pool.join();
123 info!("...background jobs have finished"); 138 info!("...background jobs have finished");
139
140 info!("waiting for file watcher to finish...");
141 watcher.stop()?;
142 info!("...file watcher has finished");
143
124 res 144 res
125 }?; 145 }?;
126 146
diff --git a/crates/server/src/main_loop/mod.rs b/crates/server/src/main_loop/mod.rs
index 5b7093ad7..f954e632c 100644
--- a/crates/server/src/main_loop/mod.rs
+++ b/crates/server/src/main_loop/mod.rs
@@ -1,6 +1,9 @@
1mod handlers; 1mod handlers;
2 2
3use std::collections::HashSet; 3use std::{
4 path::PathBuf,
5 collections::{HashSet, HashMap},
6};
4 7
5use threadpool::ThreadPool; 8use threadpool::ThreadPool;
6use crossbeam_channel::{Sender, Receiver}; 9use crossbeam_channel::{Sender, Receiver};
@@ -13,6 +16,7 @@ use {
13 Task, Result, 16 Task, Result,
14 io::{Io, RawMsg, RawRequest, RawNotification}, 17 io::{Io, RawMsg, RawRequest, RawNotification},
15 util::FilePath, 18 util::FilePath,
19 vfs::{FileEvent, FileEventKind},
16 main_loop::handlers::{ 20 main_loop::handlers::{
17 handle_syntax_tree, 21 handle_syntax_tree,
18 handle_extend_selection, 22 handle_extend_selection,
@@ -28,24 +32,33 @@ pub(super) fn main_loop(
28 io: &mut Io, 32 io: &mut Io,
29 world: &mut WorldState, 33 world: &mut WorldState,
30 pool: &mut ThreadPool, 34 pool: &mut ThreadPool,
31 sender: Sender<Task>, 35 task_sender: Sender<Task>,
32 receiver: Receiver<Task>, 36 task_receiver: Receiver<Task>,
37 fs_events_receiver: Receiver<Vec<FileEvent>>,
33) -> Result<()> { 38) -> Result<()> {
34 info!("server initialized, serving requests"); 39 info!("server initialized, serving requests");
35 let mut next_request_id = 0; 40 let mut next_request_id = 0;
36 let mut pending_requests: HashSet<u64> = HashSet::new(); 41 let mut pending_requests: HashSet<u64> = HashSet::new();
42 let mut mem_map: HashMap<PathBuf, Option<String>> = HashMap::new();
43 let mut fs_events_receiver = Some(&fs_events_receiver);
37 loop { 44 loop {
38 enum Event { 45 enum Event {
39 Msg(RawMsg), 46 Msg(RawMsg),
40 Task(Task), 47 Task(Task),
48 Fs(Vec<FileEvent>),
41 ReceiverDead, 49 ReceiverDead,
50 FsWatcherDead,
42 } 51 }
43 let event = select! { 52 let event = select! {
44 recv(io.receiver(), msg) => match msg { 53 recv(io.receiver(), msg) => match msg {
45 Some(msg) => Event::Msg(msg), 54 Some(msg) => Event::Msg(msg),
46 None => Event::ReceiverDead, 55 None => Event::ReceiverDead,
47 }, 56 },
48 recv(receiver, task) => Event::Task(task.unwrap()), 57 recv(task_receiver, task) => Event::Task(task.unwrap()),
58 recv(fs_events_receiver, events) => match events {
59 Some(events) => Event::Fs(events),
60 None => Event::FsWatcherDead,
61 }
49 }; 62 };
50 63
51 match event { 64 match event {
@@ -53,6 +66,9 @@ pub(super) fn main_loop(
53 io.cleanup_receiver()?; 66 io.cleanup_receiver()?;
54 unreachable!(); 67 unreachable!();
55 } 68 }
69 Event::FsWatcherDead => {
70 fs_events_receiver = None;
71 }
56 Event::Task(task) => { 72 Event::Task(task) => {
57 match task { 73 match task {
58 Task::Request(mut request) => { 74 Task::Request(mut request) => {
@@ -70,15 +86,36 @@ pub(super) fn main_loop(
70 } 86 }
71 continue; 87 continue;
72 } 88 }
89 Event::Fs(events) => {
90 trace!("fs change, {} events", events.len());
91 let changes = events.into_iter()
92 .map(|event| {
93 let text = match event.kind {
94 FileEventKind::Add(text) => Some(text),
95 FileEventKind::Remove => None,
96 };
97 (event.path, text)
98 })
99 .filter_map(|(path, text)| {
100 if mem_map.contains_key(path.as_path()) {
101 mem_map.insert(path, text);
102 None
103 } else {
104 Some((path, text))
105 }
106 });
107
108 world.change_files(changes);
109 }
73 Event::Msg(msg) => { 110 Event::Msg(msg) => {
74 match msg { 111 match msg {
75 RawMsg::Request(req) => { 112 RawMsg::Request(req) => {
76 if !on_request(io, world, pool, &sender, req)? { 113 if !on_request(io, world, pool, &task_sender, req)? {
77 return Ok(()); 114 return Ok(());
78 } 115 }
79 } 116 }
80 RawMsg::Notification(not) => { 117 RawMsg::Notification(not) => {
81 on_notification(io, world, pool, &sender, not)? 118 on_notification(io, world, pool, &task_sender, not, &mut mem_map)?
82 } 119 }
83 RawMsg::Response(resp) => { 120 RawMsg::Response(resp) => {
84 if !pending_requests.remove(&resp.id) { 121 if !pending_requests.remove(&resp.id) {
@@ -160,11 +197,13 @@ fn on_notification(
160 pool: &ThreadPool, 197 pool: &ThreadPool,
161 sender: &Sender<Task>, 198 sender: &Sender<Task>,
162 not: RawNotification, 199 not: RawNotification,
200 mem_map: &mut HashMap<PathBuf, Option<String>>,
163) -> Result<()> { 201) -> Result<()> {
164 let mut not = Some(not); 202 let mut not = Some(not);
165 dispatch::handle_notification::<req::DidOpenTextDocument, _>(&mut not, |params| { 203 dispatch::handle_notification::<req::DidOpenTextDocument, _>(&mut not, |params| {
166 let path = params.text_document.file_path()?; 204 let path = params.text_document.file_path()?;
167 world.change_overlay(path, Some(params.text_document.text)); 205 mem_map.insert(path.clone(), None);
206 world.change_file(path, Some(params.text_document.text));
168 update_file_notifications_on_threadpool( 207 update_file_notifications_on_threadpool(
169 pool, world.snapshot(), sender.clone(), params.text_document.uri, 208 pool, world.snapshot(), sender.clone(), params.text_document.uri,
170 ); 209 );
@@ -175,7 +214,7 @@ fn on_notification(
175 let text = params.content_changes.pop() 214 let text = params.content_changes.pop()
176 .ok_or_else(|| format_err!("empty changes"))? 215 .ok_or_else(|| format_err!("empty changes"))?
177 .text; 216 .text;
178 world.change_overlay(path, Some(text)); 217 world.change_file(path, Some(text));
179 update_file_notifications_on_threadpool( 218 update_file_notifications_on_threadpool(
180 pool, world.snapshot(), sender.clone(), params.text_document.uri, 219 pool, world.snapshot(), sender.clone(), params.text_document.uri,
181 ); 220 );
@@ -183,7 +222,11 @@ fn on_notification(
183 })?; 222 })?;
184 dispatch::handle_notification::<req::DidCloseTextDocument, _>(&mut not, |params| { 223 dispatch::handle_notification::<req::DidCloseTextDocument, _>(&mut not, |params| {
185 let path = params.text_document.file_path()?; 224 let path = params.text_document.file_path()?;
186 world.change_overlay(path, None); 225 let text = match mem_map.remove(&path) {
226 Some(text) => text,
227 None => bail!("unmatched close notification"),
228 };
229 world.change_file(path, text);
187 let not = req::PublishDiagnosticsParams { 230 let not = req::PublishDiagnosticsParams {
188 uri: params.text_document.uri, 231 uri: params.text_document.uri,
189 diagnostics: Vec::new(), 232 diagnostics: Vec::new(),
diff --git a/crates/server/src/vfs.rs b/crates/server/src/vfs.rs
new file mode 100644
index 000000000..a5c367494
--- /dev/null
+++ b/crates/server/src/vfs.rs
@@ -0,0 +1,79 @@
1use std::{
2 path::PathBuf,
3 thread,
4 fs,
5};
6
7use crossbeam_channel::{Sender, Receiver, bounded};
8use drop_bomb::DropBomb;
9use walkdir::WalkDir;
10
11use Result;
12
13
14pub struct FileEvent {
15 pub path: PathBuf,
16 pub kind: FileEventKind,
17}
18
19pub enum FileEventKind {
20 Add(String),
21 #[allow(unused)]
22 Remove,
23}
24
25pub struct Watcher {
26 thread: thread::JoinHandle<()>,
27 bomb: DropBomb,
28}
29
30impl Watcher {
31 pub fn stop(mut self) -> Result<()> {
32 self.bomb.defuse();
33 self.thread.join()
34 .map_err(|_| format_err!("file watcher died"))
35 }
36}
37
38pub fn watch(roots: Vec<PathBuf>) -> (Receiver<Vec<FileEvent>>, Watcher) {
39 let (sender, receiver) = bounded(16);
40 let thread = thread::spawn(move || run(roots, sender));
41 (receiver, Watcher {
42 thread,
43 bomb: DropBomb::new("Watcher should be stopped explicitly"),
44 })
45}
46
47fn run(roots: Vec<PathBuf>, sender: Sender<Vec<FileEvent>>) {
48 for root in roots {
49 let mut events = Vec::new();
50 for entry in WalkDir::new(root.as_path()) {
51 let entry = match entry {
52 Ok(entry) => entry,
53 Err(e) => {
54 warn!("watcher error: {}", e);
55 continue;
56 }
57 };
58 if !entry.file_type().is_file() {
59 continue;
60 }
61 let path = entry.path();
62 if path.extension().and_then(|os| os.to_str()) != Some("rs") {
63 continue;
64 }
65 let text = match fs::read_to_string(path) {
66 Ok(text) => text,
67 Err(e) => {
68 warn!("watcher error: {}", e);
69 continue;
70 }
71 };
72 events.push(FileEvent {
73 path: path.to_owned(),
74 kind: FileEventKind::Add(text),
75 })
76 }
77 sender.send(events)
78 }
79}