-
Notifications
You must be signed in to change notification settings - Fork 2
dot-viewer in TUI #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1639a6c
68f647b
b413422
9229cd5
bd7b4cc
8e0eb97
1790601
5d5a780
e37d51f
6573659
f8d6e64
53c9e0d
90568a1
d1e7866
811cda8
dd16ba8
c0f403c
b9aaec5
fddec68
a4aa701
4320aae
7bb88fe
77c8658
0868c1a
d3633e0
2b61020
9ab5fba
d353a93
55a67ef
1cb9504
d9a1309
6f8569a
eda6c44
a5a1730
84f40f3
359ec08
0d6bb54
fd11195
65e02cd
e0bdd44
e5ae467
a142ff8
8f900c9
3052cab
cdc2ca0
ffc5470
6f76cf9
87819e1
76f1f1e
227966e
5ca25b7
a8a0065
668ceb4
699a28e
1718e87
c8abaf4
f61f958
1ecedcb
1ce68ee
5f0dc0b
bb30473
dc42c7e
eba9d64
6350c5c
32239e5
adfa0c6
dabf4a8
28be06a
d57611d
644928e
754b76b
0db6fbc
e216232
08418c9
3f57f7c
4c4054c
5159ccd
1711476
079151e
171f012
774950d
a6ed878
50b7d14
0397467
c8df475
0ec4ece
5ae54eb
0f82419
af8d802
b28d3f2
a7a6898
455f947
9a88f90
afbacd2
79c2134
0270702
ad745c8
8e24d6b
56a5cec
8a09a6c
c4123ea
0c58110
555f99a
bb28baa
212fd7f
2e82d17
e7b6117
ec91f1e
c978ab1
88e4831
87e3fad
ffcd8f0
1c0a797
bffc593
d3d4d02
7e70882
3ef8c33
1d5b3a2
74d030b
a10fa75
ff367cf
8237cfc
1a29844
07eea5a
4c8949c
be930c1
821d93d
ae1f0e6
cebf5ec
78198ce
7e8b894
b149710
c21d611
5d73098
34882e3
385e242
8b8f8ad
2e78c7f
69a1aad
406e692
60184fb
5c61e93
9158323
f9ed0fb
d9f0321
3575aa7
54ca904
922594d
7a35a04
cc724d2
7c8f109
ceae0ce
8c43fac
c44fe6c
b12c27c
5b2e634
63d2570
fe6ac15
8493a5e
0015bc5
67f318a
792b969
2fe7d79
9a5dd8a
34573fa
8e96d4a
962a5a5
5b3122e
c4c86f0
c57a43e
3e8dcfb
a43d885
5f37437
834b18d
b55afe3
72ce24f
75e6729
720fc94
66c0977
dc78a03
29031e8
2a26a29
595d1b8
bc14c5e
e90d6f7
66a7ac3
2b7dd13
20e1436
86f31bb
ce7bae6
9bbcd6d
98e3a24
1c44fbd
fa43a97
0878cbb
adfb375
1f18e78
e2abbc0
42d1f21
11a6e5f
6feb76a
4cb4ef4
2f2d2a9
22c8ffb
45a3af5
b6e1183
209f293
0772b4d
1858ab7
62abc0d
dd8f8ff
610ad87
f1dad60
e95ca8d
8891075
22512be
27f16a1
a372127
328c542
b4f9869
160a87d
df08507
0a6af95
5b61c04
4f9c224
468b71e
f4b4a5c
d7c8c48
fe1ef41
1f42289
ff77711
e876fab
00c244d
22a3634
384a545
a886c92
1fe4285
3022aae
103a188
3a79dd0
c608ccb
6a7841a
c0f8c43
b644832
cd3c7e7
3555ecd
b77bb46
4578f30
7676830
92faa12
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| [target.aarch-apple-darwin] | ||
| rustflags = ["-C", "link-args=-L/opt/homebrew/lib"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| Cargo.lock | ||
| /logs | ||
| /target | ||
| .DS_STORE | ||
| *.dot | ||
| *.log |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| [submodule "dot-graph"] | ||
| path = dot-graph | ||
| url = https://github.com/furiosa-ai/dot-graph.git | ||
| branch = main |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| [package] | ||
| name = "dot-viewer" | ||
| version = "0.1.0" | ||
| authors = ["FuriosaAI, Inc."] | ||
| description = "A viewer/debugger for large DAGs in Vim-like TUI" | ||
| readme = "README.md" | ||
| keywords = ["graph", "dag", "dot", "visualize", "tui"] | ||
| license = "MIT" | ||
| repository = "https://github.com/furiosa-ai/dot-viewer" | ||
| edition = "2021" | ||
|
|
||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
|
||
| [dependencies] | ||
| dot-graph = { path = "dot-graph" } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 일단 dot-graph 빌드시에는 graphviz를 찾을 수 없다고 떠서 이 부분은 따로 얘기하도록 하겠습니다. 제가 실행한 환경(apple m1)에서는 빌드가 되지 않았늗데 이는 homebrew로 설치된 library path를 linker한테 따로 지정하지 않아 생긴 문제였습니다. [target.aarch64-apple-darwin]
rustflags=["-C", "link-args=-L/opt/homebrew/lib"]위의 설정의 뜻은 apple arm으로 빌드 진행할 시 linker한테(여기서는 ld 기준) /opt/homebrew/lib 도 library search path 목록에 추가하라는 뜻입니다.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
나머지 플랫폼에 대해서는 차후에 CI 구축과 함께 진행하겠습니다 :) |
||
| tui = "0.19.0" | ||
| tui-tree-widget = "0.11.0" | ||
| crossterm = "0.25" | ||
| clap = { version = "4.1.1", features = ["derive"] } | ||
| trie-rs = "0.1.1" | ||
| fuzzy-matcher = "0.3.7" | ||
| html_parser = "0.6.3" | ||
| thiserror = "1.0.38" | ||
| regex = "1.7.1" | ||
| rayon = "1.6.1" | ||
| better-panic = "0.3.0" | ||
| log = "0.4.17" | ||
| simplelog = "0.12.0" | ||
| chrono = "0.4.23" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| # dot-viewer | ||
|
|
||
| `dot-viewer` is a dot-format graph debugger in TUI, inspired by Vim. | ||
|
|
||
| # 1. Getting Started | ||
|
|
||
| ## a. Prerequisites | ||
|
|
||
| ### i. Graphviz | ||
|
|
||
| `dot-viewer` parses a dot format file using C bindings to [Graphviz (v7.0.6)](https://gitlab.com/graphviz/graphviz/-/tree/7.0.6/lib). | ||
|
|
||
| The system environment should be able to find and include the following header files. | ||
|
|
||
| ```C | ||
| #include <gvc.h> | ||
| #include <cgraph.h> | ||
| ``` | ||
|
|
||
| #### Option 1. Installing Graphviz from Package Manager | ||
|
|
||
| Coming from Linux, | ||
| ```console | ||
| $ sudo apt install graphviz-dev | ||
| ``` | ||
|
|
||
| And coming from vanilla Ubuntu, you may want to install these too. | ||
| ```console | ||
| $ sudo apt install build-essentials cmake | ||
| $ sudo apt install clang | ||
| ``` | ||
|
|
||
| Coming from Mac, | ||
| ```console | ||
| $ brew install graphviz | ||
| ``` | ||
|
|
||
| And coming from Apple Silicon Mac, and [add an environment variable](https://apple.stackexchange.com/questions/414622/installing-a-c-c-library-with-homebrew-on-m1-macs), | ||
| ```shell | ||
| export CPATH=/opt/homebrew/include | ||
| ``` | ||
|
|
||
| #### Option 2. Building Graphviz from Source | ||
|
|
||
| Or, try building from the source code following the [guide](https://graphviz.org/download/source/). | ||
|
|
||
| ### ii. xdot.py | ||
|
|
||
| `dot-viewer` renders a subgraph with `xdot.py`, an interactive dot visualizer. | ||
|
|
||
| It is required that [xdot is executable in command-line](https://github.com/jrfonseca/xdot.py) beforehand such that the following works. | ||
| ```console | ||
| $ xdot *.dot | ||
| ``` | ||
|
|
||
| ## b. Installation | ||
|
|
||
| ### i. Initialize | ||
|
|
||
| First initialize and update the submodule `dot-graph`. | ||
|
|
||
| ```console | ||
| $ git submodule init | ||
| $ git submodule update | ||
| ``` | ||
|
|
||
| ### ii. Run | ||
|
|
||
| Then run crate. | ||
|
|
||
| ```console | ||
| $ cargo run --release [path-to-dot-file] | ||
| ``` | ||
|
|
||
| This will open a TUI screen on the terminal. | ||
|
|
||
| # 2. Features | ||
|
|
||
| With `dot-viewer`, users may | ||
|
|
||
| **traverse the graph in TUI** using, | ||
| - goto next/prev node of the currently selected node | ||
| - fuzzy search on node name | ||
| - regex search on node name and attributes | ||
|
|
||
|
|
||
| **make and export subgraphs** using, | ||
| - subgraph tree selection | ||
| - applying filter on search matches | ||
| - neighboring `n` nodes of the currently selected node | ||
|
|
||
| ## Keybindings | ||
|
|
||
| ### General | ||
|
|
||
| Key | Command | Actions | ||
| --- | --- | --- | ||
| `q` | | quit `dot-viewer` | ||
| | `:help<CR>` | show help | ||
| `esc` | | go back to the main screen | ||
|
|
||
| ### Mode Switches | ||
|
|
||
| Key | From | To | ||
| --- | --- | --- | ||
| `esc` | All | Normal | ||
| `/` | Normal | Fuzzy Search | ||
| `r` | Normal | Regex Search | ||
| `:` | Normal | Command | ||
|
|
||
| ### Normal | ||
|
|
||
| Key | Actions | ||
| --- | --- | ||
| `c` | close the current tab(view) | ||
| `h/l` | move focus between current, prevs, nexts list | ||
| `j/k` | traverse in focused list | ||
| `n/N` | move between matched nodes | ||
| `gg` | move to the topmost node in focused list | ||
| `G` | move to the bottom node in focused list | ||
| `tab`/`backtab` | move between tabs | ||
|
|
||
| ### Search | ||
| Key | Actions | ||
| --- | --- | ||
| `tab` | autocomplete search keyword | ||
| `enter` | apply search | ||
|
|
||
| e.g., in fuzzy search mode, `/g1_s14_t100` and in regex search mode, `r\(H: ., D: .\)` | ||
|
|
||
| ### Command | ||
|
|
||
| Key | Command | Actions | ||
| --- | --- | --- | ||
| | `filter` | apply filter on current matches, opening a new tab(view) | ||
| | `neighbors [depth]` | get up to `depth` neighbors of the current node in a new tab(view) | ||
| | `export [(opt) filename]` | export the current tab(view) to dot | ||
| | `xdot [(opt) filename]` | launch `xdot` with the filename or `exports/current.dot` by default | ||
| | `subgraph` | open a popup showing subgraph tree | ||
| `tab` | | autocomplete command | ||
| `enter` | | execute command | ||
|
|
||
| All exported files are saved in `exports` directory in the project root. | ||
|
|
||
| Most recently exported file is copied in `exports/current.dot`. | ||
|
|
||
| ### Subgraph Popup | ||
|
|
||
| Key | Actions | ||
| --- | --- | ||
| `h/j/k/l` | traverse the tree | ||
| `enter` | change root to the selected subgraph, opening a new tab(view) | ||
|
|
||
| ### Help Popup | ||
|
|
||
| Key | Actions | ||
| --- | --- | ||
| `h/j/k/l` | traverse help messages |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| edition = "2021" | ||
|
|
||
| # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#use_small_heuristics | ||
| use_small_heuristics = "Max" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| mod terminal; | ||
| mod ui; | ||
| mod viewer; | ||
|
|
||
| use std::error::Error; | ||
| use std::fs; | ||
|
|
||
| use chrono::prelude::*; | ||
| use clap::Parser; | ||
| use simplelog::{Config, LevelFilter, WriteLogger}; | ||
|
|
||
| use terminal::launch; | ||
|
|
||
| #[derive(Parser, Default, Debug)] | ||
| struct Cli { | ||
| path: String, | ||
| } | ||
|
|
||
| fn main() -> Result<(), Box<dyn Error>> { | ||
| let args = Cli::parse(); | ||
|
|
||
| fs::create_dir_all("./logs")?; | ||
| let file = fs::File::create(format!("logs/log_{}.log", Local::now()))?; | ||
| WriteLogger::init(LevelFilter::Info, Config::default(), file)?; | ||
|
|
||
| launch(args.path)?; | ||
|
|
||
| Ok(()) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| use crate::{ui, viewer::App}; | ||
|
|
||
| use std::io::Stdout; | ||
| use std::{error::Error, io}; | ||
|
|
||
| use crossterm::{ | ||
jaehyun1ee-furiosa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| event::{self, DisableMouseCapture, EnableMouseCapture, Event}, | ||
| execute, | ||
| terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, | ||
| }; | ||
| use log::error; | ||
| use tui::{ | ||
| backend::{Backend, CrosstermBackend}, | ||
| Terminal, | ||
| }; | ||
|
|
||
| pub fn launch(path: String) -> Result<(), Box<dyn Error>> { | ||
| // setup terminal | ||
| let mut terminal = setup()?; | ||
|
|
||
| // create and run app | ||
| let app = App::new(&path).map_err(|_| { | ||
| let _ = cleanup(); | ||
|
|
||
| Box::<dyn Error>::from("user should provide path to a valid dot file") | ||
| })?; | ||
| let _ = run(&mut terminal, app); | ||
|
|
||
| // restore terminal | ||
| cleanup()?; | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| fn setup() -> Result<Terminal<CrosstermBackend<Stdout>>, Box<dyn Error>> { | ||
| enable_raw_mode()?; | ||
| let mut stdout = io::stdout(); | ||
| execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; | ||
| let backend = CrosstermBackend::new(stdout); | ||
| let terminal = Terminal::new(backend)?; | ||
|
|
||
| // setup panic hook to ensure that the terminal is restored even on panics | ||
| setup_panic_hook(); | ||
|
|
||
| Ok(terminal) | ||
| } | ||
|
|
||
| fn setup_panic_hook() { | ||
| let panic_handler = better_panic::Settings::auto().create_panic_handler(); | ||
| std::panic::set_hook(Box::new(move |info| { | ||
| let _ = cleanup(); | ||
|
|
||
| error!("dot-viewer {}", info); | ||
|
|
||
| panic_handler(info); | ||
| })); | ||
| } | ||
|
|
||
| fn run<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> { | ||
| loop { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분은 thread이용해서 좀 더 안정적인 형태로 만들 수 있지 않을까 생각이 드네요. |
||
| terminal.draw(|f| ui::draw_app(f, &mut app))?; | ||
|
|
||
| if let Event::Key(key) = event::read()? { | ||
| app.key(key); | ||
| } | ||
|
|
||
| if app.quit { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| fn cleanup() -> Result<(), Box<dyn Error>> { | ||
| let mut stdout = io::stdout(); | ||
| execute!(stdout, LeaveAlternateScreen, DisableMouseCapture)?; | ||
| disable_raw_mode()?; | ||
|
|
||
| Ok(()) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| use crate::ui::{input::draw_input, popup::draw_popup, tabs::draw_tabs}; | ||
| use crate::viewer::{App, Mode}; | ||
|
|
||
| use tui::{ | ||
| backend::Backend, | ||
| layout::{Alignment, Constraint, Direction, Layout, Rect}, | ||
| widgets::{Block, BorderType, Borders}, | ||
| Frame, | ||
| }; | ||
|
|
||
| pub(crate) fn draw_app<B: Backend>(f: &mut Frame<B>, app: &mut App) { | ||
| let size = f.size(); | ||
|
|
||
| let block = Block::default() | ||
| .borders(Borders::ALL) | ||
| .title("Dot-Viewer (v0.1.0)") | ||
| .title_alignment(Alignment::Center) | ||
| .border_type(BorderType::Rounded); | ||
|
|
||
| f.render_widget(block, size); | ||
|
|
||
| match &app.mode { | ||
| Mode::Normal | Mode::Command | Mode::Search(_) => draw_main(f, size, app), | ||
| Mode::Popup(_) => draw_popup(f, size, app), | ||
| } | ||
| } | ||
|
|
||
| fn draw_main<B: Backend>(f: &mut Frame<B>, size: Rect, app: &mut App) { | ||
| let chunks = Layout::default() | ||
| .direction(Direction::Vertical) | ||
| .constraints([Constraint::Percentage(90), Constraint::Percentage(10)].as_ref()) | ||
| .split(size); | ||
|
|
||
| draw_tabs(f, chunks[0], app); | ||
| draw_input(f, chunks[1], app); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.