Repository: helix-editor/helix (language: rust)

Issue:
A macro executed while recording a macro is recorded twice
### Summary

If a macro is executed while recording a macro in a different register, the recording records both the macro invocation and the keys pressed by the macro. When the new macro is executed, it replays the inner macro and the keys pressed by the macro. This results in the inner macro effectively being executed twice.

### Reproduction Steps

<!-- Ideally provide a key sequence and/or asciinema.org recording. --> 

I tried this:

1. `hx`
2. `"aQihello<esc>Q`
3. `Q"aqi<space>world<esc>Q`
4. `q`

I expected this to happen: The macro in `@` types "hello world".

Instead, this happened: The macro in `@` types "hellohello world".


### Helix log

_No response_

### Platform

Linux

### Terminal Emulator

alacritty 0.15.0 (53395536)

### Installation Method

pacman

### Helix Version

helix 25.01.1


Discussion / hints:

Fix patch:
diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs
index a57fd6177c47..28c8651a2e40 100644
--- a/helix-term/src/compositor.rs
+++ b/helix-term/src/compositor.rs
@@ -137,9 +137,12 @@ impl Compositor {
     }
 
     pub fn handle_event(&mut self, event: &Event, cx: &mut Context) -> bool {
-        // If it is a key event and a macro is being recorded, push the key event to the recording.
+        // If it is a key event, a macro is being recorded, and a macro isn't being replayed,
+        // push the key event to the recording.
         if let (Event::Key(key), Some((_, keys))) = (event, &mut cx.editor.macro_recording) {
-            keys.push(*key);
+            if cx.editor.macro_replaying.is_empty() {
+                keys.push(*key);
+            }
         }
 
         let mut callbacks = Vec::new();


Test patch:
diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs
index baa2835cddfe..2af1a054fccc 100644
--- a/helix-term/tests/test/commands.rs
+++ b/helix-term/tests/test/commands.rs
@@ -793,3 +793,30 @@ fn foo() {
 
     Ok(())
 }
+
+#[tokio::test(flavor = "multi_thread")]
+async fn macro_play_within_macro_record() -> anyhow::Result<()> {
+    // <https://github.com/helix-editor/helix/issues/12697>
+    //
+    // * `"aQihello<esc>Q` record a macro to register 'a' which inserts "hello"
+    // * `Q"aq<space>world<esc>Q` record a macro to the default macro register which plays the
+    //   macro in register 'a' and then inserts " world"
+    // * `%d` clear the buffer
+    // * `q` replay the macro in the default macro register
+    // * `i<ret>` add a newline at the end
+    //
+    // The inner macro in register 'a' should replay within the outer macro exactly once to insert
+    // "hello world".
+    test((
+        indoc! {"\
+            #[|]#
+        "},
+        r#""aQihello<esc>QQ"aqi<space>world<esc>Q%dqi<ret>"#,
+        indoc! {"\
+            hello world
+            #[|]#"},
+    ))
+    .await?;
+
+    Ok(())
+}