fe_common/
files.rs

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// NOTE: all file paths are stored as utf8 strings.
8//  Non-utf8 paths (for user code) should be reported
9//  as an error.
10//  If include_dir paths aren't utf8, we panic and fix
11//  our stdlib/test-file path names.
12
13#[derive(Debug, PartialEq, Eq, Hash, Clone)]
14pub struct File {
15    /// Differentiates between local source files and fe std lib
16    /// files, which may have the same path (for salsa's sake).
17    pub kind: FileKind,
18
19    /// Path of the file. May include `src/` dir or longer prefix;
20    /// this prefix will be stored in the `Ingot::src_path`, and stripped
21    /// off as needed.
22    pub path: Rc<Utf8PathBuf>,
23}
24
25#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
26pub enum FileKind {
27    /// User file; either part of the target project or an imported ingot
28    Local,
29    /// File is part of the fe standard library
30    Std,
31}
32
33/// Returns the common *prefix* of two paths. If the paths are identical,
34/// returns the path parent.
35pub 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// from rust-analyzer {
44#[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// } from rust-analyzer
58
59// TODO: rename to FileId
60#[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        // Used by unit tests and benchmarks
111        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}