@@ -11,7 +11,7 @@ use lsp_types::{Position, Range, TextDocumentContentChangeEvent};
1111use serde:: { Deserialize , Serialize } ;
1212use serde_json:: { self , json} ;
1313use socketioxide:: extract:: { AckSender , Data , SocketRef , State } ;
14- use std:: path:: PathBuf ;
14+ use std:: path:: { Component , Path , PathBuf } ;
1515use tracing:: { error, info, warn} ;
1616
1717/// Apply edits to a Code instance and return LSP change events.
@@ -448,6 +448,14 @@ pub async fn handle_create(
448448 let name = & request. name ;
449449 let is_file = request. is_file ;
450450
451+ let mut name_components = Path :: new ( name) . components ( ) ;
452+ if name. is_empty ( )
453+ || !matches ! ( name_components. next( ) , Some ( Component :: Normal ( _) ) )
454+ || name_components. next ( ) . is_some ( )
455+ {
456+ error_ack ! ( ack, & request. name, "Name must be a single path component" ) ;
457+ }
458+
451459 // Build path using PathBuf for cross-platform compatibility
452460 let full_path = if parent_path. is_empty ( ) || parent_path == "." || parent_path == "./" {
453461 PathBuf :: from ( name)
@@ -465,6 +473,9 @@ pub async fn handle_create(
465473 } ;
466474
467475 let full_path_str = full_path. to_string_lossy ( ) . to_string ( ) ;
476+ if full_path. exists ( ) {
477+ error_ack ! ( ack, & request. name, "Destination path already exists" ) ;
478+ }
468479
469480 // Create parent directories if they don't exist
470481 if let Some ( parent) = full_path. parent ( ) {
@@ -484,10 +495,8 @@ pub async fn handle_create(
484495 Ok ( _) => {
485496 info ! ( "File created successfully: {}" , full_path_str) ;
486497 let mut f2c = state. file2code . lock ( ) . await ;
487- let code = f2c
488- . entry ( full_path_str. clone ( ) )
489- . or_insert_with ( || Code :: new ( ) ) ;
490- code. set_file_name ( full_path_str. clone ( ) ) ;
498+ f2c. entry ( full_path_str. clone ( ) )
499+ . or_insert_with ( || Code :: new_empty ( & full_path_str, & state. config ) ) ;
491500
492501 socket
493502 . broadcast ( )
@@ -520,3 +529,227 @@ pub async fn handle_create(
520529 }
521530 }
522531}
532+
533+ #[ derive( Debug , Serialize , Deserialize , Clone ) ]
534+ pub struct DeleteRequest {
535+ pub path : String ,
536+ }
537+
538+ pub async fn handle_delete (
539+ socket : SocketRef ,
540+ Data ( request) : Data < DeleteRequest > ,
541+ state : State < AppState > ,
542+ ack : AckSender ,
543+ ) {
544+ info ! ( "Received delete request: {:?}" , request) ;
545+
546+ let abs_path = match abs_file ( & request. path ) {
547+ Ok ( p) => p,
548+ Err ( e) => error_ack ! ( ack, & request. path, "Failed to resolve path: {:?}" , e) ,
549+ } ;
550+
551+ let path = std:: path:: Path :: new ( & abs_path) ;
552+ if !path. exists ( ) {
553+ error_ack ! ( ack, & request. path, "Path does not exist" ) ;
554+ }
555+
556+ let result = if path. is_file ( ) {
557+ std:: fs:: remove_file ( path)
558+ } else {
559+ std:: fs:: remove_dir_all ( path)
560+ } ;
561+
562+ match result {
563+ Ok ( _) => {
564+ info ! ( "Deleted successfully: {}" , abs_path) ;
565+
566+ let prefix = format ! ( "{}/" , abs_path) ;
567+
568+ let files_to_close: Vec < ( String , String ) > = {
569+ let f2c = state. file2code . lock ( ) . await ;
570+ f2c. iter ( )
571+ . filter ( |( k, _) | * * k == abs_path || k. starts_with ( & prefix) )
572+ . map ( |( k, code) | ( k. clone ( ) , code. lang . clone ( ) ) )
573+ . collect ( )
574+ } ;
575+
576+ if !files_to_close. is_empty ( ) {
577+ let mut lsp_manager = state. lsp_manager . lock ( ) . await ;
578+ for ( file_path, lang) in & files_to_close {
579+ if let Some ( lsp) = lsp_manager. get ( lang) . await {
580+ if let Err ( e) = lsp. did_close ( file_path) {
581+ error ! (
582+ "Failed to notify LSP didClose for deleted file {}: {:?}" ,
583+ file_path, e
584+ ) ;
585+ }
586+ }
587+ }
588+ }
589+
590+ {
591+ let mut f2c = state. file2code . lock ( ) . await ;
592+ f2c. retain ( |k, _| k != & abs_path && !k. starts_with ( & prefix) ) ;
593+ }
594+
595+ {
596+ let mut sockets_data = state. socket2data . lock ( ) . await ;
597+ for data in sockets_data. values_mut ( ) {
598+ data. opened_files
599+ . retain ( |k| k != & abs_path && !k. starts_with ( & prefix) ) ;
600+ }
601+ }
602+
603+ socket. broadcast ( ) . emit ( "file:deleted" , & abs_path) . await . ok ( ) ;
604+ ack. send ( & json ! ( { "success" : true , "path" : abs_path } ) ) . ok ( ) ;
605+ }
606+ Err ( e) => {
607+ error_ack ! ( ack, & request. path, "Failed to delete: {:?}" , e) ;
608+ }
609+ }
610+ }
611+
612+ #[ derive( Debug , Serialize , Deserialize , Clone ) ]
613+ pub struct RenameRequest {
614+ pub old_path : String ,
615+ pub new_path : String ,
616+ }
617+
618+ pub async fn handle_rename (
619+ socket : SocketRef ,
620+ Data ( request) : Data < RenameRequest > ,
621+ state : State < AppState > ,
622+ ack : AckSender ,
623+ ) {
624+ info ! ( "Received rename request: {:?}" , request) ;
625+
626+ let old_abs_path = match abs_file ( & request. old_path ) {
627+ Ok ( p) => p,
628+ Err ( e) => error_ack ! (
629+ ack,
630+ & request. old_path,
631+ "Failed to resolve old path: {:?}" ,
632+ e
633+ ) ,
634+ } ;
635+
636+ let new_abs_path = match abs_file ( & request. new_path ) {
637+ Ok ( p) => p,
638+ Err ( e) => error_ack ! (
639+ ack,
640+ & request. new_path,
641+ "Failed to resolve new path: {:?}" ,
642+ e
643+ ) ,
644+ } ;
645+
646+ let old_path = std:: path:: Path :: new ( & old_abs_path) ;
647+ if !old_path. exists ( ) {
648+ error_ack ! ( ack, & request. old_path, "Old path does not exist" ) ;
649+ }
650+ if old_abs_path == new_abs_path {
651+ ack. send ( & json ! ( { "success" : true , "old" : old_abs_path, "new" : new_abs_path } ) )
652+ . ok ( ) ;
653+ return ;
654+ }
655+ if std:: path:: Path :: new ( & new_abs_path) . exists ( ) {
656+ error_ack ! ( ack, & request. new_path, "Destination path already exists" ) ;
657+ }
658+
659+ let result = std:: fs:: rename ( & old_abs_path, & new_abs_path) ;
660+
661+ match result {
662+ Ok ( _) => {
663+ info ! ( "Renamed successfully: {} -> {}" , old_abs_path, new_abs_path) ;
664+
665+ let old_prefix = format ! ( "{}/" , old_abs_path) ;
666+ let new_prefix = format ! ( "{}/" , new_abs_path) ;
667+
668+ let mut files_to_rename = Vec :: new ( ) ;
669+ {
670+ let f2c = state. file2code . lock ( ) . await ;
671+ for ( k, code) in f2c. iter ( ) {
672+ if * k == old_abs_path {
673+ let new_lang = Code :: new_empty ( & new_abs_path, & state. config ) . lang ;
674+ files_to_rename. push ( (
675+ k. clone ( ) ,
676+ new_abs_path. clone ( ) ,
677+ code. lang . clone ( ) ,
678+ new_lang,
679+ code. get_content ( ) ,
680+ ) ) ;
681+ } else if k. starts_with ( & old_prefix) {
682+ let sub_path = k. strip_prefix ( & old_prefix) . unwrap ( ) ;
683+ let new_sub_path = format ! ( "{}{}" , new_prefix, sub_path) ;
684+ let new_lang = Code :: new_empty ( & new_sub_path, & state. config ) . lang ;
685+ files_to_rename. push ( (
686+ k. clone ( ) ,
687+ new_sub_path,
688+ code. lang . clone ( ) ,
689+ new_lang,
690+ code. get_content ( ) ,
691+ ) ) ;
692+ }
693+ }
694+ }
695+
696+ if !files_to_rename. is_empty ( ) {
697+ let mut lsp_manager = state. lsp_manager . lock ( ) . await ;
698+
699+ for ( old_path, _, old_lang, _, _) in & files_to_rename {
700+ if let Some ( lsp) = lsp_manager. get ( old_lang) . await {
701+ let _ = lsp. did_close ( old_path) ;
702+ }
703+ }
704+
705+ {
706+ let mut f2c = state. file2code . lock ( ) . await ;
707+ for ( old_path, new_path, _, new_lang, _) in & files_to_rename {
708+ if let Some ( mut code) = f2c. remove ( old_path) {
709+ code. abs_path = new_path. clone ( ) ;
710+ code. file_name = crate :: utils:: file_name ( new_path) ;
711+ code. lang = new_lang. clone ( ) ;
712+ f2c. insert ( new_path. clone ( ) , code) ;
713+ }
714+ }
715+ }
716+
717+ {
718+ let mut sockets_data = state. socket2data . lock ( ) . await ;
719+ for data in sockets_data. values_mut ( ) {
720+ for ( old_path, new_path, _, _, _) in & files_to_rename {
721+ if data. opened_files . remove ( old_path) {
722+ data. opened_files . insert ( new_path. clone ( ) ) ;
723+ }
724+ }
725+ }
726+ }
727+
728+ for ( _, new_path, _, new_lang, content) in & files_to_rename {
729+ if let Some ( lsp) = lsp_manager. get ( new_lang) . await {
730+ let _ = lsp. did_open ( new_lang, new_path, content) ;
731+ }
732+ }
733+ }
734+
735+ let _ = socket. emit (
736+ "file:renamed" ,
737+ & json ! ( { "old" : old_abs_path, "new" : new_abs_path } ) ,
738+ ) ;
739+
740+ socket
741+ . broadcast ( )
742+ . emit (
743+ "file:renamed" ,
744+ & json ! ( { "old" : old_abs_path, "new" : new_abs_path } ) ,
745+ )
746+ . await
747+ . ok ( ) ;
748+ ack. send ( & json ! ( { "success" : true , "old" : old_abs_path, "new" : new_abs_path } ) )
749+ . ok ( ) ;
750+ }
751+ Err ( e) => {
752+ error_ack ! ( ack, & request. old_path, "Failed to rename: {:?}" , e) ;
753+ }
754+ }
755+ }
0 commit comments