use std::{cmp::Ordering, ops::Range, sync::Arc};
use tracing::debug;
use crate::project::file::FileContents;
#[derive(Debug)]
pub struct OriginTable {
table: Vec<Origin>,
}
impl OriginTable {
pub fn new(root: Arc<FileContents>) -> Self {
Self {
table: vec![
Origin {
origin: 0,
span: FileSpan::default(),
file: root.clone(),
reason: OriginReason::Root,
},
Origin {
origin: 0,
span: FileSpan::default(),
file: root.clone(),
reason: OriginReason::Builtin,
},
Origin {
origin: 0,
span: FileSpan::default(),
file: root,
reason: OriginReason::Cli,
},
],
}
}
pub fn get(&self, id: OriginId) -> &Origin {
&self.table[id as usize]
}
pub fn add(&mut self, origin: Origin) -> OriginId {
let id = self.table.len() as u32;
self.table.push(origin);
id
}
pub fn root(&self) -> Arc<FileContents> {
self.get(ROOT_ORIGIN).file.clone()
}
}
pub type OriginId = u32;
pub const ROOT_ORIGIN: OriginId = 0;
pub const BUILTIN_ORIGIN: OriginId = 1;
pub const CLI_ORIGIN: OriginId = 2;
#[derive(Debug)]
pub struct Origin {
pub origin: OriginId,
pub span: FileSpan,
pub file: Arc<FileContents>,
pub reason: OriginReason,
}
#[derive(Debug, Clone, Copy)]
pub enum OriginReason {
Root,
Macro,
MacroInterp,
Include,
Builtin,
Cli,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Span {
pub origin: OriginId,
pub text_span: FileSpan,
}
impl Span {
pub fn cross_origin(l: Pos, r: Pos, ot: &OriginTable) -> Self {
if l.origin != r.origin {
debug!("TODO: unhandled cross-origin span between: {l:?}, {r:?}");
return Self {
origin: l.origin,
text_span: FileSpan::empty_at(l.text_pos),
};
}
let file = &ot.get(l.origin).file;
Self {
origin: l.origin,
text_span: FileSpan::from_range(l.text_pos..r.text_pos, file),
}
}
pub fn empty_at(pos: Pos) -> Self {
Self {
origin: pos.origin,
text_span: FileSpan::empty_at(pos.text_pos),
}
}
pub fn empty_at_start(self) -> Self {
Self {
origin: self.origin,
text_span: self.text_span.empty_at_start(),
}
}
pub fn empty_at_end(self, ot: &OriginTable) -> Self {
Self {
origin: self.origin,
text_span: self.text_span.empty_at_end(&ot.get(self.origin).file),
}
}
pub fn start(&self) -> Pos {
Pos {
origin: self.origin,
text_pos: self.text_span.start,
}
}
pub fn end(&self, ot: &OriginTable) -> Pos {
Pos {
origin: self.origin,
text_pos: self.text_span.end(&ot.get(self.origin).file),
}
}
pub fn contains(&self, pos: &Pos, ot: &OriginTable) -> Option<bool> {
let start = self.start();
let end = self.end(ot);
Some(start.try_cmp(pos)?.is_le() && pos.try_cmp(&end)?.is_le())
}
pub fn parent_span(&self, ot: &OriginTable) -> Option<Self> {
if self.origin == ROOT_ORIGIN {
None
} else {
let origin = ot.get(self.origin);
Some(Span {
origin: origin.origin,
text_span: origin.span,
})
}
}
pub fn to_lsp(&self, ot: &OriginTable) -> lsp_types::Range {
let mut span = *self;
while let Some(parent) = span.parent_span(ot) {
span = parent;
}
span.text_span.to_lsp(&ot.get(span.origin).file)
}
pub fn root_span(&self) -> Option<FileSpan> {
if self.origin != ROOT_ORIGIN {
return None;
}
Some(self.text_span)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Pos {
pub origin: OriginId,
pub text_pos: FilePos,
}
impl Pos {
pub const fn zero() -> Self {
Pos {
origin: ROOT_ORIGIN,
text_pos: FilePos {
line: 0,
col: LspCol(0),
},
}
}
pub fn from_lsp(lsp: lsp_types::Position) -> Self {
Self {
origin: ROOT_ORIGIN,
text_pos: FilePos::from_lsp(lsp),
}
}
pub fn parent_span(&self, ot: &OriginTable) -> Option<Self> {
if self.origin == ROOT_ORIGIN {
None
} else {
let origin = ot.get(self.origin);
Some(Pos {
origin: origin.origin,
text_pos: origin.span.start,
})
}
}
pub fn to_lsp(&self, ot: &OriginTable) -> lsp_types::Position {
let mut pos = *self;
while let Some(parent) = pos.parent_span(ot) {
pos = parent;
}
pos.text_pos.to_lsp()
}
pub fn at(origin: OriginId, line: u32, col: u32) -> Self {
Self {
origin,
text_pos: FilePos {
line,
col: LspCol::from_raw(col),
},
}
}
pub fn try_cmp(&self, other: &Self) -> Option<Ordering> {
if self.origin != other.origin {
None
} else {
Some(self.text_pos.cmp(&other.text_pos))
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct FileSpan {
pub start: FilePos,
pub len: LspCol,
}
impl FileSpan {
pub fn empty_at(start: FilePos) -> Self {
Self {
start,
len: LspCol::from_raw(0),
}
}
pub fn from_range(range: Range<FilePos>, file: &FileContents) -> Self {
let start = range.start;
let len = file.pos_diff(range);
Self { start, len }
}
pub fn to_lsp(&self, file: &FileContents) -> lsp_types::Range {
let start = self.start;
let end = self.end(file);
lsp_types::Range {
start: start.to_lsp(),
end: end.to_lsp(),
}
}
pub fn end(&self, file: &FileContents) -> FilePos {
file.pos_add(self.start, self.len)
}
pub fn empty_at_start(&self) -> Self {
FileSpan {
start: self.start,
len: LspCol::from_raw(0),
}
}
pub fn empty_at_end(&self, file: &FileContents) -> Self {
FileSpan {
start: self.end(file),
len: LspCol::from_raw(0),
}
}
pub fn overlaps(&self, other: &Self, file: &FileContents) -> bool {
let self_end = self.end(file);
let other_end = other.end(file);
self.start < other_end && self_end > other.start
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct FilePos {
pub line: u32,
pub col: LspCol,
}
impl FilePos {
pub fn to_lsp(&self) -> lsp_types::Position {
lsp_types::Position {
line: self.line,
character: self.col.to_lsp(),
}
}
pub fn from_lsp(lsp: lsp_types::Position) -> Self {
Self {
line: lsp.line,
col: LspCol::from_raw(lsp.character),
}
}
pub fn advance(&mut self, char: char) {
match char {
'\r' => unreachable!(),
'\n' => {
self.line += 1;
self.col = LspCol::start();
}
c => self.col.advance(c),
}
}
}
impl PartialOrd for FilePos {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FilePos {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.line.cmp(&other.line).then(self.col.cmp(&other.col))
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct LineColCounter {
pos: FilePos,
post_cr: bool,
}
impl LineColCounter {
pub fn start_at(pos: FilePos) -> Self {
Self {
pos,
post_cr: false,
}
}
pub fn pos(&self, next: Option<char>) -> FilePos {
let mut pos = self.pos;
if self.post_cr && next != Some('\n') {
pos.advance('\n');
}
pos
}
pub fn advance(&mut self, c: char) {
if self.post_cr && c != '\n' {
self.pos.advance('\n');
}
self.post_cr = false;
match c {
'\r' => self.post_cr = true,
c => self.pos.advance(c),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct LspCol(u32);
impl LspCol {
pub fn from_raw(col: u32) -> Self {
LspCol(col)
}
pub fn to_raw(self) -> u32 {
self.0
}
pub fn start() -> Self {
LspCol(0)
}
pub fn advance(&mut self, char: char) {
self.0 += char.len_utf16() as u32;
}
pub fn to_lsp(&self) -> u32 {
self.0
}
}