diff --git a/lib/src/sql/error/mod.rs b/lib/src/sql/error/mod.rs index 67a25c21..ca7773a8 100644 --- a/lib/src/sql/error/mod.rs +++ b/lib/src/sql/error/mod.rs @@ -137,32 +137,35 @@ impl ParseError { #[derive(Clone, Copy, Debug)] pub struct Location { pub line: usize, + /// In chars. pub column: usize, } impl Location { - /// Returns the location of a substring in the larger string. - pub fn of_in(substr: &str, s: &str) -> Self { - let offset = s - .len() - .checked_sub(substr.len()) + /// Returns the location of the start of substring in the larger input string. + /// + /// Assumption: substr must be a subslice of input. + pub fn of_in(substr: &str, input: &str) -> Self { + // Bytes of input before substr. + let offset = (substr.as_ptr() as usize) + .checked_sub(input.as_ptr() as usize) .expect("tried to find location of substring in unrelated string"); - let lines = s.split('\n').enumerate(); - let mut total = 0; - for (idx, line) in lines { + // Bytes of input prior to line being iteratated. + let mut bytes_prior = 0; + for (line_idx, line) in input.split('\n').enumerate() { // +1 for the '\n' - let new_total = total + line.len() + 1; - if new_total > offset { + let bytes_so_far = bytes_prior + line.len() + 1; + if bytes_so_far > offset { // found line. - let line_offset = offset - total; + let line_offset = offset - bytes_prior; let column = line[..line_offset].chars().count(); // +1 because line and column are 1 index. return Self { - line: idx + 1, + line: line_idx + 1, column: column + 1, }; } - total = new_total; + bytes_prior = bytes_so_far; } unreachable!() } diff --git a/lib/src/sql/error/render.rs b/lib/src/sql/error/render.rs index 253af87b..ec6fcf3f 100644 --- a/lib/src/sql/error/render.rs +++ b/lib/src/sql/error/render.rs @@ -40,7 +40,7 @@ pub struct Snippet { truncation: Truncation, /// The location of the snippet in the orignal source code. location: Location, - /// The offset into the snippet where the location is. + /// The offset, in chars, into the snippet where the location is. offset: usize, /// A possible explanation for this snippet. explain: Option, @@ -58,7 +58,7 @@ impl Snippet { explain: Option<&'static str>, ) -> Self { let line = source.split('\n').nth(location.line - 1).unwrap(); - let (line, truncation, offset) = Self::truncate_line(line, location.column); + let (line, truncation, offset) = Self::truncate_line(line, location.column - 1); Snippet { source: line.to_owned(), @@ -71,9 +71,9 @@ impl Snippet { /// Trims whitespace of an line and additionally truncates a string if it is too long. fn truncate_line(mut line: &str, around_offset: usize) -> (&str, Truncation, usize) { - let full_line_length = line.len(); + let full_line_length = line.chars().count(); line = line.trim_start(); - let mut offset = around_offset - (full_line_length - line.len()); + let mut offset = around_offset - (full_line_length - line.chars().count()); line = line.trim_end(); let mut truncation = Truncation::None;