feat(console): Add scrolling support for System Logs and Editor panels
System Logs: - Add scroll_offset tracking with auto-scroll to bottom on new logs - Up/Down/j/k keys to scroll line by line - PageUp/PageDown for page scrolling - Home/End to jump to top/bottom - Show scroll indicators in title: [^v], [SCROLL] when not auto-scrolling - Display log count in title Editor: - Fix scroll_offset to follow cursor when moving up/down - Add PageUp/PageDown for faster navigation - Add Ctrl+Home/Ctrl+End to jump to start/end of file - ensure_cursor_visible() keeps cursor in view Tab Navigation: - FileTree -> Editor (if open) or Logs -> Chat -> back to start - Consistent cycling through all panels
This commit is contained in:
parent
824b12365b
commit
715a60315e
3 changed files with 277 additions and 22 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::shared::state::AppState;
|
use crate::shared::state::AppState;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct Editor {
|
pub struct Editor {
|
||||||
file_path: String,
|
file_path: String,
|
||||||
bucket: String,
|
bucket: String,
|
||||||
|
|
@ -8,6 +9,7 @@ pub struct Editor {
|
||||||
content: String,
|
content: String,
|
||||||
cursor_pos: usize,
|
cursor_pos: usize,
|
||||||
scroll_offset: usize,
|
scroll_offset: usize,
|
||||||
|
visible_lines: usize,
|
||||||
modified: bool,
|
modified: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,6 +46,7 @@ impl Editor {
|
||||||
content,
|
content,
|
||||||
cursor_pos: 0,
|
cursor_pos: 0,
|
||||||
scroll_offset: 0,
|
scroll_offset: 0,
|
||||||
|
visible_lines: 20,
|
||||||
modified: false,
|
modified: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -63,11 +66,34 @@ impl Editor {
|
||||||
pub fn file_path(&self) -> &str {
|
pub fn file_path(&self) -> &str {
|
||||||
&self.file_path
|
&self.file_path
|
||||||
}
|
}
|
||||||
|
pub fn set_visible_lines(&mut self, lines: usize) {
|
||||||
|
self.visible_lines = lines.max(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cursor_line(&self) -> usize {
|
||||||
|
self.content[..self.cursor_pos].lines().count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_cursor_visible(&mut self) {
|
||||||
|
let cursor_line = self.get_cursor_line();
|
||||||
|
|
||||||
|
// Scroll up if cursor is above visible area
|
||||||
|
if cursor_line < self.scroll_offset {
|
||||||
|
self.scroll_offset = cursor_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll down if cursor is below visible area (leave some margin)
|
||||||
|
let visible = self.visible_lines.saturating_sub(3);
|
||||||
|
if cursor_line >= self.scroll_offset + visible {
|
||||||
|
self.scroll_offset = cursor_line.saturating_sub(visible) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render(&self, cursor_blink: bool) -> String {
|
pub fn render(&self, cursor_blink: bool) -> String {
|
||||||
let lines: Vec<&str> = self.content.lines().collect();
|
let lines: Vec<&str> = self.content.lines().collect();
|
||||||
let total_lines = lines.len().max(1);
|
let total_lines = lines.len().max(1);
|
||||||
let visible_lines = 25;
|
let visible_lines = self.visible_lines;
|
||||||
let cursor_line = self.content[..self.cursor_pos].lines().count();
|
let cursor_line = self.get_cursor_line();
|
||||||
let cursor_col = self.content[..self.cursor_pos]
|
let cursor_col = self.content[..self.cursor_pos]
|
||||||
.lines()
|
.lines()
|
||||||
.last()
|
.last()
|
||||||
|
|
@ -121,6 +147,7 @@ impl Editor {
|
||||||
self.cursor_pos = (self.cursor_pos - prev_line_end - 1).min(prev_line_end);
|
self.cursor_pos = (self.cursor_pos - prev_line_end - 1).min(prev_line_end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.ensure_cursor_visible();
|
||||||
}
|
}
|
||||||
pub fn move_down(&mut self) {
|
pub fn move_down(&mut self) {
|
||||||
if let Some(next_line_start) = self.content[self.cursor_pos..].find('\n') {
|
if let Some(next_line_start) = self.content[self.cursor_pos..].find('\n') {
|
||||||
|
|
@ -140,6 +167,37 @@ impl Editor {
|
||||||
self.cursor_pos = target_pos;
|
self.cursor_pos = target_pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.ensure_cursor_visible();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page_up(&mut self) {
|
||||||
|
for _ in 0..self.visible_lines.saturating_sub(2) {
|
||||||
|
if self.content[..self.cursor_pos].rfind('\n').is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.move_up();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page_down(&mut self) {
|
||||||
|
for _ in 0..self.visible_lines.saturating_sub(2) {
|
||||||
|
if self.content[self.cursor_pos..].find('\n').is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.move_down();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_up(&mut self) {
|
||||||
|
self.scroll_offset = self.scroll_offset.saturating_sub(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_down(&mut self) {
|
||||||
|
let total_lines = self.content.lines().count().max(1);
|
||||||
|
let max_scroll = total_lines.saturating_sub(self.visible_lines.saturating_sub(3));
|
||||||
|
if self.scroll_offset < max_scroll {
|
||||||
|
self.scroll_offset += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn move_left(&mut self) {
|
pub fn move_left(&mut self) {
|
||||||
if self.cursor_pos > 0 {
|
if self.cursor_pos > 0 {
|
||||||
|
|
@ -167,5 +225,20 @@ impl Editor {
|
||||||
self.modified = true;
|
self.modified = true;
|
||||||
self.content.insert(self.cursor_pos, '\n');
|
self.content.insert(self.cursor_pos, '\n');
|
||||||
self.cursor_pos += 1;
|
self.cursor_pos += 1;
|
||||||
|
self.ensure_cursor_visible();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn goto_line(&mut self, line: usize) {
|
||||||
|
let lines: Vec<&str> = self.content.lines().collect();
|
||||||
|
let target_line = line.saturating_sub(1).min(lines.len().saturating_sub(1));
|
||||||
|
|
||||||
|
self.cursor_pos = 0;
|
||||||
|
for (i, line_content) in lines.iter().enumerate() {
|
||||||
|
if i == target_line {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.cursor_pos += line_content.len() + 1; // +1 for newline
|
||||||
|
}
|
||||||
|
self.ensure_cursor_visible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use log::{LevelFilter, Log, Metadata, Record, SetLoggerError};
|
use log::{LevelFilter, Log, Metadata, Record, SetLoggerError};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
pub struct LogPanel {
|
pub struct LogPanel {
|
||||||
logs: Vec<String>,
|
logs: Vec<String>,
|
||||||
max_logs: usize,
|
max_logs: usize,
|
||||||
|
scroll_offset: usize,
|
||||||
|
auto_scroll: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for LogPanel {
|
impl std::fmt::Debug for LogPanel {
|
||||||
|
|
@ -11,39 +14,130 @@ impl std::fmt::Debug for LogPanel {
|
||||||
f.debug_struct("LogPanel")
|
f.debug_struct("LogPanel")
|
||||||
.field("logs_count", &self.logs.len())
|
.field("logs_count", &self.logs.len())
|
||||||
.field("max_logs", &self.max_logs)
|
.field("max_logs", &self.max_logs)
|
||||||
|
.field("scroll_offset", &self.scroll_offset)
|
||||||
|
.field("auto_scroll", &self.auto_scroll)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogPanel {
|
impl LogPanel {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
logs: Vec::with_capacity(1000),
|
logs: Vec::with_capacity(1000),
|
||||||
max_logs: 1000,
|
max_logs: 1000,
|
||||||
|
scroll_offset: 0,
|
||||||
|
auto_scroll: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_log(&mut self, entry: &str) {
|
pub fn add_log(&mut self, entry: &str) {
|
||||||
if self.logs.len() >= self.max_logs {
|
if self.logs.len() >= self.max_logs {
|
||||||
self.logs.remove(0);
|
self.logs.remove(0);
|
||||||
|
// Adjust scroll offset if we removed a log
|
||||||
|
if self.scroll_offset > 0 {
|
||||||
|
self.scroll_offset = self.scroll_offset.saturating_sub(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.logs.push(entry.to_string());
|
self.logs.push(entry.to_string());
|
||||||
|
|
||||||
|
// Auto-scroll to bottom if enabled
|
||||||
|
if self.auto_scroll {
|
||||||
|
self.scroll_to_bottom();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn render(&self) -> String {
|
|
||||||
let visible_logs = if self.logs.len() > 10 {
|
pub fn scroll_up(&mut self, lines: usize) {
|
||||||
&self.logs[self.logs.len() - 10..]
|
self.scroll_offset = self.scroll_offset.saturating_sub(lines);
|
||||||
|
self.auto_scroll = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_down(&mut self, lines: usize, visible_lines: usize) {
|
||||||
|
let max_scroll = self.logs.len().saturating_sub(visible_lines);
|
||||||
|
self.scroll_offset = (self.scroll_offset + lines).min(max_scroll);
|
||||||
|
|
||||||
|
// Re-enable auto-scroll if we're at the bottom
|
||||||
|
if self.scroll_offset >= max_scroll {
|
||||||
|
self.auto_scroll = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_to_bottom(&mut self) {
|
||||||
|
// This will be adjusted when rendering based on visible lines
|
||||||
|
self.scroll_offset = usize::MAX;
|
||||||
|
self.auto_scroll = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_to_top(&mut self) {
|
||||||
|
self.scroll_offset = 0;
|
||||||
|
self.auto_scroll = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page_up(&mut self, visible_lines: usize) {
|
||||||
|
self.scroll_up(visible_lines.saturating_sub(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page_down(&mut self, visible_lines: usize) {
|
||||||
|
self.scroll_down(visible_lines.saturating_sub(1), visible_lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&self, visible_lines: usize) -> String {
|
||||||
|
if self.logs.is_empty() {
|
||||||
|
return " Waiting for logs...".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let total_logs = self.logs.len();
|
||||||
|
|
||||||
|
// Calculate actual scroll offset
|
||||||
|
let max_scroll = total_logs.saturating_sub(visible_lines);
|
||||||
|
let actual_offset = if self.scroll_offset == usize::MAX {
|
||||||
|
max_scroll
|
||||||
} else {
|
} else {
|
||||||
&self.logs[..]
|
self.scroll_offset.min(max_scroll)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get visible slice
|
||||||
|
let end = (actual_offset + visible_lines).min(total_logs);
|
||||||
|
let start = actual_offset;
|
||||||
|
|
||||||
|
let visible_logs = &self.logs[start..end];
|
||||||
visible_logs.join("\n")
|
visible_logs.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_with_scroll_indicator(&self, visible_lines: usize) -> (String, bool, bool) {
|
||||||
|
let content = self.render(visible_lines);
|
||||||
|
let total_logs = self.logs.len();
|
||||||
|
let max_scroll = total_logs.saturating_sub(visible_lines);
|
||||||
|
let actual_offset = if self.scroll_offset == usize::MAX {
|
||||||
|
max_scroll
|
||||||
|
} else {
|
||||||
|
self.scroll_offset.min(max_scroll)
|
||||||
|
};
|
||||||
|
|
||||||
|
let can_scroll_up = actual_offset > 0;
|
||||||
|
let can_scroll_down = actual_offset < max_scroll;
|
||||||
|
|
||||||
|
(content, can_scroll_up, can_scroll_down)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn logs_count(&self) -> usize {
|
||||||
|
self.logs.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_auto_scroll(&self) -> bool {
|
||||||
|
self.auto_scroll
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UiLogger {
|
pub struct UiLogger {
|
||||||
log_panel: Arc<Mutex<LogPanel>>,
|
log_panel: Arc<Mutex<LogPanel>>,
|
||||||
filter: LevelFilter,
|
filter: LevelFilter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Log for UiLogger {
|
impl Log for UiLogger {
|
||||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||||
metadata.level() <= self.filter
|
metadata.level() <= self.filter
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log(&self, record: &Record) {
|
fn log(&self, record: &Record) {
|
||||||
if self.enabled(record.metadata()) {
|
if self.enabled(record.metadata()) {
|
||||||
let timestamp = Local::now().format("%H:%M:%S");
|
let timestamp = Local::now().format("%H:%M:%S");
|
||||||
|
|
@ -60,8 +154,10 @@ impl Log for UiLogger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&self) {}
|
fn flush(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_logger(log_panel: Arc<Mutex<LogPanel>>) -> Result<(), SetLoggerError> {
|
pub fn init_logger(log_panel: Arc<Mutex<LogPanel>>) -> Result<(), SetLoggerError> {
|
||||||
let logger = Box::new(UiLogger {
|
let logger = Box::new(UiLogger {
|
||||||
log_panel,
|
log_panel,
|
||||||
|
|
|
||||||
|
|
@ -506,8 +506,10 @@ impl XtreeUI {
|
||||||
.border_style(Style::default().fg(border))
|
.border_style(Style::default().fg(border))
|
||||||
.style(Style::default().bg(bg));
|
.style(Style::default().bg(bg));
|
||||||
|
|
||||||
|
// Calculate visible lines for log panel (area height minus borders)
|
||||||
|
let logs_visible_lines = main_chunks[2].height.saturating_sub(2) as usize;
|
||||||
let logs_content = if let Ok(panel) = self.log_panel.lock() {
|
let logs_content = if let Ok(panel) = self.log_panel.lock() {
|
||||||
panel.render()
|
panel.render(logs_visible_lines)
|
||||||
} else {
|
} else {
|
||||||
String::from(" Waiting for logs...")
|
String::from(" Waiting for logs...")
|
||||||
};
|
};
|
||||||
|
|
@ -720,12 +722,24 @@ impl XtreeUI {
|
||||||
title_bg: Color,
|
title_bg: Color,
|
||||||
title_fg: Color,
|
title_fg: Color,
|
||||||
) {
|
) {
|
||||||
|
// Calculate visible lines (area height minus borders)
|
||||||
|
let visible_lines = area.height.saturating_sub(2) as usize;
|
||||||
|
|
||||||
let log_panel = self.log_panel.try_lock();
|
let log_panel = self.log_panel.try_lock();
|
||||||
let log_lines = if let Ok(panel) = log_panel {
|
let (log_lines, can_scroll_up, can_scroll_down, logs_count, auto_scroll) =
|
||||||
panel.render()
|
if let Ok(panel) = log_panel {
|
||||||
} else {
|
let (content, up, down) = panel.render_with_scroll_indicator(visible_lines);
|
||||||
"Loading logs...".to_string()
|
(
|
||||||
};
|
content,
|
||||||
|
up,
|
||||||
|
down,
|
||||||
|
panel.logs_count(),
|
||||||
|
panel.is_auto_scroll(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
("Loading logs...".to_string(), false, false, 0, true)
|
||||||
|
};
|
||||||
|
|
||||||
let is_active = self.active_panel == ActivePanel::Logs;
|
let is_active = self.active_panel == ActivePanel::Logs;
|
||||||
let border_color = if is_active {
|
let border_color = if is_active {
|
||||||
border_active
|
border_active
|
||||||
|
|
@ -740,8 +754,26 @@ impl XtreeUI {
|
||||||
} else {
|
} else {
|
||||||
Style::default().fg(title_fg).bg(title_bg)
|
Style::default().fg(title_fg).bg(title_bg)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Build title with scroll indicators
|
||||||
|
let scroll_indicator = if can_scroll_up && can_scroll_down {
|
||||||
|
" [^v] "
|
||||||
|
} else if can_scroll_up {
|
||||||
|
" [^] "
|
||||||
|
} else if can_scroll_down {
|
||||||
|
" [v] "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
let auto_indicator = if auto_scroll { "" } else { " [SCROLL] " };
|
||||||
|
let title_text = format!(
|
||||||
|
" SYSTEM LOGS ({}) {}{}",
|
||||||
|
logs_count, scroll_indicator, auto_indicator
|
||||||
|
);
|
||||||
|
|
||||||
let block = Block::default()
|
let block = Block::default()
|
||||||
.title(Span::styled(" SYSTEM LOGS ", title_style))
|
.title(Span::styled(title_text, title_style))
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_style(Style::default().fg(border_color))
|
.border_style(Style::default().fg(border_color))
|
||||||
.style(Style::default().bg(bg));
|
.style(Style::default().bg(bg));
|
||||||
|
|
@ -816,7 +848,11 @@ impl XtreeUI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Tab => {
|
KeyCode::Tab => {
|
||||||
self.active_panel = ActivePanel::Chat;
|
if self.editor.is_some() {
|
||||||
|
self.active_panel = ActivePanel::Editor;
|
||||||
|
} else {
|
||||||
|
self.active_panel = ActivePanel::Logs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Char('q') => {
|
KeyCode::Char('q') => {
|
||||||
self.should_quit = true;
|
self.should_quit = true;
|
||||||
|
|
@ -841,6 +877,19 @@ impl XtreeUI {
|
||||||
KeyCode::Down => editor.move_down(),
|
KeyCode::Down => editor.move_down(),
|
||||||
KeyCode::Left => editor.move_left(),
|
KeyCode::Left => editor.move_left(),
|
||||||
KeyCode::Right => editor.move_right(),
|
KeyCode::Right => editor.move_right(),
|
||||||
|
KeyCode::PageUp => editor.page_up(),
|
||||||
|
KeyCode::PageDown => editor.page_down(),
|
||||||
|
KeyCode::Home => {
|
||||||
|
if modifiers.contains(KeyModifiers::CONTROL) {
|
||||||
|
editor.goto_line(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::End => {
|
||||||
|
if modifiers.contains(KeyModifiers::CONTROL) {
|
||||||
|
let line_count = editor.file_path().lines().count().max(1);
|
||||||
|
editor.goto_line(line_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
KeyCode::Char(c) => editor.insert_char(c),
|
KeyCode::Char(c) => editor.insert_char(c),
|
||||||
KeyCode::Backspace => editor.backspace(),
|
KeyCode::Backspace => editor.backspace(),
|
||||||
KeyCode::Enter => editor.insert_newline(),
|
KeyCode::Enter => editor.insert_newline(),
|
||||||
|
|
@ -857,9 +906,52 @@ impl XtreeUI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ActivePanel::Logs => match key {
|
||||||
|
KeyCode::Up | KeyCode::Char('k') => {
|
||||||
|
if let Ok(mut panel) = self.log_panel.lock() {
|
||||||
|
panel.scroll_up(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Down | KeyCode::Char('j') => {
|
||||||
|
if let Ok(mut panel) = self.log_panel.lock() {
|
||||||
|
panel.scroll_down(1, 10); // approximate visible lines
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::PageUp => {
|
||||||
|
if let Ok(mut panel) = self.log_panel.lock() {
|
||||||
|
panel.page_up(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::PageDown => {
|
||||||
|
if let Ok(mut panel) = self.log_panel.lock() {
|
||||||
|
panel.page_down(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Home => {
|
||||||
|
if let Ok(mut panel) = self.log_panel.lock() {
|
||||||
|
panel.scroll_to_top();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::End => {
|
||||||
|
if let Ok(mut panel) = self.log_panel.lock() {
|
||||||
|
panel.scroll_to_bottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Tab => {
|
||||||
|
self.active_panel = ActivePanel::Chat;
|
||||||
|
}
|
||||||
|
KeyCode::Char('q') => {
|
||||||
|
self.should_quit = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
ActivePanel::Chat => match key {
|
ActivePanel::Chat => match key {
|
||||||
KeyCode::Tab => {
|
KeyCode::Tab => {
|
||||||
self.active_panel = ActivePanel::FileTree;
|
if self.editor.is_some() {
|
||||||
|
self.active_panel = ActivePanel::Editor;
|
||||||
|
} else {
|
||||||
|
self.active_panel = ActivePanel::FileTree;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
if let (Some(chat_panel), Some(file_tree), Some(app_state)) =
|
if let (Some(chat_panel), Some(file_tree), Some(app_state)) =
|
||||||
|
|
@ -887,13 +979,7 @@ impl XtreeUI {
|
||||||
},
|
},
|
||||||
ActivePanel::Status => match key {
|
ActivePanel::Status => match key {
|
||||||
KeyCode::Tab => {
|
KeyCode::Tab => {
|
||||||
self.active_panel = ActivePanel::Logs;
|
self.active_panel = ActivePanel::Chat;
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
ActivePanel::Logs => match key {
|
|
||||||
KeyCode::Tab => {
|
|
||||||
self.active_panel = ActivePanel::FileTree;
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue