use std::path::PathBuf;
use std::{fs::read_dir, path::Path};
use eframe::egui::{CollapsingHeader, Ui};
use egui::{emath, Color32, Pos2, Rect, Stroke, Vec2};
use lang::{CQuery, JavaQuery, JsQuery, RustQuery, SymbolQuery};
use tree_sitter::Node;
use tree_sitter::Parser;
use uuid::Uuid;
pub mod lang;
#[derive(Clone, PartialEq)]
pub enum TreeEvent {
Clicked(String),
None,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TreeType {
File,
Directory,
}
#[derive(Clone, Default, Debug)]
pub struct Tree {
pub label: String,
full_path: String,
select_path: String,
children: Vec<Tree>,
tree_type: Option<TreeType>,
clicked: bool,
}
impl Tree {
pub fn new(name: &str, full_path: &str, tree_type: TreeType) -> Self {
Self {
label: name.to_owned(),
full_path: full_path.to_owned(),
children: vec![],
tree_type: Some(tree_type),
clicked: false,
select_path: "".to_owned(),
}
}
pub fn ui(&mut self, ui: &mut Ui) -> TreeEvent {
let root_name = self.label.clone();
self.ui_impl(ui, 0, root_name.as_str())
}
}
impl Tree {
fn ui_impl(&mut self, ui: &mut Ui, depth: usize, name: &str) -> TreeEvent {
let tree_type = self.tree_type.clone().unwrap_or(TreeType::File);
if self.children.len() > 0 || tree_type == TreeType::Directory {
return CollapsingHeader::new(name)
.default_open(depth < 1)
.show(ui, |ui| self.children_ui(ui, depth))
.body_returned
.unwrap_or(TreeEvent::None);
} else {
let full_path = self.full_path.clone();
if ui
.selectable_value(&mut self.select_path, full_path, name)
.clicked()
{
return TreeEvent::Clicked(self.full_path.to_string());
}
return TreeEvent::None;
}
}
pub fn clicked(&self) -> bool {
if self.clicked {
return true;
} else if self.children.len() > 0 {
for child in &self.children {
if child.clicked() {
return true;
}
}
}
return false;
}
fn children_ui(&mut self, ui: &mut Ui, depth: usize) -> TreeEvent {
for ele in &mut self.children {
let name = ele.label.clone();
let event = ele.ui_impl(ui, depth + 1, &name);
if let TreeEvent::Clicked(_) = event {
return event;
}
}
TreeEvent::None
}
}
pub fn recursion_dir(root_path: &Path, pathes: &mut Vec<PathBuf>, mut root_tree: Tree) -> Tree {
if root_path.is_dir() {
for entry in read_dir(root_path).expect("Error read Dir") {
let dir_entry = entry.expect("Error");
let path_buf = dir_entry.path();
let is_dir = path_buf.is_dir();
let tree_type = if is_dir {
TreeType::Directory
} else {
TreeType::File
};
let mut tree = Tree::new(
path_buf.file_name().unwrap().to_str().unwrap(),
path_buf.as_os_str().to_str().unwrap(),
tree_type,
);
if path_buf.is_dir() {
tree = recursion_dir(path_buf.as_path(), pathes, tree);
} else if path_buf.is_file() {
pathes.push(path_buf);
}
root_tree.children.push(tree);
}
}
return root_tree;
}
#[derive(Debug, Clone)]
pub enum CodeBlockType {
FUNCTION,
METHOD,
STRUCT,
IMPL,
CLASS,
CONST,
NORMAL,
CALL,
}
#[derive(Debug, Clone)]
pub struct CodeNode {
id: String,
pub label: String,
pub block: String,
pub file_location: usize,
pub file_path: String,
level: usize,
block_type: CodeBlockType,
position: Pos2,
}
impl Default for CodeNode {
fn default() -> Self {
Self {
block_type: CodeBlockType::NORMAL,
id: "".to_owned(),
label: "".to_owned(),
block: "".to_owned(),
file_location: 0,
level: 0,
file_path: "".to_owned(),
position: Pos2::ZERO,
}
}
}
impl CodeNode {
pub fn new(
id: &str,
label: &str,
block: &str,
file_location: usize,
block_type: CodeBlockType,
level: usize,
) -> Self {
Self {
id: id.to_owned(),
label: label.to_owned(),
block: block.to_owned(),
file_location: file_location.to_owned(),
file_path: "".to_owned(),
block_type,
position: Pos2::new(0.0, 0.0),
level,
}
}
}
#[derive(Clone, Copy)]
pub struct CodeNodeIndex(usize);
pub struct Edge {
from: usize,
to: usize,
}
pub struct Graph {
nodes: Vec<CodeNode>,
edges: Vec<Edge>,
focus_node: Option<CodeNodeIndex>,
}
impl Graph {
pub fn new() -> Self {
Self {
nodes: vec![],
edges: vec![],
focus_node: None,
}
}
pub fn get_focus_idx(&mut self) -> Option<CodeNodeIndex> {
return self.focus_node;
}
pub fn add_node(&mut self, node: CodeNode) -> CodeNodeIndex {
let index = self.nodes.len();
self.nodes.push(node);
return CodeNodeIndex(index);
}
pub fn add_edge(&mut self, from: CodeNodeIndex, to: CodeNodeIndex) {
self.edges.push(Edge {
from: from.0,
to: to.0,
})
}
pub fn clear(&mut self) {
self.nodes.clear();
self.edges.clear();
self.focus_node = None;
}
pub fn layout(&mut self, ui: &mut Ui) {
let (_, painter) = ui.allocate_painter(ui.available_size(), egui::Sense::click());
let mut sum_height = 0.0;
for (index, node) in self.nodes.iter_mut().enumerate() {
let text_size = painter
.layout_no_wrap(
node.label.clone(),
egui::FontId::default(),
egui::Color32::WHITE,
)
.size();
node.position = Pos2::new(
ui.available_size().x / 2.0 + node.level as f32 * 20.0,
index as f32 * 16.0 + sum_height + 32.0,
);
sum_height += text_size.y;
}
}
pub fn ui(&mut self, ui: &mut Ui) -> egui::Response {
let (response, painter) =
ui.allocate_painter(ui.available_size(), egui::Sense::click_and_drag());
let rect = ui.max_rect();
let cell_size = 10.0; let color = Color32::from_gray(220); let stroke = Stroke::new(0.5, color); let mut x = rect.left();
while x <= rect.right() {
let line = [Pos2::new(x, rect.top()), Pos2::new(x, rect.bottom())];
painter.line_segment(line, stroke);
x += cell_size;
}
let mut y = rect.top();
while y <= rect.bottom() {
let line = [Pos2::new(rect.left(), y), Pos2::new(rect.right(), y)];
painter.line_segment(line, stroke);
y += cell_size;
}
let to_screen = emath::RectTransform::from_to(
Rect::from_min_size(Pos2::ZERO, response.rect.size()),
response.rect,
);
let mut node_size_list = vec![];
for (index, node) in self.nodes.iter_mut().enumerate() {
let node_pos = to_screen.transform_pos(node.position);
let text_size = painter
.layout_no_wrap(
node.label.clone(),
egui::FontId::default(),
egui::Color32::WHITE,
)
.size();
node_size_list.push(text_size + Vec2::new(16.0, 8.0));
let rect = egui::Rect::from_min_size(
node_pos,
egui::vec2(text_size.x + 16.0, text_size.y + 8.0),
);
let fill_color = match node.block_type {
CodeBlockType::NORMAL => egui::Color32::LIGHT_GRAY,
CodeBlockType::FUNCTION => egui::Color32::LIGHT_BLUE,
CodeBlockType::STRUCT | CodeBlockType::CONST => egui::Color32::LIGHT_YELLOW,
CodeBlockType::CLASS => egui::Color32::LIGHT_GREEN,
_ => egui::Color32::LIGHT_GRAY,
};
painter.rect(
rect,
5.0,
fill_color,
Stroke::new(1.0, egui::Color32::DARK_GRAY),
);
painter.text(
node_pos + Vec2::new(8.0, 4.0),
egui::Align2::LEFT_TOP,
&node.label,
egui::FontId::default(),
egui::Color32::DARK_GRAY,
);
let point_id = response.id.with(&node.id);
let node_response = ui.interact(rect, point_id, egui::Sense::click_and_drag());
if node_response.dragged() {
node.position += node_response.drag_delta();
}
if node_response.clicked() {
self.focus_node = Some(CodeNodeIndex(index));
}
if let Some(f_node) = self.focus_node {
if f_node.0 == index {
painter.rect(
rect,
5.0,
egui::Color32::TRANSPARENT,
Stroke::new(2.5, egui::Color32::BLUE),
);
}
}
if response.dragged() {
node.position += response.drag_delta();
}
}
for edge in &self.edges {
let from = to_screen.transform_pos(self.nodes[edge.from].position)
+ Vec2::new(0.0, node_size_list[edge.from].y / 2.0);
let to = to_screen.transform_pos(self.nodes[edge.to].position)
+ Vec2::new(0.0, node_size_list[edge.to].y / 2.0);
painter.line_segment(
[from, from + Vec2::new(-10.0, 0.0)],
(1.0, egui::Color32::GRAY),
);
painter.line_segment(
[from + Vec2::new(-10.0, 0.0), Pos2::new(from.x - 10.0, to.y)],
(1.0, egui::Color32::GRAY),
);
painter.line_segment(
[Pos2::new(from.x - 10.0, to.y), to],
(1.0, egui::Color32::GRAY),
);
}
response
}
pub fn get_node(&mut self, index: CodeNodeIndex) -> CodeNode {
let default_node = CodeNode::default();
let node = self.nodes.get(index.0).unwrap_or(&default_node);
return node.clone();
}
pub fn node_index(&mut self, node_id: &str) -> CodeNodeIndex {
for (index, node) in self.nodes.iter().enumerate() {
if node.id == node_id {
return CodeNodeIndex(index);
}
}
CodeNodeIndex(0)
}
}
pub fn valid_file_extention(extension: &str) -> bool {
return vec!["rs", "c", "h", "java", "js", "jsx"].contains(&extension);
}
pub fn get_symbol_query(extention: &str) -> Box<dyn SymbolQuery> {
match extention {
"rs" => Box::new(RustQuery),
"java" => Box::new(JavaQuery),
"c" | "h" => Box::new(CQuery),
"js" | "jsx" => Box::new(JsQuery),
_ => Box::new(RustQuery),
}
}
pub fn fetch_calls(path: &str, code: &str, symbol_query: Box<dyn SymbolQuery>) -> Vec<CodeNode> {
let mut parser = Parser::new();
parser
.set_language(&symbol_query.get_lang())
.expect("Error load Rust grammer");
let tree = parser.parse(code, None).unwrap();
let root_node = tree.root_node();
recursion_call(root_node, path, code, &symbol_query)
}
pub fn recursion_call(
node: Node,
path: &str,
code: &str,
symbol_query: &Box<dyn SymbolQuery>,
) -> Vec<CodeNode> {
let mut nodes = vec![];
let code_node = symbol_query.get_call(code, &node);
if let Some(mut node) = code_node {
node.file_path = path.to_string();
nodes.push(node);
}
for child in node.children(&mut node.walk()) {
let sub_nodes = recursion_call(child, path, code, symbol_query);
if sub_nodes.len() > 0 {
for sub_node in sub_nodes {
nodes.push(sub_node);
}
}
}
return nodes;
}
pub fn fetch_symbols(
path: &str,
code: &str,
symbol_query: Box<dyn SymbolQuery>,
graph: &mut Graph,
) {
let mut parser = Parser::new();
parser
.set_language(&symbol_query.get_lang())
.expect("Error load Rust grammer");
let tree = parser.parse(code, None).unwrap();
let root_node = tree.root_node();
let root_code_node = CodeNode::new(
format!("{}", Uuid::new_v4()).as_str(),
path,
code,
0,
CodeBlockType::NORMAL,
0,
);
graph.add_node(root_code_node);
recursion_outline(
root_node,
CodeNodeIndex(0),
path,
code,
1,
&symbol_query,
graph,
);
}
pub fn recursion_outline(
node: Node,
parent_id: CodeNodeIndex,
path: &str,
code: &str,
level: usize,
symbol_query: &Box<dyn SymbolQuery>,
graph: &mut Graph,
) {
let mut current_id = parent_id;
let code_node = symbol_query.get_definition(code, &node);
let mut level = level;
if let Some(mut node) = code_node {
node.file_path = path.to_string();
node.level = level;
let index = graph.add_node(node);
current_id = index;
graph.add_edge(parent_id, index);
level += 1;
}
for child in node.children(&mut node.walk()) {
recursion_outline(child, current_id, path, code, level, symbol_query, graph)
}
}