makepad_studio/
app.rs

1use crate::{
2    makepad_code_editor::code_editor::*,
3    makepad_code_editor::selection::Affinity,
4    makepad_code_editor::session::SelectionMode,
5    makepad_code_editor::history::NewGroup,
6    makepad_widgets::*,
7    makepad_micro_serde::*,
8    makepad_widgets::file_tree::*,
9    makepad_platform::os::cx_stdin::*,
10    makepad_file_protocol::SearchItem,
11    makepad_file_server::FileSystemRoots,
12    file_system::file_system::*,
13    studio_editor::*,
14    run_view::*,
15    snapshot::*,
16    studio_file_tree::*,
17    makepad_platform::studio::{JumpToFile,EditFile, SelectInFile, PatchFile, SwapSelection},
18    log_list::*,
19    makepad_code_editor::{CodeSession,text::{Position}},
20    ai_chat::ai_chat_manager::AiChatManager,
21    build_manager::{
22        build_protocol::BuildProcess,
23        build_manager::{
24            BuildManager,
25            BuildManagerAction
26        },
27    }
28};   
29use std::fs::File;
30use std::io::Write;
31use std::env;
32
33live_design!{
34    use crate::app_ui::*;
35    use link::widgets::*;
36    
37    App = {{App}} {
38        ui: <Root>{
39            <AppUI> {}
40        }
41    }
42}
43 
44#[derive(Live, LiveHook)]
45pub struct App {
46    #[live] pub ui: WidgetRef,
47    #[rust] pub data: AppData,
48}
49 
50impl LiveRegister for App{
51    fn live_register(cx: &mut Cx) {
52        crate::live_design(cx);
53        // for macos
54        cx.start_stdin_service();
55    }
56}
57
58app_main!(App);
59
60impl App {
61     
62    pub fn open_code_file_by_path(&mut self, cx: &mut Cx, path: &str) {
63        if let Some(file_id) = self.data.file_system.path_to_file_node_id(&path) {
64            let dock = self.ui.dock(id!(dock));            
65            let tab_id = dock.unique_id(file_id.0);
66            self.data.file_system.request_open_file(tab_id, file_id);
67            let (tab_bar, pos) = dock.find_tab_bar_of_tab(live_id!(edit_first)).unwrap();
68            // lets pick the template
69            let template = FileSystem::get_editor_template_from_path(path);
70            dock.create_and_select_tab(cx, tab_bar, tab_id, template, "".to_string(), live_id!(CloseableTab), Some(pos));
71            self.data.file_system.ensure_unique_tab_names(cx, &dock)
72        }
73    }
74    
75    pub fn load_state(&mut self, cx:&mut Cx, slot:usize){
76        
77        if let Ok(contents) = std::fs::read_to_string(format!("makepad_state{}.ron", slot)) {
78            match AppStateRon::deserialize_ron(&contents) {
79                Ok(state)=>{
80                    // lets kill all running processes
81                    self.data.build_manager.stop_all_active_builds(cx);
82                    // Now we need to apply the saved state
83                    let dock = self.ui.dock(id!(dock));
84                    if let Some(mut dock) = dock.borrow_mut() {
85                        dock.load_state(cx, state.dock_items);
86                                                
87                        self.data.file_system.tab_id_to_file_node_id = state.tab_id_to_file_node_id.clone();
88                        for (tab_id, file_node_id) in state.tab_id_to_file_node_id.iter() {
89                            self.data.file_system.request_open_file(*tab_id, *file_node_id);
90                        }
91                        // ok lets run the processes
92                                                                       
93                        for process in state.processes{
94                            if let Some(binary_id) = self.data.build_manager.binary_name_to_id(&process.binary){
95                                self.data.build_manager.start_active_build(cx, binary_id, process.target);
96                            }       
97                        }
98                    };
99                    self.ui.clear_query_cache();
100                    return;
101                    //self.ui.redraw(cx);
102                    // cx.redraw_all();
103                    //self.data.build_manager.designer_selected_files = 
104                     //   state.designer_selected_files;
105                }
106                Err(e)=>{
107                    println!("ERR {:?}",e);
108                }
109            }
110        }
111    }
112    
113    fn save_state(&self, slot:usize){
114        let dock = self.ui.dock(id!(dock));
115        let dock_items = dock.clone_state().unwrap();
116        
117        // lets store the active build ids so we can fire them up again
118        let mut processes = Vec::new();
119        for build in self.data.build_manager.active.builds.values(){
120            processes.push(build.process.clone());
121        }
122                
123        //do we keep the runviews? we should eh.
124        // alright lets save it to disk
125        let state = AppStateRon{
126            dock_items,
127            processes,
128            tab_id_to_file_node_id: self.data.file_system.tab_id_to_file_node_id.clone()
129        };
130        let saved = state.serialize_ron();
131        let mut f = File::create(format!("makepad_state{}.ron", slot)).expect("Unable to create file");
132        f.write_all(saved.as_bytes()).expect("Unable to write data");
133    }
134}
135
136#[derive(Default)]
137pub struct AppData{ 
138    pub build_manager: BuildManager,
139    pub file_system: FileSystem,
140    pub ai_chat_manager: AiChatManager,
141}
142
143// all global app commands coming in from keybindings, and UI components
144
145#[derive(DefaultNone, Debug, Clone)]
146pub enum AppAction{
147    JumpTo(JumpToFile),
148    SelectInFile(SelectInFile),
149    SwapSelection(SwapSelection),
150    RedrawLog,
151    RedrawProfiler,
152    RedrawFile(LiveId),
153    FocusDesign(LiveId),
154    EditFile(EditFile),
155    PatchFile(PatchFile),
156    StartRecompile,
157    ReloadFileTree,
158    RecompileStarted,
159    RedrawSnapshots,
160    ClearLog, 
161    SetSnapshotMessage{message:String},
162    SendAiChatToBackend{chat_id:LiveId, history_slot:usize},
163    CancelAiGeneration{chat_id:LiveId},
164    SaveAiChat{chat_id:LiveId},
165    RedrawAiChat{chat_id:LiveId},
166    RunAiChat{chat_id:LiveId, history_slot:usize, item_id:usize},
167    DestroyRunViews{run_view_id:LiveId},
168    None
169}
170
171impl MatchEvent for App{
172    fn handle_startup(&mut self, cx:&mut Cx){
173        let mut roots = Vec::new();
174        let current_dir = env::current_dir().unwrap();
175        
176        for arg in std::env::args(){
177            if let Some(prefix) = arg.strip_prefix("--root="){
178                for root in prefix.split(","){
179                    let mut parts = root.splitn(2,":");
180                    let base = parts.next().expect("name:path expected");
181                    let path = parts.next().expect("name:path expected");
182                    let dir = current_dir.clone();
183                    roots.push((base.to_string(), dir.join(path).canonicalize().unwrap()));
184                }
185            }
186            else{
187            }
188        }
189        if roots.is_empty(){
190            let dir1 = current_dir.join("./").canonicalize().unwrap();
191            //roots.push(("ai_snake".to_string(),current_dir.join("../snapshots/ai_snake").canonicalize().unwrap()));
192            roots.push(("makepad".to_string(),dir1));
193            //roots.push(("experiments".to_string(),current_dir.join("../experiments").canonicalize().unwrap()));
194        }
195        let roots = FileSystemRoots{roots};
196        self.data.file_system.init(cx, roots.clone());
197        self.data.build_manager.init(cx, roots);
198                
199        //self.data.build_manager.discover_external_ip(cx);
200        self.data.build_manager.start_http_server();
201        // lets load the tabs
202    }
203    
204    fn handle_action(&mut self, cx:&mut Cx, action:&Action){
205        let dock = self.ui.dock(id!(dock));
206        let file_tree = self.ui.studio_file_tree(id!(file_tree));
207        let log_list = self.ui.log_list(id!(log_list));
208        let run_list = self.ui.view(id!(run_list_tab));
209        let profiler = self.ui.view(id!(profiler));
210        let search = self.ui.view(id!(search));
211        let snapshot = self.ui.snapshot(id!(snapshot_tab));
212        
213        match action.cast(){
214            AppAction::SwapSelection(ss)=>{
215                let s1_start = Position{line_index: ss.s1_line_start as usize, byte_index:ss.s1_column_start as usize};
216                let s1_end = Position{line_index: ss.s1_line_end as usize, byte_index:ss.s1_column_end as usize};
217                let s2_start = Position{line_index: ss.s2_line_start as usize, byte_index:ss.s2_column_start as usize};
218                let s2_end = Position{line_index: ss.s2_line_end as usize, byte_index:ss.s2_column_end as usize};
219                if let Some(s1_file_id) = self.data.file_system.path_to_file_node_id(&ss.s1_file_name) {
220                    if let Some(s2_file_id) = self.data.file_system.path_to_file_node_id(&ss.s1_file_name) {
221                        if let Some(OpenDocument::Code(doc1)) = self.data.file_system.open_documents.get(&s1_file_id) {
222                            if let Some(OpenDocument::Code(doc2)) = self.data.file_system.open_documents.get(&s2_file_id) {
223                                // Create sessions
224                                let mut session1 = CodeSession::new(doc1.clone());
225                                let mut session2 = CodeSession::new(doc2.clone());
226            
227                                // Set selections in both sessions
228                                session1.set_selection(session1.clamp_position(s1_start), Affinity::After, SelectionMode::Simple, NewGroup::Yes);
229                                session1.move_to(session1.clamp_position(s1_end), Affinity::Before, NewGroup::Yes);
230                                
231                                session2.set_selection(session2.clamp_position(s2_start), Affinity::After, SelectionMode::Simple, NewGroup::Yes);
232                                session2.move_to(session2.clamp_position(s2_end), Affinity::Before, NewGroup::Yes);
233            
234                                // Get the selected text from both sessions
235                                let text1 = session1.copy();
236                                let text2 = session2.copy();
237                                                                            
238                                // Swap the text by pasting into each session
239                                session1.paste(text2.into());
240                                session1.handle_changes();
241                                session2.handle_changes();
242                                self.data.file_system.handle_sessions();
243                                
244                                session2.paste(text1.into());
245                                session1.handle_changes();
246                                session2.handle_changes();
247                                self.data.file_system.handle_sessions();
248                                
249                                // lets draw any views file file 1 and 2
250                                cx.action(AppAction::RedrawFile(s1_file_id));
251                                cx.action(AppAction::RedrawFile(s2_file_id));
252                                // Handle any pending changes and save the files
253                                self.data.file_system.request_save_file_for_file_node_id(s1_file_id, false);
254                                //self.data.file_system.request_save_file_for_file_node_id(s2_file_id, false);
255                            }
256                        }
257                    }
258                }
259            }
260            AppAction::SelectInFile(sf)=>{
261                let start = Position{line_index: sf.line_start as usize, byte_index:sf.column_start as usize};
262                let end = Position{line_index: sf.line_end as usize, byte_index:sf.column_end as usize};
263                if let Some(file_id) = self.data.file_system.path_to_file_node_id(&sf.file_name) {
264                    if let Some(tab_id) = self.data.file_system.file_node_id_to_tab_id(file_id){
265                        dock.select_tab(cx, tab_id);
266                        // ok lets scroll into view
267                        if let Some(mut editor) = dock.item(tab_id).studio_code_editor(id!(editor)).borrow_mut() {
268                            if let Some(EditSession::Code(session)) = self.data.file_system.get_session_mut(tab_id) {
269                                editor.editor.set_selection_and_scroll(cx, start, end, session);
270                                editor.editor.set_key_focus(cx);
271                            }
272                        }
273                    }
274                }
275            }
276            AppAction::JumpTo(jt)=>{
277                let pos = Position{line_index: jt.line as usize, byte_index:jt.column as usize};
278                if let Some(file_id) = self.data.file_system.path_to_file_node_id(&jt.file_name) {
279                    if let Some(tab_id) = self.data.file_system.file_node_id_to_tab_id(file_id){
280                        dock.select_tab(cx, tab_id);
281                        // ok lets scroll into view
282                        if let Some(mut editor) = dock.item(tab_id).studio_code_editor(id!(editor)).borrow_mut() {
283                            if let Some(EditSession::Code(session)) = self.data.file_system.get_session_mut(tab_id) {
284                                editor.editor.set_cursor_and_scroll(cx, pos, session);
285                                editor.editor.set_key_focus(cx);
286                            }
287                        }
288                    }
289                    else{
290                        // lets open the editor
291                        let tab_id = dock.unique_id(file_id.0);
292                        self.data.file_system.request_open_file(tab_id, file_id);
293                        // lets add a file tab 'somewhere'
294                        let (tab_bar, pos) = dock.find_tab_bar_of_tab(live_id!(edit_first)).unwrap();
295                        let template = FileSystem::get_editor_template_from_path(&jt.file_name);
296                        dock.create_and_select_tab(cx, tab_bar, tab_id, template, "".to_string(), live_id!(CloseableTab), Some(pos));
297                        // lets scan the entire doc for duplicates
298                        self.data.file_system.ensure_unique_tab_names(cx, &dock)
299                    }
300                }
301            }
302            AppAction::PatchFile(_ef)=>{
303                panic!()
304                /*let start = Position{line_index: ef.line as usize, byte_index:ef.column_start as usize};
305                let end = Position{line_index: ef.line as usize, byte_index:ef.column_end as usize};
306                if let Some(file_id) = self.data.file_system.path_to_file_node_id(&ef.file_name) {
307                    if let Some(tab_id) = self.data.file_system.file_node_id_to_tab_id(file_id){
308                        //dock.select_tab(cx, tab_id);
309                        // ok lets scroll into view
310                        if let Some(mut editor) = dock.item(tab_id).studio_code_editor(id!(editor)).borrow_mut() {
311                            if let Some(EditSession::Code(session)) = self.data.file_system.get_session_mut(tab_id) {
312                                // alright lets do 
313                                session.set_selection(
314                                    start,
315                                    Affinity::After,
316                                    SelectionMode::Simple,
317                                    NewGroup::No
318                                );
319                                session.move_to(
320                                    end,
321                                    Affinity::Before,
322                                    NewGroup::No 
323                                );
324                                session.paste_grouped(ef.replace.into(), ef.undo_group);
325                            }
326                            self.data.file_system.handle_sessions();
327                            editor.redraw(cx);
328                            self.data.file_system.request_save_file_for_file_node_id(file_id, true)
329                        }
330                    }
331                }*/
332            }
333            AppAction::EditFile(ef)=>{
334                let start = Position{line_index: ef.line_start as usize, byte_index:ef.column_start as usize};
335                let end = Position{line_index: ef.line_end as usize, byte_index:ef.column_end as usize};
336                if let Some(file_id) = self.data.file_system.path_to_file_node_id(&ef.file_name) {
337                    if let Some(tab_id) = self.data.file_system.file_node_id_to_tab_id(file_id){
338                        dock.select_tab(cx, tab_id);
339                        // ok lets scroll into view
340                        if let Some(mut editor) = dock.item(tab_id).studio_code_editor(id!(editor)).borrow_mut() {
341                            if let Some(EditSession::Code(session)) = self.data.file_system.get_session_mut(tab_id) {
342                                // alright lets do 
343                                session.set_selection(
344                                    start,
345                                    Affinity::After,
346                                    SelectionMode::Simple,
347                                    NewGroup::Yes
348                                );
349                                session.move_to(
350                                    end,
351                                    Affinity::Before,
352                                    NewGroup::Yes
353                                );
354                                session.paste(ef.replace.into());
355                                // lets serialise the session
356                            }
357                            self.data.file_system.handle_sessions();
358                            editor.redraw(cx);
359                            self.data.file_system.request_save_file_for_file_node_id(file_id, false)
360                        }
361                    }
362                }
363            }
364            AppAction::RedrawFile(file_id)=>{
365                self.data.file_system.redraw_view_by_file_id(cx, file_id, &dock);
366            }
367            AppAction::ClearLog=>{
368                self.data.build_manager.clear_log(cx, &dock, &mut self.data.file_system);
369                log_list.reset_scroll(cx);
370                log_list.redraw(cx);
371                profiler.redraw(cx);
372            }
373            AppAction::ReloadFileTree=>{
374                self.data.file_system.file_client.load_file_tree();
375            }
376            AppAction::RedrawProfiler=>{
377                profiler.redraw(cx);
378            }
379            AppAction::RedrawLog=>{
380                log_list.redraw(cx);
381            }
382            AppAction::StartRecompile=>{
383                self.data.build_manager.start_recompile(cx);
384            }
385            AppAction::FocusDesign(build_id)=>{
386                let mut id = None;
387                if let Some(mut dock) = dock.borrow_mut() {
388                    for (tab_id, (_, item)) in dock.items().iter() {
389                        if let Some(run_view) = item.as_run_view().borrow_mut() {
390                            if run_view.build_id == Some(build_id) {
391                                if let WindowKindId::Design = run_view.kind_id{
392                                    // lets focus this tab
393                                    id = Some(*tab_id);
394                                    break;
395                                }
396                            }
397                        }
398                    }
399                }
400                if let Some(id) = id{
401                    dock.select_tab(cx, id);
402                }
403            }
404            AppAction::RecompileStarted=>{
405                if let Some(mut dock) = dock.borrow_mut() {
406                    for (_id, (_, item)) in dock.items().iter() {
407                        if let Some(mut run_view) = item.as_run_view().borrow_mut() {
408                            run_view.recompile_started(cx);
409                            run_view.resend_framebuffer(cx);
410                        }
411                    }
412                }
413            }
414            AppAction::None=>(),
415            AppAction::SendAiChatToBackend{chat_id, history_slot}=>{
416                self.data.ai_chat_manager.send_chat_to_backend(cx, chat_id, history_slot, &mut self.data.file_system);
417            }
418            AppAction::CancelAiGeneration{chat_id}=>{
419                self.data.ai_chat_manager.cancel_chat_generation(cx, &self.ui, chat_id,  &mut self.data.file_system);
420            }
421            AppAction::SaveAiChat{chat_id}=>{
422                self.data.file_system.request_save_file_for_file_node_id(chat_id, false);
423            }
424            AppAction::RedrawAiChat{chat_id}=>{
425                self.data.ai_chat_manager.redraw_ai_chat_by_id(cx, chat_id,&self.ui,  &mut self.data.file_system);
426            }
427            AppAction::RunAiChat{chat_id, history_slot, item_id}=>{
428                self.data.ai_chat_manager.run_ai_chat(cx, chat_id, history_slot, item_id, &mut self.data.file_system);
429            }
430            AppAction::DestroyRunViews{run_view_id} => {
431                dock.close_tab(cx, run_view_id);
432                dock.close_tab(cx, run_view_id.add(1));
433                dock.close_tab(cx, run_view_id.add(2));
434                dock.redraw(cx);
435                log_list.redraw(cx);
436            }
437            AppAction::SetSnapshotMessage{message}=>{
438                snapshot.set_message(cx, message);
439            }
440            AppAction::RedrawSnapshots=>{
441                snapshot.redraw(cx);
442            }
443        }
444                
445        match action.cast(){
446            BuildManagerAction::StdinToHost {build_id, msg} => {
447                match msg{
448                    StdinToHost::CreateWindow{window_id, kind_id}=>{
449                        let panel_id = build_id.add(window_id as u64);
450                        if let Some(name) = self.data.build_manager.process_name(build_id){
451                            
452                            let (tab_bar_id, pos) = if kind_id == 0{
453                                dock.find_tab_bar_of_tab(live_id!(run_first)).unwrap()
454                            }
455                            else if kind_id == 1{ 
456                                dock.find_tab_bar_of_tab(live_id!(design_first)).unwrap()
457                            }
458                            else{
459                                dock.find_tab_bar_of_tab(live_id!(outline_first)).unwrap()
460                            };
461                            
462                            
463                            // we might already have it
464                            
465                            let item = dock.create_and_select_tab(cx, tab_bar_id, panel_id, live_id!(RunView), name.clone(), live_id!(CloseableTab), Some(pos)).unwrap();
466                                                        
467                            if let Some(mut item) = item.as_run_view().borrow_mut(){
468                                item.window_id = window_id;
469                                item.build_id = Some(build_id);
470                                item.kind_id = WindowKindId::from_usize(kind_id);
471                            }
472                            else{
473                                println!("WHIT");
474                            }
475                            
476                            dock.redraw(cx);
477                            log_list.redraw(cx);                        
478                        }
479                    }
480                    StdinToHost::SetCursor(cursor) => {
481                        cx.set_cursor(cursor)
482                    }
483                    StdinToHost::ReadyToStart => { 
484                        // lets fetch all our runviews
485                        if let Some(mut dock) = dock.borrow_mut() {
486                            for (_, (_, item)) in dock.items().iter() {
487                                if let Some(mut run_view) = item.as_run_view().borrow_mut() {
488                                    if run_view.build_id == Some(build_id){
489                                        run_view.ready_to_start(cx);
490                                    }
491                                }
492                            }
493                        }
494                    }
495                    StdinToHost::DrawCompleteAndFlip(presentable_draw) => {
496                        if let Some(mut dock) = dock.borrow_mut() {
497                            for (_, (_, item)) in dock.items().iter() {
498                                if let Some(mut run_view) = item.as_run_view().borrow_mut() {
499                                    if run_view.build_id == Some(build_id) && run_view.window_id == presentable_draw.window_id{
500                                        run_view.draw_complete_and_flip(cx, &presentable_draw, &mut self.data.build_manager);
501                                    }
502                                }
503                            }
504                        }
505                    }
506                }
507            }
508            BuildManagerAction::None=>()
509        }
510                
511        match action.cast(){
512            FileSystemAction::TreeLoaded => {
513                file_tree.redraw(cx);
514                self.load_state(cx, 0);
515                self.data.ai_chat_manager.init(&mut self.data.file_system);
516            }
517            FileSystemAction::SnapshotImageLoaded => {
518                snapshot.redraw(cx);
519            }
520            FileSystemAction::RecompileNeeded => {
521                self.data.build_manager.start_recompile_timer(cx);
522            }
523            FileSystemAction::LiveReloadNeeded(live_file_change) => {
524                self.data.build_manager.live_reload_needed(live_file_change);
525                self.data.build_manager.clear_log(cx, &dock, &mut self.data.file_system);
526                log_list.redraw(cx);
527            }
528            FileSystemAction::FileChangedOnDisk(_res)=>{
529                
530            }
531            FileSystemAction::SearchResults=>{
532                search.redraw(cx);
533            }
534            FileSystemAction::None=>()
535        }
536        
537        if let Some(action) = action.as_widget_action(){
538            match action.cast(){
539                CodeEditorAction::UnhandledKeyDown(ke) if ke.key_code == KeyCode::F12 && !ke.modifiers.shift =>{
540                    if let Some(word) = self.data.file_system.get_word_under_cursor_for_session(action.path.from_end(1)){
541                        dock.select_tab(cx, live_id!(search));
542                        let set = vec![SearchItem{
543                            needle:word.clone(), 
544                            prefixes: Some(vec![
545                                format!("struct "),
546                                format!("enum "),
547                                format!("fn "),
548                                format!("type "),
549                                format!("trait "),
550                                format!("pub ")
551                            ]),
552                            pre_word_boundary:true,
553                            post_word_boundary:true
554                        }];
555                        search.text_input(id!(search_input)).set_text(cx, &word);
556                        self.data.file_system.search_string(cx, set);
557                    } 
558                },
559                CodeEditorAction::UnhandledKeyDown(ke) if ke.key_code == KeyCode::F12 && ke.modifiers.shift =>{
560                    if let Some(word) = self.data.file_system.get_word_under_cursor_for_session(action.path.from_end(1)){
561                        dock.select_tab(cx, live_id!(search));
562                        let set = vec![SearchItem{
563                            needle:word.clone(), 
564                            prefixes: None,
565                            pre_word_boundary:ke.modifiers.control,
566                            post_word_boundary:ke.modifiers.control
567                        }];
568                        search.text_input(id!(search_input)).set_text(cx, &word);
569                        self.data.file_system.search_string(cx, set);
570                    } 
571                },
572                CodeEditorAction::TextDidChange => {
573                    // lets write the file
574                    self.data.file_system.request_save_file_for_tab_id(action.path.from_end(1), false)
575                }
576                CodeEditorAction::UnhandledKeyDown(_)=>{}
577                CodeEditorAction::None=>{}
578            }
579            
580            match action.cast(){
581                DockAction::TabCloseWasPressed(tab_id)=>{
582                    dock.close_tab(cx, tab_id);
583                    if self.data.build_manager.handle_tab_close(tab_id) {
584                        log_list.redraw(cx);
585                        run_list.redraw(cx);
586                    }
587                    self.data.file_system.remove_tab(tab_id);
588                    self.data.file_system.ensure_unique_tab_names(cx, &dock);
589                }
590                DockAction::ShouldTabStartDrag(tab_id)=>{
591                    dock.tab_start_drag(cx, tab_id, DragItem::FilePath {
592                        path: "".to_string(), //String::from("file://") + &*path.into_unix_string().to_string_lossy(),
593                        internal_id: Some(tab_id)
594                    });
595                }
596                DockAction::Drag(drag_event)=>{
597                    if drag_event.items.len() == 1 {
598                        if drag_event.modifiers.logo {
599                            dock.accept_drag(cx, drag_event, DragResponse::Copy);
600                        }
601                        else {
602                            dock.accept_drag(cx, drag_event, DragResponse::Move);
603                        }
604                    }
605                }
606                DockAction::Drop(drop_event)=>{
607                    if let DragItem::FilePath {path, internal_id} = &drop_event.items[0] {
608                        if let Some(internal_id) = internal_id { // from inside the dock
609                            if drop_event.modifiers.logo {
610                                let tab_id = dock.unique_id(internal_id.0);
611                                dock.drop_clone(cx, drop_event.abs, *internal_id, tab_id, live_id!(CloseableTab));
612                            }
613                            else {
614                                dock.drop_move(cx, drop_event.abs, *internal_id);
615                            }
616                            self.data.file_system.ensure_unique_tab_names(cx, &dock);
617                        }
618                        else { // external file, we have to create a new tab
619                            if let Some(file_id) = self.data.file_system.path_to_file_node_id(&path) {
620                                let tab_id = dock.unique_id(file_id.0);
621                                self.data.file_system.request_open_file(tab_id, file_id);
622                                let template = FileSystem::get_editor_template_from_path(&path);
623                                dock.drop_create(cx, drop_event.abs, tab_id, template, "".to_string(), live_id!(CloseableTab));
624                                self.data.file_system.ensure_unique_tab_names(cx, &dock)
625                            }
626                        }
627                    }
628                },
629                _=>()
630            }
631        }
632    }        
633        
634    fn handle_key_down(&mut self, cx: &mut Cx, event: &KeyEvent){
635        let KeyEvent {
636            key_code,
637            modifiers: KeyModifiers {logo, control, ..},
638            ..
639        } = event;
640        if *control || *logo {
641            if let KeyCode::Backtick = key_code {
642                cx.action(AppAction::ClearLog);
643                cx.action(AppAction::RecompileStarted);
644                cx.action(AppAction::StartRecompile);
645            }
646            else if let KeyCode::KeyK = key_code {
647                cx.action(AppAction::ClearLog);
648            }
649            else if let KeyCode::KeyR = key_code{
650                cx.action(AppAction::ReloadFileTree);
651            }
652        }
653    }
654    
655    fn handle_actions(&mut self, cx: &mut Cx, actions:&Actions){
656        let file_tree = self.ui.file_tree(id!(file_tree));
657        let dock = self.ui.dock(id!(dock));
658        for action in actions{
659            self.handle_action(cx, action);
660        }
661        if let Some(file_id) = file_tree.should_file_start_drag(&actions) {
662            let path = self.data.file_system.file_node_path(file_id);
663            file_tree.file_start_drag(cx, file_id, DragItem::FilePath {
664                path,
665                internal_id: None
666            }); 
667        }
668        
669        for (i,id) in [*id!(preset_1),*id!(preset_2),*id!(preset_3),*id!(preset_4)].iter().enumerate(){
670            if let Some(km) = self.ui.button(id).pressed_modifiers(actions){
671                if km.control{
672                    self.save_state(i+1)
673                }
674                else{
675                    self.load_state(cx, i+1);
676                    cx.redraw_all();
677                }
678            }
679        }
680            
681        if let Some(file_id) = file_tree.file_clicked(&actions) {
682            // ok lets open the file
683            if let Some(tab_id) = self.data.file_system.file_node_id_to_tab_id(file_id) {
684                // If the tab is already open, focus it
685                dock.select_tab(cx, tab_id);
686            } else {
687                let tab_id = dock.unique_id(file_id.0);
688                self.data.file_system.request_open_file(tab_id, file_id);
689                self.data.file_system.request_open_file(tab_id, file_id);
690                                
691                // lets add a file tab 'some
692                let path = self.data.file_system.file_node_id_to_path(file_id).unwrap();
693                let tab_after = FileSystem::get_tab_after_from_path(path);
694                let (tab_bar, pos) = dock.find_tab_bar_of_tab(tab_after).unwrap();
695                let template = FileSystem::get_editor_template_from_path(path);
696                dock.create_and_select_tab(cx, tab_bar, tab_id, template, "".to_string(), live_id!(CloseableTab), Some(pos));
697                
698                // lets scan the entire doc for duplicates
699                self.data.file_system.ensure_unique_tab_names(cx, &dock)
700            }
701        }
702    }
703    
704    fn handle_shutdown(&mut self, _cx:&mut Cx){
705        self.data.build_manager.clear_active_builds();
706    }
707}
708
709impl AppMain for App {
710    
711    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
712        self.match_event(cx, event);
713        self.ui.handle_event(cx, event, &mut Scope::with_data(&mut self.data));
714        
715        self.data.file_system.handle_event(cx, event, &self.ui);
716        self.data.build_manager.handle_event(cx, event, &mut self.data.file_system); 
717        self.data.ai_chat_manager.handle_event(cx, event, &mut self.data.file_system);
718        if self.ui.dock(id!(dock)).check_and_clear_need_save(){
719            self.save_state(0);
720        }
721    }
722}
723
724// we should store probably also scroll position / which chat slot we're visiting
725use std::collections::HashMap;
726#[derive(SerRon, DeRon)]
727pub struct AppStateRon{
728    dock_items: HashMap<LiveId, DockItem>,
729    processes: Vec<BuildProcess>,
730    tab_id_to_file_node_id: HashMap<LiveId, LiveId>,
731}