summaryrefslogtreecommitdiffstats
path: root/src/layout.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/layout.rs')
-rw-r--r--src/layout.rs338
1 files changed, 211 insertions, 127 deletions
diff --git a/src/layout.rs b/src/layout.rs
index 4205f89..cf4af93 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -1,9 +1,18 @@
+use super::session::SortKey;
+
use super::config::*;
use super::errors::FxError;
use super::functions::*;
-use super::state::{FileType, ItemInfo, BEGINNING_ROW};
+use super::nums::*;
+use super::state::{ItemInfo, BEGINNING_ROW};
use super::term::*;
+use serde::{Deserialize, Serialize};
+use syntect::easy::HighlightLines;
+use syntect::highlighting::Theme;
+use syntect::parsing::SyntaxSet;
+use syntect::util::{as_24_bit_terminal_escaped, split_at, LinesWithEndings};
+
/// cf: https://docs.rs/image/latest/src/image/image.rs.html#84-112
pub const MAX_SIZE_TO_PREVIEW: u64 = 1_000_000_000;
pub const IMAGE_EXTENSION: [&str; 20] = [
@@ -15,25 +24,30 @@ pub const CHAFA_WARNING: &str =
pub const PROPER_WIDTH: u16 = 28;
pub const TIME_WIDTH: u16 = 16;
-pub const DEFAULT_NAME_LENGTH: u16 = 30;
-#[derive(Debug, Clone)]
+#[derive(Debug)]
pub struct Layout {
+ pub nums: Num,
pub y: u16,
pub terminal_row: u16,
pub terminal_column: u16,
pub name_max_len: usize,
pub time_start_pos: u16,
- pub use_full: Option<bool>,
- pub option_name_len: Option<usize>,
pub colors: ConfigColor,
+ pub sort_by: SortKey,
+ pub show_hidden: bool,
pub preview: bool,
- pub preview_start_column: u16,
- pub preview_width: u16,
+ pub split: Split,
+ pub preview_start: (u16, u16),
+ pub preview_space: (u16, u16),
+ pub syntax_highlight: bool,
+ pub syntax_set: SyntaxSet,
+ pub theme: Theme,
pub has_chafa: bool,
pub is_kitty: bool,
}
+#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone)]
pub enum PreviewType {
NotReadable,
TooBigSize,
@@ -43,25 +57,38 @@ pub enum PreviewType {
Binary,
}
+#[derive(Debug, PartialEq, Deserialize, Serialize, Clone, Copy)]
+pub enum Split {
+ Vertical,
+ Horizontal,
+}
+
impl Layout {
/// Print preview according to the preview type.
pub fn print_preview(&self, item: &ItemInfo, y: u16) {
- //At least print the item name
- self.print_file_name(item);
- //Clear preview space
- self.clear_preview(self.preview_start_column);
+ match self.split {
+ Split::Vertical => {
+ //At least print the item name
+ self.print_file_name(item);
+ //Clear preview space
+ self.clear_preview(self.preview_start.0);
+ }
+ Split::Horizontal => {
+ self.clear_preview(self.preview_start.1);
+ }
+ }
- match check_preview_type(item) {
- PreviewType::NotReadable => {
+ match item.preview_type {
+ Some(PreviewType::NotReadable) => {
print!("(file not readable)");
}
- PreviewType::TooBigSize => {
+ Some(PreviewType::TooBigSize) => {
print!("(file too big for preview)");
}
- PreviewType::Directory => {
- self.preview_content(item, true);
+ Some(PreviewType::Directory) => {
+ self.preview_directory(item);
}
- PreviewType::Image => {
+ Some(PreviewType::Image) => {
if self.has_chafa {
if let Err(e) = self.preview_image(item, y) {
print_warning(e, y);
@@ -69,7 +96,7 @@ impl Layout {
} else {
let help = format_txt(CHAFA_WARNING, self.terminal_column - 1, false);
for (i, line) in help.iter().enumerate() {
- move_to(self.preview_start_column, BEGINNING_ROW + i as u16);
+ move_to(self.preview_start.0, BEGINNING_ROW + i as u16);
print!("{}", line,);
if BEGINNING_ROW + i as u16 == self.terminal_row - 1 {
break;
@@ -77,73 +104,170 @@ impl Layout {
}
}
}
- PreviewType::Text => {
- self.preview_content(item, false);
+ Some(PreviewType::Text) => {
+ if self.syntax_highlight {
+ match self.preview_text_with_sh(item) {
+ Ok(_) => {}
+ Err(e) => {
+ print!("{}", e);
+ }
+ }
+ } else {
+ self.preview_text(item);
+ }
}
- PreviewType::Binary => {
+ Some(PreviewType::Binary) => {
print!("(Binary file)");
}
+ _ => {
+ print!("(Not Available)");
+ }
}
}
/// Print item name at the top.
fn print_file_name(&self, item: &ItemInfo) {
- move_to(self.preview_start_column, 1);
+ move_to(self.preview_start.0 - 1, 1);
clear_until_newline();
+ move_right(1);
let mut file_name = format!("[{}]", item.file_name);
- if file_name.len() > self.preview_width.into() {
- file_name = file_name.chars().take(self.preview_width.into()).collect();
+ if file_name.len() > self.preview_space.0.into() {
+ file_name = file_name
+ .chars()
+ .take(self.preview_space.0.into())
+ .collect();
}
print!("{}", file_name);
}
- /// Print text preview on the right half of the terminal.
- fn preview_content(&self, item: &ItemInfo, is_dir: bool) {
- let content = if is_dir {
+ fn preview_text(&self, item: &ItemInfo) {
+ let content = {
+ if let Some(content) = &item.content {
+ format_txt(content, self.preview_space.0, false)
+ } else {
+ vec![]
+ }
+ };
+ self.print_txt_in_preview_area(item, content, false);
+ }
+
+ /// Preview text with syntax highlighting.
+ fn preview_text_with_sh(&self, item: &ItemInfo) -> Result<(), FxError> {
+ if let Ok(Some(syntax)) = self.syntax_set.find_syntax_for_file(item.file_path.clone()) {
+ let mut h = HighlightLines::new(syntax, &self.theme);
+ if let Some(content) = &item.content {
+ move_to(self.preview_start.0, BEGINNING_ROW);
+ let mut result = vec![];
+ for (index, line) in LinesWithEndings::from(content).enumerate() {
+ let count = line.len() / self.preview_space.0 as usize;
+ let mut range = h.highlight_line(line, &self.syntax_set)?;
+ for _ in 0..=count + 1 {
+ let ranges = split_at(&range, (self.preview_space.0) as usize);
+ if !ranges.0.is_empty() {
+ result.push(ranges.0);
+ }
+ range = ranges.1;
+ }
+ if index > self.preview_space.1 as usize + item.preview_scroll {
+ break;
+ }
+ }
+ let result: Vec<String> = result
+ .iter()
+ .map(|x| as_24_bit_terminal_escaped(x, false))
+ .collect();
+ self.print_txt_in_preview_area(item, result, true);
+ } else {
+ print!("");
+ }
+ } else {
+ self.preview_text(item);
+ }
+ Ok(())
+ }
+
+ fn preview_directory(&self, item: &ItemInfo) {
+ let content = {
let contents = match &item.symlink_dir_path {
None => list_up_contents(&item.file_path),
Some(p) => list_up_contents(p),
};
if let Ok(contents) = contents {
if let Ok(contents) = make_tree(contents) {
- format_txt(&contents, self.preview_width, false)
+ format_txt(&contents, self.preview_space.0, false)
} else {
vec![]
}
} else {
vec![]
}
- } else {
- let item = item.file_path.clone();
- let column = self.terminal_column;
- let content = std::fs::read_to_string(item);
- if let Ok(content) = content {
- let content = content.replace('\t', " ");
- format_txt(&content, column - 1, false)
- } else {
- vec![]
- }
};
- //Print preview (wrapping)
- for (i, line) in content.iter().enumerate() {
- move_to(self.preview_start_column, BEGINNING_ROW + i as u16);
- set_color(&TermColor::ForeGround(&Colorname::LightBlack));
- print!("{}", line);
- reset_color();
- if BEGINNING_ROW + i as u16 == self.terminal_row - 1 {
- break;
+ self.print_txt_in_preview_area(item, content, false);
+ }
+
+ fn print_txt_in_preview_area(
+ &self,
+ item: &ItemInfo,
+ content: Vec<String>,
+ syntex_highlight: bool,
+ ) {
+ match self.split {
+ Split::Vertical => {
+ for (i, line) in content.iter().enumerate() {
+ if i < item.preview_scroll {
+ continue;
+ }
+ let sum = (i - item.preview_scroll) as u16;
+ let row = self.preview_start.1 + sum as u16;
+ move_to(self.preview_start.0, row);
+ if syntex_highlight {
+ print!("{}", line);
+ } else {
+ set_color(&TermColor::ForeGround(&Colorname::LightBlack));
+ print!("{}", line);
+ reset_color();
+ }
+ if sum == self.preview_space.1 - 1 {
+ reset_color();
+ break;
+ }
+ }
+ }
+ Split::Horizontal => {
+ for (i, line) in content.iter().enumerate() {
+ if i < item.preview_scroll {
+ continue;
+ }
+ let sum = (i - item.preview_scroll) as u16;
+ let row = self.preview_start.1 + sum as u16;
+ move_to(1, row);
+ if syntex_highlight {
+ print!("{}", line);
+ } else {
+ set_color(&TermColor::ForeGround(&Colorname::LightBlack));
+ print!("{}", line);
+ reset_color();
+ }
+ if row == self.terminal_row + self.preview_space.1 {
+ reset_color();
+ break;
+ }
+ }
}
}
}
/// Print text preview on the right half of the terminal (Experimental).
fn preview_image(&self, item: &ItemInfo, y: u16) -> Result<(), FxError> {
- let wxh = format!(
- "--size={}x{}",
- self.preview_width,
- self.terminal_row - BEGINNING_ROW
- );
+ let wxh = match self.split {
+ Split::Vertical => {
+ format!("--size={}x{}", self.preview_space.0, self.preview_space.1)
+ }
+ Split::Horizontal => {
+ format!("--size={}x{}", self.preview_space.0, self.preview_space.1)
+ }
+ };
let file_path = item.file_path.to_str();
if file_path.is_none() {
@@ -155,31 +279,50 @@ impl Layout {
.args(["--animate=false", &wxh, file_path.unwrap()])
.output()?
.stdout;
- let output = String::from_utf8(output).unwrap();
- for (i, line) in output.lines().enumerate() {
- let next_line: u16 = BEGINNING_ROW + (i as u16) + 1;
- print!("{}", line);
- move_to(self.preview_start_column, next_line);
+ let output = String::from_utf8(output)?;
+
+ match self.split {
+ Split::Vertical => {
+ for (i, line) in output.lines().enumerate() {
+ print!("{}", line);
+ let next_line: u16 = BEGINNING_ROW + (i as u16) + 1;
+ move_to(self.preview_start.0, next_line);
+ }
+ }
+ Split::Horizontal => {
+ for (i, line) in output.lines().enumerate() {
+ print!("{}", line);
+ let next_line: u16 = self.preview_start.1 + (i as u16) + 1;
+ move_to(1, next_line);
+ }
+ }
}
Ok(())
}
/// Clear the preview space.
- fn clear_preview(&self, preview_start_column: u16) {
- for i in 0..self.terminal_row {
- move_to(preview_start_column, BEGINNING_ROW + i as u16);
- clear_until_newline();
+ fn clear_preview(&self, preview_start_point: u16) {
+ match self.split {
+ Split::Vertical => {
+ for i in 0..=self.terminal_row {
+ move_to(preview_start_point, BEGINNING_ROW + i as u16);
+ clear_until_newline();
+ }
+ move_to(self.preview_start.0, BEGINNING_ROW);
+ }
+ Split::Horizontal => {
+ for i in 0..=self.terminal_row {
+ move_to(1, preview_start_point + i as u16);
+ clear_until_newline();
+ }
+ move_to(1, preview_start_point);
+ }
}
- move_to(self.preview_start_column, BEGINNING_ROW);
}
}
/// Make app's layout according to terminal width and app's config.
-pub fn make_layout(
- column: u16,
- use_full: Option<bool>,
- name_length: Option<usize>,
-) -> (u16, usize) {
+pub fn make_layout(column: u16) -> (u16, usize) {
let mut time_start: u16;
let mut name_max: usize;
@@ -188,30 +331,8 @@ pub fn make_layout(
name_max = (column - 2).into();
(time_start, name_max)
} else {
- match use_full {
- Some(true) | None => {
- time_start = column - TIME_WIDTH;
- name_max = (time_start - SPACES).into();
- }
- Some(false) => match name_length {
- Some(option_max) => {
- time_start = option_max as u16 + SPACES;
- name_max = option_max;
- }
- None => {
- time_start = if column >= DEFAULT_NAME_LENGTH + TIME_WIDTH + SPACES {
- DEFAULT_NAME_LENGTH + SPACES
- } else {
- column - TIME_WIDTH
- };
- name_max = if column >= DEFAULT_NAME_LENGTH + TIME_WIDTH + SPACES {
- DEFAULT_NAME_LENGTH.into()
- } else {
- (time_start - SPACES).into()
- };
- }
- },
- }
+ time_start = column - TIME_WIDTH;
+ name_max = (time_start - SPACES).into();
let required = time_start + TIME_WIDTH - 1;
if required > column {
let diff = required - column;
@@ -222,40 +343,3 @@ pub fn make_layout(
(time_start, name_max)
}
}
-
-fn check_preview_content_type(item: &ItemInfo) -> PreviewType {
- if item.file_size > MAX_SIZE_TO_PREVIEW {
- PreviewType::TooBigSize
- } else if is_supported_ext(item) {
- PreviewType::Image
- } else if let Ok(content) = &std::fs::read(&item.file_path) {
- if content_inspector::inspect(content).is_text() {
- PreviewType::Text
- } else {
- PreviewType::Binary
- }
- } else {
- // failed to resolve item to any form of supported preview
- // it is probably not accessible due to permissions, broken symlink etc.
- PreviewType::NotReadable
- }
-}
-
-/// Check preview type.
-fn check_preview_type(item: &ItemInfo) -> PreviewType {
- if item.file_type == FileType::Directory
- || (item.file_type == FileType::Symlink && item.symlink_dir_path.is_some())
- {
- // symlink was resolved to directory already in the ItemInfo
- PreviewType::Directory
- } else {
- check_preview_content_type(item)
- }
-}
-
-fn is_supported_ext(item: &ItemInfo) -> bool {
- match &item.file_ext {
- None => false,
- Some(ext) => IMAGE_EXTENSION.contains(&ext.as_str()),
- }
-}