use std::fmt; use std::fs::{metadata, Metadata}; use std::path::{Path, PathBuf}; use erg_common::normalize_path; use erg_common::traits::{DequeStream, Locational}; use erg_compiler::erg_parser::token::{Token, TokenStream}; use lsp_types::{Position, Range, Url}; use crate::server::ELSResult; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct NormalizedUrl(Url); impl fmt::Display for NormalizedUrl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl std::ops::Deref for NormalizedUrl { type Target = Url; fn deref(&self) -> &Self::Target { &self.0 } } impl AsRef for NormalizedUrl { fn as_ref(&self) -> &Url { &self.0 } } impl TryFrom<&Path> for NormalizedUrl { type Error = Box; fn try_from(path: &Path) -> Result { let path = path.to_str().unwrap(); NormalizedUrl::parse(path) } } impl NormalizedUrl { pub fn new(url: Url) -> NormalizedUrl { Self(Url::parse(&url.as_str().replace("c%3A", "C:").to_lowercase()).unwrap()) } pub fn parse(uri: &str) -> ELSResult { Ok(NormalizedUrl(Url::parse( &uri.replace("c%3A", "C:").to_lowercase(), )?)) } pub fn raw(self) -> Url { self.0 } } pub(crate) fn loc_to_range(loc: erg_common::error::Location) -> Option { let start = Position::new(loc.ln_begin()?.saturating_sub(1), loc.col_begin()?); let end = Position::new(loc.ln_end()?.saturating_sub(1), loc.col_end()?); Some(Range::new(start, end)) } pub(crate) fn loc_to_pos(loc: erg_common::error::Location) -> Option { // FIXME: should `Position::new(loc.ln_begin()? - 1, loc.col_begin()?)` // but completion doesn't work (because the newline will be included) let start = Position::new(loc.ln_begin()?.saturating_sub(1), loc.col_begin()? + 1); Some(start) } pub fn _pos_to_loc(pos: Position) -> erg_common::error::Location { erg_common::error::Location::range( pos.line + 1, pos.character.saturating_sub(1), pos.line + 1, pos.character, ) } pub(crate) fn pos_in_loc(loc: &L, pos: Position) -> bool { let ln_begin = loc.ln_begin().unwrap_or(0); let ln_end = loc.ln_end().unwrap_or(0); let in_lines = (ln_begin..=ln_end).contains(&(pos.line + 1)); if ln_begin == ln_end { in_lines && (loc.col_begin().unwrap_or(0)..loc.col_end().unwrap_or(0)).contains(&pos.character) } else { in_lines } } pub(crate) fn pos_to_byte_index(src: &str, pos: Position) -> usize { if src.is_empty() { return 0; } let mut line = 0; let mut col = 0; for (index, c) in src.char_indices() { if line == pos.line && col == pos.character { return index; } if c == '\n' { line += 1; col = 0; } else { col += 1; } } // EOF src.char_indices().last().unwrap().0 + 1 } pub(crate) fn get_token_from_stream( stream: &TokenStream, pos: Position, ) -> ELSResult> { for token in stream.iter() { if pos_in_loc(token, pos) { return Ok(Some(token.clone())); } } Ok(None) } pub(crate) fn get_metadata_from_uri(uri: &Url) -> ELSResult { let path = uri.to_file_path().unwrap(); Ok(metadata(path)?) } pub(crate) fn uri_to_path(uri: &NormalizedUrl) -> PathBuf { normalize_path( uri.to_file_path() .unwrap_or_else(|_| denormalize(uri.clone().raw()).to_file_path().unwrap()), ) } pub(crate) fn denormalize(uri: Url) -> Url { Url::parse(&uri.as_str().replace("c:", "file:///c%3A")).unwrap() }