mirror of
https://github.com/openai/codex.git
synced 2026-11-06 08:31:30 +00:00
Fix skill completion around bound mentions with suffixes (#33237)
## What changed - Use `$`-mention boundary rules when deciding whether a skill mention followed by a suffix is already bound. - Fall back to the editable skill token on the left when the token on the right is a bound mention such as `$bound/path` or `$bound.config`. ## Testing - Add completion-target coverage for path and configuration suffixes. - Add a composer snapshot covering the skill popup fallback for a bound path suffix. GitOrigin-RevId: bf72237dfd56cfe6f590a3fab10c2bfe03b088a8
This commit is contained in:
1 parent
b6f1cfa3f3
commit
b556ce3fec
4 files changed
+65
-5
No files matched your search
@@ -4064,6 +4064,12 @@ fn ends_plaintext_at_mention(bytes: &[u8], index: usize) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
fn ends_plaintext_at_dollar_mention(bytes: &[u8], index: usize) -> bool {
|
||||
bytes
|
||||
.get(index)
|
||||
.is_none_or(|byte| !is_mention_name_char(*byte))
|
||||
}
|
||||
|
||||
fn starts_plaintext_at_mention(text: &str, index: usize) -> bool {
|
||||
if index == 0 {
|
||||
return true;
|
||||
@@ -4114,9 +4120,7 @@ fn find_next_mention_token_range(text: &str, token: &str, from: usize) -> Option
|
||||
let ends_plaintext_mention = if sigil == b'@' {
|
||||
ends_plaintext_at_mention(bytes, end)
|
||||
} else {
|
||||
bytes
|
||||
.get(end)
|
||||
.is_none_or(|byte| !is_mention_name_char(*byte))
|
||||
ends_plaintext_at_dollar_mention(bytes, end)
|
||||
};
|
||||
|
||||
if starts_plaintext_mention && ends_plaintext_mention {
|
||||
@@ -6504,6 +6508,25 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skill_popup_falls_back_from_bound_skill_with_path_suffix_snapshot() {
|
||||
snapshot_composer_state(
|
||||
"skill_popup_falls_back_from_bound_skill_with_path_suffix",
|
||||
/*enhanced_keys_supported*/ false,
|
||||
|composer| {
|
||||
composer.set_skill_mentions(Some(vec![test_skill_metadata("left")]));
|
||||
composer.set_text_content_with_mention_bindings(
|
||||
"$left $bound/path".to_string(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
vec![test_skill_binding("bound")],
|
||||
);
|
||||
composer.draft.textarea.set_cursor("$left ".len());
|
||||
composer.sync_popups();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skill_popup_closes_after_trailing_space_snapshot() {
|
||||
snapshot_composer_state(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Cursor-neighborhood resolution for sigil-prefixed composer completions.
|
||||
|
||||
use super::ends_plaintext_at_dollar_mention;
|
||||
use super::ends_plaintext_at_mention;
|
||||
use super::is_mention_name_char;
|
||||
use crate::bottom_pane::textarea::TextArea;
|
||||
@@ -268,7 +269,7 @@ pub(super) fn current_prefixed_token_range_with_dollar_predicate(
|
||||
/// Returns whether a token candidate's sigil and mention name are editable plaintext.
|
||||
///
|
||||
/// A candidate is bound when either its entire range is atomic or its mention-name prefix is
|
||||
/// atomic and the remaining text begins with punctuation that terminates the mention.
|
||||
/// atomic and the remaining text begins with a terminator for that sigil.
|
||||
pub(super) fn prefixed_token_range_is_editable(
|
||||
textarea: &TextArea,
|
||||
prefix: char,
|
||||
@@ -285,9 +286,14 @@ pub(super) fn prefixed_token_range_is_editable(
|
||||
.take_while(|byte| is_mention_name_char(**byte))
|
||||
.count();
|
||||
let mention_end = range.start + prefix.len_utf8() + name_len;
|
||||
let ends_bound_mention = if prefix == '@' {
|
||||
ends_plaintext_at_mention(textarea.text().as_bytes(), mention_end)
|
||||
} else {
|
||||
ends_plaintext_at_dollar_mention(textarea.text().as_bytes(), mention_end)
|
||||
};
|
||||
!(name_len > 0
|
||||
&& mention_end < range.end
|
||||
&& ends_plaintext_at_mention(textarea.text().as_bytes(), mention_end)
|
||||
&& ends_bound_mention
|
||||
&& textarea
|
||||
.element_id_for_exact_range(range.start..mention_end)
|
||||
.is_some())
|
||||
|
||||
@@ -81,6 +81,24 @@ fn current_prefixed_token_falls_back_from_bound_at_token_on_right() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_prefixed_token_falls_back_from_bound_dollar_suffix_on_right() {
|
||||
for suffix in ["/path", ".config"] {
|
||||
let text = format!("$left $bound{suffix}");
|
||||
let bound_start = text.find("$bound").expect("bound skill mention");
|
||||
let bound_end = bound_start + "$bound".len();
|
||||
let mut textarea = TextArea::new();
|
||||
textarea.insert_str(&text);
|
||||
textarea.add_element_range(bound_start..bound_end);
|
||||
textarea.set_cursor("$left ".len());
|
||||
|
||||
assert_eq!(
|
||||
current_prefixed_token_range(&textarea, '$', /*allow_empty*/ true),
|
||||
Some((0.."$left".len(), "left".to_string()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_prefixed_token_targets_later_segment_between_bound_elements() {
|
||||
let text = "$bound1$one$bound2$two$bound3";
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: tui/src/bottom_pane/chat_composer.rs
|
||||
expression: terminal.backend()
|
||||
---
|
||||
" "
|
||||
"› $left $bound/path "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" "
|
||||
" left [Skill] Example skill used in tests. "
|
||||
" "
|
||||
" Press enter to insert or esc to close "
|
||||
Reference in new issue
Block a user