Support emoji shortcodes in chat (#8455)
Completes: https://github.com/zed-industries/zed/issues/7299 Suggestions https://github.com/zed-industries/zed/assets/53836821/2a81ba89-4634-4d94-8370-6f76ff3e9403 Automatically replacing shortcodes without using the completions (only enabled when `message_editor` > `auto_replace_emoji_shortcode` is enabled in the settings): https://github.com/zed-industries/zed/assets/53836821/10ef2b4b-c67b-4202-b958-332a37dc088e Release Notes: - Added autocompletion for emojis in chat when typing emoji shortcodes ([#7299](https://github.com/zed-industries/zed/issues/7299)). - Added support for automatically replacing emoji shortcodes in chat (e.g. typing "👋" will be converted to "👋") --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
co-authored by
Conrad Irwin
parent
c0edb5bd6c
commit
8be4b4d75d
@@ -425,6 +425,7 @@ pub struct Editor {
|
||||
editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
|
||||
show_copilot_suggestions: bool,
|
||||
use_autoclose: bool,
|
||||
auto_replace_emoji_shortcode: bool,
|
||||
custom_context_menu: Option<
|
||||
Box<
|
||||
dyn 'static
|
||||
@@ -1539,6 +1540,7 @@ impl Editor {
|
||||
use_modal_editing: mode == EditorMode::Full,
|
||||
read_only: false,
|
||||
use_autoclose: true,
|
||||
auto_replace_emoji_shortcode: false,
|
||||
leader_peer_id: None,
|
||||
remote_id: None,
|
||||
hover_state: Default::default(),
|
||||
@@ -1829,6 +1831,10 @@ impl Editor {
|
||||
self.use_autoclose = autoclose;
|
||||
}
|
||||
|
||||
pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
|
||||
self.auto_replace_emoji_shortcode = auto_replace;
|
||||
}
|
||||
|
||||
pub fn set_show_copilot_suggestions(&mut self, show_copilot_suggestions: bool) {
|
||||
self.show_copilot_suggestions = show_copilot_suggestions;
|
||||
}
|
||||
@@ -2505,6 +2511,47 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
if self.auto_replace_emoji_shortcode
|
||||
&& selection.is_empty()
|
||||
&& text.as_ref().ends_with(':')
|
||||
{
|
||||
if let Some(possible_emoji_short_code) =
|
||||
Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
|
||||
{
|
||||
if !possible_emoji_short_code.is_empty() {
|
||||
if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
|
||||
let emoji_shortcode_start = Point::new(
|
||||
selection.start.row,
|
||||
selection.start.column - possible_emoji_short_code.len() as u32 - 1,
|
||||
);
|
||||
|
||||
// Remove shortcode from buffer
|
||||
edits.push((
|
||||
emoji_shortcode_start..selection.start,
|
||||
"".to_string().into(),
|
||||
));
|
||||
new_selections.push((
|
||||
Selection {
|
||||
id: selection.id,
|
||||
start: snapshot.anchor_after(emoji_shortcode_start),
|
||||
end: snapshot.anchor_before(selection.start),
|
||||
reversed: selection.reversed,
|
||||
goal: selection.goal,
|
||||
},
|
||||
0,
|
||||
));
|
||||
|
||||
// Insert emoji
|
||||
let selection_start_anchor = snapshot.anchor_after(selection.start);
|
||||
new_selections.push((selection.map(|_| selection_start_anchor), 0));
|
||||
edits.push((selection.start..selection.end, emoji.to_string().into()));
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not handling any auto-close operation, then just replace the selected
|
||||
// text with the given input and move the selection to the end of the
|
||||
// newly inserted text.
|
||||
@@ -2588,6 +2635,32 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
fn find_possible_emoji_shortcode_at_position(
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
position: Point,
|
||||
) -> Option<String> {
|
||||
let mut chars = Vec::new();
|
||||
let mut found_colon = false;
|
||||
for char in snapshot.reversed_chars_at(position).take(100) {
|
||||
// Found a possible emoji shortcode in the middle of the buffer
|
||||
if found_colon && char.is_whitespace() {
|
||||
chars.reverse();
|
||||
return Some(chars.iter().collect());
|
||||
}
|
||||
if char.is_whitespace() || !char.is_ascii() {
|
||||
return None;
|
||||
}
|
||||
if char == ':' {
|
||||
found_colon = true;
|
||||
} else {
|
||||
chars.push(char);
|
||||
}
|
||||
}
|
||||
// Found a possible emoji shortcode at the beginning of the buffer
|
||||
chars.reverse();
|
||||
Some(chars.iter().collect())
|
||||
}
|
||||
|
||||
pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
|
||||
self.transact(cx, |this, cx| {
|
||||
let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
|
||||
|
||||
@@ -5171,6 +5171,59 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig::default(),
|
||||
Some(tree_sitter_rust::language()),
|
||||
));
|
||||
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
|
||||
.with_language(language, cx)
|
||||
});
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
|
||||
editor
|
||||
.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
|
||||
.await;
|
||||
|
||||
_ = editor.update(cx, |editor, cx| {
|
||||
editor.set_auto_replace_emoji_shortcode(true);
|
||||
|
||||
editor.handle_input("Hello ", cx);
|
||||
editor.handle_input(":wave", cx);
|
||||
assert_eq!(editor.text(cx), "Hello :wave".unindent());
|
||||
|
||||
editor.handle_input(":", cx);
|
||||
assert_eq!(editor.text(cx), "Hello 👋".unindent());
|
||||
|
||||
editor.handle_input(" :smile", cx);
|
||||
assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
|
||||
|
||||
editor.handle_input(":", cx);
|
||||
assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
|
||||
|
||||
editor.handle_input(":1:", cx);
|
||||
assert_eq!(editor.text(cx), "Hello 👋 😄:1:".unindent());
|
||||
|
||||
// Ensure shortcode does not get replaced when it is part of a word
|
||||
editor.handle_input(" Test:wave:", cx);
|
||||
assert_eq!(editor.text(cx), "Hello 👋 😄:1: Test:wave:".unindent());
|
||||
|
||||
editor.set_auto_replace_emoji_shortcode(false);
|
||||
|
||||
// Ensure shortcode does not get replaced when auto replace is off
|
||||
editor.handle_input(" :wave:", cx);
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
"Hello 👋 😄:1: Test:wave: :wave:".unindent()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_snippets(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
Reference in New Issue
Block a user