diff --git a/src/buffer.rs b/src/buffer.rs index 0916247..26d84f7 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,3 +1,4 @@ +pub mod find; pub mod helper; pub mod highlight; pub mod maps; @@ -14,15 +15,12 @@ use std::collections::HashMap; use crate::{ buffer::{ highlight::Highlight, - maps::{ - get_default_command_maps, get_default_insert_maps, get_default_normal_maps, - get_default_visual_maps, - }, + maps::{get_default_insert_maps, get_default_normal_maps, get_default_visual_maps}, mode::BufferMode, options::BufferOptions, visual_mode::Selection, }, - core::{point::Point, rectangle::Rectangle, style::Style}, + core::{point::Point, rectangle::Rectangle, style::Style, text_position::TextPosition}, editor::Editor, }; @@ -38,7 +36,6 @@ pub struct Buffer { pub cursor: Point, pub lines: Vec, pub selection: Selection, - pub actions_command: ActionMap, pub actions_insert: ActionMap, pub actions_normal: ActionMap, pub actions_visual: ActionMap, @@ -47,6 +44,7 @@ pub struct Buffer { pub options: BufferOptions, pub styles: HashMap<&'static str, Style>, pub highlights: Vec, + pub finds: Vec, } impl Buffer { @@ -63,7 +61,6 @@ impl Buffer { selection: Selection { start: Point { x: 0, y: 0 }, }, - actions_command: get_default_command_maps(), actions_insert: get_default_insert_maps(), actions_normal: get_default_normal_maps(), actions_visual: get_default_visual_maps(), @@ -72,8 +69,9 @@ impl Buffer { options: BufferOptions::default(), styles: HashMap::new(), highlights: vec![], + finds: vec![], }; - + buffer.set_static_highlights(); buffer.set_size(area); buffer } diff --git a/src/buffer/find.rs b/src/buffer/find.rs new file mode 100644 index 0000000..6bad2f0 --- /dev/null +++ b/src/buffer/find.rs @@ -0,0 +1,121 @@ +use crate::{ + buffer::{ + highlight::{Highlight, HL_FIND_TEXT}, + Buffer, + }, + core::text_position::TextPosition, +}; + +impl Buffer { + pub fn find(&mut self, text: &str) { + self.clear_highlight(HL_FIND_TEXT); + self.finds.clear(); + + for row in 0..self.get_line_count() { + let line = self.get_line(row).clone(); + if text.len() <= line.len() { + let column_end = line.len() - text.len() + 1; + for column in 0..column_end { + if &line[column..(column + text.len())] == text { + self.finds.push(TextPosition { + row, + start: column, + end: column + text.len() - 1, + }); + self.highlights.push(Highlight { + name: HL_FIND_TEXT, + row, + start: column, + end: column + text.len() - 1, + }); + } + } + } + } + } + + pub fn clear_finds(&mut self) { + self.finds.clear(); + self.clear_highlight(HL_FIND_TEXT); + } + + pub fn move_to_next_find(&mut self) { + if self.finds.len() == 0 { + return; + } + + let mut pos: Option = None; + for find in self.finds.iter() { + if find.row < self.cursor.y { + continue; + } else if find.row == self.cursor.y && find.start <= self.cursor.x { + continue; + } else { + pos = Some(find.clone()); + break; + } + } + if let Some(p) = pos { + self.move_cursor(p.row, p.start); + } else { + let p = self.finds.get(0).unwrap(); + self.move_cursor(p.row, p.start); + } + } + + pub fn move_to_previous_find(&mut self) { + if self.finds.len() == 0 { + return; + } + + let mut pos: Option = None; + for find in self.finds.iter().rev() { + if self.cursor.y < find.row { + continue; + } else if find.row == self.cursor.y && self.cursor.x <= find.start { + continue; + } else { + pos = Some(find.clone()); + break; + } + } + if let Some(p) = pos { + self.move_cursor(p.row, p.start); + } else { + let p = self.finds.last().unwrap(); + self.move_cursor(p.row, p.start); + } + } +} + +#[cfg(test)] +mod tests { + use crate::{buffer::Buffer, core::rectangle::Rectangle}; + + #[test] + fn test() { + let mut buffer = Buffer::new(Rectangle { + x: 0, + y: 0, + width: 10, + height: 10, + }); + + buffer.lines.clear(); + buffer.lines.push(String::from("123 567 90")); + buffer.lines.push(String::from("qwer yuiio")); + buffer.lines.push(String::from("")); + buffer.lines.push(String::from("1 3 56")); + buffer.lines.push(String::from("")); + + buffer.find("56"); + + assert_eq!(2, buffer.finds.len()); + assert_eq!(0, buffer.finds.get(0).unwrap().row); + assert_eq!(4, buffer.finds.get(0).unwrap().start); + assert_eq!(5, buffer.finds.get(0).unwrap().end); + assert_eq!(3, buffer.finds.get(1).unwrap().row); + assert_eq!(5, buffer.finds.get(1).unwrap().start); + assert_eq!(6, buffer.finds.get(1).unwrap().end); + } +} diff --git a/src/buffer/highlight.rs b/src/buffer/highlight.rs index 83aa9cb..17c9707 100644 --- a/src/buffer/highlight.rs +++ b/src/buffer/highlight.rs @@ -1,4 +1,8 @@ -use crate::{buffer::Buffer, core::rectangle::Rectangle}; +use crate::{ + buffer::Buffer, + core::{rectangle::Rectangle, style::Style}, + theme::THEME_ONE as T, +}; pub struct Highlight { pub name: &'static str, @@ -7,6 +11,10 @@ pub struct Highlight { pub end: usize, } +pub const HL_CURRENT_LINE: &str = "CurrentLine"; +pub const HL_CURRENT_LINE_TEXT: &str = "CurrentLineText"; +pub const HL_FIND_TEXT: &str = "FindText"; + impl Highlight { pub fn is_visible_in_area(&self, area: Rectangle) -> bool { !(self.row < area.y @@ -17,11 +25,16 @@ impl Highlight { } impl Buffer { + pub fn set_static_highlights(&mut self) { + self.styles + .insert(HL_FIND_TEXT, Style::new(T.text_finded_fg, T.text_finded_bg)); + } + pub fn get_dynamic_highlights(&self) -> Vec { let mut list: Vec = vec![]; list.push(Highlight { - name: "CurrentLine", + name: HL_CURRENT_LINE, row: self.cursor.x, start: self.scroll.x, end: self.text_area.width as usize, @@ -29,7 +42,7 @@ impl Buffer { if let Some(end) = self.get_current_line_last_char_index() { list.push(Highlight { - name: "CurrentLineText", + name: HL_CURRENT_LINE_TEXT, row: self.cursor.y, start: 0, end, @@ -38,11 +51,18 @@ impl Buffer { list } + + pub fn clear_highlight(&mut self, name: &str) { + self.highlights.retain(|h| h.name != name); + } } #[cfg(test)] mod tests { - use crate::{buffer::Highlight, core::rectangle::Rectangle}; + use crate::{ + buffer::{Buffer, Highlight}, + core::rectangle::Rectangle, + }; fn test1(row: usize, start: usize, end: usize) -> bool { let area = Rectangle { @@ -71,4 +91,37 @@ mod tests { assert_eq!(false, test1(0, 10, 10)); assert_eq!(false, test1(10, 0, 1)); } + + #[test] + fn clear_highlight_test() { + let mut buffer = Buffer::new(Rectangle { + x: 0, + y: 0, + width: 10, + height: 10, + }); + + buffer.highlights.push(Highlight { + name: "Foo", + row: 0, + start: 0, + end: 1, + }); + buffer.highlights.push(Highlight { + name: "Bar", + row: 0, + start: 0, + end: 1, + }); + buffer.highlights.push(Highlight { + name: "Baz", + row: 0, + start: 0, + end: 1, + }); + + buffer.clear_highlight("Foo"); + + assert_eq!(2, buffer.highlights.len()); + } } diff --git a/src/buffer/maps.rs b/src/buffer/maps.rs index 68a19e2..cfb592f 100644 --- a/src/buffer/maps.rs +++ b/src/buffer/maps.rs @@ -33,42 +33,42 @@ pub fn get_default_insert_maps() -> ActionMap { pub fn get_default_normal_maps() -> ActionMap { let mut map: ActionMap = HashMap::new(); map.insert("left", |editor| { - editor.get_active_buffer_or_popup_mut().move_left() + editor.get_active_buffer_or_popup_mut().move_left(); }); map.insert("down", |editor| { - editor.get_active_buffer_or_popup_mut().move_down() + editor.get_active_buffer_or_popup_mut().move_down(); }); map.insert("up", |editor| { - editor.get_active_buffer_or_popup_mut().move_up() + editor.get_active_buffer_or_popup_mut().move_up(); }); map.insert("right", |editor| { - editor.get_active_buffer_or_popup_mut().move_right() + editor.get_active_buffer_or_popup_mut().move_right(); }); map.insert("h", |editor| { - editor.get_active_buffer_or_popup_mut().move_left() + editor.get_active_buffer_or_popup_mut().move_left(); }); map.insert("j", |editor| { - editor.get_active_buffer_or_popup_mut().move_down() + editor.get_active_buffer_or_popup_mut().move_down(); }); map.insert("k", |editor| { - editor.get_active_buffer_or_popup_mut().move_up() + editor.get_active_buffer_or_popup_mut().move_up(); }); map.insert("l", |editor| { - editor.get_active_buffer_or_popup_mut().move_right() + editor.get_active_buffer_or_popup_mut().move_right(); }); map.insert("g", |editor| { - editor.get_active_buffer_or_popup_mut().move_first_row() + editor.get_active_buffer_or_popup_mut().move_first_row(); }); map.insert("G", |editor| { - editor.get_active_buffer_or_popup_mut().move_last_row() + editor.get_active_buffer_or_popup_mut().move_last_row(); }); map.insert("0", |editor| { - editor.get_active_buffer_or_popup_mut().move_first_column() + editor.get_active_buffer_or_popup_mut().move_first_column(); }); map.insert("$", |editor| { - editor.get_active_buffer_or_popup_mut().move_last_column() + editor.get_active_buffer_or_popup_mut().move_last_column(); }); map.insert("w", |editor| { @@ -95,7 +95,7 @@ pub fn get_default_normal_maps() -> ActionMap { }); map.insert("x", |editor| { - editor.get_active_buffer_or_popup_mut().delete_char() + editor.get_active_buffer_or_popup_mut().delete_char(); }); map.insert("I", |editor| { @@ -128,24 +128,24 @@ pub fn get_default_normal_maps() -> ActionMap { buffer.enter_insert_mode(); }); map.insert(":", |editor| { - editor.command.reset(); - editor.get_active_buffer_or_popup_mut().enter_command_mode() + editor.input.reset(); + editor.get_active_buffer_or_popup_mut().enter_command_mode(); }); map.insert("v", |editor| { - editor.get_active_buffer_or_popup_mut().enter_visual_mode() + editor.get_active_buffer_or_popup_mut().enter_visual_mode(); }); - return map; -} - -pub fn get_default_command_maps() -> ActionMap { - let mut map: ActionMap = HashMap::new(); - map.insert("esc", |editor| { - editor.get_active_buffer_or_popup_mut().enter_normal_mode() + map.insert("/", |editor| { + editor.input.reset(); + editor.get_active_buffer_or_popup_mut().enter_find_mode(); + }); + map.insert("n", |editor| { + editor.get_active_buffer_or_popup_mut().move_to_next_find(); + }); + map.insert("N", |editor| { + editor + .get_active_buffer_or_popup_mut() + .move_to_previous_find(); }); - map.insert("enter", |editor| editor.run_command()); - map.insert("backspace", |editor| editor.command.delete_char()); - map.insert("left", |editor| editor.command.move_left()); - map.insert("right", |editor| editor.command.move_right()); return map; } diff --git a/src/buffer/mode.rs b/src/buffer/mode.rs index 7d89643..aed1520 100644 --- a/src/buffer/mode.rs +++ b/src/buffer/mode.rs @@ -6,6 +6,7 @@ pub enum BufferMode { Insert, Visual, Command, + Find, } impl Buffer { @@ -20,6 +21,7 @@ impl Buffer { } pub fn enter_insert_mode(&mut self) { + self.clear_finds(); self.mode = BufferMode::Insert; } @@ -32,4 +34,8 @@ impl Buffer { pub fn enter_command_mode(&mut self) { self.mode = BufferMode::Command; } + + pub fn enter_find_mode(&mut self) { + self.mode = BufferMode::Find; + } } diff --git a/src/commands/explorer.rs b/src/commands/explorer.rs index f26adf2..d6a5f41 100644 --- a/src/commands/explorer.rs +++ b/src/commands/explorer.rs @@ -71,7 +71,7 @@ fn initialize_explorer_buffer(buffer: &mut Buffer, base_path: &str) { path = String::from("."); } - editor.command.text = format!("e {}", path); + editor.input.text = format!("e {}", path); editor.run_command(); }); diff --git a/src/commands/find_file.rs b/src/commands/find_file.rs index 09b0818..51ca6ed 100644 --- a/src/commands/find_file.rs +++ b/src/commands/find_file.rs @@ -76,7 +76,7 @@ impl FindFileCommand { let buffer = editor.get_active_buffer(); let popup = buffer.popups.get(buffer.popups.len() - 2).unwrap(); let filename = popup.get_line(popup.cursor.y); - editor.command.text = format!("e {}", filename).to_string(); + editor.input.text = format!("e {}", filename).to_string(); let buffer = editor.get_active_buffer_mut(); buffer.popups.pop(); diff --git a/src/commands/read_file.rs b/src/commands/read_file.rs index ae4ceaa..07c5efa 100644 --- a/src/commands/read_file.rs +++ b/src/commands/read_file.rs @@ -7,8 +7,8 @@ pub struct ReadFileCommand {} impl ReadFileCommand { pub fn run(editor: &mut Editor) { - let command = editor.command.text.clone(); - let buffer = editor.get_active_buffer_mut(); + let command = editor.input.text.clone(); + let buffer = editor.get_active_tab_mut().create_new_buffer(); let file_name = &command.trim()[2..]; if file_name.starts_with("~/") { let mut home_path = home::home_dir().unwrap(); diff --git a/src/commands/write_file.rs b/src/commands/write_file.rs index 1d99b66..29f1be6 100644 --- a/src/commands/write_file.rs +++ b/src/commands/write_file.rs @@ -7,7 +7,7 @@ pub struct WriteFileCommand {} impl WriteFileCommand { pub fn run(editor: &mut Editor) { - let command = editor.command.text.clone(); + let command = editor.input.text.clone(); let buffer = editor.get_active_buffer_mut(); let path; diff --git a/src/core/editable_text.rs b/src/core/editable_text.rs index 6fde54b..ea7fb13 100644 --- a/src/core/editable_text.rs +++ b/src/core/editable_text.rs @@ -18,6 +18,12 @@ impl EditableText { } } + pub fn delete_char_after(&mut self) { + if self.cursor_x < self.text.len() { + self.text.remove(self.cursor_x); + } + } + pub fn move_left(&mut self) { if self.cursor_x > 0 { self.cursor_x = self.cursor_x - 1; @@ -32,6 +38,21 @@ impl EditableText { self.text = String::new(); self.cursor_x = 0; } + + pub fn handle_key(&mut self, key: &str) { + match key { + "left" => self.move_left(), + "right" => self.move_right(), + "" => self.delete_char(), + "backspace" => self.delete_char(), + "delete" => self.delete_char_after(), + other => { + if other.len() == 1 { + self.insert_char(other.chars().nth(0).unwrap()); + } + } + }; + } } #[cfg(test)] diff --git a/src/core/mod.rs b/src/core/mod.rs index e274119..bada572 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -4,3 +4,4 @@ pub mod point; pub mod rectangle; pub mod size; pub mod style; +pub mod text_position; diff --git a/src/core/style.rs b/src/core/style.rs index 96c13ba..c20283c 100644 --- a/src/core/style.rs +++ b/src/core/style.rs @@ -5,6 +5,18 @@ pub struct Style { pub fg: Color, pub bg: Color, pub bold: bool, - pub underline: bool, pub italic: bool, + pub underline: bool, +} + +impl Style { + pub fn new(fg: Color, bg: Color) -> Self { + Self { + fg, + bg, + bold: false, + italic: false, + underline: false, + } + } } diff --git a/src/core/text_position.rs b/src/core/text_position.rs new file mode 100644 index 0000000..2bd22ba --- /dev/null +++ b/src/core/text_position.rs @@ -0,0 +1,6 @@ +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TextPosition { + pub row: usize, + pub start: usize, + pub end: usize, +} diff --git a/src/editor.rs b/src/editor.rs index 2e029e6..c418626 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -15,7 +15,7 @@ pub struct Editor { pub status_area: Rectangle, pub tabs: Vec, pub active_tab: usize, - pub command: EditableText, + pub input: EditableText, } impl Editor { @@ -27,7 +27,7 @@ impl Editor { status_area: Rectangle::zero(), tabs: vec![], active_tab: 0, - command: EditableText { + input: EditableText { text: String::new(), cursor_x: 0, }, @@ -121,20 +121,41 @@ impl Editor { } } }, - BufferMode::Command => match buffer.actions_command.get(&key.to_string().as_str()) { - Some(action) => action(self), - None => { - if !key.ctrl && !key.win && !key.alt && key.code.len() == 1 { - let ch = key.code.chars().nth(0).unwrap(); - self.command.insert_char(ch); - } + BufferMode::Command => match key.to_string().as_str() { + "esc" => { + self.get_active_buffer_or_popup_mut().enter_normal_mode(); + } + "enter" => { + self.run_command(); + } + other => { + self.input.handle_key(other); } }, + BufferMode::Find => { + match key.to_string().as_str() { + "esc" => { + self.get_active_buffer_or_popup_mut().enter_normal_mode(); + } + "enter" => { + self.get_active_buffer_or_popup_mut().enter_normal_mode(); + self.get_active_buffer_or_popup_mut().move_to_next_find(); + } + other => { + self.input.handle_key(other); + let text = self.input.text.clone(); + if text.len() > 0 { + let buffer = self.get_active_buffer_or_popup_mut(); + buffer.find(&text); + } + } + }; + } } } pub fn run_command(&mut self) { - let command = self.command.text.trim(); + let command = self.input.text.trim(); if command == "w" || command.starts_with("w ") { WriteFileCommand::run(self); @@ -146,7 +167,7 @@ impl Editor { FindFileCommand::run(self); } - self.command.reset(); + self.input.reset(); self.get_active_buffer_mut().enter_normal_mode(); } } diff --git a/src/screen.rs b/src/screen.rs index fdf5cd6..7b472e6 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -53,15 +53,15 @@ impl Screen { BufferMode::Visual => CursorStyle::BlinkingBlock, BufferMode::Insert => CursorStyle::BlinkingBar, BufferMode::Command => CursorStyle::BlinkingBar, + BufferMode::Find => CursorStyle::BlinkingBar, }; - screen.cursor = if let BufferMode::Command = editor.get_active_buffer_or_popup().mode { - Point { + screen.cursor = match editor.get_active_buffer_or_popup().mode { + BufferMode::Command | BufferMode::Find => Point { y: editor.status_area.y, - x: (editor.status_area.x + 1 + editor.command.cursor_x as u16), - } - } else { - editor.get_active_buffer_or_popup().get_cursor_screen_pos() + x: (editor.status_area.x + 1 + editor.input.cursor_x as u16), + }, + _ => editor.get_active_buffer_or_popup().get_cursor_screen_pos(), }; screen.print_tabs(editor); diff --git a/src/screen/print_buffer.rs b/src/screen/print_buffer.rs index 93fc0b7..e40e14d 100644 --- a/src/screen/print_buffer.rs +++ b/src/screen/print_buffer.rs @@ -99,26 +99,14 @@ impl Screen { row_index + 1, buffer.info_area.width as usize - 2 ), - Style { - fg: T.info_column_fg, - bg: T.info_column_bg, - bold: false, - italic: false, - underline: false, - }, + Style::new(T.info_column_fg, T.info_column_bg), ); } self.print_text( buffer.area.y + y, buffer.text_area.x, &text, - Style { - fg: T.text_fg, - bg: T.text_bg, - bold: false, - italic: false, - underline: false, - }, + Style::new(T.text_fg, T.text_bg), ); } None => { @@ -127,13 +115,7 @@ impl Screen { buffer.area.y + y, buffer.area.x, "~", - Style { - fg: T.info_column_fg, - bg: T.bg, - bold: false, - italic: false, - underline: false, - }, + Style::new(T.info_column_fg, T.bg), ); } } diff --git a/src/screen/print_statusbar.rs b/src/screen/print_statusbar.rs index 94d3810..e7ef320 100644 --- a/src/screen/print_statusbar.rs +++ b/src/screen/print_statusbar.rs @@ -40,7 +40,12 @@ impl Screen { T.status_visual_mode_bg, ), BufferMode::Command => ( - format!(":{}", editor.command.text), + format!(":{}", editor.input.text), + T.status_command_mode_fg, + T.status_command_mode_bg, + ), + BufferMode::Find => ( + format!("/{}", editor.input.text), T.status_command_mode_fg, T.status_command_mode_bg, ), @@ -50,13 +55,7 @@ impl Screen { editor.status_area.y, editor.status_area.x, &format!("{}", mode), - Style { - fg, - bg, - bold: false, - italic: false, - underline: false, - }, + Style::new(fg, bg), ); } } diff --git a/src/screen/print_tabs.rs b/src/screen/print_tabs.rs index 1bb5bd8..6d93c87 100644 --- a/src/screen/print_tabs.rs +++ b/src/screen/print_tabs.rs @@ -11,13 +11,7 @@ impl Screen { editor.tabs_area.y, editor.tabs_area.x, editor.tabs_area.x + editor.tabs_area.width - 1, - Style { - fg: WHITE, - bg: T.status_line_bg, - bold: false, - italic: false, - underline: false, - }, + Style::new(WHITE, T.status_line_bg), ); let row = editor.tabs_area.y; @@ -42,13 +36,7 @@ impl Screen { row, column, format!(" {} ", text).as_str(), - Style { - fg, - bg, - bold: false, - italic: false, - underline: false, - }, + Style::new(fg, bg), ); column += text.len() as u16 + 2; diff --git a/src/theme.rs b/src/theme.rs index 3800f7a..ff5cbfe 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,6 +1,8 @@ use crate::core::style::Color; pub struct Theme { + pub bg: Color, + pub tab_line_bg: Color, pub tab_fg: Color, pub tab_bg: Color, @@ -10,11 +12,12 @@ pub struct Theme { pub info_column_fg: Color, pub info_column_bg: Color, - pub bg: Color, pub text_fg: Color, pub text_bg: Color, pub text_selected_fg: Color, pub text_selected_bg: Color, + pub text_finded_fg: Color, + pub text_finded_bg: Color, pub status_line_bg: Color, pub status_normal_mode_fg: Color, @@ -25,6 +28,8 @@ pub struct Theme { pub status_visual_mode_bg: Color, pub status_command_mode_fg: Color, pub status_command_mode_bg: Color, + pub status_find_mode_fg: Color, + pub status_find_mode_bg: Color, pub border_color_fg: Color, pub border_color_bg: Color, @@ -35,11 +40,14 @@ pub const SILVER: Color = (192, 192, 192); pub const GRAY: Color = (128, 128, 128); pub const LIGHT_GRAY: Color = (175, 175, 175); pub const WHITE: Color = (255, 255, 255); +pub const RED: Color = (255, 0, 0); pub const PURPLE: Color = (128, 0, 128); pub const BLUE: Color = (100, 149, 237); pub const GREEN: Color = (0, 100, 0); pub const THEME_ONE: Theme = Theme { + bg: SILVER, + tab_line_bg: GRAY, tab_fg: WHITE, tab_bg: GRAY, @@ -49,11 +57,12 @@ pub const THEME_ONE: Theme = Theme { info_column_fg: BLACK, info_column_bg: LIGHT_GRAY, - bg: SILVER, text_fg: BLACK, text_bg: SILVER, text_selected_fg: WHITE, text_selected_bg: GRAY, + text_finded_fg: RED, + text_finded_bg: GRAY, status_line_bg: GRAY, status_normal_mode_fg: WHITE, @@ -64,6 +73,8 @@ pub const THEME_ONE: Theme = Theme { status_visual_mode_bg: PURPLE, status_command_mode_fg: WHITE, status_command_mode_bg: GRAY, + status_find_mode_fg: WHITE, + status_find_mode_bg: GRAY, border_color_fg: BLACK, border_color_bg: SILVER,