suggestions is getting more and more strong than ever before
This commit is contained in:
1234
canvas/examples/suggestions2.rs
Normal file
1234
canvas/examples/suggestions2.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -67,6 +67,15 @@ pub fn render_canvas_with_highlight<T: CanvasTheme, D: DataProvider>(
|
|||||||
let current_field_idx = ui_state.current_field();
|
let current_field_idx = ui_state.current_field();
|
||||||
let is_edit_mode = matches!(ui_state.mode(), crate::canvas::modes::AppMode::Edit);
|
let is_edit_mode = matches!(ui_state.mode(), crate::canvas::modes::AppMode::Edit);
|
||||||
|
|
||||||
|
// Precompute completion for active field
|
||||||
|
let active_completion = if ui_state.is_suggestions_active()
|
||||||
|
&& ui_state.suggestions.active_field == Some(current_field_idx)
|
||||||
|
{
|
||||||
|
ui_state.suggestions.completion_text.clone()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
render_canvas_fields(
|
render_canvas_fields(
|
||||||
f,
|
f,
|
||||||
area,
|
area,
|
||||||
@@ -111,6 +120,14 @@ pub fn render_canvas_with_highlight<T: CanvasTheme, D: DataProvider>(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// NEW: provide completion for the active field
|
||||||
|
|i| {
|
||||||
|
if i == current_field_idx {
|
||||||
|
active_completion.clone()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +145,7 @@ fn convert_selection_to_highlight(selection: &crate::canvas::state::SelectionSta
|
|||||||
|
|
||||||
/// Core canvas field rendering
|
/// Core canvas field rendering
|
||||||
#[cfg(feature = "gui")]
|
#[cfg(feature = "gui")]
|
||||||
fn render_canvas_fields<T: CanvasTheme, F1, F2>(
|
fn render_canvas_fields<T: CanvasTheme, F1, F2, F3>(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
fields: &[&str],
|
fields: &[&str],
|
||||||
@@ -141,10 +158,12 @@ fn render_canvas_fields<T: CanvasTheme, F1, F2>(
|
|||||||
has_unsaved_changes: bool,
|
has_unsaved_changes: bool,
|
||||||
get_display_value: F1,
|
get_display_value: F1,
|
||||||
has_display_override: F2,
|
has_display_override: F2,
|
||||||
|
get_completion: F3,
|
||||||
) -> Option<Rect>
|
) -> Option<Rect>
|
||||||
where
|
where
|
||||||
F1: Fn(usize) -> String,
|
F1: Fn(usize) -> String,
|
||||||
F2: Fn(usize) -> bool,
|
F2: Fn(usize) -> bool,
|
||||||
|
F3: Fn(usize) -> Option<String>,
|
||||||
{
|
{
|
||||||
// Create layout
|
// Create layout
|
||||||
let columns = Layout::default()
|
let columns = Layout::default()
|
||||||
@@ -198,6 +217,7 @@ where
|
|||||||
current_cursor_pos,
|
current_cursor_pos,
|
||||||
get_display_value,
|
get_display_value,
|
||||||
has_display_override,
|
has_display_override,
|
||||||
|
get_completion,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +249,7 @@ fn render_field_labels<T: CanvasTheme>(
|
|||||||
|
|
||||||
/// Render field values with highlighting
|
/// Render field values with highlighting
|
||||||
#[cfg(feature = "gui")]
|
#[cfg(feature = "gui")]
|
||||||
fn render_field_values<T: CanvasTheme, F1, F2>(
|
fn render_field_values<T: CanvasTheme, F1, F2, F3>(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
input_rows: Vec<Rect>,
|
input_rows: Vec<Rect>,
|
||||||
inputs: &[String],
|
inputs: &[String],
|
||||||
@@ -239,35 +259,54 @@ fn render_field_values<T: CanvasTheme, F1, F2>(
|
|||||||
current_cursor_pos: usize,
|
current_cursor_pos: usize,
|
||||||
get_display_value: F1,
|
get_display_value: F1,
|
||||||
has_display_override: F2,
|
has_display_override: F2,
|
||||||
|
get_completion: F3,
|
||||||
) -> Option<Rect>
|
) -> Option<Rect>
|
||||||
where
|
where
|
||||||
F1: Fn(usize) -> String,
|
F1: Fn(usize) -> String,
|
||||||
F2: Fn(usize) -> bool,
|
F2: Fn(usize) -> bool,
|
||||||
|
F3: Fn(usize) -> Option<String>,
|
||||||
{
|
{
|
||||||
let mut active_field_input_rect = None;
|
let mut active_field_input_rect = None;
|
||||||
|
|
||||||
for (i, _input) in inputs.iter().enumerate() {
|
for (i, _input) in inputs.iter().enumerate() {
|
||||||
let is_active = i == *current_field_idx;
|
let is_active = i == *current_field_idx;
|
||||||
let text = get_display_value(i);
|
let typed_text = get_display_value(i);
|
||||||
|
|
||||||
// Apply highlighting
|
let line = if is_active {
|
||||||
let line = apply_highlighting(
|
// Compose typed + gray completion for the active field
|
||||||
&text,
|
let normal_style = Style::default().fg(theme.fg());
|
||||||
i,
|
let gray_style = Style::default().fg(theme.suggestion_gray());
|
||||||
current_field_idx,
|
|
||||||
current_cursor_pos,
|
let mut spans: Vec<Span> = Vec::new();
|
||||||
highlight_state,
|
spans.push(Span::styled(typed_text.clone(), normal_style));
|
||||||
theme,
|
|
||||||
is_active,
|
if let Some(completion) = get_completion(i) {
|
||||||
);
|
if !completion.is_empty() {
|
||||||
|
spans.push(Span::styled(completion, gray_style));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Line::from(spans)
|
||||||
|
} else {
|
||||||
|
// Non-active fields: keep existing highlighting logic
|
||||||
|
apply_highlighting(
|
||||||
|
&typed_text,
|
||||||
|
i,
|
||||||
|
current_field_idx,
|
||||||
|
current_cursor_pos,
|
||||||
|
highlight_state,
|
||||||
|
theme,
|
||||||
|
is_active,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let input_display = Paragraph::new(line).alignment(Alignment::Left);
|
let input_display = Paragraph::new(line).alignment(Alignment::Left);
|
||||||
f.render_widget(input_display, input_rows[i]);
|
f.render_widget(input_display, input_rows[i]);
|
||||||
|
|
||||||
// Set cursor for active field
|
// Set cursor for active field at end of typed text (not after completion)
|
||||||
if is_active {
|
if is_active {
|
||||||
active_field_input_rect = Some(input_rows[i]);
|
active_field_input_rect = Some(input_rows[i]);
|
||||||
set_cursor_position(f, input_rows[i], &text, current_cursor_pos, has_display_override(i));
|
set_cursor_position(f, input_rows[i], &typed_text, current_cursor_pos, has_display_override(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ pub struct SuggestionsUIState {
|
|||||||
pub(crate) is_loading: bool,
|
pub(crate) is_loading: bool,
|
||||||
pub(crate) selected_index: Option<usize>,
|
pub(crate) selected_index: Option<usize>,
|
||||||
pub(crate) active_field: Option<usize>,
|
pub(crate) active_field: Option<usize>,
|
||||||
|
pub(crate) completion_text: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -56,6 +57,7 @@ impl EditorState {
|
|||||||
is_loading: false,
|
is_loading: false,
|
||||||
selected_index: None,
|
selected_index: None,
|
||||||
active_field: None,
|
active_field: None,
|
||||||
|
completion_text: None,
|
||||||
},
|
},
|
||||||
selection: SelectionState::None,
|
selection: SelectionState::None,
|
||||||
#[cfg(feature = "validation")]
|
#[cfg(feature = "validation")]
|
||||||
@@ -148,6 +150,7 @@ impl EditorState {
|
|||||||
self.suggestions.is_loading = true;
|
self.suggestions.is_loading = true;
|
||||||
self.suggestions.active_field = Some(field_index);
|
self.suggestions.active_field = Some(field_index);
|
||||||
self.suggestions.selected_index = None;
|
self.suggestions.selected_index = None;
|
||||||
|
self.suggestions.completion_text = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn deactivate_suggestions(&mut self) {
|
pub(crate) fn deactivate_suggestions(&mut self) {
|
||||||
@@ -155,6 +158,7 @@ impl EditorState {
|
|||||||
self.suggestions.is_loading = false;
|
self.suggestions.is_loading = false;
|
||||||
self.suggestions.active_field = None;
|
self.suggestions.active_field = None;
|
||||||
self.suggestions.selected_index = None;
|
self.suggestions.selected_index = None;
|
||||||
|
self.suggestions.completion_text = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ pub trait CanvasTheme {
|
|||||||
fn highlight(&self) -> Color;
|
fn highlight(&self) -> Color;
|
||||||
fn highlight_bg(&self) -> Color;
|
fn highlight_bg(&self) -> Color;
|
||||||
fn warning(&self) -> Color;
|
fn warning(&self) -> Color;
|
||||||
|
fn suggestion_gray(&self) -> Color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -47,4 +48,7 @@ impl CanvasTheme for DefaultCanvasTheme {
|
|||||||
fn warning(&self) -> Color {
|
fn warning(&self) -> Color {
|
||||||
Color::Red
|
Color::Red
|
||||||
}
|
}
|
||||||
|
fn suggestion_gray(&self) -> Color {
|
||||||
|
Color::DarkGray
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,24 @@ impl<D: DataProvider> FormEditor<D> {
|
|||||||
editor
|
editor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compute inline completion for current selection and current text.
|
||||||
|
fn compute_current_completion(&self) -> Option<String> {
|
||||||
|
let typed = self.current_text();
|
||||||
|
let idx = self.ui_state.suggestions.selected_index?;
|
||||||
|
let sugg = self.suggestions.get(idx)?;
|
||||||
|
if let Some(rest) = sugg.value_to_store.strip_prefix(typed) {
|
||||||
|
if !rest.is_empty() {
|
||||||
|
return Some(rest.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update UI state's completion text from current selection
|
||||||
|
fn update_inline_completion(&mut self) {
|
||||||
|
self.ui_state.suggestions.completion_text = self.compute_current_completion();
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize validation configurations from data provider
|
/// Initialize validation configurations from data provider
|
||||||
#[cfg(feature = "validation")]
|
#[cfg(feature = "validation")]
|
||||||
fn initialize_validation(&mut self) {
|
fn initialize_validation(&mut self) {
|
||||||
@@ -605,6 +623,8 @@ impl<D: DataProvider> FormEditor<D> {
|
|||||||
self.ui_state.suggestions.is_loading = false;
|
self.ui_state.suggestions.is_loading = false;
|
||||||
if !self.suggestions.is_empty() {
|
if !self.suggestions.is_empty() {
|
||||||
self.ui_state.suggestions.selected_index = Some(0);
|
self.ui_state.suggestions.selected_index = Some(0);
|
||||||
|
// Compute initial inline completion from first suggestion
|
||||||
|
self.update_inline_completion();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -619,6 +639,9 @@ impl<D: DataProvider> FormEditor<D> {
|
|||||||
let current = self.ui_state.suggestions.selected_index.unwrap_or(0);
|
let current = self.ui_state.suggestions.selected_index.unwrap_or(0);
|
||||||
let next = (current + 1) % self.suggestions.len();
|
let next = (current + 1) % self.suggestions.len();
|
||||||
self.ui_state.suggestions.selected_index = Some(next);
|
self.ui_state.suggestions.selected_index = Some(next);
|
||||||
|
|
||||||
|
// Update inline completion to reflect new highlighted item
|
||||||
|
self.update_inline_completion();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply selected suggestion
|
/// Apply selected suggestion
|
||||||
|
|||||||
Reference in New Issue
Block a user