2019-07-24 17:14:30 +00:00
|
|
|
#![feature(option_flattening)]
|
|
|
|
|
2019-07-25 01:25:17 +00:00
|
|
|
use crossterm::{cursor, terminal, RawScreen};
|
2019-07-24 17:14:30 +00:00
|
|
|
use indexmap::IndexMap;
|
|
|
|
use nu::{
|
|
|
|
serve_plugin, CallInfo, CommandConfig, Plugin, Primitive, ShellError, SourceMap, SpanSource,
|
|
|
|
Spanned, Value,
|
|
|
|
};
|
2019-07-25 01:25:17 +00:00
|
|
|
use rawkey::RawKey;
|
2019-07-24 17:14:30 +00:00
|
|
|
|
2019-07-25 05:19:19 +00:00
|
|
|
use syntect::easy::HighlightLines;
|
|
|
|
use syntect::highlighting::{Style, ThemeSet};
|
|
|
|
use syntect::parsing::SyntaxSet;
|
|
|
|
|
|
|
|
use std::io::Write;
|
2019-07-24 17:14:30 +00:00
|
|
|
use std::path::Path;
|
2019-07-25 01:07:33 +00:00
|
|
|
use std::{thread, time::Duration};
|
2019-07-24 17:14:30 +00:00
|
|
|
|
2019-07-25 05:19:19 +00:00
|
|
|
enum DrawCommand {
|
|
|
|
DrawString(Style, String),
|
|
|
|
NextLine,
|
|
|
|
}
|
2019-07-24 17:14:30 +00:00
|
|
|
struct TextView;
|
|
|
|
|
|
|
|
impl TextView {
|
|
|
|
fn new() -> TextView {
|
|
|
|
TextView
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Plugin for TextView {
|
|
|
|
fn config(&mut self) -> Result<CommandConfig, ShellError> {
|
|
|
|
Ok(CommandConfig {
|
|
|
|
name: "textview".to_string(),
|
|
|
|
positional: vec![],
|
|
|
|
is_filter: false,
|
|
|
|
is_sink: true,
|
|
|
|
named: IndexMap::new(),
|
|
|
|
rest_positional: false,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn sink(&mut self, call_info: CallInfo, input: Vec<Spanned<Value>>) {
|
|
|
|
view_text_value(&input[0], &call_info.source_map);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-25 05:19:19 +00:00
|
|
|
fn paint_textview(
|
|
|
|
draw_commands: &Vec<DrawCommand>,
|
|
|
|
starting_row: usize,
|
|
|
|
use_color_buffer: bool,
|
|
|
|
) -> usize {
|
2019-07-29 04:18:11 +00:00
|
|
|
let terminal = terminal();
|
2019-07-25 01:07:33 +00:00
|
|
|
let cursor = cursor();
|
|
|
|
|
2019-07-29 04:18:11 +00:00
|
|
|
let size = terminal.terminal_size();
|
2019-07-25 01:07:33 +00:00
|
|
|
|
2019-07-25 05:19:19 +00:00
|
|
|
// render
|
|
|
|
let mut pos = 0;
|
2019-07-29 04:18:11 +00:00
|
|
|
let width = size.0 as usize + 1;
|
|
|
|
let height = size.1 as usize;
|
2019-07-25 05:19:19 +00:00
|
|
|
let mut frame_buffer = vec![]; //(' ', 0, 0, 0); max_pos];
|
|
|
|
|
|
|
|
for command in draw_commands {
|
|
|
|
match command {
|
|
|
|
DrawCommand::DrawString(style, string) => {
|
|
|
|
for chr in string.chars() {
|
|
|
|
frame_buffer.push((
|
|
|
|
chr,
|
|
|
|
style.foreground.r,
|
|
|
|
style.foreground.g,
|
|
|
|
style.foreground.b,
|
|
|
|
));
|
|
|
|
pos += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DrawCommand::NextLine => {
|
|
|
|
for _ in 0..(width - pos % width) {
|
|
|
|
frame_buffer.push((' ', 0, 0, 0));
|
|
|
|
}
|
|
|
|
pos += width - pos % width;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-07-25 01:07:33 +00:00
|
|
|
|
2019-07-26 19:31:46 +00:00
|
|
|
let num_frame_buffer_rows = frame_buffer.len() / width;
|
|
|
|
let buffer_needs_scrolling = num_frame_buffer_rows > height;
|
2019-07-25 05:19:19 +00:00
|
|
|
|
|
|
|
// display
|
|
|
|
let mut ansi_strings = vec![];
|
|
|
|
let mut normal_chars = vec![];
|
2019-07-25 01:07:33 +00:00
|
|
|
|
2019-07-26 19:31:46 +00:00
|
|
|
for c in
|
|
|
|
&frame_buffer[starting_row * width..std::cmp::min(pos, (starting_row + height) * width)]
|
|
|
|
{
|
2019-07-25 05:19:19 +00:00
|
|
|
if use_color_buffer {
|
|
|
|
ansi_strings.push(ansi_term::Colour::RGB(c.1, c.2, c.3).paint(format!("{}", c.0)));
|
2019-07-25 01:07:33 +00:00
|
|
|
} else {
|
2019-07-25 05:19:19 +00:00
|
|
|
normal_chars.push(c.0);
|
2019-07-25 01:07:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-26 19:31:46 +00:00
|
|
|
if buffer_needs_scrolling {
|
|
|
|
let _ = cursor.goto(0, 0);
|
|
|
|
}
|
|
|
|
|
2019-07-25 05:19:19 +00:00
|
|
|
if use_color_buffer {
|
|
|
|
print!("{}", ansi_term::ANSIStrings(&ansi_strings));
|
|
|
|
} else {
|
|
|
|
let s: String = normal_chars.into_iter().collect();
|
|
|
|
print!("{}", s);
|
|
|
|
}
|
|
|
|
|
2019-07-26 19:31:46 +00:00
|
|
|
if buffer_needs_scrolling {
|
2019-07-29 04:18:11 +00:00
|
|
|
let _ = cursor.goto(0, size.1);
|
2019-07-25 16:22:22 +00:00
|
|
|
print!(
|
|
|
|
"{}",
|
|
|
|
ansi_term::Colour::Blue.paint("[ESC to quit, arrow keys to move]")
|
|
|
|
);
|
|
|
|
}
|
2019-07-25 01:07:33 +00:00
|
|
|
|
|
|
|
let _ = std::io::stdout().flush();
|
|
|
|
|
2019-07-26 19:31:46 +00:00
|
|
|
num_frame_buffer_rows
|
2019-07-25 01:07:33 +00:00
|
|
|
}
|
|
|
|
|
2019-07-25 16:22:22 +00:00
|
|
|
fn scroll_view_lines_if_needed(draw_commands: Vec<DrawCommand>, use_color_buffer: bool) {
|
2019-07-24 17:14:30 +00:00
|
|
|
let mut starting_row = 0;
|
2019-07-25 01:25:17 +00:00
|
|
|
let rawkey = RawKey::new();
|
2019-07-24 17:14:30 +00:00
|
|
|
|
|
|
|
if let Ok(_raw) = RawScreen::into_raw_mode() {
|
|
|
|
let cursor = cursor();
|
|
|
|
let _ = cursor.hide();
|
|
|
|
|
2019-07-25 02:43:51 +00:00
|
|
|
let input = crossterm::input();
|
|
|
|
let _ = input.read_async();
|
|
|
|
|
2019-07-25 05:19:19 +00:00
|
|
|
let terminal = terminal();
|
2019-07-29 04:18:11 +00:00
|
|
|
let mut size = terminal.terminal_size();
|
2019-07-25 16:22:22 +00:00
|
|
|
let mut max_bottom_line = paint_textview(&draw_commands, starting_row, use_color_buffer);
|
2019-07-25 05:19:19 +00:00
|
|
|
|
2019-07-25 16:22:22 +00:00
|
|
|
// Only scroll if needed
|
2019-07-29 04:18:11 +00:00
|
|
|
if max_bottom_line > size.1 as usize {
|
2019-07-25 16:22:22 +00:00
|
|
|
loop {
|
|
|
|
if rawkey.is_pressed(rawkey::KeyCode::Escape) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if rawkey.is_pressed(rawkey::KeyCode::UpArrow) {
|
|
|
|
if starting_row > 0 {
|
|
|
|
starting_row -= 1;
|
|
|
|
max_bottom_line =
|
|
|
|
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if rawkey.is_pressed(rawkey::KeyCode::DownArrow) {
|
2019-07-29 04:18:11 +00:00
|
|
|
if starting_row < (max_bottom_line - size.1 as usize) {
|
2019-07-25 16:22:22 +00:00
|
|
|
starting_row += 1;
|
|
|
|
}
|
2019-07-25 05:19:19 +00:00
|
|
|
max_bottom_line =
|
|
|
|
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
2019-07-25 01:25:17 +00:00
|
|
|
}
|
2019-07-25 16:22:22 +00:00
|
|
|
if rawkey.is_pressed(rawkey::KeyCode::PageUp) {
|
2019-07-29 04:18:11 +00:00
|
|
|
starting_row -= std::cmp::min(size.1 as usize, starting_row);
|
2019-07-25 16:22:22 +00:00
|
|
|
max_bottom_line =
|
|
|
|
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
2019-07-24 17:14:30 +00:00
|
|
|
}
|
2019-07-25 16:22:22 +00:00
|
|
|
if rawkey.is_pressed(rawkey::KeyCode::PageDown) {
|
2019-07-29 04:18:11 +00:00
|
|
|
if starting_row < (max_bottom_line - size.1 as usize) {
|
|
|
|
starting_row += size.1 as usize;
|
2019-07-25 01:07:33 +00:00
|
|
|
|
2019-07-29 04:18:11 +00:00
|
|
|
if starting_row > (max_bottom_line - size.1 as usize) {
|
|
|
|
starting_row = max_bottom_line - size.1 as usize;
|
2019-07-25 16:22:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
max_bottom_line =
|
|
|
|
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
|
|
}
|
2019-07-25 05:19:19 +00:00
|
|
|
|
2019-07-25 16:22:22 +00:00
|
|
|
thread::sleep(Duration::from_millis(50));
|
|
|
|
|
2019-07-29 04:18:11 +00:00
|
|
|
let new_size = terminal.terminal_size();
|
|
|
|
if size != new_size {
|
|
|
|
size = new_size;
|
2019-07-25 16:22:22 +00:00
|
|
|
let _ = terminal.clear(crossterm::ClearType::All);
|
|
|
|
max_bottom_line =
|
|
|
|
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
|
|
}
|
2019-07-25 05:19:19 +00:00
|
|
|
}
|
2019-07-24 17:14:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let _ = cursor.show();
|
|
|
|
}
|
2019-07-25 02:43:51 +00:00
|
|
|
|
|
|
|
let cursor = cursor();
|
|
|
|
let _ = cursor.show();
|
|
|
|
|
|
|
|
#[allow(unused)]
|
|
|
|
let screen = RawScreen::disable_raw_mode();
|
|
|
|
|
|
|
|
println!("");
|
2019-07-26 19:31:46 +00:00
|
|
|
//thread::sleep(Duration::from_millis(50));
|
2019-07-24 17:14:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn scroll_view(s: &str) {
|
2019-07-25 05:19:19 +00:00
|
|
|
let mut v = vec![];
|
|
|
|
for line in s.lines() {
|
|
|
|
v.push(DrawCommand::DrawString(Style::default(), line.to_string()));
|
|
|
|
v.push(DrawCommand::NextLine);
|
|
|
|
}
|
2019-07-25 16:22:22 +00:00
|
|
|
scroll_view_lines_if_needed(v, false);
|
2019-07-24 17:14:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn view_text_value(value: &Spanned<Value>, source_map: &SourceMap) {
|
|
|
|
match value {
|
|
|
|
Spanned {
|
|
|
|
item: Value::Primitive(Primitive::String(s)),
|
|
|
|
span,
|
|
|
|
} => {
|
|
|
|
let source = span.source.map(|x| source_map.get(&x)).flatten();
|
|
|
|
|
|
|
|
if let Some(source) = source {
|
|
|
|
match source {
|
|
|
|
SpanSource::File(file) => {
|
|
|
|
let path = Path::new(file);
|
|
|
|
match path.extension() {
|
|
|
|
Some(extension) => {
|
|
|
|
// Load these once at the start of your program
|
|
|
|
let ps: SyntaxSet = syntect::dumps::from_binary(include_bytes!(
|
|
|
|
"../../assets/syntaxes.bin"
|
|
|
|
));
|
|
|
|
|
|
|
|
if let Some(syntax) =
|
|
|
|
ps.find_syntax_by_extension(extension.to_str().unwrap())
|
|
|
|
{
|
|
|
|
let ts: ThemeSet = syntect::dumps::from_binary(include_bytes!(
|
|
|
|
"../../assets/themes.bin"
|
|
|
|
));
|
|
|
|
let mut h =
|
|
|
|
HighlightLines::new(syntax, &ts.themes["OneHalfDark"]);
|
|
|
|
|
|
|
|
let mut v = vec![];
|
|
|
|
for line in s.lines() {
|
|
|
|
let ranges: Vec<(Style, &str)> = h.highlight(line, &ps);
|
2019-07-25 05:19:19 +00:00
|
|
|
|
|
|
|
for range in ranges {
|
|
|
|
v.push(DrawCommand::DrawString(
|
|
|
|
range.0,
|
|
|
|
range.1.to_string(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
v.push(DrawCommand::NextLine);
|
2019-07-24 17:14:30 +00:00
|
|
|
}
|
2019-07-25 16:22:22 +00:00
|
|
|
scroll_view_lines_if_needed(v, true);
|
2019-07-24 17:14:30 +00:00
|
|
|
} else {
|
|
|
|
scroll_view(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
scroll_view(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
scroll_view(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
scroll_view(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
serve_plugin(&mut TextView::new());
|
|
|
|
}
|