Skip to content

Commit

Permalink
feat: starting to implement pretty json output
Browse files Browse the repository at this point in the history
  • Loading branch information
wllfaria committed Apr 24, 2024
1 parent 0a1bbba commit 996a26a
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 12 deletions.
61 changes: 61 additions & 0 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions reqtui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ serde.workspace = true
tokio.workspace = true
reqwest.workspace = true
serde_json.workspace = true

tree-sitter = "0.22.5"
tree-sitter-json = "0.21"

[build-dependencies]
cc="*"
1 change: 1 addition & 0 deletions reqtui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod command;
pub mod fs;
pub mod net;
pub mod schema;
pub mod syntax;
1 change: 1 addition & 0 deletions reqtui/src/syntax.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod highlighter;
49 changes: 49 additions & 0 deletions reqtui/src/syntax/highlighter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use tree_sitter::{Parser, Query, QueryCursor};

pub struct Highlighter {
parser: Parser,
query: Query,
}

#[derive(Debug)]
pub struct ColorInfo {
pub start: usize,
pub end: usize,
}

impl Default for Highlighter {
fn default() -> Self {
let mut parser = Parser::new();
let json_language = include_str!("queries/json/highlights.scm");
let query = Query::new(&tree_sitter_json::language(), json_language)
.expect("failed to load json query");

parser
.set_language(&tree_sitter_json::language())
.expect("error loading json grammar");

Highlighter { parser, query }
}
}

impl Highlighter {
pub fn apply(&mut self, buffer: &str) -> Vec<ColorInfo> {
let tree = self.parser.parse(buffer, None).unwrap();

let mut colors = Vec::new();
let mut cursor = QueryCursor::new();
let matches = cursor.matches(&self.query, tree.root_node(), buffer.as_bytes());

for m in matches {
for cap in m.captures {
let node = cap.node;
let start = node.start_byte();
let end = node.end_byte();
let _capture_name = self.query.capture_names()[cap.index as usize];
colors.push(ColorInfo { start, end });
}
}

colors
}
}
38 changes: 38 additions & 0 deletions reqtui/src/syntax/queries/json/highlights.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
(true)
(false)
] @boolean

(null) @constant.builtin

(number) @number

(pair
key: (string) @property)

(pair
value: (string) @string)

(array
(string) @string)

[
","
":"
] @punctuation.delimiter

[
"["
"]"
"{"
"}"
] @punctuation.bracket

("\"" @conceal
(#set! conceal ""))

(escape_sequence) @string.escape

((escape_sequence) @conceal
(#eq? @conceal "\\\"")
(#set! conceal "\""))
3 changes: 2 additions & 1 deletion tui/src/components/api_explorer/api_explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ impl<'a> ApiExplorer<'a> {
.and_then(|selected| self.responses_map.get(selected));

let mut state = ResViewerState::new(
self.focused_pane == PaneFocus::Preview,
self.focused_pane.eq(&PaneFocus::Preview),
self.selected_pane
.as_ref()
.map(|sel| sel.eq(&PaneFocus::Preview))
Expand All @@ -219,6 +219,7 @@ impl<'a> ApiExplorer<'a> {
fn drain_response_rx(&mut self) {
while let Ok(ReqtuiNetRequest::Response(res)) = self.response_rx.try_recv() {
if let Some(ref req) = self.selected_request {
tracing::debug!("{req:?}");
self.responses_map.insert(req.clone(), res);
}
}
Expand Down
73 changes: 62 additions & 11 deletions tui/src/components/api_explorer/res_viewer.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
use reqtui::net::request_manager::ReqtuiResponse;
use reqtui::{net::request_manager::ReqtuiResponse, syntax::highlighter::Highlighter};

use ratatui::{
buffer::Buffer,
layout::{Constraint, Direction, Layout, Rect},
style::Style,
style::{Style, Stylize},
text::Line,
widgets::{Block, Borders, Paragraph, StatefulWidget, Tabs, Widget, Wrap},
widgets::{
Block, Borders, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget,
Tabs, Widget,
},
};
use std::{
iter,
ops::{Add, Deref},
};
use std::ops::{Add, Sub};

pub struct ResViewerState<'a> {
is_focused: bool,
Expand Down Expand Up @@ -107,7 +113,7 @@ impl<'a> ResViewer<'a> {

fn draw_preview_tab(&self, state: &mut ResViewerState, buf: &mut Buffer, size: Rect) {
match state.curr_tab {
ResViewerTabs::Preview => {}
ResViewerTabs::Preview => self.draw_preview_response(state, buf, size),
ResViewerTabs::Raw => self.draw_raw_response(state, buf, size),
ResViewerTabs::Cookies => {}
ResViewerTabs::Headers => {}
Expand All @@ -120,22 +126,54 @@ impl<'a> ResViewer<'a> {
.body
.chars()
.collect::<Vec<_>>()
.chunks(size.width.into())
// accounting for the scrollbar width when splitting the lines
.chunks(size.width.saturating_sub(2).into())
.map(|row| Line::from(row.iter().collect::<String>()))
.collect::<Vec<_>>();

if state.raw_scroll >= &mut lines.len().sub(2) {
*state.raw_scroll = lines.len().sub(2);
// allow for scrolling down until theres only one line left into view
if state.raw_scroll.deref().ge(&lines.len().saturating_sub(1)) {
*state.raw_scroll = lines.len().saturating_sub(1);
}

let lines = lines
let [request_pane, scrollbar_pane] = build_preview_layout(size);

self.draw_scrollbar(lines.len(), *state.raw_scroll, buf, scrollbar_pane);

let lines_in_view = lines
.into_iter()
.skip(*state.raw_scroll)
.chain(iter::repeat(Line::from("~".fg(self.colors.normal.magenta))))
.take(size.height.into())
.collect::<Vec<_>>();

let raw_response = Paragraph::new(lines).wrap(Wrap { trim: true });
raw_response.render(size, buf);
let raw_response = Paragraph::new(lines_in_view);
raw_response.render(request_pane, buf);
}
}

fn draw_scrollbar(
&self,
total_ines: usize,
current_scroll: usize,
buf: &mut Buffer,
size: Rect,
) {
let mut scrollbar_state = ScrollbarState::new(total_ines).position(current_scroll);

let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
.style(Style::default().fg(self.colors.normal.magenta.into()))
.begin_symbol(Some("↑"))
.end_symbol(Some("↓"));

scrollbar.render(size, buf, &mut scrollbar_state);
}

fn draw_preview_response(&self, state: &mut ResViewerState, _buf: &mut Buffer, _size: Rect) {
if let Some(ref response) = state.response {
let mut high = Highlighter::default();
let res = high.apply(&response.body);
tracing::debug!("{res:?}")
}
}
}
Expand Down Expand Up @@ -174,3 +212,16 @@ fn build_layout(size: Rect) -> ResViewerLayout {
content_pane,
}
}

fn build_preview_layout(size: Rect) -> [Rect; 2] {
let [request_pane, _, scrollbar_pane] = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Fill(1),
Constraint::Length(1),
Constraint::Length(1),
])
.areas(size);

[request_pane, scrollbar_pane]
}

0 comments on commit 996a26a

Please sign in to comment.