summaryrefslogtreecommitdiff
path: root/src/source.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-08-10 11:28:12 +0200
committerLaurenz <laurmaedje@gmail.com>2021-08-10 11:28:12 +0200
commit8207c31aec6336b773fbf4661fdb87625c8b584e (patch)
treec1642033478081bec6c3eed693e92a469f0500e1 /src/source.rs
parent3932bb2cb93be95d67fc56998423eb9ce047fdfa (diff)
Minor refactorings
- Reorder parser methods and use `Pos` everywhere - Remove tab special handling for columns and adapt heading/list/enum indent handling - Don't panic when a file has an empty path
Diffstat (limited to 'src/source.rs')
-rw-r--r--src/source.rs114
1 files changed, 61 insertions, 53 deletions
diff --git a/src/source.rs b/src/source.rs
index 20ba137f..0a5a2860 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -63,7 +63,7 @@ impl SourceStore {
io::Error::new(io::ErrorKind::InvalidData, "file is not valid utf-8")
})?;
- Ok(self.insert(Some(hash), path, src))
+ Ok(self.insert(path, src, Some(hash)))
}
/// Directly provide a source file.
@@ -82,16 +82,16 @@ impl SourceStore {
id
} else {
// Not loaded yet.
- self.insert(Some(hash), path, src)
+ self.insert(path, src, Some(hash))
}
} else {
// Not known to the loader.
- self.insert(None, path, src)
+ self.insert(path, src, None)
}
}
/// Insert a new source file.
- fn insert(&mut self, hash: Option<FileHash>, path: &Path, src: String) -> SourceId {
+ fn insert(&mut self, path: &Path, src: String, hash: Option<FileHash>) -> SourceId {
let id = SourceId(self.sources.len() as u32);
if let Some(hash) = hash {
self.files.insert(hash, id);
@@ -112,6 +112,9 @@ impl SourceStore {
}
/// A single source file.
+///
+/// _Note_: All line and column indices start at zero, just like byte indices.
+/// Only for user-facing display, you should add 1 to them.
pub struct SourceFile {
id: SourceId,
path: PathBuf,
@@ -120,7 +123,8 @@ pub struct SourceFile {
}
impl SourceFile {
- fn new(id: SourceId, path: &Path, src: String) -> Self {
+ /// Create a new source file.
+ pub fn new(id: SourceId, path: &Path, src: String) -> Self {
let mut line_starts = vec![Pos::ZERO];
let mut s = Scanner::new(&src);
@@ -151,7 +155,7 @@ impl SourceFile {
self.id
}
- /// The path to the source file.
+ /// The normalized path to the source file.
pub fn path(&self) -> &Path {
&self.path
}
@@ -161,6 +165,11 @@ impl SourceFile {
&self.src
}
+ /// Slice out the part of the source code enclosed by the span.
+ pub fn get(&self, span: impl Into<Span>) -> Option<&str> {
+ self.src.get(span.into().to_range())
+ }
+
/// Get the length of the file in bytes.
pub fn len_bytes(&self) -> usize {
self.src.len()
@@ -171,11 +180,6 @@ impl SourceFile {
self.line_starts.len()
}
- /// Slice out the part of the source code enclosed by the span.
- pub fn get(&self, span: Span) -> Option<&str> {
- self.src.get(span.to_range())
- }
-
/// Return the index of the line that contains the given byte position.
pub fn pos_to_line(&self, byte_pos: Pos) -> Option<usize> {
(byte_pos.to_usize() <= self.src.len()).then(|| {
@@ -186,14 +190,15 @@ impl SourceFile {
})
}
- /// Return the column of the byte index.
+ /// Return the index of the column at the byte index.
///
- /// Tabs are counted as occupying two columns.
+ /// The column is defined as the number of characters in the line before the
+ /// byte position.
pub fn pos_to_column(&self, byte_pos: Pos) -> Option<usize> {
let line = self.pos_to_line(byte_pos)?;
let start = self.line_to_pos(line)?;
let head = self.get(Span::new(start, byte_pos))?;
- Some(head.chars().map(width).sum())
+ Some(head.chars().count())
}
/// Return the byte position at which the given line starts.
@@ -210,32 +215,19 @@ impl SourceFile {
/// Return the byte position of the given (line, column) pair.
///
- /// Tabs are counted as occupying two columns.
+ /// The column defines the number of characters to go beyond the start of
+ /// the line.
pub fn line_column_to_pos(&self, line_idx: usize, column_idx: usize) -> Option<Pos> {
let span = self.line_to_span(line_idx)?;
let line = self.get(span)?;
-
- if column_idx == 0 {
- return Some(span.start);
- }
-
- let mut column = 0;
- for (i, c) in line.char_indices() {
- column += width(c);
- if column >= column_idx {
- return Some(span.start + Pos::from(i + c.len_utf8()));
- }
+ let mut chars = line.chars();
+ for _ in 0 .. column_idx {
+ chars.next();
}
-
- None
+ Some(span.start + (line.len() - chars.as_str().len()))
}
}
-/// The display width of the character.
-fn width(c: char) -> usize {
- if c == '\t' { 2 } else { 1 }
-}
-
impl AsRef<str> for SourceFile {
fn as_ref(&self) -> &str {
&self.src
@@ -256,14 +248,34 @@ impl<'a> Files<'a> for SourceStore {
Ok(self.get(id))
}
- fn line_index(
+ fn line_index(&'a self, id: SourceId, given: usize) -> Result<usize, files::Error> {
+ let source = self.get(id);
+ source
+ .pos_to_line(given.into())
+ .ok_or_else(|| files::Error::IndexTooLarge { given, max: source.len_bytes() })
+ }
+
+ fn line_range(
&'a self,
id: SourceId,
- byte_index: usize,
+ given: usize,
+ ) -> Result<std::ops::Range<usize>, files::Error> {
+ let source = self.get(id);
+ source
+ .line_to_span(given)
+ .map(Span::to_range)
+ .ok_or_else(|| files::Error::LineTooLarge { given, max: source.len_lines() })
+ }
+
+ fn column_number(
+ &'a self,
+ id: SourceId,
+ _: usize,
+ given: usize,
) -> Result<usize, files::Error> {
let source = self.get(id);
- source.pos_to_line(byte_index.into()).ok_or_else(|| {
- let (given, max) = (byte_index, source.len_bytes());
+ source.pos_to_column(given.into()).ok_or_else(|| {
+ let max = source.len_bytes();
if given <= max {
files::Error::InvalidCharBoundary { given }
} else {
@@ -271,28 +283,13 @@ impl<'a> Files<'a> for SourceStore {
}
})
}
-
- fn line_range(
- &'a self,
- id: SourceId,
- line_index: usize,
- ) -> Result<std::ops::Range<usize>, files::Error> {
- let source = self.get(id);
- match source.line_to_span(line_index) {
- Some(span) => Ok(span.to_range()),
- None => Err(files::Error::LineTooLarge {
- given: line_index,
- max: source.len_lines(),
- }),
- }
- }
}
#[cfg(test)]
mod tests {
use super::*;
- const TEST: &str = "äbcde\nf💛g\r\nhi\rjkl";
+ const TEST: &str = "ä\tcde\nf💛g\r\nhi\rjkl";
#[test]
fn test_source_file_new() {
@@ -314,6 +311,17 @@ mod tests {
}
#[test]
+ fn test_source_file_pos_to_column() {
+ let source = SourceFile::detached(TEST);
+ assert_eq!(source.pos_to_column(Pos(0)), Some(0));
+ assert_eq!(source.pos_to_column(Pos(2)), Some(1));
+ assert_eq!(source.pos_to_column(Pos(6)), Some(5));
+ assert_eq!(source.pos_to_column(Pos(7)), Some(0));
+ assert_eq!(source.pos_to_column(Pos(8)), Some(1));
+ assert_eq!(source.pos_to_column(Pos(12)), Some(2));
+ }
+
+ #[test]
fn test_source_file_roundtrip() {
#[track_caller]
fn roundtrip(source: &SourceFile, byte_pos: Pos) {