Skip to content

Added "Repeat Last Action" command#32928

Open
joxeankoret wants to merge 1 commit intomusescore:masterfrom
joxeankoret:joxean/repeat-last-action
Open

Added "Repeat Last Action" command#32928
joxeankoret wants to merge 1 commit intomusescore:masterfrom
joxeankoret:joxean/repeat-last-action

Conversation

@joxeankoret
Copy link
Copy Markdown

@joxeankoret joxeankoret commented Apr 7, 2026

Hi!

This PR basically lets redo the last action done. I find it annoying to have to go to the palette to choose again the same articulation or have to find some menu to re-apply some action to some bar or selection, specially when I have to do it many-many times for the same composition or arrangement I'm working on at many different points. With these changes, now I only need to do "Shift + R" for my new selection and it makes it much more efficient for my workflow. Hopefully it's also useful for somebody else.

  • I signed the CLA
  • The title of the PR describes the problem it addresses
  • Each commit's message describes its purpose and effects, and references the issue it resolves
  • If changes are extensive, there is a sequence of easily reviewable commits
  • The code in the PR follows the coding rules
  • There are no unnecessary changes
  • The code compiles and runs on my machine, preferably after each commit individually

It basically lets redo the last action. I find it annoying to have to go
to the palette to choose again some articulation or have to find some menu
to re-apply some action to some bar or selection, specially when I have
to do it many-many things for the same composition or arrangement I'm
working on. With these changes, now I only need to do "Shift + R" for my
new selection and it makes it much more efficient for my workflow.

Hopefully it's also useful for somebody else.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

This change introduces a "repeat last action" feature across the application framework. It adds keyboard shortcut Shift+R and Tools menu entry for a new repeat-last-action action. The implementation tracks the most recently executed repeatable action in the actions dispatcher and the most recently applied palette element in notation interaction. When invoked, the action compares sequence identifiers to determine whether to re-apply the last palette element or re-dispatch the last repeatable action. Supporting infrastructure includes new interface methods, implementation in both the actions dispatcher and notation interaction, a sequence tracking helper, and test mocks.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main feature added: a new 'Repeat Last Action' command. It directly reflects the primary functionality introduced across the changeset.
Description check ✅ Passed The PR description includes personal motivation, workflow context, and confirms completion of all checklist items. However, it lacks a structured 'Resolves' issue link and technical details about implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/framework/actions/actiontypes.h`:
- Around line 44-48: The static uint64_t counter in nextActionSequence() is not
thread-safe; replace it with a std::atomic<uint64_t> (e.g., make counter an
atomic<uint64_t>) and perform an atomic increment (fetch_add(1) or ++ with
appropriate memory ordering) inside nextActionSequence() so concurrent calls are
safe and undefined behavior is avoided; also add the necessary <atomic> include
and keep the function signature nextActionSequence() unchanged.

In `@src/framework/actions/iactionsdispatcher.h`:
- Around line 58-61: Add a brief documentation comment to the dispatcher
interface clarifying how callers should detect absence of a repeatable action:
state that lastRepeatableAction() returns an empty/invalid ActionCode when none
exists and lastRepeatableActionSequence() returns 0 in that case, and mention
that repeatLastAction() should be called only when a repeatable action is
present; reference the existing methods lastRepeatableAction(),
lastRepeatableActionSequence(), and repeatLastAction() and mirror the intent of
INotationInteraction::canReapplyLastPaletteElement() for clarity.

In `@src/framework/actions/internal/actionsdispatcher.cpp`:
- Around line 217-281: ActionsDispatcher::isRepeatable currently keeps a
hardcoded static unordered_set repeatableActions which will drift; replace this
by consulting a single source of truth in the action registry/config (e.g., add
a repeatable metadata flag to your existing Action or ActionDescriptor and
expose a lookup like ActionRegistry::isRepeatable(const ActionCode&)). Change
isRepeatable to return ActionRegistry::isRepeatable(code) (or query
Action(code).descriptor().isRepeatable()) and remove the static
repeatableActions set; optionally keep the set as a temporary fallback only
while migrating, but ensure the canonical metadata lives in the
registry/ActionDescriptor so new actions automatically honor repeatability.

In `@src/notation/internal/notationinteraction.cpp`:
- Around line 2365-2367: Repeat currently loses original keyboard modifiers when
storing palette actions; extend NotationInteraction to save and replay
modifiers: add a member Qt::KeyboardModifiers m_lastPaletteModifiers (default
{}) and set it where you call m_lastPaletteElement.reset(element->clone()) and
m_lastPaletteElementSequence = muse::actions::nextActionSequence(); then update
the replay path that calls applyPaletteElement(m_lastPaletteElement.get()) to
pass or apply m_lastPaletteModifiers so the palette action is executed with the
original Ctrl/Shift state (ensure any calls to applyPaletteElement or its
wrapper accept/forward the modifiers).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8bfd5fff-34e0-47b3-9ffd-21e6d4ecfa56

📥 Commits

Reviewing files that changed from the base of the PR and between 4e25547 and 558919e.

📒 Files selected for processing (13)
  • src/app/configs/data/shortcuts.xml
  • src/appshell/qml/MuseScore/AppShell/appmenumodel.cpp
  • src/framework/actions/actiontypes.h
  • src/framework/actions/iactionsdispatcher.h
  • src/framework/actions/internal/actionsdispatcher.cpp
  • src/framework/actions/internal/actionsdispatcher.h
  • src/framework/actions/tests/mocks/actionsdispatchermock.h
  • src/notation/inotationinteraction.h
  • src/notation/internal/notationinteraction.cpp
  • src/notation/internal/notationinteraction.h
  • src/notation/tests/mocks/notationinteractionmock.h
  • src/notationscene/internal/notationactioncontroller.cpp
  • src/notationscene/internal/notationuiactions.cpp

Comment on lines +44 to +48
inline uint64_t nextActionSequence()
{
static uint64_t counter = 0;
return ++counter;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's confirm the file exists and check the actual content at lines 44-48
cd /repo && wc -l src/framework/actions/actiontypes.h

Repository: musescore/MuseScore

Length of output: 118


🏁 Script executed:

# Read the relevant section of the file
cd /repo && sed -n '40,55p' src/framework/actions/actiontypes.h

Repository: musescore/MuseScore

Length of output: 118


🏁 Script executed:

# Search for all usages of nextActionSequence to understand calling context
cd /repo && rg -n "nextActionSequence" --type cpp --type h

Repository: musescore/MuseScore

Length of output: 118


🏁 Script executed:

# Check for any threading/concurrency includes or context in the file
cd /repo && head -40 src/framework/actions/actiontypes.h

Repository: musescore/MuseScore

Length of output: 118


🏁 Script executed:

# Search for mutex, lock, or atomic usage patterns related to action sequences
cd /repo && rg -n "mutex|atomic|thread|lock" src/framework/actions/ --type cpp --type h -i

Repository: musescore/MuseScore

Length of output: 118


🏁 Script executed:

# Check file size and read the relevant section
wc -l src/framework/actions/actiontypes.h

Repository: musescore/MuseScore

Length of output: 102


🏁 Script executed:

# Read lines 40-55 to see the function context
sed -n '40,55p' src/framework/actions/actiontypes.h

Repository: musescore/MuseScore

Length of output: 350


🏁 Script executed:

# Search for all usages of nextActionSequence
rg -n "nextActionSequence" --type cpp --type h

Repository: musescore/MuseScore

Length of output: 375


🏁 Script executed:

# Check the beginning of the file for includes and context
head -40 src/framework/actions/actiontypes.h

Repository: musescore/MuseScore

Length of output: 1275


🏁 Script executed:

# Search for threading/concurrency patterns in the framework actions directory
rg -n "mutex|atomic|thread|lock|std::thread" src/framework/actions/ --type cpp --type h -i | head -30

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Check the actionsdispatcher context where nextActionSequence is called
sed -n '130,150p' src/framework/actions/internal/actionsdispatcher.cpp

Repository: musescore/MuseScore

Length of output: 596


🏁 Script executed:

# Check more context in actionsdispatcher.cpp - class definition
rg -n "class.*ActionDispatcher|nextActionSequence" src/framework/actions/internal/actionsdispatcher.cpp -B 5 -A 5

Repository: musescore/MuseScore

Length of output: 408


🏁 Script executed:

# Check the notationinteraction context
sed -n '2360,2375p' src/notation/internal/notationinteraction.cpp

Repository: musescore/MuseScore

Length of output: 373


🏁 Script executed:

# Check if MuseScore has threading infrastructure - look for thread pools, worker threads
rg -n "QThread|std::thread|ThreadPool|Worker" src/framework/ --type cpp --type h | head -20

Repository: musescore/MuseScore

Length of output: 2373


🏁 Script executed:

# Check if the actionsdispatcher or notationinteraction are used in threaded contexts
rg -n "moveToThread|emit|signal|Worker" src/framework/actions/internal/actionsdispatcher.cpp src/notation/internal/notationinteraction.cpp --type cpp -B 2 -A 2 | head -40

Repository: musescore/MuseScore

Length of output: 670


🏁 Script executed:

# Check ActionDispatcher dispatch method signature and class
rg -n "class ActionDispatcher|void dispatch\(" src/framework/actions/internal/actionsdispatcher.h src/framework/actions/internal/actionsdispatcher.cpp -B 3 -A 8 | head -50

Repository: musescore/MuseScore

Length of output: 1538


🏁 Script executed:

# Search for calls to dispatch() from async/worker thread contexts
rg -n "taskScheduler|async|thread|worker" src/framework/actions/internal/actionsdispatcher.cpp --type cpp -B 2 -A 2

Repository: musescore/MuseScore

Length of output: 179


🏁 Script executed:

# Check NotationInteraction class for thread affinity hints
rg -n "class NotationInteraction|mainThreadOnly|threadSafe|mutex" src/notation/internal/notationinteraction.h -B 2 -A 5 | head -40

Repository: musescore/MuseScore

Length of output: 570


🏁 Script executed:

# Search for how NotationInteraction methods are invoked - from slots, from main thread, etc.
rg -n "NotationInteraction|Q_SLOT|qobject_cast|QThread" src/notation/internal/notationinteraction.cpp --type cpp | head -20

Repository: musescore/MuseScore

Length of output: 1374


🏁 Script executed:

# Check if there are any thread safety comments or annotations in the codebase
rg -n "thread.safe|thread-safe|not.thread.safe|data.race|synchroniz" src/framework/actions/ --type cpp --type h -i | head -20

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Check if ActionDispatcher has thread affinity constraints or Qt thread enforcement
rg -n "mainThread|moveToThread|QThread|Q_OBJECT" src/framework/actions/internal/actionsdispatcher.h src/framework/actions/internal/actionsdispatcher.cpp --type cpp --type h

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Search for how dispatch is invoked - look for calls to dispatch
rg -n "\.dispatch\(" src/ --type cpp | head -30

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Check if there's any runtime thread ID verification in actionsdispatcher
rg -n "thread|Thread|THREAD" src/framework/actions/internal/actionsdispatcher.cpp --type cpp -i -B 2 -A 2

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Check the async module for thread safety guarantees
rg -n "mainThread|affinity|singleThread" src/framework/global/async/ --type cpp --type h | head -20

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Look for any Qt integration with ActionDispatcher (Qt objects are main-thread)
rg -n "QObject|Q_OBJECT|signals|slots" src/framework/actions/internal/actionsdispatcher.h src/framework/actions/internal/actionsdispatcher.cpp --type cpp --type h

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Broader search for dispatch calls without the parenthesis
rg -n "dispatch" src/framework/actions/ --type cpp --type h | head -30

Repository: musescore/MuseScore

Length of output: 3125


🏁 Script executed:

# Check ActionDispatcher instantiation and usage
rg -n "ActionsDispatcher\|ActionDispatcher" src/ --type cpp | head -20

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Look at the header to see if there are any comments about thread safety
cat src/framework/actions/internal/actionsdispatcher.h

Repository: musescore/MuseScore

Length of output: 2713


🏁 Script executed:

# Check the Module/initialization of ActionDispatcher - might show thread model
rg -n "ActionDispatcher\|actionsDispatcher" src/framework/actions/ --type cpp -B 2 -A 2 | head -40

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Check if the async::Channel used for postDispatch has thread safety implications
rg -n "class Channel|postDispatch" src/framework/global/async/ --type h -B 2 -A 10 | head -50

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Final check: look at async::Asyncable to understand thread affinity
rg -n "class Asyncable" src/framework/global/async/ --type h -A 15

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Search for Asyncable class definition
rg -n "Asyncable" src/framework/global/ --type h | head -20

Repository: musescore/MuseScore

Length of output: 2702


🏁 Script executed:

# Look for documentation or comments about thread safety in async module
rg -n "thread|Thread" src/framework/global/async/ --type h --type cpp -i | head -20

Repository: musescore/MuseScore

Length of output: 163


🏁 Script executed:

# Check if there's explicit single-threaded enforcement or main thread checks
rg -n "mainThread\|isMainThread\|CHECK_IS_ON_MAIN_THREAD" src/framework/ --type h --type cpp | head -15

Repository: musescore/MuseScore

Length of output: 45


🏁 Script executed:

# Check if there's documentation or usage patterns showing thread affinity
rg -n "dispatch" src/framework/actions/actionsmodule.cpp --type cpp -B 5 -A 10

Repository: musescore/MuseScore

Length of output: 575


🏁 Script executed:

# Search for any thread safety comments in the codebase around action code
rg -n "thread.safe|single.thread|main.thread" src/framework/actions/ --type cpp --type h -i

Repository: musescore/MuseScore

Length of output: 45


Use atomic increment for global sequence generation.

++counter on a shared static variable can race if nextActionSequence() is called from multiple threads, causing undefined behavior. Please make it atomic (or enforce and document strict single-thread access).

🔧 Proposed fix
+#include <atomic>
 ...
 inline uint64_t nextActionSequence()
 {
-    static uint64_t counter = 0;
-    return ++counter;
+    static std::atomic_uint64_t counter { 0 };
+    return counter.fetch_add(1, std::memory_order_relaxed) + 1;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/actions/actiontypes.h` around lines 44 - 48, The static
uint64_t counter in nextActionSequence() is not thread-safe; replace it with a
std::atomic<uint64_t> (e.g., make counter an atomic<uint64_t>) and perform an
atomic increment (fetch_add(1) or ++ with appropriate memory ordering) inside
nextActionSequence() so concurrent calls are safe and undefined behavior is
avoided; also add the necessary <atomic> include and keep the function signature
nextActionSequence() unchanged.

Comment on lines +58 to +61
virtual ActionCode lastRepeatableAction() const = 0;
virtual uint64_t lastRepeatableActionSequence() const = 0;
virtual void repeatLastAction() = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Interface API asymmetry with INotationInteraction.

The dispatcher interface provides lastRepeatableAction() and lastRepeatableActionSequence() but lacks an explicit availability check method like INotationInteraction::canReapplyLastPaletteElement(). This is likely intentional, as callers can check for an empty ActionCode or sequence value of 0 to determine availability.

Consider adding a brief comment documenting how to determine if a repeatable action is available (e.g., "Returns empty ActionCode if no repeatable action exists").

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/actions/iactionsdispatcher.h` around lines 58 - 61, Add a brief
documentation comment to the dispatcher interface clarifying how callers should
detect absence of a repeatable action: state that lastRepeatableAction() returns
an empty/invalid ActionCode when none exists and lastRepeatableActionSequence()
returns 0 in that case, and mention that repeatLastAction() should be called
only when a repeatable action is present; reference the existing methods
lastRepeatableAction(), lastRepeatableActionSequence(), and repeatLastAction()
and mirror the intent of INotationInteraction::canReapplyLastPaletteElement()
for clarity.

Comment on lines +217 to +281
bool ActionsDispatcher::isRepeatable(const ActionCode& code) const
{
// Query-style action codes (e.g., "action://...") cannot be safely
// re-dispatched with ActionData, so never treat them as repeatable.
if (ActionQuery(code).isValid()) {
return false;
}

static const std::unordered_set<ActionCode> repeatableActions = {
// Articulations
"add-marcato", "add-sforzato", "add-tenuto", "add-staccato",
// Ornaments
"add-turn", "add-turn-inverted", "add-turn-slash",
"add-turn-up", "add-turn-inverted-up",
"add-trill", "add-short-trill", "add-mordent", "add-haydn",
"add-tremblement", "add-prall-mordent", "add-shake",
"add-shake-muffat", "add-tremblement-couperin",
// Bowing
"add-up-bow", "add-down-bow",
// Slurs, ties, lines
"add-slur", "tie", "chord-tie", "lv", "hammer-on-pull-off",
// Hairpins, ottava
"add-hairpin", "add-hairpin-reverse", "add-8va", "add-8vb",
"add-dynamic", "add-noteline",
// Accidentals
"sharp2-post", "sharp-post", "nat-post", "flat-post", "flat2-post",
"flat2", "flat", "nat", "sharp", "sharp2",
// Brackets
"add-brackets", "add-parentheses", "add-braces",
// Beam modes
"beam-auto", "beam-none", "beam-break-left",
"beam-break-inner-8th", "beam-break-inner-16th", "beam-join",
// Grace notes
"acciaccatura", "appoggiatura", "grace4", "grace16", "grace32",
"grace8after", "grace16after", "grace32after",
// Layout breaks
"system-break", "page-break", "section-break",
// Visibility
"toggle-visible", "set-visible", "unset-visible",
// Flip
"flip", "flip-horizontally",
// Enharmonic
"enh-both", "enh-current",
// Transposition
"transpose-up", "transpose-down",
// Text
"system-text", "staff-text", "expression-text",
"rehearsalmark-text", "tempo", "fingering-text",
"sticking-text", "chord-text", "roman-numeral-text",
"nashville-number-text", "lyrics",
// Guitar bends
"standard-bend", "pre-bend", "grace-note-bend", "slight-bend",
"dive", "pre-dive", "dip", "scoop",
// Pitch
"pitch-up", "pitch-down", "pitch-up-octave", "pitch-down-octave",
"pitch-up-diatonic", "pitch-down-diatonic",
// Misc
"toggle-autoplace", "autoplace-enabled", "rest",
"toggle-snap-to-previous", "toggle-snap-to-next",
"mirror-note", "toggle-mmrest", "toggle-hide-empty",
"full-measure-rest",
};

return repeatableActions.find(code) != repeatableActions.end();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Repeatability as a hardcoded whitelist will drift over time

isRepeatable() duplicates product behavior in a local static set. New repeat-worthy actions added elsewhere will silently stop being repeatable unless this list is manually updated.

Consider moving repeatability metadata to a single source of truth (e.g., action registry/config) and deriving this check from that metadata.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/actions/internal/actionsdispatcher.cpp` around lines 217 - 281,
ActionsDispatcher::isRepeatable currently keeps a hardcoded static unordered_set
repeatableActions which will drift; replace this by consulting a single source
of truth in the action registry/config (e.g., add a repeatable metadata flag to
your existing Action or ActionDescriptor and expose a lookup like
ActionRegistry::isRepeatable(const ActionCode&)). Change isRepeatable to return
ActionRegistry::isRepeatable(code) (or query
Action(code).descriptor().isRepeatable()) and remove the static
repeatableActions set; optionally keep the set as a temporary fallback only
while migrating, but ensure the canonical metadata lives in the
registry/ActionDescriptor so new actions automatically honor repeatability.

Comment on lines +2365 to +2367
m_lastPaletteElement.reset(element->clone());
m_lastPaletteElementSequence = muse::actions::nextActionSequence();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Repeat currently drops original keyboard modifiers

At Line 2387, replay calls applyPaletteElement(m_lastPaletteElement.get()) with default modifiers. Palette operations that depend on Ctrl/Shift will not reproduce the original action semantics.

💡 Suggested fix
-    m_lastPaletteElement.reset(element->clone());
-    m_lastPaletteElementSequence = muse::actions::nextActionSequence();
+    m_lastPaletteElement.reset(element->clone());
+    m_lastPaletteModifiers = modifiers;
+    m_lastPaletteElementSequence = muse::actions::nextActionSequence();
@@
-    return applyPaletteElement(m_lastPaletteElement.get());
+    return applyPaletteElement(m_lastPaletteElement.get(), m_lastPaletteModifiers);

Also add a member in NotationInteraction (header):

Qt::KeyboardModifiers m_lastPaletteModifiers = {};

Also applies to: 2381-2388

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notation/internal/notationinteraction.cpp` around lines 2365 - 2367,
Repeat currently loses original keyboard modifiers when storing palette actions;
extend NotationInteraction to save and replay modifiers: add a member
Qt::KeyboardModifiers m_lastPaletteModifiers (default {}) and set it where you
call m_lastPaletteElement.reset(element->clone()) and
m_lastPaletteElementSequence = muse::actions::nextActionSequence(); then update
the replay path that calls applyPaletteElement(m_lastPaletteElement.get()) to
pass or apply m_lastPaletteModifiers so the palette action is executed with the
original Ctrl/Shift state (ensure any calls to applyPaletteElement or its
wrapper accept/forward the modifiers).

@MarcSabatella
Copy link
Copy Markdown
Contributor

MarcSabatella commented Apr 7, 2026

FWIW, I have nothing against this basic idea, but just wanted to point out that there is already the "apply current palette element" command (for which you can define a shortcut) that does the palette part of this - no need to special-case those that also have actions.

Of course, having actions for palette items would be great if it allows them to be assigned shortcuts, but such a facility should ideally support all palette items, including custom ones. That's a more complicated design problem, though, which is the main reason this wasn't done long ago.

@mathesoncalum mathesoncalum added the needs design approval Feature/change requests that need a go/no-go from the design team before being worked on label Apr 7, 2026
@joxeankoret
Copy link
Copy Markdown
Author

FWIW, I have nothing against this basic idea, but just wanted to point out that there is already the "apply current palette element" command (for which you can define a shortcut) that does the palette part of this - no need to special-case those that also have actions.

There are more actions than palette elements that I like to have re-applied sometimes, like, for example, Paste double/half duration, transposing or switching voices (or even re-running plugins, something I haven't added support in this PR).

Of course, having actions for palette items would be great if it allows them to be assigned shortcuts, but such a facility should ideally support all palette items, including custom ones. That's a more complicated design problem, though, which is the main reason this wasn't done long ago.

I would be happy adapting my changes to support whatever you might want, or doing so in alternate PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs design approval Feature/change requests that need a go/no-go from the design team before being worked on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants