autocomplete is now powerful
This commit is contained in:
@@ -11,6 +11,7 @@ use crate::services::GrpcClient;
|
|||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crate::components::common::text_editor::TextEditor;
|
use crate::components::common::text_editor::TextEditor;
|
||||||
|
use crate::services::ui_service::UiService;
|
||||||
|
|
||||||
pub type SaveLogicResultSender = mpsc::Sender<Result<String>>;
|
pub type SaveLogicResultSender = mpsc::Sender<Result<String>>;
|
||||||
|
|
||||||
@@ -21,8 +22,8 @@ pub fn handle_add_logic_navigation(
|
|||||||
add_logic_state: &mut AddLogicState,
|
add_logic_state: &mut AddLogicState,
|
||||||
is_edit_mode: &mut bool,
|
is_edit_mode: &mut bool,
|
||||||
buffer_state: &mut BufferState,
|
buffer_state: &mut BufferState,
|
||||||
_grpc_client: GrpcClient,
|
grpc_client: GrpcClient,
|
||||||
_save_logic_sender: SaveLogicResultSender,
|
save_logic_sender: SaveLogicResultSender,
|
||||||
command_message: &mut String,
|
command_message: &mut String,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// === FULLSCREEN SCRIPT EDITING - COMPLETE ISOLATION ===
|
// === FULLSCREEN SCRIPT EDITING - COMPLETE ISOLATION ===
|
||||||
@@ -134,11 +135,14 @@ pub fn handle_add_logic_navigation(
|
|||||||
let trigger_pos = add_logic_state.script_editor_trigger_position;
|
let trigger_pos = add_logic_state.script_editor_trigger_position;
|
||||||
let filter_len = add_logic_state.script_editor_filter_text.len();
|
let filter_len = add_logic_state.script_editor_filter_text.len();
|
||||||
|
|
||||||
// Deactivate autocomplete first
|
// Check if this is a table name selection
|
||||||
|
let is_table_selection = add_logic_state.is_table_name_suggestion(&suggestion);
|
||||||
|
|
||||||
|
// Deactivate current autocomplete first
|
||||||
add_logic_state.deactivate_script_editor_autocomplete();
|
add_logic_state.deactivate_script_editor_autocomplete();
|
||||||
add_logic_state.has_unsaved_changes = true;
|
add_logic_state.has_unsaved_changes = true;
|
||||||
|
|
||||||
// Then replace text
|
// Replace text in editor
|
||||||
if let Some(pos) = trigger_pos {
|
if let Some(pos) = trigger_pos {
|
||||||
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
|
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
|
||||||
replace_autocomplete_text(
|
replace_autocomplete_text(
|
||||||
@@ -147,9 +151,43 @@ pub fn handle_add_logic_navigation(
|
|||||||
filter_len,
|
filter_len,
|
||||||
&suggestion,
|
&suggestion,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
|
// If it's a table selection, append "." and trigger column autocomplete
|
||||||
|
if is_table_selection {
|
||||||
|
editor_borrow.insert_str(".");
|
||||||
|
|
||||||
|
// Get the new cursor position (after table name and dot)
|
||||||
|
let new_cursor = editor_borrow.cursor();
|
||||||
|
drop(editor_borrow); // Release the borrow
|
||||||
|
|
||||||
|
// Set up for column autocomplete
|
||||||
|
add_logic_state.script_editor_trigger_position = Some(new_cursor);
|
||||||
|
add_logic_state.script_editor_autocomplete_active = true;
|
||||||
|
add_logic_state.script_editor_filter_text.clear();
|
||||||
|
add_logic_state.trigger_column_autocomplete_for_table(suggestion.clone());
|
||||||
|
|
||||||
|
// Initiate async column fetch
|
||||||
|
let profile_name = add_logic_state.profile_name.clone();
|
||||||
|
let table_name = suggestion.clone();
|
||||||
|
let mut client_clone = grpc_client.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
match UiService::fetch_columns_for_table(&mut client_clone, &profile_name, &table_name).await {
|
||||||
|
Ok(columns) => {
|
||||||
|
// Note: In a real implementation, you'd need to send this back to the main thread
|
||||||
|
// For now, we'll handle this synchronously in the main thread
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to fetch columns for {}.{}: {}", profile_name, table_name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
*command_message = format!("Selected table '{}', fetching columns...", suggestion);
|
||||||
|
} else {
|
||||||
*command_message = format!("Inserted: {}", suggestion);
|
*command_message = format!("Inserted: {}", suggestion);
|
||||||
|
}
|
||||||
|
}
|
||||||
return true; // Consume the key
|
return true; // Consume the key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,15 +18,15 @@ impl UiService {
|
|||||||
let profile_name_clone_opt = Some(add_logic_state.profile_name.clone());
|
let profile_name_clone_opt = Some(add_logic_state.profile_name.clone());
|
||||||
let table_name_opt_clone = add_logic_state.selected_table_name.clone();
|
let table_name_opt_clone = add_logic_state.selected_table_name.clone();
|
||||||
|
|
||||||
// Collect all table names from all profiles
|
// Collect table names from SAME profile only
|
||||||
let all_table_names: Vec<String> = profile_tree.profiles
|
let same_profile_table_names: Vec<String> = profile_tree.profiles
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|profile| profile.tables.iter())
|
.find(|profile| profile.name == add_logic_state.profile_name)
|
||||||
.map(|table| table.name.clone())
|
.map(|profile| profile.tables.iter().map(|table| table.name.clone()).collect())
|
||||||
.collect();
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Set all table names for autocomplete
|
// Set same profile table names for autocomplete
|
||||||
add_logic_state.set_all_table_names(all_table_names.clone());
|
add_logic_state.set_same_profile_table_names(same_profile_table_names.clone());
|
||||||
|
|
||||||
if let (Some(profile_name_clone), Some(table_name_clone)) = (profile_name_clone_opt, table_name_opt_clone) {
|
if let (Some(profile_name_clone), Some(table_name_clone)) = (profile_name_clone_opt, table_name_opt_clone) {
|
||||||
match grpc_client.get_table_structure(profile_name_clone.clone(), table_name_clone.clone()).await {
|
match grpc_client.get_table_structure(profile_name_clone.clone(), table_name_clone.clone()).await {
|
||||||
@@ -39,10 +39,11 @@ impl UiService {
|
|||||||
add_logic_state.set_table_columns(column_names.clone());
|
add_logic_state.set_table_columns(column_names.clone());
|
||||||
|
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"Loaded {} columns for table '{}' and {} total tables for autocomplete",
|
"Loaded {} columns for table '{}' and {} tables from profile '{}'",
|
||||||
column_names.len(),
|
column_names.len(),
|
||||||
table_name_clone,
|
table_name_clone,
|
||||||
all_table_names.len()
|
same_profile_table_names.len(),
|
||||||
|
add_logic_state.profile_name
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -53,20 +54,43 @@ impl UiService {
|
|||||||
e
|
e
|
||||||
);
|
);
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"Warning: Could not load table structure for '{}'. Autocomplete will use basic suggestions with {} tables.",
|
"Warning: Could not load table structure for '{}'. Autocomplete will use {} tables from profile '{}'.",
|
||||||
table_name_clone,
|
table_name_clone,
|
||||||
all_table_names.len()
|
same_profile_table_names.len(),
|
||||||
|
add_logic_state.profile_name
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"No table selected for Add Logic. Loaded {} tables for autocomplete.",
|
"No table selected for Add Logic. Loaded {} tables from profile '{}' for autocomplete.",
|
||||||
all_table_names.len()
|
same_profile_table_names.len(),
|
||||||
|
add_logic_state.profile_name
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches columns for a specific table (used for table.column autocomplete)
|
||||||
|
pub async fn fetch_columns_for_table(
|
||||||
|
grpc_client: &mut GrpcClient,
|
||||||
|
profile_name: &str,
|
||||||
|
table_name: &str,
|
||||||
|
) -> Result<Vec<String>> {
|
||||||
|
match grpc_client.get_table_structure(profile_name.to_string(), table_name.to_string()).await {
|
||||||
|
Ok(response) => {
|
||||||
|
let column_names: Vec<String> = response.columns
|
||||||
|
.into_iter()
|
||||||
|
.map(|col| col.name)
|
||||||
|
.collect();
|
||||||
|
Ok(column_names)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Failed to fetch columns for {}.{}: {}", profile_name, table_name, e);
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn initialize_app_state(
|
pub async fn initialize_app_state(
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ pub struct AddLogicState {
|
|||||||
pub script_editor_trigger_position: Option<(usize, usize)>, // (line, column)
|
pub script_editor_trigger_position: Option<(usize, usize)>, // (line, column)
|
||||||
pub all_table_names: Vec<String>,
|
pub all_table_names: Vec<String>,
|
||||||
pub script_editor_filter_text: String,
|
pub script_editor_filter_text: String,
|
||||||
|
|
||||||
|
// New fields for same-profile table names and column autocomplete
|
||||||
|
pub same_profile_table_names: Vec<String>, // Tables from same profile only
|
||||||
|
pub script_editor_awaiting_column_autocomplete: Option<String>, // Table name waiting for column fetch
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddLogicState {
|
impl AddLogicState {
|
||||||
@@ -78,13 +82,15 @@ impl AddLogicState {
|
|||||||
selected_target_column_suggestion_index: None,
|
selected_target_column_suggestion_index: None,
|
||||||
in_target_column_suggestion_mode: false,
|
in_target_column_suggestion_mode: false,
|
||||||
|
|
||||||
// Script Editor Autocomplete initialization
|
|
||||||
script_editor_autocomplete_active: false,
|
script_editor_autocomplete_active: false,
|
||||||
script_editor_suggestions: Vec::new(),
|
script_editor_suggestions: Vec::new(),
|
||||||
script_editor_selected_suggestion_index: None,
|
script_editor_selected_suggestion_index: None,
|
||||||
script_editor_trigger_position: None,
|
script_editor_trigger_position: None,
|
||||||
all_table_names: Vec::new(),
|
all_table_names: Vec::new(),
|
||||||
script_editor_filter_text: String::new(),
|
script_editor_filter_text: String::new(),
|
||||||
|
|
||||||
|
same_profile_table_names: Vec::new(),
|
||||||
|
script_editor_awaiting_column_autocomplete: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,16 +119,10 @@ impl AddLogicState {
|
|||||||
|
|
||||||
self.show_target_column_suggestions = !self.target_column_suggestions.is_empty();
|
self.show_target_column_suggestions = !self.target_column_suggestions.is_empty();
|
||||||
if self.show_target_column_suggestions {
|
if self.show_target_column_suggestions {
|
||||||
// If suggestions are shown, ensure a selection (usually the first)
|
|
||||||
// or maintain current if it's still valid.
|
|
||||||
if let Some(selected_idx) = self.selected_target_column_suggestion_index {
|
if let Some(selected_idx) = self.selected_target_column_suggestion_index {
|
||||||
if selected_idx >= self.target_column_suggestions.len() {
|
if selected_idx >= self.target_column_suggestions.len() {
|
||||||
self.selected_target_column_suggestion_index = Some(0);
|
self.selected_target_column_suggestion_index = Some(0);
|
||||||
}
|
}
|
||||||
// If the previously selected item is no longer in the filtered list, reset.
|
|
||||||
// This is a bit more complex to check perfectly without iterating again.
|
|
||||||
// For now, just ensuring it's within bounds is a good start.
|
|
||||||
// A more robust way would be to check if the string at selected_idx still matches.
|
|
||||||
} else {
|
} else {
|
||||||
self.selected_target_column_suggestion_index = Some(0);
|
self.selected_target_column_suggestion_index = Some(0);
|
||||||
}
|
}
|
||||||
@@ -143,8 +143,8 @@ impl AddLogicState {
|
|||||||
// Add column names from the current table
|
// Add column names from the current table
|
||||||
suggestions.extend(self.table_columns_for_suggestions.clone());
|
suggestions.extend(self.table_columns_for_suggestions.clone());
|
||||||
|
|
||||||
// Add all table names from all profiles
|
// Add table names from SAME profile only
|
||||||
suggestions.extend(self.all_table_names.clone());
|
suggestions.extend(self.same_profile_table_names.clone());
|
||||||
|
|
||||||
if self.script_editor_filter_text.is_empty() {
|
if self.script_editor_filter_text.is_empty() {
|
||||||
self.script_editor_suggestions = suggestions;
|
self.script_editor_suggestions = suggestions;
|
||||||
@@ -172,7 +172,6 @@ impl AddLogicState {
|
|||||||
/// Sets table columns for autocomplete suggestions
|
/// Sets table columns for autocomplete suggestions
|
||||||
pub fn set_table_columns(&mut self, columns: Vec<String>) {
|
pub fn set_table_columns(&mut self, columns: Vec<String>) {
|
||||||
self.table_columns_for_suggestions = columns.clone();
|
self.table_columns_for_suggestions = columns.clone();
|
||||||
// Also update target column suggestions for the input field
|
|
||||||
if !columns.is_empty() {
|
if !columns.is_empty() {
|
||||||
self.update_target_column_suggestions();
|
self.update_target_column_suggestions();
|
||||||
}
|
}
|
||||||
@@ -183,6 +182,47 @@ impl AddLogicState {
|
|||||||
self.all_table_names = table_names;
|
self.all_table_names = table_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets table names from the same profile for autocomplete suggestions
|
||||||
|
pub fn set_same_profile_table_names(&mut self, table_names: Vec<String>) {
|
||||||
|
self.same_profile_table_names = table_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a suggestion is a table name (for triggering column autocomplete)
|
||||||
|
pub fn is_table_name_suggestion(&self, suggestion: &str) -> bool {
|
||||||
|
if suggestion == "sql" {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref current_table) = self.selected_table_name {
|
||||||
|
if suggestion == current_table {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.table_columns_for_suggestions.contains(&suggestion.to_string()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.same_profile_table_names.contains(&suggestion.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Triggers waiting for column autocomplete for a specific table
|
||||||
|
pub fn trigger_column_autocomplete_for_table(&mut self, table_name: String) {
|
||||||
|
self.script_editor_awaiting_column_autocomplete = Some(table_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates autocomplete with columns for a specific table
|
||||||
|
pub fn set_columns_for_table_autocomplete(&mut self, columns: Vec<String>) {
|
||||||
|
self.script_editor_suggestions = columns;
|
||||||
|
self.script_editor_selected_suggestion_index = if self.script_editor_suggestions.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(0)
|
||||||
|
};
|
||||||
|
self.script_editor_autocomplete_active = !self.script_editor_suggestions.is_empty();
|
||||||
|
self.script_editor_awaiting_column_autocomplete = None;
|
||||||
|
}
|
||||||
|
|
||||||
/// Deactivates script editor autocomplete and clears related state
|
/// Deactivates script editor autocomplete and clears related state
|
||||||
pub fn deactivate_script_editor_autocomplete(&mut self) {
|
pub fn deactivate_script_editor_autocomplete(&mut self) {
|
||||||
self.script_editor_autocomplete_active = false;
|
self.script_editor_autocomplete_active = false;
|
||||||
@@ -257,10 +297,9 @@ impl CanvasState for AddLogicState {
|
|||||||
0 => AddLogicFocus::InputLogicName,
|
0 => AddLogicFocus::InputLogicName,
|
||||||
1 => AddLogicFocus::InputTargetColumn,
|
1 => AddLogicFocus::InputTargetColumn,
|
||||||
2 => AddLogicFocus::InputDescription,
|
2 => AddLogicFocus::InputDescription,
|
||||||
_ => return, // Or handle error/default
|
_ => return,
|
||||||
};
|
};
|
||||||
if self.current_focus != new_focus {
|
if self.current_focus != new_focus {
|
||||||
// If changing field, exit suggestion mode for target column
|
|
||||||
if self.current_focus == AddLogicFocus::InputTargetColumn {
|
if self.current_focus == AddLogicFocus::InputTargetColumn {
|
||||||
self.in_target_column_suggestion_mode = false;
|
self.in_target_column_suggestion_mode = false;
|
||||||
self.show_target_column_suggestions = false;
|
self.show_target_column_suggestions = false;
|
||||||
@@ -276,12 +315,10 @@ impl CanvasState for AddLogicState {
|
|||||||
self.logic_name_cursor_pos = pos.min(self.logic_name_input.len());
|
self.logic_name_cursor_pos = pos.min(self.logic_name_input.len());
|
||||||
}
|
}
|
||||||
AddLogicFocus::InputTargetColumn => {
|
AddLogicFocus::InputTargetColumn => {
|
||||||
self.target_column_cursor_pos =
|
self.target_column_cursor_pos = pos.min(self.target_column_input.len());
|
||||||
pos.min(self.target_column_input.len());
|
|
||||||
}
|
}
|
||||||
AddLogicFocus::InputDescription => {
|
AddLogicFocus::InputDescription => {
|
||||||
self.description_cursor_pos =
|
self.description_cursor_pos = pos.min(self.description_input.len());
|
||||||
pos.min(self.description_input.len());
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -292,7 +329,7 @@ impl CanvasState for AddLogicState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_suggestions(&self) -> Option<&[String]> {
|
fn get_suggestions(&self) -> Option<&[String]> {
|
||||||
if self.current_field() == 1 // Target Column field index
|
if self.current_field() == 1
|
||||||
&& self.in_target_column_suggestion_mode
|
&& self.in_target_column_suggestion_mode
|
||||||
&& self.show_target_column_suggestions
|
&& self.show_target_column_suggestions
|
||||||
{
|
{
|
||||||
@@ -303,7 +340,7 @@ impl CanvasState for AddLogicState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_selected_suggestion_index(&self) -> Option<usize> {
|
fn get_selected_suggestion_index(&self) -> Option<usize> {
|
||||||
if self.current_field() == 1 // Target Column field index
|
if self.current_field() == 1
|
||||||
&& self.in_target_column_suggestion_mode
|
&& self.in_target_column_suggestion_mode
|
||||||
&& self.show_target_column_suggestions
|
&& self.show_target_column_suggestions
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -224,6 +224,29 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
needs_redraw = false;
|
needs_redraw = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Handle Pending Column Autocomplete for Table Selection ---
|
||||||
|
if let Some(table_name) = admin_state.add_logic_state.script_editor_awaiting_column_autocomplete.clone() {
|
||||||
|
if app_state.ui.show_add_logic {
|
||||||
|
let profile_name = admin_state.add_logic_state.profile_name.clone();
|
||||||
|
|
||||||
|
info!("Fetching columns for table selection: {}.{}", profile_name, table_name);
|
||||||
|
match UiService::fetch_columns_for_table(&mut grpc_client, &profile_name, &table_name).await {
|
||||||
|
Ok(columns) => {
|
||||||
|
admin_state.add_logic_state.set_columns_for_table_autocomplete(columns.clone());
|
||||||
|
info!("Loaded {} columns for table '{}'", columns.len(), table_name);
|
||||||
|
event_handler.command_message = format!("Columns for '{}' loaded. Select a column.", table_name);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to fetch columns for {}.{}: {}", profile_name, table_name, e);
|
||||||
|
admin_state.add_logic_state.script_editor_awaiting_column_autocomplete = None;
|
||||||
|
admin_state.add_logic_state.deactivate_script_editor_autocomplete();
|
||||||
|
event_handler.command_message = format!("Error loading columns for '{}': {}", table_name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Cursor Visibility Logic ---
|
// --- Cursor Visibility Logic ---
|
||||||
let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &admin_state);
|
let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &admin_state);
|
||||||
match current_mode {
|
match current_mode {
|
||||||
|
|||||||
Reference in New Issue
Block a user