1use {
2 std::collections::{HashMap, hash_map},
3 std::path::{Path,PathBuf},
4 std::sync::Arc,
5 std::cell::RefCell,
6 crate::{
7 makepad_code_editor::{CodeDocument, decoration::{Decoration, DecorationSet}, CodeSession},
8 makepad_platform::makepad_live_compiler::LiveFileChange,
9 app::AppAction,
10 makepad_widgets::*,
11 makepad_widgets::file_tree::*,
12 file_system::FileClient,
13 makepad_file_server::FileSystemRoots,
14 ai_chat::ai_chat_manager::AiChatDocument,
15 makepad_file_protocol::{
16 SearchResult,
17 SearchItem,
18 FileRequest,
19 FileError,
20 FileResponse,
21 FileClientMessage,
22 FileNotification,
23 FileNodeData,
24 FileTreeData,
25 GitLog,
26 GitCommit,
27 SaveKind,
28 SaveFileResponse
29 },
30 },
31};
32
33#[derive(Default)]
34pub struct FileSystem {
35 pub file_client: FileClient,
36 pub file_nodes: LiveIdMap<LiveId, FileNode>,
37 pub path_to_file_node_id: HashMap<String, LiveId>,
38 pub tab_id_to_file_node_id: HashMap<LiveId, LiveId>,
39 pub tab_id_to_session: HashMap<LiveId, EditSession>,
40 pub open_documents: HashMap<LiveId, OpenDocument>,
41 pub git_logs: Vec<GitLog>,
42 pub snapshot_image_data: RefCell<HashMap<String, SnapshotImageData>>,
43 pub search_results_id: u64,
44 pub search_results: Vec<SearchResult>,
45 pub snapshot_creation: SnapshotCreation
46}
47
48pub enum SnapshotCreation{
49 Idle,
50 Started{message:String},
51 ReceivedImage{root:String, message:String, data:Option<Vec<u8>>},
52 ReceivedHash{hash: String, message:String}
53}
54impl Default for SnapshotCreation{
55 fn default()->Self{SnapshotCreation::Idle}
56}
57impl SnapshotCreation{
58 fn handle_image(&mut self, cx:&mut Cx, file_client:&mut FileClient, git_logs:&mut Vec<GitLog>, root:String, data:Vec<u8> ){
59 if let Self::ReceivedHash{hash, message} = self{
60 file_client.send_request(FileRequest::SaveSnapshotImage {root:root.to_string(), hash:hash.clone(), data});
61 if let Some(git_log) = git_logs.iter_mut().find(|v| v.root == root){
62 git_log.commits.insert(0, GitCommit{
63 hash: hash.clone(),
64 message:message.clone()
65 });
66 cx.action(AppAction::RedrawSnapshots);
67 }
68 *self = Self::Idle
69 }
70 else if let Self::Started{message} = self{
71 *self = Self::ReceivedImage{root, data:Some(data), message:message.clone()}
72 }
73 }
74 fn handle_hash(&mut self, cx:&mut Cx, file_client:&mut FileClient, git_logs:&mut Vec<GitLog>, hash:String){
75 if let Self::ReceivedImage{root, data, message} = self{
76 file_client.send_request(FileRequest::SaveSnapshotImage {root:root.clone(), hash: hash.clone(), data: data.take().unwrap()});
77 if let Some(git_log) = git_logs.iter_mut().find(|v| v.root == *root){
78 git_log.commits.insert(0, GitCommit{
79 hash: hash,
80 message:message.clone()
81 });
82 cx.action(AppAction::RedrawSnapshots);
83 }
84 *self = Self::Idle
85 }
86 else if let Self::Started{message} = self{
87 *self = Self::ReceivedHash{hash, message:message.clone()}
88 }
89 }
90}
91
92pub enum SnapshotImageData{
93 Loading,
94 Error,
95 Loaded{data:Arc<Vec<u8>>, path:PathBuf}
96}
97
98
99pub enum EditSession {
100 Code(CodeSession),
101 AiChat(LiveId)
102}
103
104pub enum OpenDocument {
105 CodeLoading(DecorationSet),
106 Code(CodeDocument),
107 AiChatLoading,
108 AiChat(AiChatDocument)
109}
110
111#[derive(Debug)]
112pub struct FileNode {
113 pub parent_edge: Option<FileEdge>,
114 pub name: String,
115 pub child_edges: Option<Vec<FileEdge >>,
116}
117
118impl FileNode {
119 pub fn is_file(&self) -> bool {
120 self.child_edges.is_none()
121 }
122}
123
124#[derive(Debug)]
125pub struct FileEdge {
126 pub name: String,
127 pub file_node_id: LiveId,
128}
129
130#[derive(DefaultNone, Debug, Clone)]
131pub enum FileSystemAction {
132 TreeLoaded,
133 RecompileNeeded,
134 LiveReloadNeeded(LiveFileChange),
135 FileChangedOnDisk(SaveFileResponse),
136 SnapshotImageLoaded,
137 SearchResults,
138 None
139}
140
141impl FileSystem {
142 pub fn get_editor_template_from_path(path:&str)->LiveId{
143 let p = Path::new(path);
144 match p.extension()
145 .and_then(|ext| ext.to_str())
146 .map(|ext_str| ext_str.to_lowercase()) {
147 Some(ext) => match ext.as_str() {
148 "mpai"=>live_id!(AiChat),
149 _=>live_id!(CodeEditor)
150 }
151 _=>{
152 live_id!(CodeEditor)
153 }
154 }
155 }
156
157 pub fn get_tab_after_from_path(path:&str)->LiveId{
158 match Self::get_editor_template_from_path(path){
159 live_id!(AiChat)=>live_id!(ai_first),
160 _=>live_id!(edit_first),
161 }
162 }
163
164
165 pub fn load_file_tree(&self) {
166 self.file_client.send_request(FileRequest::LoadFileTree {with_data: false});
167 }
168
169 pub fn load_snapshot(&mut self, root:String, hash:String) {
170 self.file_client.send_request(FileRequest::LoadSnapshot {root:root, hash});
171 }
172
173 pub fn create_snapshot(&mut self, root:String, message:String) {
174 self.snapshot_creation = SnapshotCreation::Started{message: message.clone()};
175 self.file_client.send_request(FileRequest::CreateSnapshot {root:root, message});
176 }
177
178 pub fn load_snapshot_image(&self, root:&str, hash:&str) {
179 let mut image_data = self.snapshot_image_data.borrow_mut();
180 if image_data.get(hash).is_none(){
181 image_data.insert(root.to_string(), SnapshotImageData::Loading);
182 self.file_client.send_request(FileRequest::LoadSnapshotImage {root:root.to_string(), hash:hash.to_string()});
183 }
184
185 }
186
187 pub fn save_snapshot_image(&mut self, cx:&mut Cx, root:&str, hash:&str, width:usize, height: usize, data:Vec<u8>) {
188 let mut jpeg = Vec::new();
189 let encoder = jpeg_encoder::Encoder::new(&mut jpeg, 100);
190 encoder.encode(&data, width as u16, height as u16, jpeg_encoder::ColorType::Bgra).unwrap();
191
192 let mut image_data = self.snapshot_image_data.borrow_mut();
193 if image_data.get(hash).is_none(){
194
195 self.snapshot_creation.handle_image(cx, &mut self.file_client, &mut self.git_logs, root.to_string(), jpeg.clone());
196
197 image_data.insert(root.to_string(), SnapshotImageData::Loaded{
198 data: Arc::new(jpeg),
199 path: Path::new(hash).with_extension("jpg")
200 });
201 }
202 }
203
204 pub fn get_editor_template_from_file_id(&self, file_id:LiveId)->Option<LiveId>{
205 if let Some(path) = self.file_node_id_to_path(file_id){
206 Some(Self::get_editor_template_from_path(path))
207 }
208 else{
209 None
210 }
211 }
212
213 pub fn init(&mut self, cx: &mut Cx, roots:FileSystemRoots) {
214 self.file_client.init(cx, roots);
215 self.file_client.load_file_tree();
216 }
217
218 pub fn search_string(&mut self, _cx:&mut Cx, set:Vec<SearchItem>){
219 self.search_results_id += 1;
220 self.search_results.clear();
221 self.file_client.send_request(FileRequest::Search{
222 id: self.search_results_id,
223 set
224 });
225 }
227
228 pub fn remove_tab(&mut self, tab_id: LiveId) {
229 self.tab_id_to_file_node_id.remove(&tab_id);
230 self.tab_id_to_session.remove(&tab_id);
231 }
232
233 pub fn path_to_file_node_id(&self, path: &str) -> Option<LiveId> {
234 self.path_to_file_node_id.get(path).cloned()
235 }
236
237 pub fn file_node_id_to_path(&self, file_id:LiveId) -> Option<&str> {
238 for (path, id) in &self.path_to_file_node_id{
239 if *id == file_id{
240 return Some(path)
241 }
242 }
243 None
244 }
245
246 pub fn file_node_id_to_tab_id(&self, file_node: LiveId) -> Option<LiveId> {
247 for (tab, id) in &self.tab_id_to_file_node_id {
248 if *id == file_node {
249 return Some(*tab)
250 }
251 }
252 None
253 }
254
255 pub fn get_word_under_cursor_for_session(&mut self, tab_id: LiveId)->Option<String> {
256 if let Some(EditSession::Code(session)) = self.tab_id_to_session.get(&tab_id){
257 return session.word_at_cursor();
258 }
259 None
260 }
261
262 pub fn get_session_mut(&mut self, tab_id: LiveId) -> Option<&mut EditSession> {
263 if let Some(file_id) = self.tab_id_to_file_node_id.get(&tab_id) {
265 match self.open_documents.get(file_id){
266 Some(OpenDocument::Code(document))=>{
267 return Some(match self.tab_id_to_session.entry(tab_id) {
268 hash_map::Entry::Occupied(o) => o.into_mut(),
269 hash_map::Entry::Vacant(v) => {
270 v.insert(EditSession::Code(CodeSession::new(document.clone())))
271 }
272 })
273 }
274 Some(OpenDocument::AiChat(_document))=>{
275 return Some(match self.tab_id_to_session.entry(tab_id) {
276 hash_map::Entry::Occupied(o) => o.into_mut(),
277 hash_map::Entry::Vacant(v) => {
278 v.insert(EditSession::AiChat(*file_id))
279 }
280 })
281 }
282 Some(_)| None=>()
283 }
284 }
285 None
286 }
287
288 pub fn handle_event(&mut self, cx: &mut Cx, event: &Event, ui: &WidgetRef) {
289
290 if let Event::Signal = event{
291 while let Ok(message) = self.file_client.inner.as_mut().unwrap().message_receiver.try_recv() {
292 match message {
293 FileClientMessage::Response(response) => match response {
294 FileResponse::CreateSnapshot(response)=>{
295 match response{
296 Ok(res)=>{
297 self.snapshot_creation.handle_hash(cx, &mut self.file_client, &mut self.git_logs, res.hash);
298 }
299 Err(res)=>{
300 crate::log!("ERROR {:?}", res);
301 }
302 }
303 }
304 FileResponse::SearchInProgress(_)=>{
305 }
306 FileResponse::SaveSnapshotImage(_)=>{
307 }
308 FileResponse::LoadSnapshot(res)=>{
309 if let Err(e) = res{
310 crate::log!("Error loading snapshot {:?}", e);
311 }
312 }
313 FileResponse::LoadSnapshotImage(response)=>{
314 match response{
316 Ok(res)=>{
317 let path = Path::new(&res.hash).to_path_buf().with_extension("jpg");
318 self.snapshot_image_data.borrow_mut().insert(res.hash,
319 SnapshotImageData::Loaded{
320 data:Arc::new(res.data),
321 path
322 });
323 cx.action( FileSystemAction::SnapshotImageLoaded);
324 }
325 Err(res)=>{
326 self.snapshot_image_data.borrow_mut().insert(res.hash, SnapshotImageData::Error);
327 cx.action( FileSystemAction::SnapshotImageLoaded);
328 }
329 }
330 }
331 FileResponse::LoadFileTree(response) => {
332 self.process_load_file_tree(response.unwrap());
333 cx.action(FileSystemAction::TreeLoaded)
334 }
336 FileResponse::OpenFile(result) => {
337 match result {
338 Ok(response) => {
339 let file_id = LiveId(response.id);
340 let dock = ui.dock(id!(dock));
341 for (tab_id, file_id) in &self.tab_id_to_file_node_id {
342 if response.id == file_id.0 {
343 dock.redraw_tab(cx, *tab_id);
344 }
345 }
346 match self.open_documents.get(&file_id){
347 Some(OpenDocument::CodeLoading(dec))=>{
348 let dec = dec.clone();
349 self.open_documents.insert(file_id, OpenDocument::Code(CodeDocument::new(response.data.into(), dec)));
350 }
351 Some(OpenDocument::Code(_))=>{
352 }
353 Some(OpenDocument::AiChatLoading)=>{
354 self.open_documents.insert(file_id, OpenDocument::AiChat(AiChatDocument::load_or_empty(&response.data)));
355 }
356 Some(OpenDocument::AiChat(_))=>{
357 }
358 _=>panic!()
359 }
360
361 dock.redraw(cx);
362 }
363 Err(FileError::CannotOpen(_unix_path)) => {
364 }
365 Err(FileError::RootNotFound(_unix_path)) => {
366 }
367 Err(FileError::Unknown(err)) => {
368 log!("File error unknown {}", err);
369 }
371 }
372 }
373 FileResponse::SaveFile(result) => match result {
374 Ok(response) => {
375 self.process_save_response(cx, response);
376 }
377 Err(_) => {}
378 }
382 },
383 FileClientMessage::Notification(notification) => {
384 match notification{
385 FileNotification::SearchResults{id, results} =>{
386 if self.search_results_id == id{
387 self.search_results.extend(results);
388 }
389 cx.action( FileSystemAction::SearchResults );
390 }
391 FileNotification::FileChangedOnDisk(response)=>{
392 if let Some(file_id) = self.path_to_file_node_id.get(&response.path){
394
395 if let Some(OpenDocument::Code(doc)) = self.open_documents.get_mut(&file_id){
396 doc.replace(response.new_data.clone().into());
397 }
398 ui.redraw(cx);
399 }
400 self.process_save_response(cx, response.clone());
401 cx.action( FileSystemAction::FileChangedOnDisk(response));
404 }
405 }
406 }
408 }
409 }
410 }
411 }
412
413 pub fn replace_live_design(&self, cx:&mut Cx, file_id:LiveId, new_data:&str){
414 let mut old_neg = Vec::new();
415 let mut new_neg = Vec::new();
416
417 match self.open_documents.get(&file_id){
418 Some(OpenDocument::Code(doc))=>{
419 let old_data = doc.as_text().to_string();
420 match LiveRegistry::tokenize_from_str_live_design(&old_data, Default::default(), Default::default(), Some(&mut old_neg)) {
421 Err(e) => {
422 log!("Cannot tokenize old file {}", e)
423 }
424 Ok(old_tokens) if old_tokens.len()>2 => match LiveRegistry::tokenize_from_str_live_design(new_data, Default::default(), Default::default(), Some(&mut new_neg)) {
425 Err(e) => {
426 log!("Cannot tokenize new file {}", e);
427 }
428 Ok(new_tokens) if new_tokens.len()>2 => {
429 let old_start = old_tokens[0].span.start.to_byte_offset(&old_data);
430 let old_end = old_tokens.iter().rev().nth(1).unwrap().span.end.to_byte_offset(&old_data);
431 let new_start = new_tokens[0].span.start.to_byte_offset(&new_data);
432 let new_end = new_tokens.iter().rev().nth(1).unwrap().span.end.to_byte_offset(&new_data);
433 if old_start.is_none() || old_end.is_none() || new_start.is_none() || new_end.is_none(){
434 log!("Cannot find range correctly {:?} {:?} {:?} {:?}", old_start, old_end, new_start, new_end);
435 }
436 else{
437 let mut combined_data = old_data.to_string();
438 combined_data.replace_range(old_start.unwrap()..old_end.unwrap(), &new_data[new_start.unwrap()..new_end.unwrap()]);
439 cx.action( FileSystemAction::LiveReloadNeeded(LiveFileChange {
440 file_name: self.file_node_id_to_path(file_id).unwrap().to_string(),
441 content: combined_data.to_string(),
442 }));
443 doc.replace(combined_data.into());
444 }
445 }
446 _ => {
447 log!("Cannot tokenize new file");
448 }
449 }
450 _ => {
451 log!("Cannot tokenize new file");
452 }
453 }
454 }
455 _=>()
456 }
457
458 }
459
460
461 pub fn process_possible_live_reload(&mut self, cx:&mut Cx, path:&str, old_data:&str, new_data:&str, recompile:bool){
462 let mut old_neg = Vec::new();
463 let mut new_neg = Vec::new();
464 match LiveRegistry::tokenize_from_str_live_design(old_data, Default::default(), Default::default(), Some(&mut old_neg)) {
465 Err(e) => {
466 log!("Cannot tokenize old file {}", e)
467 }
468 Ok(old_tokens) => match LiveRegistry::tokenize_from_str_live_design(new_data, Default::default(), Default::default(), Some(&mut new_neg)) {
469 Err(e) => {
470 log!("Cannot tokenize new file {}", e);
471 }
472 Ok(new_tokens) => {
473 if recompile && old_neg != new_neg {
475 cx.action(FileSystemAction::RecompileNeeded)
476 }
477 if old_tokens != new_tokens{
478 cx.action( FileSystemAction::LiveReloadNeeded(LiveFileChange {
480 file_name: path.to_string(),
481 content: new_data.to_string(),
482 }));
483 }
484 }
485 }
486 }
487 }
488
489
490 pub fn process_save_response(&mut self, cx:&mut Cx, response:SaveFileResponse){
491 if Self::get_editor_template_from_path(&response.path) != live_id!(CodeEditor){
494 return
495 }
496
497 if response.old_data != response.new_data && response.kind != SaveKind::Patch {
498 self.process_possible_live_reload(cx, &response.path, &response.old_data, &response.new_data, true);
499 }
500 }
501
502 pub fn handle_sessions(&mut self) {
503 for session in self.tab_id_to_session.values_mut() {
504 match session{
505 EditSession::Code(session)=>{
506 session.handle_changes();
507 }
508 EditSession::AiChat(_id)=>{
509 }
510 }
511 }
512 }
513
514 pub fn request_open_file(&mut self, tab_id: LiveId, file_id: LiveId) {
515 if tab_id != LiveId(0){
518 self.tab_id_to_file_node_id.insert(tab_id, file_id);
519 }
520
521 let dec = match self.open_documents.get(&file_id){
523 Some(OpenDocument::CodeLoading(_))=> if let Some(OpenDocument::CodeLoading(dec)) = self.open_documents.remove(&file_id){
524 dec
525 }
526 else{
527 panic!()
528 },
529 Some(OpenDocument::Code(_))=>{
530 return
531 }
532 Some(_) | None=>DecorationSet::new()
533 };
534
535 let template = self.get_editor_template_from_file_id(file_id);
536
537 match template{
538 Some(live_id!(CodeEditor))=>{
539 self.open_documents.insert(file_id, OpenDocument::CodeLoading(dec));
540 }
541 Some(live_id!(AiChat))=>{
542 self.open_documents.insert(file_id, OpenDocument::AiChatLoading);
543 }
544 None=>{
545 error!("File id {:?} does not have a template", file_id);
546 return
547 }
548 _=>panic!()
549 }
550
551 let path = self.file_node_path(file_id);
552 self.file_client.send_request(FileRequest::OpenFile{path, id: file_id.0});
553 }
554
555 pub fn request_save_file_for_tab_id(&mut self, tab_id: LiveId, was_patch:bool) {
556 if let Some(file_id) = self.tab_id_to_file_node_id.get(&tab_id) {
559 self.request_save_file_for_file_node_id(*file_id, was_patch)
560 };
561 }
562
563 pub fn replace_code_document(&self, file_id:LiveId, text:&str){
564 match self.open_documents.get(&file_id){
565 Some(OpenDocument::Code(doc))=>{
566 doc.replace(text.into());
567 }
568 _=>()
569 }
570
571 }
572
573 pub fn file_path_as_string(&self, path:&str)->Option<String>{
574 if let Some(file_id) = self.path_to_file_node_id(&path){
575 self.file_id_as_string(file_id)
576 }
577 else{
578 None
579 }
580 }
581
582 pub fn file_id_as_string(&self, file_id: LiveId)->Option<String>{
583 match self.open_documents.get(&file_id){
584 Some(OpenDocument::Code(doc))=>{
585 Some(doc.as_text().to_string())
586 }
587 Some(OpenDocument::CodeLoading(_))=>{
588 None
589 }
590 Some(OpenDocument::AiChat(doc))=>{
591 Some(doc.file.to_string())
592 }
593 _=>None
594 }
595 }
596
597 pub fn request_save_file_for_file_node_id(&mut self, file_id: LiveId, patch:bool) {
598 if let Some(text) = self.file_id_as_string(file_id){
599 let path = self.file_node_path(file_id);
600 self.file_client.send_request(FileRequest::SaveFile{
601 path: path.clone(),
602 data: text,
603 id: file_id.0,
604 patch
605 });
606 }
607 }
608
609 pub fn clear_decorations(&mut self, file_node_id: &LiveId) {
610 match self.open_documents.get_mut(file_node_id) {
613 Some(OpenDocument::CodeLoading(dec)) => dec.clear(),
614 Some(OpenDocument::Code(doc)) => doc.clear_decorations(),
615 Some(_) | None=>()
616 };
617 }
618
619 pub fn clear_all_decorations(&mut self) {
620 for document in self.open_documents.values_mut() {
623 match document {
624 OpenDocument::CodeLoading(dec) => dec.clear(),
625 OpenDocument::Code(doc) => doc.clear_decorations(),
626 _=>()
627 }
628 }
629 }
630
631 pub fn redraw_view_by_file_id(&mut self, cx: &mut Cx, id: LiveId, dock: &DockRef) {
632 for (tab_id, file_id) in &self.tab_id_to_file_node_id {
633 if id == *file_id {
634 dock.item(*tab_id).redraw(cx)
635 }
636 }
637 }
638
639 pub fn redraw_all_views(&mut self, cx: &mut Cx, dock: &DockRef) {
640 for (tab_id, _) in &self.tab_id_to_file_node_id {
641 dock.item(*tab_id).redraw(cx)
642 }
643 }
644
645 pub fn add_decoration(&mut self, file_id: LiveId, dec: Decoration) {
646 match self.open_documents.get_mut(&file_id) {
649 Some(OpenDocument::CodeLoading(decs)) => decs.add_decoration(dec),
650 Some(OpenDocument::Code(doc)) => {
651 doc.add_decoration(dec);
652 }
653 Some(_) =>{}
654 None => {
655 let mut set = DecorationSet::new();
656 set.add_decoration(dec);
657 self.open_documents.insert(file_id, OpenDocument::CodeLoading(set));
658 }
659 };
660 }
661
662 pub fn draw_file_node(&self, cx: &mut Cx2d, file_node_id: LiveId, level: usize, file_tree: &mut FileTree) {
663 if let Some(file_node) = self.file_nodes.get(&file_node_id) {
664 match &file_node.child_edges {
665 Some(child_edges) => {
666 if level == 0{
667 for child_edge in child_edges {
668 self.draw_file_node(cx, child_edge.file_node_id, level + 1, file_tree);
669 }
670 }
671 else{
672 if file_tree.begin_folder(cx, file_node_id, &file_node.name).is_ok() {
673 for child_edge in child_edges {
674 self.draw_file_node(cx, child_edge.file_node_id, level + 1, file_tree);
675 }
676 file_tree.end_folder();
677 }
678 }
679 }
680 None => {
681 file_tree.file(cx, file_node_id, &file_node.name);
682 }
683 }
684 }
685 }
686
687 pub fn file_node_name(&self, file_node_id: LiveId) -> String {
688 self.file_nodes.get(&file_node_id).unwrap().name.clone()
689 }
690
691 pub fn file_node_path(&self, file_node_id: LiveId) -> String {
692 let mut path = String::new();
693 let mut file_node = &self.file_nodes[file_node_id];
694 while let Some(edge) = &file_node.parent_edge {
695 path.insert_str(0, &edge.name);
696 file_node = &self.file_nodes[edge.file_node_id];
697 if file_node.parent_edge.is_some() {
698 path.insert_str(0, "/");
699 }
700 }
701 path
702 }
703 pub fn ensure_unique_tab_names(&self, cx: &mut Cx, dock: &DockRef) {
704
705 fn longest_common_suffix(a: &[&str], b: &[&str]) -> Option<usize> {
706 if a == b{
707 return None }
709 let mut ai = a.len();
710 let mut bi = b.len();
711 let mut count = 0;
712 while ai > 0 && bi > 0 {
713 ai -= 1;
714 bi -= 1;
715 if a[ai] == b[bi] {
716 count += 1;
717 } else {
718 break;
719 }
720 }
721 Some(count)
722 }
723 let mut tabs: Vec<(LiveId, Vec<&str>, usize)> = Vec::new();
725 for (&tab_id, &file_id) in &self.tab_id_to_file_node_id {
726 let mut path_components = Vec::new();
727 let mut file_node = &self.file_nodes[file_id];
728
729 while let Some(edge) = &file_node.parent_edge {
730 path_components.push(edge.name.as_str());
732 file_node = &self.file_nodes[edge.file_node_id];
733 }
734 path_components.reverse();
736
737 tabs.push((tab_id, path_components, 1));
738 }
739
740 tabs.sort_by(|a, b| a.1.cmp(&b.1));
742
743 let mut changing = true;
745 while changing{
746 changing = false;
747 for i in 0..tabs.len() {
748 let (_, ref path, minsfx) = tabs[i];
749 let mut min_suffix_len = minsfx;
750 if i > 0 {
752 let (_, ref prev_path, _) = tabs[i - 1];
753 if let Some(common)= longest_common_suffix(path, prev_path){
754 min_suffix_len = min_suffix_len.max(common + 1)
755 }
756 }
757 if i + 1 < tabs.len() {
759 let (_, ref next_path, minsfx) = tabs[i + 1];
760 if let Some(common) = longest_common_suffix(path, next_path){
761 min_suffix_len = min_suffix_len.max(common + 1).max(minsfx);
762 }
763 else{
764 min_suffix_len = minsfx;
765 }
766 }
767 let (_,_, ref mut minsfx) = tabs[i];
769 if *minsfx != min_suffix_len{
770 changing = true;
771 *minsfx = min_suffix_len;
772 }
773 }
774 }
775 for i in 0..tabs.len() {
776 let (tab_id, ref path, minsfx) = tabs[i];
777 let start = path.len().saturating_sub(minsfx);
778 let title = path[start..].join("/");
779 dock.set_tab_title(cx, tab_id, title);
780 }
781
782 }
783
784 pub fn process_load_file_tree(&mut self, tree_data: FileTreeData) {
785 fn create_file_node(
786 file_node_id: Option<LiveId>,
787 node_path: String,
788 path_to_file_id: &mut HashMap<String, LiveId>,
789 file_nodes: &mut LiveIdMap<LiveId, FileNode>,
790 parent_edge: Option<FileEdge>,
791 node: FileNodeData,
792 git_logs: &mut Vec<GitLog>
793 ) -> LiveId {
794
795 let file_node_id = file_node_id.unwrap_or(LiveId::from_str(&node_path).into());
796 let name = parent_edge.as_ref().map_or_else(
797 || String::from("root"),
798 | edge | edge.name.clone(),
799 );
800 let node = FileNode {
801 parent_edge,
802 name,
803 child_edges: match node {
804 FileNodeData::Directory {entries, git_log} => Some({
805 if let Some(git_log) = git_log{
806 git_logs.push(git_log);
807 }
808 entries
809 .into_iter()
810 .map( | entry | FileEdge {
811 name: entry.name.clone(),
812 file_node_id: create_file_node(
813 None,
814 if node_path.len()>0 {
815 format!("{}/{}", node_path, entry.name.clone())
816 }
817 else {
818 format!("{}", entry.name.clone())
819 },
820 path_to_file_id,
821 file_nodes,
822 Some(FileEdge {
823 name: entry.name,
824 file_node_id,
825 }),
826 entry.node,
827 git_logs,
828 ),
829 })
830 .collect::<Vec<_ >> ()
831 }),
832 FileNodeData::File {..} => None,
833 },
834 };
835 path_to_file_id.insert(node_path, file_node_id);
836 file_nodes.insert(file_node_id, node);
837 file_node_id
838 }
839
840 self.file_nodes.clear();
841 self.git_logs.clear();
842 create_file_node(
843 Some(live_id!(root).into()),
844 "".to_string(),
845 &mut self.path_to_file_node_id,
846 &mut self.file_nodes,
847 None,
848 tree_data.root,
849 &mut self.git_logs
850 );
851 }
852}