@@ -111,9 +111,9 @@ impl FileInfo {
111111 diagnostic_filters : Vec :: new ( ) ,
112112 }
113113 }
114- pub fn update ( & mut self , session : & mut SessionInfo , uri : & str , content : Option < & Vec < TextDocumentContentChangeEvent > > , version : Option < i32 > , in_workspace : bool , force : bool ) -> bool {
114+ pub fn update ( & mut self , session : & mut SessionInfo , path : & str , content : Option < & Vec < TextDocumentContentChangeEvent > > , version : Option < i32 > , in_workspace : bool , force : bool , is_untitled : bool ) -> bool {
115115 // update the file info with the given information.
116- // uri : indicates the path of the file
116+ // path : indicates the path of the file
117117 // content: if content is given, it will be used to update the ast and text_rope, if not, the loading will be from the disk
118118 // version: if the version is provided, the file_info wil be updated only if the new version is higher.
119119 // -100 can be given as version number to indicates that the file has not been opened yet, and that we have to load it ourself
@@ -144,13 +144,16 @@ impl FileInfo {
144144 for change in content. iter ( ) {
145145 self . apply_change ( change) ;
146146 }
147+ } else if is_untitled {
148+ session. log_message ( MessageType :: ERROR , format ! ( "Attempt to update untitled file {}, without changes" , path) ) ;
149+ return false ;
147150 } else {
148- match fs:: read_to_string ( uri ) {
151+ match fs:: read_to_string ( path ) {
149152 Ok ( content) => {
150153 self . file_info_ast . borrow_mut ( ) . text_rope = Some ( ropey:: Rope :: from ( content. as_str ( ) ) ) ;
151154 } ,
152155 Err ( e) => {
153- session. log_message ( MessageType :: ERROR , format ! ( "Failed to read file {}, with error {}" , uri , e) ) ;
156+ session. log_message ( MessageType :: ERROR , format ! ( "Failed to read file {}, with error {}" , path , e) ) ;
154157 return false ;
155158 } ,
156159 } ;
@@ -474,6 +477,7 @@ impl FileInfo {
474477#[ derive( Debug ) ]
475478pub struct FileMgr {
476479 pub files : HashMap < String , Rc < RefCell < FileInfo > > > ,
480+ untitled_files : HashMap < String , Rc < RefCell < FileInfo > > > , // key: untitled URI or unique name
477481 workspace_folders : HashMap < String , String > ,
478482 has_repeated_workspace_folders : bool ,
479483}
@@ -483,6 +487,7 @@ impl FileMgr {
483487 pub fn new ( ) -> Self {
484488 Self {
485489 files : HashMap :: new ( ) ,
490+ untitled_files : HashMap :: new ( ) ,
486491 workspace_folders : HashMap :: new ( ) ,
487492 has_repeated_workspace_folders : false ,
488493 }
@@ -496,17 +501,30 @@ impl FileMgr {
496501 }
497502
498503 pub fn get_file_info ( & self , path : & String ) -> Option < Rc < RefCell < FileInfo > > > {
499- self . files . get ( path) . cloned ( )
504+ if Self :: is_untitled ( path) {
505+ self . untitled_files . get ( path) . cloned ( )
506+ } else {
507+ self . files . get ( path) . cloned ( )
508+ }
500509 }
501510
502511 pub fn text_range_to_range ( & self , session : & mut SessionInfo , path : & String , range : & TextRange ) -> Range {
503- let file = self . files . get ( path) ;
512+ let file = if Self :: is_untitled ( path) {
513+ self . untitled_files . get ( path)
514+ } else {
515+ self . files . get ( path)
516+ } ;
504517 if let Some ( file) = file {
505518 if file. borrow ( ) . file_info_ast . borrow ( ) . text_rope . is_none ( ) {
506519 file. borrow_mut ( ) . prepare_ast ( session) ;
507520 }
508521 return file. borrow ( ) . text_range_to_range ( range) ;
509522 }
523+ // For untitled, never try to read from disk
524+ if Self :: is_untitled ( path) {
525+ session. log_message ( MessageType :: ERROR , format ! ( "Untitled file {} not found in memory" , path) ) ;
526+ return Range :: default ( ) ;
527+ }
510528 //file not in cache, let's load rope on the fly
511529 match fs:: read_to_string ( path) {
512530 Ok ( content) => {
@@ -523,13 +541,22 @@ impl FileMgr {
523541
524542
525543 pub fn std_range_to_range ( & self , session : & mut SessionInfo , path : & String , range : & std:: ops:: Range < usize > ) -> Range {
526- let file = self . files . get ( path) ;
544+ let file = if Self :: is_untitled ( path) {
545+ self . untitled_files . get ( path)
546+ } else {
547+ self . files . get ( path)
548+ } ;
527549 if let Some ( file) = file {
528550 if file. borrow ( ) . file_info_ast . borrow ( ) . text_rope . is_none ( ) {
529551 file. borrow_mut ( ) . prepare_ast ( session) ;
530552 }
531553 return file. borrow ( ) . std_range_to_range ( range) ;
532554 }
555+ // For untitled, never try to read from disk
556+ if Self :: is_untitled ( path) {
557+ session. log_message ( MessageType :: ERROR , format ! ( "Untitled file {} not found in memory" , path) ) ;
558+ return Range :: default ( ) ;
559+ }
533560 //file not in cache, let's load rope on the fly
534561 match fs:: read_to_string ( path) {
535562 Ok ( content) => {
@@ -544,8 +571,20 @@ impl FileMgr {
544571 Range :: default ( )
545572 }
546573
574+ /// Returns true if the path/uri is an untitled (in-memory) file.
575+ /// by convention, untitled files start with "untitled:".
576+ pub fn is_untitled ( path : & str ) -> bool {
577+ path. starts_with ( "untitled:" )
578+ }
579+
547580 pub fn update_file_info ( & mut self , session : & mut SessionInfo , uri : & str , content : Option < & Vec < TextDocumentContentChangeEvent > > , version : Option < i32 > , force : bool ) -> ( bool , Rc < RefCell < FileInfo > > ) {
548- let file_info = self . files . entry ( uri. to_string ( ) ) . or_insert_with ( || {
581+ let is_untitled = Self :: is_untitled ( uri) ;
582+ let entry = if is_untitled {
583+ self . untitled_files . entry ( uri. to_string ( ) )
584+ } else {
585+ self . files . entry ( uri. to_string ( ) )
586+ } ;
587+ let file_info = entry. or_insert_with ( || {
549588 let mut file_info = FileInfo :: new ( uri. to_string ( ) ) ;
550589 file_info. update_diagnostic_filters ( session) ;
551590 Rc :: new ( RefCell :: new ( file_info) )
@@ -555,7 +594,7 @@ impl FileMgr {
555594 let mut updated: bool = false ;
556595 if ( version. is_some ( ) && version. unwrap ( ) != -100 ) || !file_info. borrow ( ) . opened || force {
557596 let mut file_info_mut = ( * return_info) . borrow_mut ( ) ;
558- updated = file_info_mut. update ( session, uri, content, version, self . is_in_workspace ( uri) , force) ;
597+ updated = file_info_mut. update ( session, uri, content, version, self . is_in_workspace ( uri) , force, is_untitled ) ;
559598 drop ( file_info_mut) ;
560599 }
561600 ( updated, return_info)
@@ -645,28 +684,35 @@ impl FileMgr {
645684 }
646685
647686 pub fn pathname2uri ( s : & String ) -> lsp_types:: Uri {
648- let mut slash = "" ;
649- if cfg ! ( windows) {
650- slash = "/" ;
651- }
652- // If the path starts with \\\\, we want to remove it and also set slash to empty string
653- // Such that we have file://wsl.localhost/<path> for example
654- // For normal paths we do want file:///C:/...
655- // For some editors like PyCharm they use the legacy windows UNC urls so we have file:////wsl.localhost/<path>
656- let ( replaced, unc) = if s. starts_with ( "\\ \\ " ) {
657- slash = "" ;
658- ( s. replacen ( "\\ \\ " , "" , 1 ) , true )
687+ let pre_uri = if s. starts_with ( "untitled:" ) {
688+ s. clone ( )
659689 } else {
660- ( s. clone ( ) , false )
661- } ;
662- // Use legacy UNC flag to determine if we need four slashes
663- let mut pre_uri = match url:: Url :: parse ( & format ! ( "file://{}{}" , slash, replaced) ) {
664- Ok ( pre_uri) => pre_uri. to_string ( ) ,
665- Err ( err) => panic ! ( "unable to transform pathname to uri: {s}, {}" , err)
690+ let mut slash = "" ;
691+ if cfg ! ( windows) {
692+ slash = "/" ;
693+ }
694+ // If the path starts with \\\\, we want to remove it and also set slash to empty string
695+ // Such that we have file://wsl.localhost/<path> for example
696+ // For normal paths we do want file:///C:/...
697+ // For some editors like PyCharm they use the legacy windows UNC urls so we have file:////wsl.localhost/<path>
698+ let ( replaced, unc) = if s. starts_with ( "\\ \\ " ) {
699+ slash = "" ;
700+ ( s. replacen ( "\\ \\ " , "" , 1 ) , true )
701+ } else {
702+ ( s. clone ( ) , false )
703+ } ;
704+ // Use legacy UNC flag to determine if we need four slashes
705+ match url:: Url :: parse ( & format ! ( "file://{}{}" , slash, replaced) ) {
706+ Ok ( pre_uri) => {
707+ if unc && legacy_unc_paths ( ) . load ( Ordering :: Relaxed ) {
708+ pre_uri. to_string ( ) . replace ( "file://" , "file:////" )
709+ } else {
710+ pre_uri. to_string ( )
711+ }
712+ } ,
713+ Err ( err) => panic ! ( "unable to transform pathname to uri: {s}, {}" , err)
714+ }
666715 } ;
667- if unc && legacy_unc_paths ( ) . load ( Ordering :: Relaxed ) {
668- pre_uri = pre_uri. replace ( "file://" , "file:////" ) ;
669- }
670716 match lsp_types:: Uri :: from_str ( & pre_uri) {
671717 Ok ( url) => url,
672718 Err ( err) => panic ! ( "unable to transform pathname to uri: {s}, {}" , err)
0 commit comments