Skip to content

Commit 0b8f02f

Browse files
committed
[IMP] handle untitled files
1 parent c41dca4 commit 0b8f02f

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
};
@@ -474,6 +477,7 @@ impl FileInfo {
474477
#[derive(Debug)]
475478
pub 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

Comments
 (0)