1use crate::db::SourceDb;
2pub use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
3pub use fe_library::include_dir;
4use std::ops::Range;
5use std::rc::Rc;
6
7#[derive(Debug, PartialEq, Eq, Hash, Clone)]
14pub struct File {
15 pub kind: FileKind,
18
19 pub path: Rc<Utf8PathBuf>,
23}
24
25#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
26pub enum FileKind {
27 Local,
29 Std,
31}
32
33pub fn common_prefix(left: &Utf8Path, right: &Utf8Path) -> Utf8PathBuf {
36 left.components()
37 .zip(right.components())
38 .take_while(|(l, r)| l == r)
39 .map(|(l, _)| l)
40 .collect()
41}
42
43#[macro_export]
45macro_rules! impl_intern_key {
46 ($name:ident) => {
47 impl salsa::InternKey for $name {
48 fn from_intern_id(v: salsa::InternId) -> Self {
49 $name(v.as_u32())
50 }
51 fn as_intern_id(&self) -> salsa::InternId {
52 salsa::InternId::from(self.0)
53 }
54 }
55 };
56}
57#[derive(Debug, serde::Deserialize, PartialEq, Eq, Hash, Copy, Clone)]
61pub struct SourceFileId(pub(crate) u32);
62impl_intern_key!(SourceFileId);
63
64impl SourceFileId {
65 pub fn new_local(db: &mut dyn SourceDb, path: &str, content: Rc<str>) -> Self {
66 Self::new(db, FileKind::Std, path, content)
67 }
68
69 pub fn new_std(db: &mut dyn SourceDb, path: &str, content: Rc<str>) -> Self {
70 Self::new(db, FileKind::Std, path, content)
71 }
72
73 pub fn new(db: &mut dyn SourceDb, kind: FileKind, path: &str, content: Rc<str>) -> Self {
74 let id = db.intern_file(File {
75 kind,
76 path: Rc::new(path.into()),
77 });
78 db.set_file_content(id, content);
79 id
80 }
81
82 pub fn path(&self, db: &dyn SourceDb) -> Rc<Utf8PathBuf> {
83 db.lookup_intern_file(*self).path
84 }
85
86 pub fn content(&self, db: &dyn SourceDb) -> Rc<str> {
87 db.file_content(*self)
88 }
89
90 pub fn line_index(&self, db: &dyn SourceDb, byte_index: usize) -> usize {
91 db.file_line_starts(*self)
92 .binary_search(&byte_index)
93 .unwrap_or_else(|next_line| next_line - 1)
94 }
95
96 pub fn line_range(&self, db: &dyn SourceDb, line_index: usize) -> Option<Range<usize>> {
97 let line_starts = db.file_line_starts(*self);
98 let end = if line_index == line_starts.len() - 1 {
99 self.content(db).len()
100 } else {
101 *line_starts.get(line_index + 1)?
102 };
103 Some(Range {
104 start: *line_starts.get(line_index)?,
105 end,
106 })
107 }
108
109 pub fn dummy_file() -> Self {
110 Self(u32::MAX)
112 }
113 pub fn is_dummy(self) -> bool {
114 self == Self::dummy_file()
115 }
116}
117
118#[test]
119fn test_common_prefix() {
120 assert_eq!(
121 common_prefix(Utf8Path::new("a/b/c/d/e"), Utf8Path::new("a/b/d/e")),
122 Utf8Path::new("a/b")
123 );
124 assert_eq!(
125 common_prefix(Utf8Path::new("src/foo.x"), Utf8Path::new("tests/bar.fe")),
126 Utf8Path::new("")
127 );
128 assert_eq!(
129 common_prefix(Utf8Path::new("/src/foo.x"), Utf8Path::new("src/bar.fe")),
130 Utf8Path::new("")
131 );
132}