use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use crate::tui::app::{
App, AutocompleteKind, AutocompleteState, DetailRegion, EditHistory, EditTarget, FlatItem,
Mode, StateFilter, View,
};
use super::*;
pub(super) fn handle_navigate(app: &mut App, key: KeyEvent) {
if app.recovery_message.is_some()
&& let Some(at) = app.recovery_message_at
&& at.elapsed() >= std::time::Duration::from_secs(3)
{
app.recovery_message = None;
app.recovery_message_at = None;
}
if app.conflict_text.is_some() {
if matches!(key.code, KeyCode::Esc) {
if let Some(ref text) = app.conflict_text {
crate::io::recovery::log_recovery(
&app.project.frame_dir,
crate::io::recovery::RecoveryEntry {
timestamp: chrono::Utc::now(),
category: crate::io::recovery::RecoveryCategory::Conflict,
description: "dismissed conflict".to_string(),
fields: vec![],
body: text.clone(),
},
);
}
app.conflict_text = None;
}
return;
}
if app.show_help {
match key.code {
KeyCode::Char('?') | KeyCode::Esc => {
app.show_help = false;
}
KeyCode::Char('j') | KeyCode::Down => {
app.help_scroll = app.help_scroll.saturating_add(1);
}
KeyCode::Char('k') | KeyCode::Up => {
app.help_scroll = app.help_scroll.saturating_sub(1);
}
KeyCode::Char('g') => {
app.help_scroll = 0;
}
KeyCode::Char('G') => {
app.help_scroll = usize::MAX;
}
_ => {}
}
return;
}
if app.project_picker.is_some() {
handle_project_picker_key(app, key);
return;
}
if app.tag_color_popup.is_some() {
handle_tag_color_popup_key(app, key);
return;
}
if app.dep_popup.is_some() {
handle_dep_popup_key(app, key);
return;
}
if let Some(ref pr) = app.prefix_rename
&& pr.confirming
{
match key.code {
KeyCode::Enter => {
execute_prefix_rename(app);
}
KeyCode::Esc => {
if let Some(ref mut pr) = app.prefix_rename {
let old_prefix = pr.old_prefix.clone();
let track_id = pr.track_id.clone();
let new_prefix = pr.new_prefix.clone();
pr.confirming = false;
app.edit_buffer = new_prefix.clone();
app.edit_cursor = new_prefix.len();
app.edit_target = Some(EditTarget::ExistingPrefix {
track_id,
original_prefix: old_prefix,
});
app.edit_history = Some(EditHistory::new(&new_prefix, new_prefix.len(), 0));
app.edit_selection_anchor = None;
app.mode = Mode::Edit;
}
}
_ => {}
}
return;
}
if app.filter_pending {
app.filter_pending = false;
handle_filter_key(app, key);
return;
}
app.status_message = None;
app.status_is_error = false;
if matches!(key.code, KeyCode::Esc) {
app.esc_streak = app.esc_streak.saturating_add(1);
if app.esc_streak >= 5 {
app.status_message = Some("type QQ to quit".to_string());
}
} else {
app.esc_streak = 0;
}
if app.quit_pending {
if matches!(
(key.modifiers, key.code),
(KeyModifiers::SHIFT, KeyCode::Char('Q'))
) {
app.should_quit = true;
return;
} else {
app.quit_pending = false;
return;
}
}
match (key.modifiers, key.code) {
(m, KeyCode::Char('q')) if m.contains(KeyModifiers::CONTROL) => {
app.should_quit = true;
}
(m, KeyCode::Char('d')) if m.contains(KeyModifiers::CONTROL) => {
app.key_debug = !app.key_debug;
if !app.key_debug {
app.last_key_event = None;
}
}
(KeyModifiers::SHIFT, KeyCode::Char('Q')) => {
app.quit_pending = true;
app.status_message = Some("press Q again to quit".to_string());
}
(_, KeyCode::Esc) => {
if let View::Detail { .. } = &app.view {
if let Some((parent_track, parent_task)) = app.detail_stack.pop() {
let return_view = app
.detail_state
.as_ref()
.map(|ds| ds.return_view.clone())
.unwrap_or(crate::tui::app::ReturnView::Track(0));
app.detail_state = None;
app.view = View::Detail {
track_id: parent_track.clone(),
task_id: parent_task.clone(),
};
let regions = if let Some(track) =
App::find_track_in_project(&app.project, &parent_track)
{
if let Some(task) =
crate::ops::task_ops::find_task_in_track(track, &parent_task)
{
App::build_detail_regions(task)
} else {
vec![DetailRegion::Title]
}
} else {
vec![DetailRegion::Title]
};
let region = if regions.contains(&DetailRegion::Subtasks) {
DetailRegion::Subtasks
} else {
regions.first().copied().unwrap_or(DetailRegion::Title)
};
app.detail_state = Some(crate::tui::app::DetailState {
region,
scroll_offset: 0,
regions,
return_view,
editing: false,
edit_buffer: String::new(),
edit_cursor_line: 0,
edit_cursor_col: 0,
edit_original: String::new(),
subtask_cursor: 0,
flat_subtask_ids: Vec::new(),
multiline_selection_anchor: None,
note_h_scroll: 0,
sticky_col: None,
total_lines: 0,
note_view_line: None,
note_header_line: None,
note_content_end: 0,
regions_populated: Vec::new(),
});
} else {
let return_view = app
.detail_state
.as_ref()
.map(|ds| ds.return_view.clone())
.unwrap_or(crate::tui::app::ReturnView::Track(0));
match return_view {
crate::tui::app::ReturnView::Track(idx) => app.view = View::Track(idx),
crate::tui::app::ReturnView::Recent => app.view = View::Recent,
crate::tui::app::ReturnView::Board => app.view = View::Board,
}
app.close_detail_fully();
}
} else if app.view == View::Search {
if let Some(sr) = app.project_search_results.take() {
app.view = sr.return_view;
}
} else if app.project_search_results.is_some() {
app.view = View::Search;
} else if app.last_search.is_some() {
app.last_search = None;
app.search_match_idx = 0;
app.search_wrap_message = None;
app.search_match_count = None;
app.search_zero_confirmed = false;
}
}
(_, KeyCode::Backspace)
if app.last_search.is_some() && matches!(app.view, View::Detail { .. }) =>
{
app.last_search = None;
app.search_match_idx = 0;
app.search_wrap_message = None;
app.search_match_count = None;
app.search_zero_confirmed = false;
}
(KeyModifiers::NONE, KeyCode::Char('?')) => {
app.show_help = true;
app.help_scroll = 0;
}
(KeyModifiers::NONE, KeyCode::Char('/')) if !matches!(app.view, View::Search) => {
app.mode = Mode::Search;
app.search_input.clear();
app.search_draft.clear();
app.search_history_index = None;
app.search_wrap_message = None;
app.search_match_count = None;
app.search_zero_confirmed = false;
}
(KeyModifiers::NONE, KeyCode::Char('n')) => {
if matches!(app.view, View::Search) {
move_cursor(app, 1);
} else if app.last_search.is_some() {
search_next(app, 1);
} else if matches!(app.view, View::Detail { .. }) {
detail_jump_to_region_and_edit(app, DetailRegion::Note, true);
} else if matches!(app.view, View::Inbox) {
inbox_edit_note(app, true);
}
}
(KeyModifiers::SHIFT, KeyCode::Char('N')) => {
if matches!(app.view, View::Search) {
move_cursor(app, -1);
} else if app.last_search.is_some() {
search_next(app, -1);
} else if matches!(app.view, View::Detail { .. }) {
detail_jump_to_region_and_edit(app, DetailRegion::Note, false);
} else if matches!(app.view, View::Inbox) {
inbox_edit_note(app, false);
}
}
(KeyModifiers::NONE, KeyCode::Char(c @ '1'..='9')) => {
let idx = (c as usize) - ('1' as usize);
if idx < app.active_track_ids.len() {
app.close_detail_fully();
app.project_search_results = None;
app.view = View::Track(idx);
}
}
(KeyModifiers::NONE, KeyCode::Tab) => {
if matches!(app.view, View::Detail { .. }) {
detail_jump_editable(app, 1);
} else {
switch_tab(app, 1);
}
}
(KeyModifiers::SHIFT, KeyCode::BackTab) => {
if matches!(app.view, View::Detail { .. }) {
detail_jump_editable(app, -1);
} else {
switch_tab(app, -1);
}
}
(KeyModifiers::NONE, KeyCode::Char('i')) => {
app.close_detail_fully();
app.project_search_results = None;
app.view = View::Inbox;
}
(KeyModifiers::NONE, KeyCode::Char('r')) => {
app.close_detail_fully();
app.project_search_results = None;
app.view = View::Recent;
}
(KeyModifiers::NONE, KeyCode::Char('0') | KeyCode::Char('`')) => {
app.close_detail_fully();
app.project_search_results = None;
app.tracks_name_col_min = 0;
app.view = View::Tracks;
}
(KeyModifiers::SHIFT, KeyCode::Char('K')) => {
app.close_detail_fully();
app.project_search_results = None;
app.view = View::Board;
}
(KeyModifiers::SHIFT, KeyCode::Char('S')) => {
app.project_search_active = true;
app.project_search_input.clear();
app.project_search_draft.clear();
app.project_search_history_index = None;
app.mode = Mode::Search;
}
(KeyModifiers::NONE, KeyCode::Up | KeyCode::Char('k')) => {
move_cursor(app, -1);
}
(KeyModifiers::NONE, KeyCode::Down | KeyCode::Char('j')) => {
move_cursor(app, 1);
}
(m, KeyCode::Up) if m.contains(KeyModifiers::ALT) => {
move_paragraph(app, -1);
}
(m, KeyCode::Down) if m.contains(KeyModifiers::ALT) => {
move_paragraph(app, 1);
}
(KeyModifiers::NONE, KeyCode::Char('g')) => {
jump_to_top(app);
}
(m, KeyCode::Up) if m.contains(KeyModifiers::SUPER) => {
jump_to_top(app);
}
(_, KeyCode::Home) => {
jump_to_top(app);
}
(KeyModifiers::SHIFT, KeyCode::Char('G')) => {
jump_to_bottom(app);
}
(m, KeyCode::Down) if m.contains(KeyModifiers::SUPER) => {
jump_to_bottom(app);
}
(_, KeyCode::End) => {
jump_to_bottom(app);
}
(KeyModifiers::NONE, KeyCode::Enter) => {
if matches!(app.view, View::Search) {
search_result_jump(app);
} else if matches!(app.view, View::Board) {
board_open_detail(app);
} else if matches!(app.view, View::Inbox) {
inbox_begin_triage(app);
} else if matches!(app.view, View::Recent) {
open_recent_detail(app);
} else {
handle_enter(app);
}
}
(KeyModifiers::NONE, KeyCode::Right | KeyCode::Char('l')) => {
if matches!(app.view, View::Board) {
board_switch_column(app, 1);
} else if matches!(app.view, View::Recent) {
expand_recent(app);
} else {
expand_or_enter(app);
}
}
(KeyModifiers::NONE, KeyCode::Left | KeyCode::Char('h')) => {
if matches!(app.view, View::Board) {
board_switch_column(app, -1);
} else if matches!(app.view, View::Recent) {
collapse_recent(app);
} else {
collapse_or_parent(app);
}
}
(KeyModifiers::NONE, KeyCode::Char(' ')) => {
if matches!(app.view, View::Recent) {
reopen_recent_task(app);
} else {
task_state_action(app, StateAction::Cycle);
}
}
(KeyModifiers::NONE, KeyCode::Char('x')) => {
if matches!(app.view, View::Inbox) {
inbox_delete_item(app);
} else {
task_state_action(app, StateAction::Done);
}
}
(KeyModifiers::NONE, KeyCode::Char('b')) => {
task_state_action(app, StateAction::ToggleBlocked);
}
(KeyModifiers::NONE, KeyCode::Char('~')) => {
task_state_action(app, StateAction::ToggleParked);
}
(KeyModifiers::NONE, KeyCode::Char('a')) => {
if matches!(app.view, View::Inbox) {
inbox_add_item(app);
} else if matches!(app.view, View::Tracks) {
tracks_add_track(app);
} else {
add_task_action(app, AddPosition::Bottom);
}
}
(KeyModifiers::NONE, KeyCode::Char('o')) => {
task_state_action(app, StateAction::SetTodo);
}
(KeyModifiers::NONE, KeyCode::Char('-')) => {
if matches!(app.view, View::Inbox) {
inbox_insert_after(app);
} else if matches!(app.view, View::Tracks) {
tracks_insert_after(app);
} else {
add_task_action(app, AddPosition::AfterCursor);
}
}
(KeyModifiers::NONE, KeyCode::Char('p')) => {
if matches!(app.view, View::Inbox) {
inbox_prepend_item(app);
} else if matches!(app.view, View::Tracks) {
tracks_prepend(app);
} else {
add_task_action(app, AddPosition::Top);
}
}
(KeyModifiers::SHIFT, KeyCode::Char('A')) => {
add_subtask_action(app);
}
(KeyModifiers::NONE, KeyCode::Char('=')) => {
if matches!(app.view, View::Inbox) {
inbox_add_item(app);
} else if matches!(app.view, View::Tracks) {
tracks_add_track(app);
} else {
append_sibling_action(app);
}
}
(KeyModifiers::NONE, KeyCode::Char('e')) => {
if matches!(app.view, View::Detail { .. }) {
detail_enter_edit(app, false);
} else if matches!(app.view, View::Board) {
board_enter_title_edit(app);
} else if matches!(app.view, View::Inbox) {
inbox_edit_title(app);
} else if matches!(app.view, View::Tracks) {
tracks_edit_name(app);
} else {
enter_title_edit(app);
}
}
(KeyModifiers::NONE, KeyCode::Char('t')) => {
if matches!(app.view, View::Detail { .. }) {
detail_jump_to_region_and_edit(app, DetailRegion::Tags, false);
} else if matches!(app.view, View::Board) {
board_enter_tag_edit(app);
} else if matches!(app.view, View::Inbox) {
inbox_edit_tags(app);
} else {
enter_tag_edit(app);
}
}
(KeyModifiers::NONE, KeyCode::Char('@')) => {
if matches!(app.view, View::Detail { .. }) {
detail_jump_to_region_and_edit(app, DetailRegion::Refs, false);
}
}
(KeyModifiers::NONE, KeyCode::Char('d')) => {
if matches!(app.view, View::Detail { .. }) {
detail_jump_to_region_and_edit(app, DetailRegion::Deps, false);
}
}
(KeyModifiers::NONE, KeyCode::Char('s')) => {
if matches!(app.view, View::Tracks) {
tracks_toggle_shelve(app);
}
}
(KeyModifiers::SHIFT, KeyCode::Char('D')) => {
if matches!(app.view, View::Track(_)) {
open_dep_popup_from_track_view(app);
} else if matches!(app.view, View::Board) {
board_open_dep_popup(app);
} else if matches!(app.view, View::Detail { .. }) {
open_dep_popup_from_detail_view(app);
}
}
(KeyModifiers::NONE, KeyCode::Char('c')) => {
if matches!(app.view, View::Board) {
board_toggle_mode(app);
} else {
toggle_cc_tag(app);
}
}
(KeyModifiers::SHIFT, KeyCode::Char('C')) => {
set_cc_focus_current(app);
}
(KeyModifiers::NONE, KeyCode::Char('w')) if matches!(app.view, View::Detail { .. }) => {
app.toggle_note_wrap();
app.status_message = Some(
if app.note_wrap {
"wrap: on"
} else {
"wrap: off"
}
.into(),
);
}
(KeyModifiers::NONE, KeyCode::Char('m')) => {
if matches!(app.view, View::Inbox) {
inbox_enter_move_mode(app);
} else {
enter_move_mode(app);
}
}
(KeyModifiers::SHIFT, KeyCode::Char('M')) => {
if matches!(app.view, View::Board) {
if let Some((track_id, task_id)) = app.board_cursor_task_id() {
app.view = View::Track(
app.active_track_ids
.iter()
.position(|id| id == &track_id)
.unwrap_or(0),
);
app.jump_to_task(&task_id);
begin_cross_track_move(app);
}
} else {
begin_cross_track_move(app);
}
}
(m, KeyCode::Char('y')) if m.contains(KeyModifiers::CONTROL) => {
perform_redo(app);
}
(m, KeyCode::Char('z') | KeyCode::Char('Z'))
if m.contains(KeyModifiers::CONTROL) && m.contains(KeyModifiers::SHIFT) =>
{
perform_redo(app);
}
(m, KeyCode::Char('z') | KeyCode::Char('Z'))
if m.contains(KeyModifiers::SUPER) && m.contains(KeyModifiers::SHIFT) =>
{
perform_redo(app);
}
(KeyModifiers::SHIFT, KeyCode::Char('Z')) => {
perform_redo(app);
}
(KeyModifiers::NONE, KeyCode::Char('u') | KeyCode::Char('z')) => {
perform_undo(app);
}
(m, KeyCode::Char('z')) if m.contains(KeyModifiers::CONTROL) => {
perform_undo(app);
}
(m, KeyCode::Char('z')) if m.contains(KeyModifiers::SUPER) => {
perform_undo(app);
}
(KeyModifiers::NONE, KeyCode::Char('f')) => {
if matches!(app.view, View::Track(_) | View::Board) {
app.filter_pending = true;
}
}
(KeyModifiers::NONE, KeyCode::Char('v')) => {
if matches!(app.view, View::Track(_)) {
enter_select_mode(app);
}
}
(KeyModifiers::SHIFT, KeyCode::Char('V')) => {
if matches!(app.view, View::Track(_)) {
begin_range_select(app);
}
}
(m, KeyCode::Char('a')) if m.contains(KeyModifiers::CONTROL) => {
if matches!(app.view, View::Track(_)) {
select_all(app);
}
}
(KeyModifiers::SHIFT, KeyCode::Char('J')) => {
begin_jump_to(app);
}
(KeyModifiers::NONE, KeyCode::Char('.')) => {
repeat_last_action(app);
}
(KeyModifiers::SHIFT, KeyCode::Char('T')) => {
app.open_tag_color_popup();
}
(KeyModifiers::SHIFT, KeyCode::Char('P')) => {
open_project_picker(app);
}
(_, KeyCode::Char('>')) => {
open_command_palette(app);
}
_ => {}
}
}
fn search_result_jump(app: &mut App) {
let item = match &app.project_search_results {
Some(sr) => match sr.items.get(sr.cursor) {
Some(item) => item.clone(),
None => return,
},
None => return,
};
match &item.kind {
crate::tui::app::SearchResultKind::Track { .. } => {
if !item.task_id.is_empty() {
app.jump_to_task(&item.task_id);
}
}
crate::tui::app::SearchResultKind::Inbox { item_index } => {
let idx = *item_index;
app.view = View::Inbox;
let count = app.inbox_count();
app.inbox_cursor = idx.min(count.saturating_sub(1));
}
crate::tui::app::SearchResultKind::Archive { track_id } => {
app.status_message = Some(format!("Archive task: {} (read-only)", track_id));
}
}
}
pub(super) fn handle_filter_key(app: &mut App, key: KeyEvent) {
let is_board = matches!(app.view, View::Board);
if !matches!(app.view, View::Track(_)) && !is_board {
return;
}
let prev_task_id = if is_board {
None
} else {
get_cursor_task_id(app)
};
match key.code {
KeyCode::Char('a') if !is_board => {
app.filter_state.state_filter = Some(StateFilter::Active);
reset_cursor_for_filter(app, prev_task_id.as_deref());
}
KeyCode::Char('o') if !is_board => {
app.filter_state.state_filter = Some(StateFilter::Todo);
reset_cursor_for_filter(app, prev_task_id.as_deref());
}
KeyCode::Char('b') if !is_board => {
app.filter_state.state_filter = Some(StateFilter::Blocked);
reset_cursor_for_filter(app, prev_task_id.as_deref());
}
KeyCode::Char('p') if !is_board => {
app.filter_state.state_filter = Some(StateFilter::Parked);
reset_cursor_for_filter(app, prev_task_id.as_deref());
}
KeyCode::Char('r') if !is_board => {
app.filter_state.state_filter = Some(StateFilter::Ready);
reset_cursor_for_filter(app, prev_task_id.as_deref());
}
KeyCode::Char('t') => {
begin_filter_tag_select(app);
}
KeyCode::Char(' ') if !is_board => {
app.filter_state.clear_state();
reset_cursor_for_filter(app, prev_task_id.as_deref());
}
KeyCode::Char('f') => {
app.filter_state.clear_all();
if !is_board {
reset_cursor_for_filter(app, prev_task_id.as_deref());
}
}
_ => {
}
}
}
pub(super) fn begin_filter_tag_select(app: &mut App) {
let candidates = app.collect_all_tags();
if candidates.is_empty() {
return;
}
app.mode = Mode::Edit;
app.edit_buffer = String::new();
app.edit_cursor = 0;
app.edit_selection_anchor = None;
app.edit_target = Some(EditTarget::FilterTag);
app.edit_history = Some(EditHistory::new("", 0, 0));
let mut ac = AutocompleteState::new(AutocompleteKind::Tag, candidates);
ac.filter("");
app.autocomplete = Some(ac);
}
pub(super) fn begin_jump_to(app: &mut App) {
let candidates = app.collect_active_track_task_ids();
if candidates.is_empty() {
app.status_message = Some("no tasks to jump to".to_string());
return;
}
app.mode = Mode::Edit;
app.edit_buffer = String::new();
app.edit_cursor = 0;
app.edit_selection_anchor = None;
app.edit_target = Some(EditTarget::JumpTo);
app.edit_history = Some(EditHistory::new("", 0, 0));
let mut ac = AutocompleteState::new(AutocompleteKind::JumpTaskId, candidates);
ac.filter("");
app.autocomplete = Some(ac);
}
pub(super) fn expand_or_enter(app: &mut App) {
if let View::Track(idx) = &app.view {
let track_id = match app.active_track_ids.get(*idx) {
Some(id) => id.clone(),
None => return,
};
let flat_items = app.build_flat_items(&track_id);
let cursor = app.track_states.get(&track_id).map_or(0, |s| s.cursor);
if cursor >= flat_items.len() {
return;
}
if let FlatItem::Task {
has_children,
is_expanded,
section,
path,
..
} = &flat_items[cursor]
{
if *has_children && !is_expanded {
let track = match app.current_track() {
Some(t) => t,
None => return,
};
if let Some(task) = resolve_task_from_track(track, *section, path) {
let key = crate::tui::app::task_expand_key(task, *section, path);
let state = app.get_track_state(&track_id);
state.expanded.insert(key);
}
} else if *has_children && *is_expanded && cursor + 1 < flat_items.len() {
let target = super::common::skip_non_selectable(&flat_items, cursor + 1, 1);
let state = app.get_track_state(&track_id);
state.cursor = target;
}
}
}
}
pub(super) fn collapse_or_parent(app: &mut App) {
if let View::Track(idx) = &app.view {
let track_id = match app.active_track_ids.get(*idx) {
Some(id) => id.clone(),
None => return,
};
let flat_items = app.build_flat_items(&track_id);
let cursor = app.track_states.get(&track_id).map_or(0, |s| s.cursor);
if cursor >= flat_items.len() {
return;
}
if let FlatItem::Task {
has_children: _,
is_expanded,
section,
path,
depth,
..
} = &flat_items[cursor]
{
if *is_expanded {
let track = match app.current_track() {
Some(t) => t,
None => return,
};
if let Some(task) = resolve_task_from_track(track, *section, path) {
let key = crate::tui::app::task_expand_key(task, *section, path);
let state = app.get_track_state(&track_id);
state.expanded.remove(&key);
}
} else if *depth > 0 {
let parent_depth = depth - 1;
for i in (0..cursor).rev() {
if let FlatItem::Task { depth: d, .. } = &flat_items[i]
&& *d == parent_depth
{
app.get_track_state(&track_id).cursor = i;
break;
}
}
}
}
}
}
pub(super) fn resolve_task_from_track<'a>(
track: &'a crate::model::Track,
section: crate::model::SectionKind,
path: &[usize],
) -> Option<&'a crate::model::Task> {
let tasks = track.section_tasks(section);
if path.is_empty() {
return None;
}
let mut current = tasks.get(path[0])?;
for &idx in &path[1..] {
current = current.subtasks.get(idx)?;
}
Some(current)
}
pub(super) fn count_tracks(app: &App) -> usize {
app.project.config.tracks.len()
}
pub(super) fn count_recent_tasks(app: &App) -> usize {
super::build_recent_entries(app).len()
}
fn board_switch_column(app: &mut App, direction: i32) {
use crate::tui::app::BoardColumn;
let max_col = (app.board_state.visible_columns as i32 - 1).max(0);
let cur = app.board_state.focus_column.index() as i32;
let new = (cur + direction).clamp(0, max_col);
app.board_state.focus_column = BoardColumn::from_index(new as usize);
}
fn board_toggle_mode(app: &mut App) {
use crate::tui::app::BoardMode;
app.board_state.mode = match app.board_state.mode {
BoardMode::Cc => BoardMode::All,
BoardMode::All => BoardMode::Cc,
};
app.board_state.cursor = [0; 3];
app.board_state.scroll = [0; 3];
}
fn board_open_detail(app: &mut App) {
if let Some((track_id, task_id)) = app.board_cursor_task_id() {
app.open_detail(track_id, task_id);
}
}
fn board_open_dep_popup(app: &mut App) {
if let Some((track_id, task_id)) = app.board_cursor_task_id() {
if let Some(idx) = app.active_track_ids.iter().position(|id| id == &track_id) {
let prev_view = app.view.clone();
app.view = View::Track(idx);
app.jump_to_task(&task_id);
open_dep_popup_from_track_view(app);
if app.dep_popup.is_none() {
app.view = prev_view;
}
}
}
}
fn board_enter_title_edit(app: &mut App) {
if let Some((track_id, task_id)) = app.board_cursor_task_id() {
if let Some(idx) = app.active_track_ids.iter().position(|id| id == &track_id) {
app.view = View::Track(idx);
app.jump_to_task(&task_id);
enter_title_edit(app);
}
}
}
fn board_enter_tag_edit(app: &mut App) {
if let Some((track_id, task_id)) = app.board_cursor_task_id()
&& let Some(idx) = app.active_track_ids.iter().position(|id| id == &track_id)
{
app.view = View::Track(idx);
app.jump_to_task(&task_id);
enter_tag_edit(app);
}
}