aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs17
-rw-r--r--crates/ra_vfs/src/io.rs57
-rw-r--r--crates/ra_vfs/src/lib.rs81
-rw-r--r--crates/ra_vfs/src/watcher.rs62
-rw-r--r--crates/ra_vfs/tests/vfs.rs22
5 files changed, 136 insertions, 103 deletions
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index e5a0603d1..4f984ebc7 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -113,7 +113,6 @@ enum Event {
113 Msg(RawMessage), 113 Msg(RawMessage),
114 Task(Task), 114 Task(Task),
115 Vfs(VfsTask), 115 Vfs(VfsTask),
116 Watcher(WatcherChange),
117 Lib(LibraryData), 116 Lib(LibraryData),
118} 117}
119 118
@@ -150,7 +149,6 @@ impl fmt::Debug for Event {
150 Event::Task(it) => fmt::Debug::fmt(it, f), 149 Event::Task(it) => fmt::Debug::fmt(it, f),
151 Event::Vfs(it) => fmt::Debug::fmt(it, f), 150 Event::Vfs(it) => fmt::Debug::fmt(it, f),
152 Event::Lib(it) => fmt::Debug::fmt(it, f), 151 Event::Lib(it) => fmt::Debug::fmt(it, f),
153 Event::Watcher(it) => fmt::Debug::fmt(it, f),
154 } 152 }
155 } 153 }
156} 154}
@@ -185,10 +183,6 @@ fn main_loop_inner(
185 Ok(task) => Event::Vfs(task), 183 Ok(task) => Event::Vfs(task),
186 Err(RecvError) => bail!("vfs died"), 184 Err(RecvError) => bail!("vfs died"),
187 }, 185 },
188 recv(state.vfs.read().change_receiver()) -> change => match change {
189 Ok(change) => Event::Watcher(change),
190 Err(RecvError) => bail!("vfs watcher died"),
191 },
192 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()) 186 recv(libdata_receiver) -> data => Event::Lib(data.unwrap())
193 }; 187 };
194 log::info!("loop_turn = {:?}", event); 188 log::info!("loop_turn = {:?}", event);
@@ -200,10 +194,6 @@ fn main_loop_inner(
200 state.vfs.write().handle_task(task); 194 state.vfs.write().handle_task(task);
201 state_changed = true; 195 state_changed = true;
202 } 196 }
203 Event::Watcher(change) => {
204 state.vfs.write().handle_change(change);
205 state_changed = true;
206 }
207 Event::Lib(lib) => { 197 Event::Lib(lib) => {
208 feedback(internal_mode, "library loaded", msg_sender); 198 feedback(internal_mode, "library loaded", msg_sender);
209 state.add_lib(lib); 199 state.add_lib(lib);
@@ -375,7 +365,7 @@ fn on_notification(
375 if let Some(file_id) = state 365 if let Some(file_id) = state
376 .vfs 366 .vfs
377 .write() 367 .write()
378 .add_file_overlay(&path, Some(params.text_document.text)) 368 .add_file_overlay(&path, params.text_document.text)
379 { 369 {
380 subs.add_sub(FileId(file_id.0.into())); 370 subs.add_sub(FileId(file_id.0.into()));
381 } 371 }
@@ -394,10 +384,7 @@ fn on_notification(
394 .pop() 384 .pop()
395 .ok_or_else(|| format_err!("empty changes"))? 385 .ok_or_else(|| format_err!("empty changes"))?
396 .text; 386 .text;
397 state 387 state.vfs.write().change_file_overlay(path.as_path(), text);
398 .vfs
399 .write()
400 .change_file_overlay(path.as_path(), Some(text));
401 return Ok(()); 388 return Ok(());
402 } 389 }
403 Err(not) => not, 390 Err(not) => not,
diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs
index 80328ad18..79dea5dc7 100644
--- a/crates/ra_vfs/src/io.rs
+++ b/crates/ra_vfs/src/io.rs
@@ -10,17 +10,47 @@ use relative_path::RelativePathBuf;
10 10
11use crate::{VfsRoot, has_rs_extension}; 11use crate::{VfsRoot, has_rs_extension};
12 12
13pub(crate) struct Task { 13pub(crate) enum Task {
14 pub(crate) root: VfsRoot, 14 AddRoot {
15 pub(crate) path: PathBuf, 15 root: VfsRoot,
16 pub(crate) filter: Box<Fn(&DirEntry) -> bool + Send>, 16 path: PathBuf,
17 filter: Box<Fn(&DirEntry) -> bool + Send>,
18 },
19 WatcherChange(crate::watcher::WatcherChange),
17} 20}
18 21
19pub struct TaskResult { 22#[derive(Debug)]
23pub struct AddRootResult {
20 pub(crate) root: VfsRoot, 24 pub(crate) root: VfsRoot,
21 pub(crate) files: Vec<(RelativePathBuf, String)>, 25 pub(crate) files: Vec<(RelativePathBuf, String)>,
22} 26}
23 27
28#[derive(Debug)]
29pub enum WatcherChangeResult {
30 Create {
31 path: PathBuf,
32 text: String,
33 },
34 Write {
35 path: PathBuf,
36 text: String,
37 },
38 Remove {
39 path: PathBuf,
40 },
41 // can this be replaced and use Remove and Create instead?
42 Rename {
43 src: PathBuf,
44 dst: PathBuf,
45 text: String,
46 },
47}
48
49pub enum TaskResult {
50 AddRoot(AddRootResult),
51 WatcherChange(WatcherChangeResult),
52}
53
24impl fmt::Debug for TaskResult { 54impl fmt::Debug for TaskResult {
25 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 55 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26 f.write_str("TaskResult { ... }") 56 f.write_str("TaskResult { ... }")
@@ -40,11 +70,18 @@ pub(crate) fn start() -> (Worker, WorkerHandle) {
40} 70}
41 71
42fn handle_task(task: Task) -> TaskResult { 72fn handle_task(task: Task) -> TaskResult {
43 let Task { root, path, filter } = task; 73 match task {
44 log::debug!("loading {} ...", path.as_path().display()); 74 Task::AddRoot { root, path, filter } => {
45 let files = load_root(path.as_path(), &*filter); 75 log::debug!("loading {} ...", path.as_path().display());
46 log::debug!("... loaded {}", path.as_path().display()); 76 let files = load_root(path.as_path(), &*filter);
47 TaskResult { root, files } 77 log::debug!("... loaded {}", path.as_path().display());
78 TaskResult::AddRoot(AddRootResult { root, files })
79 }
80 Task::WatcherChange(change) => {
81 // TODO
82 unimplemented!()
83 }
84 }
48} 85}
49 86
50fn load_root(root: &Path, filter: &dyn Fn(&DirEntry) -> bool) -> Vec<(RelativePathBuf, String)> { 87fn load_root(root: &Path, filter: &dyn Fn(&DirEntry) -> bool) -> Vec<(RelativePathBuf, String)> {
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs
index 1ca94dcd6..889ed788d 100644
--- a/crates/ra_vfs/src/lib.rs
+++ b/crates/ra_vfs/src/lib.rs
@@ -60,7 +60,7 @@ impl RootFilter {
60 } 60 }
61} 61}
62 62
63fn has_rs_extension(p: &Path) -> bool { 63pub(crate) fn has_rs_extension(p: &Path) -> bool {
64 p.extension() == Some(OsStr::new("rs")) 64 p.extension() == Some(OsStr::new("rs"))
65} 65}
66 66
@@ -98,7 +98,7 @@ impl Vfs {
98 pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { 98 pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
99 let (worker, worker_handle) = io::start(); 99 let (worker, worker_handle) = io::start();
100 100
101 let watcher = Watcher::start().unwrap(); // TODO return Result? 101 let watcher = Watcher::start(worker.inp.clone()).unwrap(); // TODO return Result?
102 102
103 let mut res = Vfs { 103 let mut res = Vfs {
104 roots: Arena::default(), 104 roots: Arena::default(),
@@ -127,7 +127,7 @@ impl Vfs {
127 nested.iter().all(|it| it != entry.path()) 127 nested.iter().all(|it| it != entry.path())
128 } 128 }
129 }; 129 };
130 let task = io::Task { 130 let task = io::Task::AddRoot {
131 root, 131 root,
132 path: path.clone(), 132 path: path.clone(),
133 filter: Box::new(filter), 133 filter: Box::new(filter),
@@ -188,58 +188,43 @@ impl Vfs {
188 &self.worker.out 188 &self.worker.out
189 } 189 }
190 190
191 pub fn change_receiver(&self) -> &Receiver<WatcherChange> {
192 &self.watcher.change_receiver()
193 }
194
195 pub fn handle_task(&mut self, task: io::TaskResult) { 191 pub fn handle_task(&mut self, task: io::TaskResult) {
196 let mut files = Vec::new(); 192 match task {
197 // While we were scanning the root in the backgound, a file might have 193 io::TaskResult::AddRoot(task) => {
198 // been open in the editor, so we need to account for that. 194 let mut files = Vec::new();
199 let exising = self.root2files[&task.root] 195 // While we were scanning the root in the backgound, a file might have
200 .iter() 196 // been open in the editor, so we need to account for that.
201 .map(|&file| (self.files[file].path.clone(), file)) 197 let exising = self.root2files[&task.root]
202 .collect::<FxHashMap<_, _>>(); 198 .iter()
203 for (path, text) in task.files { 199 .map(|&file| (self.files[file].path.clone(), file))
204 if let Some(&file) = exising.get(&path) { 200 .collect::<FxHashMap<_, _>>();
205 let text = Arc::clone(&self.files[file].text); 201 for (path, text) in task.files {
206 files.push((file, path, text)); 202 if let Some(&file) = exising.get(&path) {
207 continue; 203 let text = Arc::clone(&self.files[file].text);
208 } 204 files.push((file, path, text));
209 let text = Arc::new(text); 205 continue;
210 let file = self.add_file(task.root, path.clone(), Arc::clone(&text)); 206 }
211 files.push((file, path, text)); 207 let text = Arc::new(text);
212 } 208 let file = self.add_file(task.root, path.clone(), Arc::clone(&text));
213 209 files.push((file, path, text));
214 let change = VfsChange::AddRoot { 210 }
215 root: task.root,
216 files,
217 };
218 self.pending_changes.push(change);
219 }
220 211
221 pub fn handle_change(&mut self, change: WatcherChange) { 212 let change = VfsChange::AddRoot {
222 match change { 213 root: task.root,
223 WatcherChange::Create(path) => { 214 files,
224 self.add_file_overlay(&path, None); 215 };
225 } 216 self.pending_changes.push(change);
226 WatcherChange::Remove(path) => {
227 self.remove_file_overlay(&path);
228 }
229 WatcherChange::Rename(src, dst) => {
230 self.remove_file_overlay(&src);
231 self.add_file_overlay(&dst, None);
232 } 217 }
233 WatcherChange::Write(path) => { 218 io::TaskResult::WatcherChange(change) => {
234 self.change_file_overlay(&path, None); 219 // TODO
220 unimplemented!()
235 } 221 }
236 } 222 }
237 } 223 }
238 224
239 pub fn add_file_overlay(&mut self, path: &Path, text: Option<String>) -> Option<VfsFile> { 225 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
240 let mut res = None; 226 let mut res = None;
241 if let Some((root, rel_path, file)) = self.find_root(path) { 227 if let Some((root, rel_path, file)) = self.find_root(path) {
242 let text = text.unwrap_or_else(|| fs::read_to_string(&path).unwrap_or_default());
243 let text = Arc::new(text); 228 let text = Arc::new(text);
244 let change = if let Some(file) = file { 229 let change = if let Some(file) = file {
245 res = Some(file); 230 res = Some(file);
@@ -260,10 +245,8 @@ impl Vfs {
260 res 245 res
261 } 246 }
262 247
263 pub fn change_file_overlay(&mut self, path: &Path, new_text: Option<String>) { 248 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
264 if let Some((_root, _path, file)) = self.find_root(path) { 249 if let Some((_root, _path, file)) = self.find_root(path) {
265 let new_text =
266 new_text.unwrap_or_else(|| fs::read_to_string(&path).unwrap_or_default());
267 let file = file.expect("can't change a file which wasn't added"); 250 let file = file.expect("can't change a file which wasn't added");
268 let text = Arc::new(new_text); 251 let text = Arc::new(new_text);
269 self.change_file(file, Arc::clone(&text)); 252 self.change_file(file, Arc::clone(&text));
diff --git a/crates/ra_vfs/src/watcher.rs b/crates/ra_vfs/src/watcher.rs
index 1aac23616..a6d0496c0 100644
--- a/crates/ra_vfs/src/watcher.rs
+++ b/crates/ra_vfs/src/watcher.rs
@@ -5,12 +5,12 @@ use std::{
5 time::Duration, 5 time::Duration,
6}; 6};
7 7
8use crossbeam_channel::Receiver; 8use crossbeam_channel::Sender;
9use drop_bomb::DropBomb; 9use drop_bomb::DropBomb;
10use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher}; 10use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher};
11use crate::{has_rs_extension, io};
11 12
12pub struct Watcher { 13pub struct Watcher {
13 receiver: Receiver<WatcherChange>,
14 watcher: RecommendedWatcher, 14 watcher: RecommendedWatcher,
15 thread: thread::JoinHandle<()>, 15 thread: thread::JoinHandle<()>,
16 bomb: DropBomb, 16 bomb: DropBomb,
@@ -21,24 +21,54 @@ pub enum WatcherChange {
21 Create(PathBuf), 21 Create(PathBuf),
22 Write(PathBuf), 22 Write(PathBuf),
23 Remove(PathBuf), 23 Remove(PathBuf),
24 // can this be replaced and use Remove and Create instead?
24 Rename(PathBuf, PathBuf), 25 Rename(PathBuf, PathBuf),
25} 26}
26 27
27impl WatcherChange { 28impl WatcherChange {
28 fn from_debounced_event(ev: DebouncedEvent) -> Option<WatcherChange> { 29 fn try_from_debounced_event(ev: DebouncedEvent) -> Option<WatcherChange> {
29 match ev { 30 match ev {
30 DebouncedEvent::NoticeWrite(_) 31 DebouncedEvent::NoticeWrite(_)
31 | DebouncedEvent::NoticeRemove(_) 32 | DebouncedEvent::NoticeRemove(_)
32 | DebouncedEvent::Chmod(_) 33 | DebouncedEvent::Chmod(_) => {
33 | DebouncedEvent::Rescan => {
34 // ignore 34 // ignore
35 None 35 None
36 } 36 }
37 DebouncedEvent::Create(path) => Some(WatcherChange::Create(path)), 37 DebouncedEvent::Rescan => {
38 DebouncedEvent::Write(path) => Some(WatcherChange::Write(path)), 38 // TODO should we rescan the root?
39 DebouncedEvent::Remove(path) => Some(WatcherChange::Remove(path)), 39 None
40 DebouncedEvent::Rename(src, dst) => Some(WatcherChange::Rename(src, dst)), 40 }
41 DebouncedEvent::Create(path) => {
42 if has_rs_extension(&path) {
43 Some(WatcherChange::Create(path))
44 } else {
45 None
46 }
47 }
48 DebouncedEvent::Write(path) => {
49 if has_rs_extension(&path) {
50 Some(WatcherChange::Write(path))
51 } else {
52 None
53 }
54 }
55 DebouncedEvent::Remove(path) => {
56 if has_rs_extension(&path) {
57 Some(WatcherChange::Remove(path))
58 } else {
59 None
60 }
61 }
62 DebouncedEvent::Rename(src, dst) => {
63 match (has_rs_extension(&src), has_rs_extension(&dst)) {
64 (true, true) => Some(WatcherChange::Rename(src, dst)),
65 (true, false) => Some(WatcherChange::Remove(src)),
66 (false, true) => Some(WatcherChange::Create(dst)),
67 (false, false) => None,
68 }
69 }
41 DebouncedEvent::Error(err, path) => { 70 DebouncedEvent::Error(err, path) => {
71 // TODO should we reload the file contents?
42 log::warn!("watch error {}, {:?}", err, path); 72 log::warn!("watch error {}, {:?}", err, path);
43 None 73 None
44 } 74 }
@@ -47,20 +77,20 @@ impl WatcherChange {
47} 77}
48 78
49impl Watcher { 79impl Watcher {
50 pub fn start() -> Result<Watcher, Box<std::error::Error>> { 80 pub(crate) fn start(
81 output_sender: Sender<io::Task>,
82 ) -> Result<Watcher, Box<std::error::Error>> {
51 let (input_sender, input_receiver) = mpsc::channel(); 83 let (input_sender, input_receiver) = mpsc::channel();
52 let watcher = notify::watcher(input_sender, Duration::from_millis(250))?; 84 let watcher = notify::watcher(input_sender, Duration::from_millis(250))?;
53 let (output_sender, output_receiver) = crossbeam_channel::unbounded();
54 let thread = thread::spawn(move || { 85 let thread = thread::spawn(move || {
55 input_receiver 86 input_receiver
56 .into_iter() 87 .into_iter()
57 // forward relevant events only 88 // forward relevant events only
58 .filter_map(WatcherChange::from_debounced_event) 89 .filter_map(WatcherChange::try_from_debounced_event)
59 .try_for_each(|change| output_sender.send(change)) 90 .try_for_each(|change| output_sender.send(io::Task::WatcherChange(change)))
60 .unwrap() 91 .unwrap()
61 }); 92 });
62 Ok(Watcher { 93 Ok(Watcher {
63 receiver: output_receiver,
64 watcher, 94 watcher,
65 thread, 95 thread,
66 bomb: DropBomb::new(format!("Watcher was not shutdown")), 96 bomb: DropBomb::new(format!("Watcher was not shutdown")),
@@ -72,10 +102,6 @@ impl Watcher {
72 Ok(()) 102 Ok(())
73 } 103 }
74 104
75 pub fn change_receiver(&self) -> &Receiver<WatcherChange> {
76 &self.receiver
77 }
78
79 pub fn shutdown(mut self) -> thread::Result<()> { 105 pub fn shutdown(mut self) -> thread::Result<()> {
80 self.bomb.defuse(); 106 self.bomb.defuse();
81 drop(self.watcher); 107 drop(self.watcher);
diff --git a/crates/ra_vfs/tests/vfs.rs b/crates/ra_vfs/tests/vfs.rs
index 8634be9c4..21d5633b1 100644
--- a/crates/ra_vfs/tests/vfs.rs
+++ b/crates/ra_vfs/tests/vfs.rs
@@ -59,15 +59,15 @@ fn test_vfs_works() -> std::io::Result<()> {
59 59
60 // on disk change 60 // on disk change
61 fs::write(&dir.path().join("a/b/baz.rs"), "quux").unwrap(); 61 fs::write(&dir.path().join("a/b/baz.rs"), "quux").unwrap();
62 let change = vfs.change_receiver().recv().unwrap(); 62 let task = vfs.task_receiver().recv().unwrap();
63 vfs.handle_change(change); 63 vfs.handle_task(task);
64 match vfs.commit_changes().as_slice() { 64 match vfs.commit_changes().as_slice() {
65 [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "quux"), 65 [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "quux"),
66 _ => panic!("unexpected changes"), 66 _ => panic!("unexpected changes"),
67 } 67 }
68 68
69 // in memory change 69 // in memory change
70 vfs.change_file_overlay(&dir.path().join("a/b/baz.rs"), Some("m".to_string())); 70 vfs.change_file_overlay(&dir.path().join("a/b/baz.rs"), "m".to_string());
71 match vfs.commit_changes().as_slice() { 71 match vfs.commit_changes().as_slice() {
72 [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "m"), 72 [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "m"),
73 _ => panic!("unexpected changes"), 73 _ => panic!("unexpected changes"),
@@ -81,7 +81,7 @@ fn test_vfs_works() -> std::io::Result<()> {
81 } 81 }
82 82
83 // in memory add 83 // in memory add
84 vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), Some("spam".to_string())); 84 vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), "spam".to_string());
85 match vfs.commit_changes().as_slice() { 85 match vfs.commit_changes().as_slice() {
86 [VfsChange::AddFile { text, path, .. }] => { 86 [VfsChange::AddFile { text, path, .. }] => {
87 assert_eq!(text.as_str(), "spam"); 87 assert_eq!(text.as_str(), "spam");
@@ -99,8 +99,8 @@ fn test_vfs_works() -> std::io::Result<()> {
99 99
100 // on disk add 100 // on disk add
101 fs::write(&dir.path().join("a/new.rs"), "new hello").unwrap(); 101 fs::write(&dir.path().join("a/new.rs"), "new hello").unwrap();
102 let change = vfs.change_receiver().recv().unwrap(); 102 let task = vfs.task_receiver().recv().unwrap();
103 vfs.handle_change(change); 103 vfs.handle_task(task);
104 match vfs.commit_changes().as_slice() { 104 match vfs.commit_changes().as_slice() {
105 [VfsChange::AddFile { text, path, .. }] => { 105 [VfsChange::AddFile { text, path, .. }] => {
106 assert_eq!(text.as_str(), "new hello"); 106 assert_eq!(text.as_str(), "new hello");
@@ -111,8 +111,8 @@ fn test_vfs_works() -> std::io::Result<()> {
111 111
112 // on disk rename 112 // on disk rename
113 fs::rename(&dir.path().join("a/new.rs"), &dir.path().join("a/new1.rs")).unwrap(); 113 fs::rename(&dir.path().join("a/new.rs"), &dir.path().join("a/new1.rs")).unwrap();
114 let change = vfs.change_receiver().recv().unwrap(); 114 let task = vfs.task_receiver().recv().unwrap();
115 vfs.handle_change(change); 115 vfs.handle_task(task);
116 match vfs.commit_changes().as_slice() { 116 match vfs.commit_changes().as_slice() {
117 [VfsChange::RemoveFile { 117 [VfsChange::RemoveFile {
118 path: removed_path, .. 118 path: removed_path, ..
@@ -130,14 +130,14 @@ fn test_vfs_works() -> std::io::Result<()> {
130 130
131 // on disk remove 131 // on disk remove
132 fs::remove_file(&dir.path().join("a/new1.rs")).unwrap(); 132 fs::remove_file(&dir.path().join("a/new1.rs")).unwrap();
133 let change = vfs.change_receiver().recv().unwrap(); 133 let task = vfs.task_receiver().recv().unwrap();
134 vfs.handle_change(change); 134 vfs.handle_task(task);
135 match vfs.commit_changes().as_slice() { 135 match vfs.commit_changes().as_slice() {
136 [VfsChange::RemoveFile { path, .. }] => assert_eq!(path, "new1.rs"), 136 [VfsChange::RemoveFile { path, .. }] => assert_eq!(path, "new1.rs"),
137 _ => panic!("unexpected changes"), 137 _ => panic!("unexpected changes"),
138 } 138 }
139 139
140 match vfs.change_receiver().try_recv() { 140 match vfs.task_receiver().try_recv() {
141 Err(crossbeam_channel::TryRecvError::Empty) => (), 141 Err(crossbeam_channel::TryRecvError::Empty) => (),
142 res => panic!("unexpected {:?}", res), 142 res => panic!("unexpected {:?}", res),
143 } 143 }