Skip to content

Commit 1c533e4

Browse files
committed
[IMP] handle untitled files
1 parent 572c29d commit 1c533e4

File tree

3 files changed

+466
-143
lines changed

3 files changed

+466
-143
lines changed

server/src/core/entry_point.rs

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub struct EntryPointMgr {
1414
pub main_entry_point: Option<Rc<RefCell<EntryPoint>>>,
1515
pub addons_entry_points: Vec<Rc<RefCell<EntryPoint>>>,
1616
pub custom_entry_points: Vec<Rc<RefCell<EntryPoint>>>,
17+
pub untitled_entry_points: Vec<Rc<RefCell<EntryPoint>>>,
1718
}
1819

1920
impl EntryPointMgr {
@@ -25,8 +26,27 @@ impl EntryPointMgr {
2526
main_entry_point: None,
2627
addons_entry_points: vec![],
2728
custom_entry_points: vec![],
29+
untitled_entry_points: vec![],
2830
}
2931
}
32+
/// Create a new entry for an untitled (in-memory) file.
33+
/// Returns the file symbol for the untitled entry.
34+
pub fn add_entry_to_untitled(session: &mut SessionInfo, path: String) -> Rc<RefCell<Symbol>> {
35+
// For untitled files, we use a minimal tree: just the name as a single OYarn
36+
let tree = vec![OYarn::from(path.clone())];
37+
let entry = EntryPoint::new(
38+
path.clone(),
39+
tree,
40+
EntryPointType::UNTITLED,
41+
None,
42+
None,
43+
);
44+
session.sync_odoo.entry_point_mgr.borrow_mut().untitled_entry_points.push(entry.clone());
45+
// Create one file symbol under the root for the untitled file
46+
let name: String = PathBuf::from(&path).with_extension("").components().last().unwrap().as_os_str().to_str().unwrap().to_string();
47+
let file_sym = entry.borrow().root.borrow_mut().add_new_file(session, &name, &path);
48+
file_sym.clone()
49+
}
3050

3151
/**
3252
* Create each required directory symbols for a given path.
@@ -193,6 +213,13 @@ impl EntryPointMgr {
193213
true
194214
}
195215

216+
pub fn create_new_untitled_entry_for_path(session: &mut SessionInfo, file_name: &String) -> bool {
217+
let new_sym = EntryPointMgr::add_entry_to_untitled(session, file_name.clone());
218+
new_sym.borrow_mut().as_file_mut().self_import = true;
219+
SyncOdoo::add_to_rebuild_arch(session.sync_odoo, new_sym);
220+
true
221+
}
222+
196223
pub fn iter_for_import(&self, current_entry: &Rc<RefCell<EntryPoint>>) -> Box<dyn Iterator<Item = &Rc<RefCell<EntryPoint>>> + '_> {
197224
let mut is_main = false;
198225
for entry in self.iter_main() {
@@ -214,12 +241,12 @@ impl EntryPointMgr {
214241
}
215242

216243
pub fn iter_all(&self) -> impl Iterator<Item = &Rc<RefCell<EntryPoint>>> {
217-
self.addons_entry_points.iter().chain(
218-
self.main_entry_point.iter()).chain(
219-
self.builtins_entry_points.iter()).chain(
220-
self.public_entry_points.iter()).chain(
221-
self.custom_entry_points.iter()
222-
)
244+
self.addons_entry_points.iter()
245+
.chain(self.main_entry_point.iter())
246+
.chain(self.builtins_entry_points.iter())
247+
.chain(self.public_entry_points.iter())
248+
.chain(self.custom_entry_points.iter())
249+
.chain(self.untitled_entry_points.iter())
223250
}
224251

225252
//iter through all main entry points, sorted by tree length (from bigger to smaller)
@@ -256,6 +283,15 @@ impl EntryPointMgr {
256283
self.clean_entries();
257284
}
258285

286+
pub fn remove_untitled_entries_with_path(&mut self, path: &String) {
287+
for entry in self.untitled_entry_points.iter() {
288+
if &entry.borrow().path.clone() == path {
289+
entry.borrow_mut().to_delete = true;
290+
}
291+
}
292+
self.clean_entries();
293+
}
294+
259295
pub fn check_custom_entry_to_delete_with_path(&mut self, path: &String) {
260296
for entry in self.custom_entry_points.iter() {
261297
if entry.borrow().path == *path {
@@ -292,7 +328,7 @@ impl EntryPointMgr {
292328
entry_index += 1;
293329
}
294330
}
295-
let mut entry_index = 0;
331+
entry_index = 0;
296332
while entry_index < self.custom_entry_points.len() {
297333
let entry = self.custom_entry_points[entry_index].clone();
298334
if entry.borrow().to_delete {
@@ -302,6 +338,16 @@ impl EntryPointMgr {
302338
entry_index += 1;
303339
}
304340
}
341+
entry_index = 0;
342+
while entry_index < self.untitled_entry_points.len() {
343+
let entry = self.untitled_entry_points[entry_index].clone();
344+
if entry.borrow().to_delete {
345+
info!("Dropping untitled entry point {}", entry.borrow().path);
346+
self.untitled_entry_points.remove(entry_index);
347+
} else {
348+
entry_index += 1;
349+
}
350+
}
305351
}
306352

307353
/// Transform the path of an addon to the odoo relative path.
@@ -323,7 +369,8 @@ pub enum EntryPointType {
323369
BUILTIN,
324370
PUBLIC,
325371
ADDON,
326-
CUSTOM
372+
CUSTOM,
373+
UNTITLED,
327374
}
328375

329376
#[derive(Debug, Clone)]
@@ -359,6 +406,9 @@ impl EntryPoint {
359406
}
360407

361408
pub fn is_valid_for(&self, path: &PathBuf) -> bool {
409+
if self.typ == EntryPointType::UNTITLED {
410+
return false;
411+
}
362412
path.starts_with(&self.path)
363413
}
364414

server/src/core/file_mgr.rs

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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)]
449452
pub 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

Comments
 (0)