compiled still not working
This commit is contained in:
@@ -133,6 +133,21 @@ impl<D: DataProvider> FormEditor<D> {
|
|||||||
self.update_inline_completion();
|
self.update_inline_completion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn suggestions_prev(&mut self) {
|
||||||
|
if !self.ui_state.suggestions.is_active || self.suggestions.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = self.ui_state.suggestions.selected_index.unwrap_or(0);
|
||||||
|
let prev = if current == 0 {
|
||||||
|
self.suggestions.len() - 1
|
||||||
|
} else {
|
||||||
|
current - 1
|
||||||
|
};
|
||||||
|
self.ui_state.suggestions.selected_index = Some(prev);
|
||||||
|
self.update_inline_completion();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_suggestion(&mut self) -> Option<String> {
|
pub fn apply_suggestion(&mut self) -> Option<String> {
|
||||||
if let Some(selected_index) = self.ui_state.suggestions.selected_index {
|
if let Some(selected_index) = self.ui_state.suggestions.selected_index {
|
||||||
if let Some(suggestion) = self.suggestions.get(selected_index).cloned()
|
if let Some(suggestion) = self.suggestions.get(selected_index).cloned()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ license.workspace = true
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
async-trait = "0.1.88"
|
async-trait = "0.1.88"
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
canvas = { path = "../canvas", features = ["gui", "suggestions", "cursor-style"] }
|
canvas = { path = "../canvas", features = ["gui", "suggestions", "cursor-style", "keymap"] }
|
||||||
|
|
||||||
ratatui = { workspace = true }
|
ratatui = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use std::collections::HashMap;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use crossterm::event::{KeyCode, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
|
use canvas::CanvasKeyMap;
|
||||||
|
|
||||||
// NEW: Editor Keybinding Mode Enum
|
// NEW: Editor Keybinding Mode Enum
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
@@ -760,4 +761,43 @@ impl Config {
|
|||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unified action resolver for app-level actions
|
||||||
|
pub fn get_app_action(
|
||||||
|
&self,
|
||||||
|
key_code: crossterm::event::KeyCode,
|
||||||
|
modifiers: crossterm::event::KeyModifiers,
|
||||||
|
) -> Option<&str> {
|
||||||
|
// First check common actions
|
||||||
|
if let Some(action) = self.get_common_action(key_code, modifiers) {
|
||||||
|
return Some(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check read-only mode actions
|
||||||
|
if let Some(action) = self.get_read_only_action_for_key(key_code, modifiers) {
|
||||||
|
return Some(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check highlight mode actions
|
||||||
|
if let Some(action) = self.get_highlight_action_for_key(key_code, modifiers) {
|
||||||
|
return Some(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check edit mode actions
|
||||||
|
if let Some(action) = self.get_edit_action_for_key(key_code, modifiers) {
|
||||||
|
return Some(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_canvas_keymap(&self) -> CanvasKeyMap {
|
||||||
|
CanvasKeyMap::from_mode_maps(
|
||||||
|
&self.keybindings.read_only,
|
||||||
|
&self.keybindings.edit,
|
||||||
|
&self.keybindings.highlight,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ use crate::tui::{
|
|||||||
};
|
};
|
||||||
use crate::ui::handlers::context::UiContext;
|
use crate::ui::handlers::context::UiContext;
|
||||||
use crate::ui::handlers::rat_state::UiStateHandler;
|
use crate::ui::handlers::rat_state::UiStateHandler;
|
||||||
|
use canvas::KeyEventOutcome;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use common::proto::komp_ac::search::search_response::Hit;
|
use common::proto::komp_ac::search::search_response::Hit;
|
||||||
use crossterm::event::KeyModifiers;
|
use crossterm::event::KeyModifiers;
|
||||||
@@ -650,80 +651,29 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AppMode::ReadOnly => {
|
AppMode::ReadOnly => {
|
||||||
// Handle highlight mode transitions (delegated to FormEditor)
|
// First let the canvas editor try to handle the key
|
||||||
if config.get_read_only_action_for_key(key_code, modifiers)
|
if app_state.ui.show_form {
|
||||||
== Some("enter_highlight_mode_linewise")
|
|
||||||
&& ModeManager::can_enter_highlight_mode(current_mode)
|
|
||||||
{
|
|
||||||
if let Some(editor) = &mut app_state.form_editor {
|
if let Some(editor) = &mut app_state.form_editor {
|
||||||
editor.enter_highlight_line_mode();
|
match editor.handle_key_event(key_event) {
|
||||||
|
KeyEventOutcome::Consumed(Some(msg)) => {
|
||||||
|
return Ok(EventOutcome::Ok(msg));
|
||||||
|
}
|
||||||
|
KeyEventOutcome::Consumed(None) => {
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
|
}
|
||||||
|
KeyEventOutcome::Pending => {
|
||||||
|
// Waiting for multi-key sequence (e.g. after pressing 'g')
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
|
}
|
||||||
|
KeyEventOutcome::NotMatched => {
|
||||||
|
// Fall through to client-level actions
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.command_message = "-- LINE HIGHLIGHT --".to_string();
|
|
||||||
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
|
||||||
} else if config.get_read_only_action_for_key(key_code, modifiers)
|
|
||||||
== Some("enter_highlight_mode")
|
|
||||||
&& ModeManager::can_enter_highlight_mode(current_mode)
|
|
||||||
{
|
|
||||||
if let Some(editor) = &mut app_state.form_editor {
|
|
||||||
editor.enter_highlight_mode();
|
|
||||||
}
|
|
||||||
self.command_message = "-- HIGHLIGHT --".to_string();
|
|
||||||
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle edit mode transitions
|
// Entering command mode is still a client-level action
|
||||||
else if config.get_read_only_action_for_key(key_code, modifiers).as_deref()
|
if config.get_app_action(key_code, modifiers) == Some("enter_command_mode")
|
||||||
== Some("enter_edit_mode_before")
|
|
||||||
&& ModeManager::can_enter_edit_mode(current_mode)
|
|
||||||
{
|
|
||||||
if let Some(editor) = &mut app_state.form_editor {
|
|
||||||
editor.enter_edit_mode();
|
|
||||||
}
|
|
||||||
self.is_edit_mode = true;
|
|
||||||
self.edit_mode_cooldown = true;
|
|
||||||
self.command_message = "Edit mode".to_string();
|
|
||||||
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
|
||||||
} else if config.get_read_only_action_for_key(key_code, modifiers).as_deref()
|
|
||||||
== Some("enter_edit_mode_after")
|
|
||||||
&& ModeManager::can_enter_edit_mode(current_mode)
|
|
||||||
{
|
|
||||||
let current_input = Self::get_current_input_for_state(
|
|
||||||
app_state,
|
|
||||||
login_state,
|
|
||||||
register_state,
|
|
||||||
form_state,
|
|
||||||
);
|
|
||||||
let current_cursor_pos =
|
|
||||||
Self::get_cursor_pos_for_mixed_state(app_state, login_state, form_state);
|
|
||||||
|
|
||||||
// Move cursor forward if possible
|
|
||||||
if !current_input.is_empty() && current_cursor_pos < current_input.len() {
|
|
||||||
let new_cursor_pos = current_cursor_pos + 1;
|
|
||||||
Self::set_current_cursor_pos_for_state(
|
|
||||||
app_state,
|
|
||||||
login_state,
|
|
||||||
register_state,
|
|
||||||
form_state,
|
|
||||||
new_cursor_pos,
|
|
||||||
);
|
|
||||||
self.ideal_cursor_column = Self::get_current_cursor_pos_for_state(
|
|
||||||
app_state,
|
|
||||||
login_state,
|
|
||||||
register_state,
|
|
||||||
form_state,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(editor) = &mut app_state.form_editor {
|
|
||||||
editor.enter_edit_mode();
|
|
||||||
}
|
|
||||||
self.is_edit_mode = true;
|
|
||||||
self.edit_mode_cooldown = true;
|
|
||||||
app_state.ui.focus_outside_canvas = false;
|
|
||||||
self.command_message = "Edit mode (after cursor)".to_string();
|
|
||||||
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
|
||||||
} else if config.get_read_only_action_for_key(key_code, modifiers)
|
|
||||||
== Some("enter_command_mode")
|
|
||||||
&& ModeManager::can_enter_command_mode(current_mode)
|
&& ModeManager::can_enter_command_mode(current_mode)
|
||||||
{
|
{
|
||||||
if let Some(editor) = &mut app_state.form_editor {
|
if let Some(editor) = &mut app_state.form_editor {
|
||||||
@@ -736,7 +686,7 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle common actions (save, quit, etc.)
|
// Handle common actions (save, quit, etc.)
|
||||||
if let Some(action) = config.get_common_action(key_code, modifiers) {
|
if let Some(action) = config.get_app_action(key_code, modifiers) {
|
||||||
match action {
|
match action {
|
||||||
"save" | "force_quit" | "save_and_quit" | "revert" => {
|
"save" | "force_quit" | "save_and_quit" | "revert" => {
|
||||||
return self
|
return self
|
||||||
@@ -755,37 +705,27 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try canvas action for form first
|
|
||||||
if app_state.ui.show_form {
|
|
||||||
if let Some(editor) = &mut app_state.form_editor {
|
|
||||||
if let Ok(Some(canvas_message)) =
|
|
||||||
self.handle_form_canvas_action(key_event, editor, config, false).await
|
|
||||||
{
|
|
||||||
return Ok(EventOutcome::Ok(canvas_message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
AppMode::Highlight => {
|
AppMode::Highlight => {
|
||||||
if config.get_highlight_action_for_key(key_code, modifiers)
|
if app_state.ui.show_form {
|
||||||
== Some("exit_highlight_mode")
|
|
||||||
{
|
|
||||||
if let Some(editor) = &mut app_state.form_editor {
|
if let Some(editor) = &mut app_state.form_editor {
|
||||||
editor.exit_highlight_mode();
|
match editor.handle_key_event(key_event) {
|
||||||
|
KeyEventOutcome::Consumed(Some(msg)) => {
|
||||||
|
return Ok(EventOutcome::Ok(msg));
|
||||||
|
}
|
||||||
|
KeyEventOutcome::Consumed(None) => {
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
|
}
|
||||||
|
KeyEventOutcome::Pending => {
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
|
}
|
||||||
|
KeyEventOutcome::NotMatched => {
|
||||||
|
// Fall through to client-level actions
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.command_message = "Exited highlight mode".to_string();
|
|
||||||
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
|
||||||
} else if config.get_highlight_action_for_key(key_code, modifiers)
|
|
||||||
== Some("enter_highlight_mode_linewise")
|
|
||||||
{
|
|
||||||
if let Some(editor) = &mut app_state.form_editor {
|
|
||||||
editor.enter_highlight_line_mode();
|
|
||||||
}
|
|
||||||
self.command_message = "-- LINE HIGHLIGHT --".to_string();
|
|
||||||
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
||||||
@@ -793,7 +733,7 @@ impl EventHandler {
|
|||||||
|
|
||||||
AppMode::Edit => {
|
AppMode::Edit => {
|
||||||
// Handle common actions (save, quit, etc.)
|
// Handle common actions (save, quit, etc.)
|
||||||
if let Some(action) = config.get_common_action(key_code, modifiers) {
|
if let Some(action) = config.get_app_action(key_code, modifiers) {
|
||||||
match action {
|
match action {
|
||||||
"save" | "force_quit" | "save_and_quit" | "revert" => {
|
"save" | "force_quit" | "save_and_quit" | "revert" => {
|
||||||
return self
|
return self
|
||||||
@@ -812,19 +752,24 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try canvas action for form first
|
// Let the canvas editor handle edit-mode keys
|
||||||
if app_state.ui.show_form {
|
if app_state.ui.show_form {
|
||||||
if let Some(editor) = &mut app_state.form_editor {
|
if let Some(editor) = &mut app_state.form_editor {
|
||||||
if let Ok(Some(canvas_message)) = self.handle_form_canvas_action(
|
match editor.handle_key_event(key_event) {
|
||||||
key_event,
|
KeyEventOutcome::Consumed(Some(msg)) => {
|
||||||
editor,
|
self.command_message = msg.clone();
|
||||||
config,
|
return Ok(EventOutcome::Ok(msg));
|
||||||
true,
|
}
|
||||||
).await {
|
KeyEventOutcome::Consumed(None) => {
|
||||||
if !canvas_message.is_empty() {
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
self.command_message = canvas_message.clone();
|
}
|
||||||
|
KeyEventOutcome::Pending => {
|
||||||
|
// Rare in edit mode, but allow multi-key sequences if defined
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
|
}
|
||||||
|
KeyEventOutcome::NotMatched => {
|
||||||
|
// Fall through to client-level actions
|
||||||
}
|
}
|
||||||
return Ok(EventOutcome::Ok(canvas_message));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -973,66 +918,6 @@ impl EventHandler {
|
|||||||
matches!(command, "w" | "q" | "q!" | "wq" | "r")
|
matches!(command, "w" | "q" | "q!" | "wq" | "r")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_form_canvas_action(
|
|
||||||
&mut self,
|
|
||||||
key_event: KeyEvent,
|
|
||||||
editor: &mut FormEditor<FormState>,
|
|
||||||
config: &Config,
|
|
||||||
is_edit_mode: bool,
|
|
||||||
) -> Result<Option<String>> {
|
|
||||||
use crossterm::event::KeyCode;
|
|
||||||
|
|
||||||
if is_edit_mode {
|
|
||||||
if let KeyCode::Char(c) = key_event.code {
|
|
||||||
if key_event.modifiers.is_empty() || key_event.modifiers == KeyModifiers::SHIFT {
|
|
||||||
editor.insert_char(c)?;
|
|
||||||
return Ok(Some(format!("Inserted '{}'", c)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use your config to resolve actions
|
|
||||||
if let Some(action) = config.get_edit_action_for_key(key_event.code, key_event.modifiers) {
|
|
||||||
match action {
|
|
||||||
"delete_char_backward" => { editor.delete_backward()?; return Ok(Some("Deleted backward".to_string())); }
|
|
||||||
"delete_char_forward" => { editor.delete_forward()?; return Ok(Some("Deleted forward".to_string())); }
|
|
||||||
"move_left" => { editor.move_left()?; return Ok(Some("Moved left".to_string())); }
|
|
||||||
"move_right" => { editor.move_right()?; return Ok(Some("Moved right".to_string())); }
|
|
||||||
"move_up" => { editor.move_up()?; return Ok(Some("Moved up".to_string())); }
|
|
||||||
"move_down" => { editor.move_down()?; return Ok(Some("Moved down".to_string())); }
|
|
||||||
|
|
||||||
"move_line_start" => { editor.move_line_start(); return Ok(Some("Line start".to_string())); }
|
|
||||||
"move_line_end" => { editor.move_line_end(); return Ok(Some("Line end".to_string())); }
|
|
||||||
"move_word_next" => { editor.move_word_next(); return Ok(Some("Next word".to_string())); }
|
|
||||||
"move_word_prev" => { editor.move_word_prev(); return Ok(Some("Prev word".to_string())); }
|
|
||||||
"move_word_end" => { editor.move_word_end(); return Ok(Some("Word end".to_string())); }
|
|
||||||
"move_word_end_prev" => { editor.move_word_end_prev(); return Ok(Some("Prev word end".to_string())); }
|
|
||||||
|
|
||||||
"next_field" => { editor.next_field()?; return Ok(Some("Next field".to_string())); }
|
|
||||||
"prev_field" => { editor.prev_field()?; return Ok(Some("Prev field".to_string())); }
|
|
||||||
"open_suggestions" => {
|
|
||||||
let field_index = editor.current_field();
|
|
||||||
editor.open_suggestions(field_index);
|
|
||||||
return Ok(Some("Opened suggestions".to_string()));
|
|
||||||
}
|
|
||||||
"apply_suggestion" | "enter_decider" => {
|
|
||||||
if let Some(s) = editor.apply_suggestion() {
|
|
||||||
return Ok(Some(format!("Applied suggestion: {}", s)));
|
|
||||||
} else {
|
|
||||||
return Ok(Some("No suggestion applied".to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"exit" | "exit_edit_mode" => {
|
|
||||||
editor.exit_edit_mode()?;
|
|
||||||
return Ok(Some("Exited edit mode".to_string()));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_core_action(
|
async fn handle_core_action(
|
||||||
&mut self,
|
&mut self,
|
||||||
action: &str,
|
action: &str,
|
||||||
|
|||||||
Reference in New Issue
Block a user