Skip to content

Commit

Permalink
feat: initial naive implementation of syntax highlighting
Browse files Browse the repository at this point in the history
  • Loading branch information
wllfaria committed May 1, 2024
1 parent 996a26a commit 5c37430
Show file tree
Hide file tree
Showing 15 changed files with 406 additions and 296 deletions.
246 changes: 131 additions & 115 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ tokio = { version = "1.37.0", features = ["full"] }
tracing = "0.1.40"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.115"
reqwest = "0.12"
reqwest = { version = "0.12", features = ["json"] }
ratatui = { version = "0.26.1", features = ["all-widgets", "crossterm"] }
1 change: 1 addition & 0 deletions colors/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2021"
tracing.workspace = true
anyhow.workspace = true
crossterm.workspace = true
ratatui.workspace = true
serde.workspace = true

toml = { version = "0.8.12" }
152 changes: 50 additions & 102 deletions colors/src/colors.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,38 @@
use crossterm::style::Color;
use std::collections::HashMap;

#[derive(Debug, Default, PartialEq)]
use ratatui::style::{Color, Style};

#[derive(Debug, PartialEq)]
pub struct Colors {
pub primary: PrimaryColors,
pub normal: NormalColors,
pub bright: BrightColors,
pub tokens: HashMap<String, Style>,
}

impl Default for Colors {
fn default() -> Self {
Colors {
primary: Default::default(),
normal: Default::default(),
bright: Default::default(),
tokens: token_highlight(),
}
}
}

fn token_highlight() -> HashMap<String, Style> {
let mut tokens = HashMap::new();
let colors = NormalColors::default();

tokens.insert("conceal".into(), Style::new().fg(colors.red));
tokens.insert("number".into(), Style::new().fg(colors.blue));
tokens.insert("property".into(), Style::new().fg(colors.green));
tokens.insert("punctuation.bracket".into(), Style::new().fg(colors.yellow));
tokens.insert("punctuation.delimiter".into(), Style::new().fg(colors.cyan));
tokens.insert("string".into(), Style::new().fg(colors.magenta));

tokens
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -42,120 +70,40 @@ pub struct BrightColors {
impl Default for PrimaryColors {
fn default() -> Self {
PrimaryColors {
foreground: Color::Rgb {
r: 0xCE,
g: 0xCE,
b: 0xCE,
},
background: Color::Rgb {
r: 0x0F,
g: 0x14,
b: 0x19,
},
accent: Color::Rgb {
r: 0x12,
g: 0x21,
b: 0x32,
},
hover: Color::Rgb {
r: 0x1A,
g: 0x1F,
b: 0x29,
},
foreground: Color::Rgb(0xCE, 0xCE, 0xCE),
background: Color::Rgb(0x0F, 0x14, 0x19),
accent: Color::Rgb(0x12, 0x21, 0x32),
hover: Color::Rgb(0x1A, 0x1F, 0x29),
}
}
}

impl Default for NormalColors {
fn default() -> Self {
NormalColors {
black: Color::Rgb {
r: 0x03,
g: 0x03,
b: 0x03,
},
red: Color::Rgb {
r: 0xD9,
g: 0x57,
b: 0x57,
},
green: Color::Rgb {
r: 0xAA,
g: 0xd9,
b: 0x4C,
},
yellow: Color::Rgb {
r: 0xE6,
g: 0xB4,
b: 0x50,
},
blue: Color::Rgb {
r: 0x59,
g: 0xBA,
b: 0xE6,
},
magenta: Color::Rgb {
r: 0x6C,
g: 0x59,
b: 0x80,
},
cyan: Color::Rgb {
r: 0x95,
g: 0xE6,
b: 0xCB,
},
white: Color::Rgb {
r: 0xBF,
g: 0xBD,
b: 0xB6,
},
black: Color::Rgb(0x03, 0x03, 0x03),
red: Color::Rgb(0xD9, 0x57, 0x57),
green: Color::Rgb(0xAA, 0xd9, 0x4C),
yellow: Color::Rgb(0xE6, 0xB4, 0x50),
blue: Color::Rgb(0x59, 0xBA, 0xE6),
magenta: Color::Rgb(0x6C, 0x59, 0x80),
cyan: Color::Rgb(0x95, 0xE6, 0xCB),
white: Color::Rgb(0xBF, 0xBD, 0xB6),
}
}
}

impl Default for BrightColors {
fn default() -> Self {
BrightColors {
black: Color::Rgb {
r: 0x11,
g: 0x15,
b: 0x1C,
},
red: Color::Rgb {
r: 0xFB,
g: 0x73,
b: 0x73,
},
green: Color::Rgb {
r: 0x7F,
g: 0xD9,
b: 0x4C,
},
yellow: Color::Rgb {
r: 0xE6,
g: 0xB6,
b: 0x73,
},
blue: Color::Rgb {
r: 0x73,
g: 0xB8,
b: 0xFF,
},
magenta: Color::Rgb {
r: 0xD2,
g: 0xA6,
b: 0xFF,
},
cyan: Color::Rgb {
r: 0x95,
g: 0xE6,
b: 0xCB,
},
white: Color::Rgb {
r: 0xFC,
g: 0xFC,
b: 0xFC,
},
black: Color::Rgb(0x11, 0x15, 0x1C),
red: Color::Rgb(0xFB, 0x73, 0x73),
green: Color::Rgb(0x7F, 0xD9, 0x4C),
yellow: Color::Rgb(0xE6, 0xB6, 0x73),
blue: Color::Rgb(0x73, 0xB8, 0xFF),
magenta: Color::Rgb(0xD2, 0xA6, 0xFF),
cyan: Color::Rgb(0x95, 0xE6, 0xCB),
white: Color::Rgb(0xFC, 0xFC, 0xFC),
}
}
}
3 changes: 3 additions & 0 deletions reqtui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ serde.workspace = true
tokio.workspace = true
reqwest.workspace = true
serde_json.workspace = true
ratatui.workspace = true

tree-sitter = "0.22.5"
tree-sitter-json = "0.21"
ropey = "1.6.1"
lazy_static = "1.4"

[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 @@ -3,3 +3,4 @@ pub mod fs;
pub mod net;
pub mod schema;
pub mod syntax;
pub mod text_object;
55 changes: 45 additions & 10 deletions reqtui/src/net/request_manager.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,67 @@
use serde::Serialize;
use std::collections::HashMap;

use ratatui::style::Style;
use tokio::sync::mpsc::UnboundedSender;

use crate::schema::types::Request;
use crate::{
schema::types::Request,
syntax::highlighter::HIGHLIGHTER,
text_object::{Readonly, TextObject},
};

#[derive(Debug, Serialize, Clone, PartialEq)]
#[derive(Debug, PartialEq)]
pub struct ReqtuiResponse {
pub body: String,
pub pretty_body: String,
pub pretty_body: TextObject<Readonly>,
}

#[derive(Debug, Serialize, PartialEq)]
#[derive(Debug, PartialEq)]
pub enum ReqtuiNetRequest {
Request(Request),
Response(ReqtuiResponse),
Error(String),
}

#[tracing::instrument(skip(response_tx))]
pub fn handle_request(request: Request, response_tx: UnboundedSender<ReqtuiNetRequest>) {
pub fn handle_request(
request: Request,
response_tx: UnboundedSender<ReqtuiNetRequest>,
tokens: HashMap<String, Style>,
) {
tracing::debug!("starting to handle user request");
tokio::spawn(async move {
match reqwest::get(request.uri).await {
let client = reqwest::Client::new();
match client.get(request.uri).send().await {
Ok(res) => {
tracing::debug!("request handled successfully, sending response");
let body = res.text().await.expect("failed to decode body");
let pretty_body = serde_json::to_string_pretty(&body).unwrap();
let body: serde_json::Value = res
.json()
.await
.map_err(|_| {
response_tx.send(ReqtuiNetRequest::Error(
"failed to decode json response".into(),
))
})
.expect("failed to send response through channel");

let pretty_body = serde_json::to_string_pretty(&body)
.map_err(|_| {
response_tx.send(ReqtuiNetRequest::Error(
"failed to decode json response".into(),
))
})
.expect("failed to send response through channel");

let mut highlighter = HIGHLIGHTER.write().unwrap();
let body = body.to_string();

let tree = highlighter.parse(&pretty_body);
let highlight = highlighter.apply(&pretty_body, tree.as_ref(), &tokens);
let pretty_body = TextObject::from(&pretty_body).with_highlight(highlight);

response_tx
.send(ReqtuiNetRequest::Response(ReqtuiResponse {
body,
body: body.to_string(),
pretty_body,
}))
.expect("failed to send response through channel");
Expand Down
60 changes: 46 additions & 14 deletions reqtui/src/syntax/highlighter.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
use tree_sitter::{Parser, Query, QueryCursor};
use lazy_static::lazy_static;
use ratatui::style::Style;

use std::{collections::HashMap, fmt::Debug, sync::RwLock};

use tree_sitter::{Parser, Query, QueryCursor, Tree};

lazy_static! {
pub static ref HIGHLIGHTER: RwLock<Highlighter> = RwLock::new(Highlighter::default());
}

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

#[derive(Debug)]
impl Debug for Highlighter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Highlighter").finish()
}
}

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

impl Default for Highlighter {
Expand All @@ -27,20 +43,36 @@ impl Default for Highlighter {
}

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

pub fn apply(
&self,
buffer: &str,
tree: Option<&Tree>,
tokens: &HashMap<String, Style>,
) -> Vec<ColorInfo> {
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 });

if let Some(tree) = tree {
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];
if let Some(style) = tokens.get(capture_name) {
colors.push(ColorInfo {
start,
end,
style: *style,
});
}
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions reqtui/src/text_object.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[allow(clippy::module_inception)]
mod text_object;

pub use text_object::{Readonly, TextObject, Write};
Loading

0 comments on commit 5c37430

Please sign in to comment.