makepad_studio/file_system/
file_system.rs

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        //cx.action( FileSystemAction::SearchResults );
226    }
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        // lets see if we have a document yet
264        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                            // lets store this in our snapshot cache
315                            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                            // dock.select_tab(cx, dock, state, live_id!(file_tree).into(), live_id!(file_tree).into(), Animate::No);
335                        }
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                                    // ignore
370                                }
371                            }
372                        }
373                        FileResponse::SaveFile(result) => match result {
374                            Ok(response) => {
375                                self.process_save_response(cx, response);
376                            }
377                            Err(_) => {}
378                            // ok we saved a file, we should check however what changed
379                            // to see if we need a recompile
380                            
381                        }
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                               //println!("FILE CHANGED ON DISK {}", response.path);
393                                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                                // alright now what.
402                                // we should chuck this into the load comparison
403                                cx.action( FileSystemAction::FileChangedOnDisk(response));
404                            }
405                        }
406                        //self.editors.handle_collab_notification(cx, &mut state.editor_state, notification)
407                    }
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                    // we need the space 'outside' of these tokens
474                    if recompile && old_neg != new_neg {
475                        cx.action(FileSystemAction::RecompileNeeded)
476                    }
477                    if old_tokens != new_tokens{
478                        // design code changed, hotreload it
479                        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        // alright file has been saved
492        // now we need to check if a live_design!{} changed or something outside it
493        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        // ok lets see if we have a document
516        // ifnot, we create a new one
517        if tab_id != LiveId(0){
518            self.tab_id_to_file_node_id.insert(tab_id, file_id);
519        }
520            
521        // fetch decoration set
522        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        // ok lets see if we have a document
557        // ifnot, we create a new one
558        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        // ok lets see if we have a document
611        // ifnot, we create a new one
612        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        // ok lets see if we have a document
621        // ifnot, we create a new one
622        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        // ok lets see if we have a document
647        // ifnot, we create a new one
648        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 // same file
708            }
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        // Collect the path components for each open tab
724        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                // Collect references to the file node names without cloning
731                path_components.push(edge.name.as_str());
732                file_node = &self.file_nodes[edge.file_node_id];
733            }
734            // Reverse the components so they go from root to leaf
735            path_components.reverse();
736            
737            tabs.push((tab_id, path_components, 1));
738        }
739        
740        // Sort the tabs by their path components
741        tabs.sort_by(|a, b| a.1.cmp(&b.1));
742        
743        // Determine the minimal unique suffix for each tab
744        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                // Compare with previous tab
751                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                // Compare with next tab
758                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                // lets store this one 
768                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}