Add a UI hint when you press @ (#1903)
This will make @ more discoverable (even though it is currently not super useful, IMO it should be used to bring files into context from outside CWD) --------- Co-authored-by: Gabriel Peal <gpeal@users.noreply.github.com>
This commit is contained in:
@@ -416,7 +416,9 @@ impl App<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AppEvent::StartFileSearch(query) => {
|
AppEvent::StartFileSearch(query) => {
|
||||||
self.file_search.on_user_query(query);
|
if !query.is_empty() {
|
||||||
|
self.file_search.on_user_query(query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AppEvent::FileSearchResult { query, matches } => {
|
AppEvent::FileSearchResult { query, matches } => {
|
||||||
if let AppState::Chat { widget } = &mut self.app_state {
|
if let AppState::Chat { widget } = &mut self.app_state {
|
||||||
|
|||||||
@@ -331,8 +331,9 @@ impl ChatComposer {
|
|||||||
/// - The cursor may be anywhere *inside* the token (including on the
|
/// - The cursor may be anywhere *inside* the token (including on the
|
||||||
/// leading `@`). It does **not** need to be at the end of the line.
|
/// leading `@`). It does **not** need to be at the end of the line.
|
||||||
/// - A token is delimited by ASCII whitespace (space, tab, newline).
|
/// - A token is delimited by ASCII whitespace (space, tab, newline).
|
||||||
/// - If the token under the cursor starts with `@` and contains at least
|
/// - If the token under the cursor starts with `@`, that token is
|
||||||
/// one additional character, that token (without `@`) is returned.
|
/// returned without the leading `@`. This includes the case where the
|
||||||
|
/// token is just "@" (empty query), which is used to trigger a UI hint
|
||||||
fn current_at_token(textarea: &TextArea) -> Option<String> {
|
fn current_at_token(textarea: &TextArea) -> Option<String> {
|
||||||
let cursor_offset = textarea.cursor();
|
let cursor_offset = textarea.cursor();
|
||||||
let text = textarea.text();
|
let text = textarea.text();
|
||||||
@@ -403,14 +404,20 @@ impl ChatComposer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let left_at = token_left
|
let left_at = token_left
|
||||||
.filter(|t| t.starts_with('@') && t.len() > 1)
|
.filter(|t| t.starts_with('@'))
|
||||||
.map(|t| t[1..].to_string());
|
.map(|t| t[1..].to_string());
|
||||||
let right_at = token_right
|
let right_at = token_right
|
||||||
.filter(|t| t.starts_with('@') && t.len() > 1)
|
.filter(|t| t.starts_with('@'))
|
||||||
.map(|t| t[1..].to_string());
|
.map(|t| t[1..].to_string());
|
||||||
|
|
||||||
if at_whitespace {
|
if at_whitespace {
|
||||||
return right_at.or(left_at);
|
if right_at.is_some() {
|
||||||
|
return right_at;
|
||||||
|
}
|
||||||
|
if token_left.is_some_and(|t| t == "@") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
return left_at;
|
||||||
}
|
}
|
||||||
if after_cursor.starts_with('@') {
|
if after_cursor.starts_with('@') {
|
||||||
return right_at.or(left_at);
|
return right_at.or(left_at);
|
||||||
@@ -607,16 +614,26 @@ impl ChatComposer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.app_event_tx
|
if !query.is_empty() {
|
||||||
.send(AppEvent::StartFileSearch(query.clone()));
|
self.app_event_tx
|
||||||
|
.send(AppEvent::StartFileSearch(query.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
match &mut self.active_popup {
|
match &mut self.active_popup {
|
||||||
ActivePopup::File(popup) => {
|
ActivePopup::File(popup) => {
|
||||||
popup.set_query(&query);
|
if query.is_empty() {
|
||||||
|
popup.set_empty_prompt();
|
||||||
|
} else {
|
||||||
|
popup.set_query(&query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let mut popup = FileSearchPopup::new();
|
let mut popup = FileSearchPopup::new();
|
||||||
popup.set_query(&query);
|
if query.is_empty() {
|
||||||
|
popup.set_empty_prompt();
|
||||||
|
} else {
|
||||||
|
popup.set_query(&query);
|
||||||
|
}
|
||||||
self.active_popup = ActivePopup::File(popup);
|
self.active_popup = ActivePopup::File(popup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -773,7 +790,12 @@ mod tests {
|
|||||||
("@👍", 2, Some("👍".to_string()), "Emoji token"),
|
("@👍", 2, Some("👍".to_string()), "Emoji token"),
|
||||||
// Invalid cases (should return None)
|
// Invalid cases (should return None)
|
||||||
("hello", 2, None, "No @ symbol"),
|
("hello", 2, None, "No @ symbol"),
|
||||||
("@", 1, None, "Only @ symbol"),
|
(
|
||||||
|
"@",
|
||||||
|
1,
|
||||||
|
Some("".to_string()),
|
||||||
|
"Only @ symbol triggers empty query",
|
||||||
|
),
|
||||||
("@ hello", 2, None, "@ followed by space"),
|
("@ hello", 2, None, "@ followed by space"),
|
||||||
("test @ world", 6, None, "@ with spaces around"),
|
("test @ world", 6, None, "@ with spaces around"),
|
||||||
];
|
];
|
||||||
@@ -807,7 +829,7 @@ mod tests {
|
|||||||
"Second token",
|
"Second token",
|
||||||
),
|
),
|
||||||
// Edge cases
|
// Edge cases
|
||||||
("@", 0, None, "Only @ symbol"),
|
("@", 0, Some("".to_string()), "Only @ symbol"),
|
||||||
("@a", 2, Some("a".to_string()), "Single character after @"),
|
("@a", 2, Some("a".to_string()), "Single character after @"),
|
||||||
("", 0, None, "Empty input"),
|
("", 0, None, "Empty input"),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -54,6 +54,17 @@ impl FileSearchPopup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Put the popup into an "idle" state used for an empty query (just "@").
|
||||||
|
/// Shows a hint instead of matches until the user types more characters.
|
||||||
|
pub(crate) fn set_empty_prompt(&mut self) {
|
||||||
|
self.display_query.clear();
|
||||||
|
self.pending_query.clear();
|
||||||
|
self.waiting = false;
|
||||||
|
self.matches.clear();
|
||||||
|
// Reset selection/scroll state when showing the empty prompt.
|
||||||
|
self.state.reset();
|
||||||
|
}
|
||||||
|
|
||||||
/// Replace matches when a `FileSearchResult` arrives.
|
/// Replace matches when a `FileSearchResult` arrives.
|
||||||
/// Replace matches. Only applied when `query` matches `pending_query`.
|
/// Replace matches. Only applied when `query` matches `pending_query`.
|
||||||
pub(crate) fn set_matches(&mut self, query: &str, matches: Vec<FileMatch>) {
|
pub(crate) fn set_matches(&mut self, query: &str, matches: Vec<FileMatch>) {
|
||||||
|
|||||||
Reference in New Issue
Block a user