Run and fix pedantic clippy and 2018 idioms
This commit is contained in:
parent
db92090916
commit
c6343a4805
44
src/app.rs
44
src/app.rs
@ -17,21 +17,12 @@ pub struct Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Deserialize, Serialize, Debug, Clone, Copy)]
|
// #[derive(Deserialize, Serialize, Debug, Clone, Copy)]
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Default, Deserialize, Serialize, Debug)]
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Task {
|
|
||||||
fn default() -> Self {
|
|
||||||
Task {
|
|
||||||
title: String::new(),
|
|
||||||
description: String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type used mainly for serialization at this time
|
/// Type used mainly for serialization at this time
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
@ -75,16 +66,17 @@ impl Default for TaskState<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppState<'a> {
|
pub struct State<'a> {
|
||||||
pub project: Project,
|
pub project: Project,
|
||||||
pub quit: bool,
|
pub quit: bool,
|
||||||
pub columns: Vec<Column>,
|
pub columns: Vec<Column>,
|
||||||
pub task_edit_state: Option<TaskState<'a>>,
|
pub task_edit_state: Option<TaskState<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState<'_> {
|
impl State<'_> {
|
||||||
|
#[must_use]
|
||||||
pub fn new(project: Project) -> Self {
|
pub fn new(project: Project) -> Self {
|
||||||
AppState {
|
State {
|
||||||
quit: false,
|
quit: false,
|
||||||
task_edit_state: None,
|
task_edit_state: None,
|
||||||
project,
|
project,
|
||||||
@ -94,6 +86,7 @@ impl AppState<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Column {
|
impl<'a> Column {
|
||||||
|
#[must_use]
|
||||||
pub fn new(name: &str) -> Self {
|
pub fn new(name: &str) -> Self {
|
||||||
Column {
|
Column {
|
||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
@ -113,6 +106,7 @@ impl<'a> Column {
|
|||||||
self.select_next_task();
|
self.select_next_task();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn get_selected_task(&self) -> Option<&Task> {
|
pub fn get_selected_task(&self) -> Option<&Task> {
|
||||||
self.tasks.get(self.selected_task_idx)
|
self.tasks.get(self.selected_task_idx)
|
||||||
}
|
}
|
||||||
@ -123,18 +117,19 @@ impl<'a> Column {
|
|||||||
|
|
||||||
pub fn select_previous_task(&mut self) {
|
pub fn select_previous_task(&mut self) {
|
||||||
let task_idx = &mut self.selected_task_idx;
|
let task_idx = &mut self.selected_task_idx;
|
||||||
*task_idx = task_idx.saturating_sub(1)
|
*task_idx = task_idx.saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_next_task(&mut self) {
|
pub fn select_next_task(&mut self) {
|
||||||
let task_idx = &mut self.selected_task_idx;
|
let task_idx = &mut self.selected_task_idx;
|
||||||
*task_idx = min(*task_idx + 1, self.tasks.len().saturating_sub(1))
|
*task_idx = min(*task_idx + 1, self.tasks.len().saturating_sub(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_last_task(&mut self) {
|
pub fn select_last_task(&mut self) {
|
||||||
self.selected_task_idx = self.tasks.len() - 1;
|
self.selected_task_idx = self.tasks.len() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn get_task_state_from_curr_selected_task(&self) -> Option<TaskState<'a>> {
|
pub fn get_task_state_from_curr_selected_task(&self) -> Option<TaskState<'a>> {
|
||||||
self.get_selected_task().map(|t| {
|
self.get_selected_task().map(|t| {
|
||||||
TaskState {
|
TaskState {
|
||||||
@ -148,6 +143,7 @@ impl<'a> Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
|
#[must_use]
|
||||||
pub fn new(name: &str, filepath: String) -> Self {
|
pub fn new(name: &str, filepath: String) -> Self {
|
||||||
Project {
|
Project {
|
||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
@ -166,6 +162,9 @@ impl Project {
|
|||||||
serde_json::from_str(json).map_err(|_| KanbanError::BadJson)
|
serde_json::from_str(json).map_err(|_| KanbanError::BadJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return `Err` if `file` contains json that doesn't match State schema
|
||||||
pub fn load(path: String, mut file: &File) -> Result<Self, KanbanError> {
|
pub fn load(path: String, mut file: &File) -> Result<Self, KanbanError> {
|
||||||
let mut json = String::new();
|
let mut json = String::new();
|
||||||
file.read_to_string(&mut json)?;
|
file.read_to_string(&mut json)?;
|
||||||
@ -176,11 +175,16 @@ impl Project {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Will panic if there's an error serializing the Json or there's an issue
|
||||||
|
/// writing the file
|
||||||
pub fn save(&self) {
|
pub fn save(&self) {
|
||||||
let json = serde_json::to_string_pretty(&self).unwrap();
|
let json = serde_json::to_string_pretty(&self).unwrap();
|
||||||
std::fs::write(&self.filepath, json).unwrap();
|
std::fs::write(&self.filepath, json).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn get_selected_column(&self) -> &Column {
|
pub fn get_selected_column(&self) -> &Column {
|
||||||
&self.columns[self.selected_column_idx]
|
&self.columns[self.selected_column_idx]
|
||||||
}
|
}
|
||||||
@ -211,7 +215,7 @@ impl Project {
|
|||||||
} else {
|
} else {
|
||||||
col_idx > 0
|
col_idx > 0
|
||||||
};
|
};
|
||||||
if cond && column.tasks.len() > 0 {
|
if cond && !column.tasks.is_empty() {
|
||||||
let t = column.tasks.remove(column.selected_task_idx);
|
let t = column.tasks.remove(column.selected_task_idx);
|
||||||
column.select_previous_task();
|
column.select_previous_task();
|
||||||
if move_next {
|
if move_next {
|
||||||
@ -227,18 +231,18 @@ impl Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_task_previous_column(&mut self) {
|
pub fn move_task_previous_column(&mut self) {
|
||||||
self.move_task_to_column(false)
|
self.move_task_to_column(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_task_next_column(&mut self) {
|
pub fn move_task_next_column(&mut self) {
|
||||||
self.move_task_to_column(true)
|
self.move_task_to_column(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_task_up(&mut self) {
|
pub fn move_task_up(&mut self) {
|
||||||
let column = self.get_selected_column_mut();
|
let column = self.get_selected_column_mut();
|
||||||
if column.selected_task_idx > 0 {
|
if column.selected_task_idx > 0 {
|
||||||
column.tasks.swap(column.selected_task_idx, column.selected_task_idx - 1);
|
column.tasks.swap(column.selected_task_idx, column.selected_task_idx - 1);
|
||||||
column.selected_task_idx = column.selected_task_idx - 1;
|
column.selected_task_idx -= 1;
|
||||||
self.save();
|
self.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -247,7 +251,7 @@ impl Project {
|
|||||||
let column = self.get_selected_column_mut();
|
let column = self.get_selected_column_mut();
|
||||||
if column.selected_task_idx < column.tasks.len() - 1 {
|
if column.selected_task_idx < column.tasks.len() - 1 {
|
||||||
column.tasks.swap(column.selected_task_idx, column.selected_task_idx + 1);
|
column.tasks.swap(column.selected_task_idx, column.selected_task_idx + 1);
|
||||||
column.selected_task_idx = column.selected_task_idx + 1;
|
column.selected_task_idx += 1;
|
||||||
self.save();
|
self.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
use super::*;
|
// use super::*;
|
||||||
|
27
src/input.rs
27
src/input.rs
@ -1,11 +1,11 @@
|
|||||||
#![allow(unused_imports)]
|
|
||||||
use crossterm::event;
|
use crossterm::event;
|
||||||
use crossterm::event::{Event, KeyCode};
|
use crossterm::event::{Event, KeyCode};
|
||||||
use crate::app::{TaskState, AppState, TaskEditFocus};
|
use crate::app::{TaskState, State, TaskEditFocus};
|
||||||
use std::io::{stdout, Write};
|
|
||||||
use tui_textarea::TextArea;
|
|
||||||
|
|
||||||
pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> {
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Crossterm `event::read()` might return an error
|
||||||
|
pub fn handle(state: &mut State<'_>) -> Result<(), std::io::Error> {
|
||||||
let project = &mut state.project;
|
let project = &mut state.project;
|
||||||
let column = project.get_selected_column_mut();
|
let column = project.get_selected_column_mut();
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
@ -35,7 +35,7 @@ pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> {
|
|||||||
KeyCode::BackTab => task.focus = TaskEditFocus::Description,
|
KeyCode::BackTab => task.focus = TaskEditFocus::Description,
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
let title = task.title.clone().into_lines().join("\n");
|
let title = task.title.clone().into_lines().join("\n");
|
||||||
let description = task.description.clone().into_lines().clone().join("\n");
|
let description = task.description.clone().into_lines().join("\n");
|
||||||
if task.is_edit {
|
if task.is_edit {
|
||||||
if let Some(selected_task) = column.get_selected_task_mut() {
|
if let Some(selected_task) = column.get_selected_task_mut() {
|
||||||
selected_task.title = title;
|
selected_task.title = title;
|
||||||
@ -55,7 +55,7 @@ pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> {
|
|||||||
KeyCode::Tab => task.focus = TaskEditFocus::Title,
|
KeyCode::Tab => task.focus = TaskEditFocus::Title,
|
||||||
KeyCode::BackTab => task.focus = TaskEditFocus::ConfirmBtn,
|
KeyCode::BackTab => task.focus = TaskEditFocus::ConfirmBtn,
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
state.task_edit_state = None
|
state.task_edit_state = None;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
@ -73,14 +73,10 @@ pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> {
|
|||||||
KeyCode::Up => column.select_previous_task(),
|
KeyCode::Up => column.select_previous_task(),
|
||||||
KeyCode::Char('l') |
|
KeyCode::Char('l') |
|
||||||
KeyCode::Right => { project.select_next_column(); },
|
KeyCode::Right => { project.select_next_column(); },
|
||||||
KeyCode::Char('<') |
|
KeyCode::Char('<' | 'H') => project.move_task_previous_column(),
|
||||||
KeyCode::Char('H') => project.move_task_previous_column(),
|
KeyCode::Char('>' | 'L') => project.move_task_next_column(),
|
||||||
KeyCode::Char('>') |
|
KeyCode::Char('=' | 'J') => project.move_task_down(),
|
||||||
KeyCode::Char('L') => project.move_task_next_column(),
|
KeyCode::Char('-' | 'K') => project.move_task_up(),
|
||||||
KeyCode::Char('=') |
|
|
||||||
KeyCode::Char('J') => project.move_task_down(),
|
|
||||||
KeyCode::Char('-') |
|
|
||||||
KeyCode::Char('K') => project.move_task_up(),
|
|
||||||
KeyCode::Char('n') => state.task_edit_state = Some(TaskState::default()),
|
KeyCode::Char('n') => state.task_edit_state = Some(TaskState::default()),
|
||||||
KeyCode::Char('e') =>
|
KeyCode::Char('e') =>
|
||||||
state.task_edit_state = column.get_task_state_from_curr_selected_task(),
|
state.task_edit_state = column.get_task_state_from_curr_selected_task(),
|
||||||
@ -95,4 +91,3 @@ pub fn handle_input(state: &mut AppState) -> Result<(), std::io::Error> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// #![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
mod app;
|
mod app;
|
||||||
mod ui;
|
mod ui;
|
||||||
mod input;
|
mod input;
|
||||||
|
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
pub use ui::draw;
|
pub use ui::draw;
|
||||||
pub use input::handle_input;
|
pub use input::handle;
|
||||||
|
20
src/main.rs
20
src/main.rs
@ -1,9 +1,7 @@
|
|||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
#![allow(unused_imports)]
|
use kanban_tui::{Project, State};
|
||||||
#![allow(dead_code)]
|
|
||||||
use kanban_tui::*;
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::*,
|
event::{DisableMouseCapture, EnableMouseCapture},
|
||||||
terminal::{
|
terminal::{
|
||||||
disable_raw_mode,
|
disable_raw_mode,
|
||||||
enable_raw_mode,
|
enable_raw_mode,
|
||||||
@ -13,7 +11,6 @@ use crossterm::{
|
|||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
env,
|
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
fs::{File, OpenOptions},
|
fs::{File, OpenOptions},
|
||||||
error::Error
|
error::Error
|
||||||
@ -37,7 +34,7 @@ fn prompt_project_init(default_name: &str) -> (String, io::Result<File>) {
|
|||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
|
||||||
println!("Database not found, select the name of the database if it exists or enter a name to start a new project");
|
println!("Database not found, select the name of the database if it exists or enter a name to start a new project");
|
||||||
print!("Database name (default: {}): ", default_name);
|
print!("Database name (default: {default_name}): ");
|
||||||
io::stdout().flush().unwrap();
|
io::stdout().flush().unwrap();
|
||||||
|
|
||||||
let result = io::stdin().read_line(&mut input);
|
let result = io::stdin().read_line(&mut input);
|
||||||
@ -71,13 +68,12 @@ fn main() -> anyhow::Result<(), Box<dyn Error>> {
|
|||||||
.read(true)
|
.read(true)
|
||||||
.open(&fpath);
|
.open(&fpath);
|
||||||
|
|
||||||
match file {
|
if let Ok(f) = file {
|
||||||
Ok(f) => (fpath, f),
|
(fpath, f)
|
||||||
Err(_) => {
|
} else {
|
||||||
let (fp, fname) = prompt_project_init(&fpath);
|
let (fp, fname) = prompt_project_init(&fpath);
|
||||||
(fp, fname.unwrap())
|
(fp, fname.unwrap())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
CliArgs { filepath: None } => {
|
CliArgs { filepath: None } => {
|
||||||
let file = OpenOptions::new()
|
let file = OpenOptions::new()
|
||||||
@ -92,7 +88,7 @@ fn main() -> anyhow::Result<(), Box<dyn Error>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut state = AppState::new(Project::load(filepath, &file)?);
|
let mut state = State::new(Project::load(filepath, &file)?);
|
||||||
|
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
@ -102,7 +98,7 @@ fn main() -> anyhow::Result<(), Box<dyn Error>> {
|
|||||||
|
|
||||||
while !state.quit {
|
while !state.quit {
|
||||||
terminal.draw(|f| kanban_tui::draw(f, &mut state))?;
|
terminal.draw(|f| kanban_tui::draw(f, &mut state))?;
|
||||||
kanban_tui::handle_input(&mut state)?;
|
kanban_tui::handle(&mut state)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// state.project.save();
|
// state.project.save();
|
||||||
|
28
src/ui.rs
28
src/ui.rs
@ -1,25 +1,25 @@
|
|||||||
use crate::app::*;
|
use crate::app::{State, TaskEditFocus};
|
||||||
use tui::backend::Backend;
|
use tui::backend::Backend;
|
||||||
use tui::layout::*;
|
use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
|
||||||
use tui::style::{Color, Modifier, Style};
|
use tui::style::{Color, Modifier, Style};
|
||||||
use tui::text::{Span, Spans};
|
use tui::text::{Span, Spans};
|
||||||
use tui::widgets::*;
|
use tui::widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph, Wrap};
|
||||||
use tui::Frame;
|
use tui::Frame;
|
||||||
|
|
||||||
fn draw_tasks<B: Backend>(f: &mut Frame<B>, area: &Rect, state: &AppState) {
|
fn draw_tasks<B: Backend>(f: &mut Frame<'_, B>, area: Rect, state: &State<'_>) {
|
||||||
let columns = Layout::default()
|
let columns = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints(
|
.constraints(
|
||||||
vec![
|
vec![
|
||||||
Constraint::Percentage(100 / state.project.columns.len() as u16);
|
Constraint::Percentage(100 / u16::try_from(state.project.columns.len()).unwrap());
|
||||||
state.project.columns.len()
|
state.project.columns.len()
|
||||||
]
|
]
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
)
|
)
|
||||||
.split(*area);
|
.split(area);
|
||||||
|
|
||||||
for (i, column) in state.project.columns.iter().enumerate() {
|
for (i, column) in state.project.columns.iter().enumerate() {
|
||||||
let items: Vec<ListItem> = column.tasks
|
let items: Vec<ListItem<'_>> = column.tasks
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(j, task)| {
|
.map(|(j, task)| {
|
||||||
@ -55,16 +55,16 @@ fn draw_tasks<B: Backend>(f: &mut Frame<B>, area: &Rect, state: &AppState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_task_info<B: Backend>(f: &mut Frame<B>, area: &Rect, state: &AppState) {
|
fn draw_task_info<B: Backend>(f: &mut Frame<'_, B>, area: Rect, state: &State<'_>) {
|
||||||
let block = Block::default().title("TASK INFO").borders(Borders::ALL);
|
let block = Block::default().title("TASK INFO").borders(Borders::ALL);
|
||||||
if let Some(task) = state.project.get_selected_column().get_selected_task() {
|
if let Some(task) = state.project.get_selected_column().get_selected_task() {
|
||||||
let p = Paragraph::new(task.description.as_str())
|
let p = Paragraph::new(task.description.as_str())
|
||||||
.block(block)
|
.block(block)
|
||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
f.render_widget(p, *area);
|
f.render_widget(p, area);
|
||||||
} else {
|
} else {
|
||||||
let p = Paragraph::new("No tasks for this column").block(block);
|
let p = Paragraph::new("No tasks for this column").block(block);
|
||||||
f.render_widget(p, *area);
|
f.render_widget(p, area);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ fn centered_rect_for_popup(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
|
|||||||
.split(popup_layout[1])[1]
|
.split(popup_layout[1])[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_task_popup<B: Backend>(f: &mut Frame<B>, state: &mut AppState, popup_title: &str) {
|
pub fn draw_task_popup<B: Backend>(f: &mut Frame<'_, B>, state: &mut State<'_>, popup_title: &str) {
|
||||||
let area = centered_rect_for_popup(45, 60, f.size());
|
let area = centered_rect_for_popup(45, 60, f.size());
|
||||||
let block = Block::default()
|
let block = Block::default()
|
||||||
.title(popup_title)
|
.title(popup_title)
|
||||||
@ -189,7 +189,7 @@ pub fn draw_task_popup<B: Backend>(f: &mut Frame<B>, state: &mut AppState, popup
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw<B: Backend>(f: &mut Frame<B>, state: &mut AppState) {
|
pub fn draw<B: Backend>(f: &mut Frame<'_, B>, state: &mut State<'_>) {
|
||||||
let main_layout = Layout::default()
|
let main_layout = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(
|
.constraints(
|
||||||
@ -206,9 +206,9 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, state: &mut AppState) {
|
|||||||
let block = Block::default().title("KANBAN BOARD").borders(Borders::ALL);
|
let block = Block::default().title("KANBAN BOARD").borders(Borders::ALL);
|
||||||
f.render_widget(block, main_layout[0]);
|
f.render_widget(block, main_layout[0]);
|
||||||
|
|
||||||
draw_tasks(f, &main_layout[1], &state);
|
draw_tasks(f, main_layout[1], state);
|
||||||
|
|
||||||
draw_task_info(f, &main_layout[2], &state);
|
draw_task_info(f, main_layout[2], state);
|
||||||
|
|
||||||
let block = Block::default().title("KEYBINDINGS").borders(Borders::TOP);
|
let block = Block::default().title("KEYBINDINGS").borders(Borders::TOP);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user