autocomplete is now powerful
This commit is contained in:
@@ -11,6 +11,7 @@ use crate::services::GrpcClient;
|
||||
use tokio::sync::mpsc;
|
||||
use anyhow::Result;
|
||||
use crate::components::common::text_editor::TextEditor;
|
||||
use crate::services::ui_service::UiService;
|
||||
|
||||
pub type SaveLogicResultSender = mpsc::Sender<Result<String>>;
|
||||
|
||||
@@ -21,8 +22,8 @@ pub fn handle_add_logic_navigation(
|
||||
add_logic_state: &mut AddLogicState,
|
||||
is_edit_mode: &mut bool,
|
||||
buffer_state: &mut BufferState,
|
||||
_grpc_client: GrpcClient,
|
||||
_save_logic_sender: SaveLogicResultSender,
|
||||
grpc_client: GrpcClient,
|
||||
save_logic_sender: SaveLogicResultSender,
|
||||
command_message: &mut String,
|
||||
) -> bool {
|
||||
// === 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 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.has_unsaved_changes = true;
|
||||
|
||||
// Then replace text
|
||||
// Replace text in editor
|
||||
if let Some(pos) = trigger_pos {
|
||||
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
|
||||
replace_autocomplete_text(
|
||||
@@ -147,9 +151,43 @@ pub fn handle_add_logic_navigation(
|
||||
filter_len,
|
||||
&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);
|
||||
}
|
||||
}
|
||||
return true; // Consume the key
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,15 @@ impl UiService {
|
||||
let profile_name_clone_opt = Some(add_logic_state.profile_name.clone());
|
||||
let table_name_opt_clone = add_logic_state.selected_table_name.clone();
|
||||
|
||||
// Collect all table names from all profiles
|
||||
let all_table_names: Vec<String> = profile_tree.profiles
|
||||
// Collect table names from SAME profile only
|
||||
let same_profile_table_names: Vec<String> = profile_tree.profiles
|
||||
.iter()
|
||||
.flat_map(|profile| profile.tables.iter())
|
||||
.map(|table| table.name.clone())
|
||||
.collect();
|
||||
.find(|profile| profile.name == add_logic_state.profile_name)
|
||||
.map(|profile| profile.tables.iter().map(|table| table.name.clone()).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
// Set all table names for autocomplete
|
||||
add_logic_state.set_all_table_names(all_table_names.clone());
|
||||
// Set same profile table names for autocomplete
|
||||
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) {
|
||||
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());
|
||||
|
||||
Ok(format!(
|
||||
"Loaded {} columns for table '{}' and {} total tables for autocomplete",
|
||||
"Loaded {} columns for table '{}' and {} tables from profile '{}'",
|
||||
column_names.len(),
|
||||
table_name_clone,
|
||||
all_table_names.len()
|
||||
same_profile_table_names.len(),
|
||||
add_logic_state.profile_name
|
||||
))
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -53,20 +54,43 @@ impl UiService {
|
||||
e
|
||||
);
|
||||
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,
|
||||
all_table_names.len()
|
||||
same_profile_table_names.len(),
|
||||
add_logic_state.profile_name
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(format!(
|
||||
"No table selected for Add Logic. Loaded {} tables for autocomplete.",
|
||||
all_table_names.len()
|
||||
"No table selected for Add Logic. Loaded {} tables from profile '{}' for autocomplete.",
|
||||
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(
|
||||
grpc_client: &mut GrpcClient,
|
||||
app_state: &mut AppState,
|
||||
|
||||
@@ -50,6 +50,10 @@ pub struct AddLogicState {
|
||||
pub script_editor_trigger_position: Option<(usize, usize)>, // (line, column)
|
||||
pub all_table_names: Vec<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 {
|
||||
@@ -78,13 +82,15 @@ impl AddLogicState {
|
||||
selected_target_column_suggestion_index: None,
|
||||
in_target_column_suggestion_mode: false,
|
||||
|
||||
// Script Editor Autocomplete initialization
|
||||
script_editor_autocomplete_active: false,
|
||||
script_editor_suggestions: Vec::new(),
|
||||
script_editor_selected_suggestion_index: None,
|
||||
script_editor_trigger_position: None,
|
||||
all_table_names: Vec::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();
|
||||
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 selected_idx >= self.target_column_suggestions.len() {
|
||||
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 {
|
||||
self.selected_target_column_suggestion_index = Some(0);
|
||||
}
|
||||
@@ -143,8 +143,8 @@ impl AddLogicState {
|
||||
// Add column names from the current table
|
||||
suggestions.extend(self.table_columns_for_suggestions.clone());
|
||||
|
||||
// Add all table names from all profiles
|
||||
suggestions.extend(self.all_table_names.clone());
|
||||
// Add table names from SAME profile only
|
||||
suggestions.extend(self.same_profile_table_names.clone());
|
||||
|
||||
if self.script_editor_filter_text.is_empty() {
|
||||
self.script_editor_suggestions = suggestions;
|
||||
@@ -172,7 +172,6 @@ impl AddLogicState {
|
||||
/// Sets table columns for autocomplete suggestions
|
||||
pub fn set_table_columns(&mut self, columns: Vec<String>) {
|
||||
self.table_columns_for_suggestions = columns.clone();
|
||||
// Also update target column suggestions for the input field
|
||||
if !columns.is_empty() {
|
||||
self.update_target_column_suggestions();
|
||||
}
|
||||
@@ -183,6 +182,47 @@ impl AddLogicState {
|
||||
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
|
||||
pub fn deactivate_script_editor_autocomplete(&mut self) {
|
||||
self.script_editor_autocomplete_active = false;
|
||||
@@ -257,10 +297,9 @@ impl CanvasState for AddLogicState {
|
||||
0 => AddLogicFocus::InputLogicName,
|
||||
1 => AddLogicFocus::InputTargetColumn,
|
||||
2 => AddLogicFocus::InputDescription,
|
||||
_ => return, // Or handle error/default
|
||||
_ => return,
|
||||
};
|
||||
if self.current_focus != new_focus {
|
||||
// If changing field, exit suggestion mode for target column
|
||||
if self.current_focus == AddLogicFocus::InputTargetColumn {
|
||||
self.in_target_column_suggestion_mode = 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());
|
||||
}
|
||||
AddLogicFocus::InputTargetColumn => {
|
||||
self.target_column_cursor_pos =
|
||||
pos.min(self.target_column_input.len());
|
||||
self.target_column_cursor_pos = pos.min(self.target_column_input.len());
|
||||
}
|
||||
AddLogicFocus::InputDescription => {
|
||||
self.description_cursor_pos =
|
||||
pos.min(self.description_input.len());
|
||||
self.description_cursor_pos = pos.min(self.description_input.len());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -292,7 +329,7 @@ impl CanvasState for AddLogicState {
|
||||
}
|
||||
|
||||
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.show_target_column_suggestions
|
||||
{
|
||||
@@ -303,7 +340,7 @@ impl CanvasState for AddLogicState {
|
||||
}
|
||||
|
||||
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.show_target_column_suggestions
|
||||
{
|
||||
|
||||
@@ -224,6 +224,29 @@ pub async fn run_ui() -> Result<()> {
|
||||
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 ---
|
||||
let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &admin_state);
|
||||
match current_mode {
|
||||
|
||||
Reference in New Issue
Block a user