Skip to content

Commit

Permalink
feat: go to definition
Browse files Browse the repository at this point in the history
  • Loading branch information
viddrobnic committed Jul 28, 2024
1 parent fe85bd2 commit eb33be2
Show file tree
Hide file tree
Showing 10 changed files with 674 additions and 11 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions language_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"

[dependencies]
parser = { path = "../parser", version = "0.1" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
139 changes: 139 additions & 0 deletions language_server/src/analyze/location.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use parser::position::{Position, PositionOrdering, Range};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
#[error("Inserted location isn't after current locations.")]
InvalidRange,
}

/// A single piece of information for a specific
/// location. Could be where the symbols at `location`
/// is defined, references for symbol at `location` or more.
#[derive(Debug, PartialEq, Eq)]
pub struct LocationEntry<T> {
pub location: Range,
pub entry: T,
}

/// Data structure for holding multiple entries of
/// location connected data and enables fast
/// querying.
#[derive(Debug, PartialEq, Eq, Default)]
pub struct LocationData<T>(Vec<LocationEntry<T>>);

impl<T> LocationData<T> {
pub fn new() -> Self {
Self(vec![])
}

/// Add location entry. The way this data structure is built, entry's
/// location must be after all other locations in the data strcture.
pub fn push(&mut self, entry: LocationEntry<T>) -> Result<(), Error> {
// Check that location is valid
if let Some(last) = self.0.last() {
if entry.location.start.cmp_range(&last.location) != PositionOrdering::After {
return Err(Error::InvalidRange);
}
}

self.0.push(entry);
Ok(())
}

// Get the index of entry in which position is located
fn bisect(&self, position: &Position) -> Option<usize> {
let mut start = 0;
let mut end = self.0.len();

loop {
let len = end - start;
if len == 0 {
return None;
}

if len == 1 {
return match position.cmp_range(&self.0[start].location) {
PositionOrdering::Inside => Some(start),
_ => None,
};
}

let middle = start + len / 2;
match position.cmp_range(&self.0[middle].location) {
PositionOrdering::Before => end = middle,
PositionOrdering::Inside => return Some(middle),
PositionOrdering::After => start = middle,
}
}
}

/// Get reference to location entry which contains the given position.
pub fn get(&self, position: &Position) -> Option<&LocationEntry<T>> {
let idx = self.bisect(position)?;
Some(&self.0[idx])
}

/// Get mutable reference to location entry which contains the given position.
pub fn get_mut(&mut self, position: &Position) -> Option<&mut LocationEntry<T>> {
let idx = self.bisect(position)?;
Some(&mut self.0[idx])
}
}

#[cfg(test)]
mod test {
use parser::position::{Position, Range};

use super::{LocationData, LocationEntry};

#[test]
fn bisect() {
let mut data = LocationData::<()>::new();
data.push(LocationEntry {
location: Range {
start: Position::new(2, 2),
end: Position::new(2, 10),
},
entry: (),
})
.unwrap();
data.push(LocationEntry {
location: Range {
start: Position::new(4, 0),
end: Position::new(5, 0),
},
entry: (),
})
.unwrap();
data.push(LocationEntry {
location: Range {
start: Position::new(7, 0),
end: Position::new(9, 3),
},
entry: (),
})
.unwrap();

let tests = [
(Position::new(0, 0), None),
(Position::new(2, 2), Some(0)),
(Position::new(2, 9), Some(0)),
(Position::new(2, 10), None),
(Position::new(3, 5123), None),
(Position::new(4, 0), Some(1)),
(Position::new(4, 42069), Some(1)),
(Position::new(5, 0), None),
(Position::new(8, 0), Some(2)),
(Position::new(8, 123), Some(2)),
(Position::new(7, 0), Some(2)),
(Position::new(9, 2), Some(2)),
(Position::new(9, 3), None),
(Position::new(10, 31), None),
];

for (pos, idx) in tests {
assert_eq!(idx, data.bisect(&pos));
}
}
}
Loading

0 comments on commit eb33be2

Please sign in to comment.