diff --git a/codespan-lsp/Cargo.toml b/codespan-lsp/Cargo.toml index 85277e9..8d49186 100644 --- a/codespan-lsp/Cargo.toml +++ b/codespan-lsp/Cargo.toml @@ -10,7 +10,7 @@ documentation = "https://docs.rs/codespan-lsp" edition = "2018" [dependencies] -codespan = { version = "0.9.5", path = "../codespan" } +codespan-reporting = { version = "0.9.5", path = "../codespan-reporting" } # WARNING: Be extremely careful when expanding this version range. # We should be confident that all of the uses of `lsp-types` in `codespan-lsp` # will be valid for all the versions in this range. Getting this range wrong @@ -18,3 +18,6 @@ codespan = { version = "0.9.5", path = "../codespan" } # absolute no-no, breaking much of what we enjoy about Cargo! lsp-types = ">=0.70, <0.75" url = "2" + +[dev-dependencies] +codespan = { version = "0.9.5", path = "../codespan" } diff --git a/codespan-lsp/src/lib.rs b/codespan-lsp/src/lib.rs index 05b76fd..8f45f02 100644 --- a/codespan-lsp/src/lib.rs +++ b/codespan-lsp/src/lib.rs @@ -1,47 +1,97 @@ //! Utilities for translating from codespan types into Language Server Protocol (LSP) types -use codespan::{ - ByteIndex, ByteOffset, ColumnIndex, FileId, Files, LineIndex, LineIndexOutOfBoundsError, - LocationError, RawIndex, RawOffset, Span, SpanOutOfBoundsError, -}; +use std::{error, fmt, ops::Range}; + +use codespan_reporting::files::Files; + // WARNING: Be extremely careful when adding new imports here, as it could break // the compatible version range that we claim in our `Cargo.toml`. This could // potentially break down-stream builds on a `cargo update`. This is an // absolute no-no, breaking much of what we enjoy about Cargo! use lsp_types::{Position as LspPosition, Range as LspRange}; -use std::ffi::OsString; -use std::path::PathBuf; -use std::{error, fmt}; #[derive(Debug, PartialEq)] pub enum Error { - UnableToCorrelateFilename(OsString), - ColumnOutOfBounds { - given: ColumnIndex, - max: ColumnIndex, - }, + ColumnOutOfBounds { given: usize, max: usize }, Location(LocationError), LineIndexOutOfBounds(LineIndexOutOfBoundsError), SpanOutOfBounds(SpanOutOfBoundsError), + MissingFile, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Error::UnableToCorrelateFilename(s) => { - let p = PathBuf::from(s); - write!(f, "Unable to correlate filename `{}` to url", p.display()) - } Error::ColumnOutOfBounds { given, max } => { write!(f, "Column out of bounds - given: {}, max: {}", given, max) } Error::Location(e) => e.fmt(f), Error::LineIndexOutOfBounds(e) => e.fmt(f), Error::SpanOutOfBounds(e) => e.fmt(f), + Error::MissingFile => write!(f, "File does not exit"), } } } +#[derive(Debug, PartialEq)] +pub struct LineIndexOutOfBoundsError { + pub given: usize, + pub max: usize, +} + +impl error::Error for LineIndexOutOfBoundsError {} + +impl fmt::Display for LineIndexOutOfBoundsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Line index out of bounds - given: {}, max: {}", + self.given, self.max + ) + } +} + +#[derive(Debug, PartialEq)] +pub enum LocationError { + OutOfBounds { given: usize, span: Range }, + InvalidCharBoundary { given: usize }, +} + +impl error::Error for LocationError {} + +impl fmt::Display for LocationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LocationError::OutOfBounds { given, span } => write!( + f, + "Byte index out of bounds - given: {}, span: {}..{}", + given, span.start, span.end + ), + LocationError::InvalidCharBoundary { given } => { + write!(f, "Byte index within character boundary - given: {}", given) + } + } + } +} + +#[derive(Debug, PartialEq)] +pub struct SpanOutOfBoundsError { + pub given: Range, + pub span: Range, +} + +impl error::Error for SpanOutOfBoundsError {} + +impl fmt::Display for SpanOutOfBoundsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Span out of bounds - given: {}..{}, span: {}..{}", + self.given.start, self.given.end, self.span.start, self.span.end + ) + } +} + impl From for Error { fn from(e: LocationError) -> Error { Error::Location(e) @@ -63,7 +113,7 @@ impl From for Error { impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { - Error::UnableToCorrelateFilename(_) | Error::ColumnOutOfBounds { .. } => None, + Error::ColumnOutOfBounds { .. } | Error::MissingFile => None, Error::Location(error) => Some(error), Error::LineIndexOutOfBounds(error) => Some(error), Error::SpanOutOfBounds(error) => Some(error), @@ -73,61 +123,82 @@ impl error::Error for Error { fn location_to_position( line_str: &str, - line: LineIndex, - column: ColumnIndex, - byte_index: ByteIndex, + line: usize, + column: usize, + byte_index: usize, ) -> Result { - if column.to_usize() > line_str.len() { - let max = ColumnIndex(line_str.len() as RawIndex); + if column > line_str.len() { + let max = line_str.len(); let given = column; Err(Error::ColumnOutOfBounds { given, max }) - } else if !line_str.is_char_boundary(column.to_usize()) { + } else if !line_str.is_char_boundary(column) { let given = byte_index; Err(LocationError::InvalidCharBoundary { given }.into()) } else { - let line_utf16 = line_str[..column.to_usize()].encode_utf16(); + let line_utf16 = line_str[..column].encode_utf16(); let character = line_utf16.count() as u64; - let line = line.to_usize() as u64; + let line = line as u64; Ok(LspPosition { line, character }) } } -pub fn byte_index_to_position>( - files: &Files, - file_id: FileId, - byte_index: ByteIndex, -) -> Result { - let location = files.location(file_id, byte_index)?; - let line_span = files.line_span(file_id, location.line)?; - let line_str = files.source_slice(file_id, line_span)?; - let column = ColumnIndex::from((byte_index - line_span.start()).0 as RawIndex); +pub fn byte_index_to_position<'a, F>( + files: &'a F, + file_id: F::FileId, + byte_index: usize, +) -> Result +where + F: Files<'a> + ?Sized, +{ + let source = files.source(file_id).ok_or_else(|| Error::MissingFile)?; + let source = source.as_ref(); - location_to_position(line_str, location.line, column, byte_index) + let line_index = + files + .line_index(file_id, byte_index) + .ok_or_else(|| LineIndexOutOfBoundsError { + given: byte_index, + max: source.lines().count(), + })?; + let line_span = files.line_range(file_id, line_index).unwrap(); + + let line_str = source + .get(line_span.clone()) + .ok_or_else(|| SpanOutOfBoundsError { + given: line_span.clone(), + span: 0..source.len(), + })?; + let column = byte_index - line_span.start; + + location_to_position(line_str, line_index, column, byte_index) } -pub fn byte_span_to_range>( - files: &Files, - file_id: FileId, - span: Span, -) -> Result { +pub fn byte_span_to_range<'a, F>( + files: &'a F, + file_id: F::FileId, + span: Range, +) -> Result +where + F: Files<'a> + ?Sized, +{ Ok(LspRange { - start: byte_index_to_position(files, file_id, span.start())?, - end: byte_index_to_position(files, file_id, span.end())?, + start: byte_index_to_position(files, file_id, span.start)?, + end: byte_index_to_position(files, file_id, span.end)?, }) } -pub fn character_to_line_offset(line: &str, character: u64) -> Result { - let line_len = ByteOffset::from(line.len() as RawOffset); +pub fn character_to_line_offset(line: &str, character: u64) -> Result { + let line_len = line.len(); let mut character_offset = 0; let mut chars = line.chars(); while let Some(ch) = chars.next() { if character_offset == character { - let chars_off = ByteOffset::from_str_len(chars.as_str()); - let ch_off = ByteOffset::from_char_len(ch); + let chars_off = chars.as_str().len(); + let ch_off = ch.len_utf8(); return Ok(line_len - chars_off - ch_off); } @@ -140,38 +211,45 @@ pub fn character_to_line_offset(line: &str, character: u64) -> Result>( - files: &Files, - file_id: FileId, +pub fn position_to_byte_index<'a, F>( + files: &'a F, + file_id: F::FileId, position: &LspPosition, -) -> Result { - let line_span = files.line_span(file_id, position.line as RawIndex)?; - let source = files.source_slice(file_id, line_span)?; +) -> Result +where + F: Files<'a> + ?Sized, +{ + let source = files.source(file_id).ok_or_else(|| Error::MissingFile)?; + let source = source.as_ref(); + + let line_span = files.line_range(file_id, position.line as usize).unwrap(); + let byte_offset = character_to_line_offset(source, position.character)?; - Ok(line_span.start() + byte_offset) + Ok(line_span.start + byte_offset) } -pub fn range_to_byte_span>( - files: &Files, - file_id: FileId, +pub fn range_to_byte_span<'a, F>( + files: &'a F, + file_id: F::FileId, range: &LspRange, -) -> Result { - Ok(Span::new( - position_to_byte_index(files, file_id, &range.start)?, - position_to_byte_index(files, file_id, &range.end)?, - )) +) -> Result, Error> +where + F: Files<'a> + ?Sized, +{ + Ok(position_to_byte_index(files, file_id, &range.start)? + ..position_to_byte_index(files, file_id, &range.end)?) } #[cfg(test)] mod tests { - use codespan::Location; + use codespan::{Files, Location}; use super::*; @@ -192,7 +270,7 @@ test character: 2, }, ) - .unwrap(); + .unwrap() as u32; assert_eq!(Location::new(3, 2), files.location(file_id, pos).unwrap()); } @@ -213,7 +291,7 @@ test character: 3, }, ); - assert_eq!(result, Ok(ByteIndex::from(5))); + assert_eq!(result, Ok(5)); let result = position_to_byte_index( &files, @@ -223,7 +301,7 @@ test character: 6, }, ); - assert_eq!(result, Ok(ByteIndex::from(10))); + assert_eq!(result, Ok(10)); } #[test] @@ -231,7 +309,7 @@ test let mut files = Files::new(); let file_id = files.add("unicode", UNICODE); - let result = byte_index_to_position(&files, file_id, ByteIndex::from(5)); + let result = byte_index_to_position(&files, file_id, 5); assert_eq!( result, Ok(LspPosition { @@ -240,7 +318,7 @@ test }) ); - let result = byte_index_to_position(&files, file_id, ByteIndex::from(10)); + let result = byte_index_to_position(&files, file_id, 10); assert_eq!( result, Ok(LspPosition {