@@ -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 } ;
@@ -448,6 +451,7 @@ impl FileInfo {
448451#[ derive( Debug ) ]
449452pub struct FileMgr {
450453 pub files : HashMap < String , Rc < RefCell < FileInfo > > > ,
454+ untitled_files : HashMap < String , Rc < RefCell < FileInfo > > > , // key: untitled URI or unique name
451455 workspace_folders : HashMap < String , String > ,
452456 has_repeated_workspace_folders : bool ,
453457}
@@ -457,6 +461,7 @@ impl FileMgr {
457461 pub fn new ( ) -> Self {
458462 Self {
459463 files : HashMap :: new ( ) ,
464+ untitled_files : HashMap :: new ( ) ,
460465 workspace_folders : HashMap :: new ( ) ,
461466 has_repeated_workspace_folders : false ,
462467 }
@@ -470,17 +475,30 @@ impl FileMgr {
470475 }
471476
472477 pub fn get_file_info ( & self , path : & String ) -> Option < Rc < RefCell < FileInfo > > > {
473- self . files . get ( path) . cloned ( )
478+ if Self :: is_untitled ( path) {
479+ self . untitled_files . get ( path) . cloned ( )
480+ } else {
481+ self . files . get ( path) . cloned ( )
482+ }
474483 }
475484
476485 pub fn text_range_to_range ( & self , session : & mut SessionInfo , path : & String , range : & TextRange ) -> Range {
477- let file = self . files . get ( path) ;
486+ let file = if Self :: is_untitled ( path) {
487+ self . untitled_files . get ( path)
488+ } else {
489+ self . files . get ( path)
490+ } ;
478491 if let Some ( file) = file {
479492 if file. borrow ( ) . file_info_ast . borrow ( ) . text_rope . is_none ( ) {
480493 file. borrow_mut ( ) . prepare_ast ( session) ;
481494 }
482495 return file. borrow ( ) . text_range_to_range ( range) ;
483496 }
497+ // For untitled, never try to read from disk
498+ if Self :: is_untitled ( path) {
499+ session. log_message ( MessageType :: ERROR , format ! ( "Untitled file {} not found in memory" , path) ) ;
500+ return Range :: default ( ) ;
501+ }
484502 //file not in cache, let's load rope on the fly
485503 match fs:: read_to_string ( path) {
486504 Ok ( content) => {
@@ -497,13 +515,22 @@ impl FileMgr {
497515
498516
499517 pub fn std_range_to_range ( & self , session : & mut SessionInfo , path : & String , range : & std:: ops:: Range < usize > ) -> Range {
500- let file = self . files . get ( path) ;
518+ let file = if Self :: is_untitled ( path) {
519+ self . untitled_files . get ( path)
520+ } else {
521+ self . files . get ( path)
522+ } ;
501523 if let Some ( file) = file {
502524 if file. borrow ( ) . file_info_ast . borrow ( ) . text_rope . is_none ( ) {
503525 file. borrow_mut ( ) . prepare_ast ( session) ;
504526 }
505527 return file. borrow ( ) . std_range_to_range ( range) ;
506528 }
529+ // For untitled, never try to read from disk
530+ if Self :: is_untitled ( path) {
531+ session. log_message ( MessageType :: ERROR , format ! ( "Untitled file {} not found in memory" , path) ) ;
532+ return Range :: default ( ) ;
533+ }
507534 //file not in cache, let's load rope on the fly
508535 match fs:: read_to_string ( path) {
509536 Ok ( content) => {
@@ -518,8 +545,20 @@ impl FileMgr {
518545 Range :: default ( )
519546 }
520547
548+ /// Returns true if the path/uri is an untitled (in-memory) file.
549+ /// by convention, untitled files start with "untitled:".
550+ pub fn is_untitled ( path : & str ) -> bool {
551+ path. starts_with ( "untitled:" )
552+ }
553+
521554 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 > > ) {
522- let file_info = self . files . entry ( uri. to_string ( ) ) . or_insert_with ( || {
555+ let is_untitled = Self :: is_untitled ( uri) ;
556+ let entry = if is_untitled {
557+ self . untitled_files . entry ( uri. to_string ( ) )
558+ } else {
559+ self . files . entry ( uri. to_string ( ) )
560+ } ;
561+ let file_info = entry. or_insert_with ( || {
523562 let mut file_info = FileInfo :: new ( uri. to_string ( ) ) ;
524563 file_info. update_diagnostic_filters ( session) ;
525564 Rc :: new ( RefCell :: new ( file_info) )
@@ -529,7 +568,7 @@ impl FileMgr {
529568 let mut updated: bool = false ;
530569 if ( version. is_some ( ) && version. unwrap ( ) != -100 ) || !file_info. borrow ( ) . opened || force {
531570 let mut file_info_mut = ( * return_info) . borrow_mut ( ) ;
532- updated = file_info_mut. update ( session, uri, content, version, self . is_in_workspace ( uri) , force) ;
571+ updated = file_info_mut. update ( session, uri, content, version, self . is_in_workspace ( uri) , force, is_untitled ) ;
533572 drop ( file_info_mut) ;
534573 }
535574 ( updated, return_info)
@@ -619,28 +658,35 @@ impl FileMgr {
619658 }
620659
621660 pub fn pathname2uri ( s : & String ) -> lsp_types:: Uri {
622- let mut slash = "" ;
623- if cfg ! ( windows) {
624- slash = "/" ;
625- }
626- // If the path starts with \\\\, we want to remove it and also set slash to empty string
627- // Such that we have file://wsl.localhost/<path> for example
628- // For normal paths we do want file:///C:/...
629- // For some editors like PyCharm they use the legacy windows UNC urls so we have file:////wsl.localhost/<path>
630- let ( replaced, unc) = if s. starts_with ( "\\ \\ " ) {
631- slash = "" ;
632- ( s. replacen ( "\\ \\ " , "" , 1 ) , true )
661+ let pre_uri = if s. starts_with ( "untitled:" ) {
662+ s. clone ( )
633663 } else {
634- ( s. clone ( ) , false )
635- } ;
636- // Use legacy UNC flag to determine if we need four slashes
637- let mut pre_uri = match url:: Url :: parse ( & format ! ( "file://{}{}" , slash, replaced) ) {
638- Ok ( pre_uri) => pre_uri. to_string ( ) ,
639- Err ( err) => panic ! ( "unable to transform pathname to uri: {s}, {}" , err)
664+ let mut slash = "" ;
665+ if cfg ! ( windows) {
666+ slash = "/" ;
667+ }
668+ // If the path starts with \\\\, we want to remove it and also set slash to empty string
669+ // Such that we have file://wsl.localhost/<path> for example
670+ // For normal paths we do want file:///C:/...
671+ // For some editors like PyCharm they use the legacy windows UNC urls so we have file:////wsl.localhost/<path>
672+ let ( replaced, unc) = if s. starts_with ( "\\ \\ " ) {
673+ slash = "" ;
674+ ( s. replacen ( "\\ \\ " , "" , 1 ) , true )
675+ } else {
676+ ( s. clone ( ) , false )
677+ } ;
678+ // Use legacy UNC flag to determine if we need four slashes
679+ match url:: Url :: parse ( & format ! ( "file://{}{}" , slash, replaced) ) {
680+ Ok ( pre_uri) => {
681+ if unc && legacy_unc_paths ( ) . load ( Ordering :: Relaxed ) {
682+ pre_uri. to_string ( ) . replace ( "file://" , "file:////" )
683+ } else {
684+ pre_uri. to_string ( )
685+ }
686+ } ,
687+ Err ( err) => panic ! ( "unable to transform pathname to uri: {s}, {}" , err)
688+ }
640689 } ;
641- if unc && legacy_unc_paths ( ) . load ( Ordering :: Relaxed ) {
642- pre_uri = pre_uri. replace ( "file://" , "file:////" ) ;
643- }
644690 match lsp_types:: Uri :: from_str ( & pre_uri) {
645691 Ok ( url) => url,
646692 Err ( err) => panic ! ( "unable to transform pathname to uri: {s}, {}" , err)
0 commit comments