use crate::app::input::drag::document_view_selected_text_from_node;
use crate::callback::KeyHandler;
use crate::core::event::{KeyCode, KeyEvent};
use crate::core::node::{NodeId, NodeKind, NodeTree};
use crate::ui::capabilities::{ReadOnlyClipboardContext, selection_range};
use crate::widgets::internal::{ScrollAction, apply_scroll_action};
use crate::widgets::{ScrollEvent, ScrollMetrics};
pub(crate) fn handle_key(
tree: &mut NodeTree,
id: NodeId,
key: KeyEvent,
ctx: &mut super::KeyCtx<'_>,
) -> bool {
let handle_key_cb = |handler: &KeyHandler| -> bool { handler.handle(key) };
let NodeKind::DocumentView(dv) = &tree.node(id).kind else {
return false;
};
let clipboard_handled;
{
let selected_text = if let Some(table_sel) = &dv.table_rect_selection {
Some(table_sel.tsv_text.to_string())
} else {
let selection = selection_range(
dv.selection_cursor,
dv.selection_anchor,
dv.visual_cache.flat_text.len(),
);
selection.and_then(|(start, end)| {
document_view_selected_text_from_node(dv, start, end, true)
})
};
let selection = selected_text.as_ref().map(|text| (0, text.len()));
let text = selected_text.as_deref().unwrap_or("");
let mut context = if let Some(table_sel) = &dv.table_rect_selection {
ReadOnlyClipboardContext::new(
table_sel.tsv_text.as_ref(),
Some((0, table_sel.tsv_text.len())),
true, false, )
} else {
ReadOnlyClipboardContext::new(text, selection, true, false)
};
let dispatch = crate::app::copy_feedback::dispatch_clipboard_with_feedback_result(
key,
ctx.keymap,
&mut context,
ctx.clipboard,
ctx.clipboard_config,
ctx.copy_feedback,
id,
);
clipboard_handled = ctx.record_copy_feedback_dispatch(dispatch);
}
if clipboard_handled {
return true;
}
let NodeKind::DocumentView(dv) = &tree.node(id).kind else {
return false;
};
let mut handled = false;
let current_offset = dv.scroll_offset;
let total = dv.total_visual_lines;
let on_key = dv.on_key.clone();
let on_scroll = dv.on_scroll.clone();
let source_target_cancel_pending = dv.scroll_to_source_line.is_some()
&& dv.cancelled_scroll_to_source_line != dv.scroll_to_source_line;
let node_rect = tree.node(id).rect;
let NodeKind::DocumentView(dv) = &tree.node(id).kind else {
return false;
};
let inner = node_rect.inner(dv.border, dv.padding);
let cl = dv.content_layout(inner);
let viewport_h = cl.content_height as usize;
let max_offset = total.saturating_sub(viewport_h);
let new_offset = match key.code {
KeyCode::Up | KeyCode::Char('k') => Some(current_offset.saturating_sub(1)),
KeyCode::Down | KeyCode::Char('j') => Some((current_offset + 1).min(max_offset)),
KeyCode::PageUp => Some(current_offset.saturating_sub(viewport_h)),
KeyCode::PageDown => Some((current_offset + viewport_h).min(max_offset)),
KeyCode::Home => Some(0),
KeyCode::End => Some(max_offset),
_ => None,
};
if let Some(offset) = new_offset {
let offset_changed = offset != current_offset;
if offset_changed || source_target_cancel_pending {
if let NodeKind::DocumentView(dv) = &mut tree.node_mut(id).kind {
dv.scroll_offset = offset;
dv.smooth_scroll.cancel_at(offset);
dv.cancelled_scroll_to_source_line = dv.scroll_to_source_line;
}
if offset_changed {
if let Some(cb) = on_scroll.as_ref() {
cb.emit(ScrollEvent {
offset,
metrics: ScrollMetrics {
len: total,
visible: viewport_h,
max_offset,
},
});
}
}
handled = true;
}
}
if !handled && let Some(cb) = on_key.as_ref() {
handled = handle_key_cb(cb);
}
handled
}
pub(crate) fn handle_scroll(tree: &mut NodeTree, id: NodeId, action: ScrollAction) -> bool {
let node = tree.node(id);
let rect = node.rect;
let NodeKind::DocumentView(dv) = &node.kind else {
return false;
};
let inner = rect.inner(dv.border, dv.padding);
let cl = dv.content_layout(inner);
let viewport_h = cl.content_height as usize;
let total = dv.total_visual_lines;
let max_offset = total.saturating_sub(viewport_h);
let current_offset = dv.scroll_offset;
let on_scroll = dv.on_scroll.clone();
let source_target_cancel_pending = dv.scroll_to_source_line.is_some()
&& dv.cancelled_scroll_to_source_line != dv.scroll_to_source_line;
if !dv.scroll_wheel {
return false;
}
if viewport_h == 0 || total <= viewport_h {
if source_target_cancel_pending {
if let NodeKind::DocumentView(dv) = &mut tree.node_mut(id).kind {
dv.smooth_scroll.cancel_at(current_offset);
dv.cancelled_scroll_to_source_line = dv.scroll_to_source_line;
dv.scroll_override = Some(current_offset);
}
return true;
}
return false;
}
let metrics = ScrollMetrics {
len: total,
visible: viewport_h,
max_offset,
};
let next = apply_scroll_action(current_offset, metrics, action).min(max_offset);
let offset_changed = next != current_offset;
if offset_changed || source_target_cancel_pending {
if let NodeKind::DocumentView(dv) = &mut tree.node_mut(id).kind {
dv.scroll_offset = next;
dv.scroll_override = Some(next);
dv.smooth_scroll.cancel_at(next);
dv.cancelled_scroll_to_source_line = dv.scroll_to_source_line;
}
if offset_changed && let Some(cb) = on_scroll.as_ref() {
cb.emit(ScrollEvent {
offset: next,
metrics,
});
}
true
} else {
false
}
}