Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions framework/rcommand/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ muse_create_module(muse_rcommand ALIAS muse::rcommand)
target_sources(muse_rcommand PRIVATE
rcommandmodule.cpp
rcommandmodule.h
ircommanddispatcher.h
rcommandable.h
rcommandtypes.h
icommanddispatcher.h
imodulecommands.h
commandable.h
commandtypes.h

internal/rcommanddispatcher.cpp
internal/rcommanddispatcher.h
internal/commanddispatcher.cpp
internal/commanddispatcher.h
internal/commandsregister.cpp
internal/commandsregister.h
)
Comment on lines 23 to 35

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check how muse_rcommand headers are surfaced/installed and whether icommandsregister.h is covered.
rg -n "target_sources\\(muse_rcommand|icommandsregister\\.h|FILE_SET|install\\(TARGETS\\s+muse_rcommand" -C3

Repository: musescore/muse_framework

Length of output: 1455


🏁 Script executed:

cat -n framework/rcommand/CMakeLists.txt

Repository: musescore/muse_framework

Length of output: 1519


🏁 Script executed:

# Search for install rules and header export patterns in this module
rg -n "install|FILE_SET|PUBLIC|INTERFACE" framework/rcommand/CMakeLists.txt -A 2

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

# Check the muse_create_module function to understand how headers are handled
rg -n "muse_create_module" --type cmake -A 5 | head -50

Repository: musescore/muse_framework

Length of output: 2696


🏁 Script executed:

# Find muse_create_module definition
rg -n "macro|function" --type cmake | rg "muse_create_module" -B 2 -A 10

Repository: musescore/muse_framework

Length of output: 2094


🏁 Script executed:

# Check if icommandsregister.h exists
ls -la framework/rcommand/ | grep -i icommands

Repository: musescore/muse_framework

Length of output: 138


🏁 Script executed:

# Look for cmake function definitions that might handle module creation
fd -name "*.cmake" -type f | head -20

Repository: musescore/muse_framework

Length of output: 299


🏁 Script executed:

sed -n '45,129p' framework/cmake/MuseCreateModule.cmake

Repository: musescore/muse_framework

Length of output: 2908


🏁 Script executed:

# Also check content of icommandsregister.h to confirm it's a public interface
cat -n framework/rcommand/icommandsregister.h

Repository: musescore/muse_framework

Length of output: 1488


🏁 Script executed:

# Search for install rules in parent CMakeLists or related cmake files
rg -n "install.*DIRECTORY|FILE_SET.*HEADERS|PUBLIC_HEADER" framework/CMakeLists.txt buildscripts/cmake/ -A 3 | head -100

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

# Check if there's a parent CMakeLists that handles module installation
cat -n framework/CMakeLists.txt | head -60

Repository: musescore/muse_framework

Length of output: 2048


🏁 Script executed:

# Verify if icommandsregister.h is accessible as a public include without being in target_sources
rg -n "icommandsregister" -A 2 -B 2 | head -80

Repository: musescore/muse_framework

Length of output: 751


🏁 Script executed:

# Search for install rules that might use target_sources or install headers
rg -n "install.*FILES|install.*DIRECTORY" buildscripts/ --type cmake | head -40

Repository: musescore/muse_framework

Length of output: 774


🏁 Script executed:

# Check if there's a top-level CMakeLists with install rules
find . -maxdepth 2 -name CMakeLists.txt -exec grep -l "install" {} \;

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

# Look for any cmake that might derive install from target_sources
rg -n "PUBLIC_HEADER|FILE_SET" buildscripts/cmake/ --type cmake

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

# Check if other interface headers (.h files not paired with .cpp) are in target_sources
rg -n "\.h" framework/rcommand/CMakeLists.txt | grep "target_sources" -A 20

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

# Look for patterns: do interface-only headers get added to target_sources elsewhere?
rg -l "\.h" framework/*/CMakeLists.txt | xargs grep -l "icommand\|idispatcher\|imodule" | head -5

Repository: musescore/muse_framework

Length of output: 192


🏁 Script executed:

# Check rcommand module structure - count .h vs .cpp files
find framework/rcommand -name "*.h" -o -name "*.cpp" | sort

Repository: musescore/muse_framework

Length of output: 519


Add icommandsregister.h to the module's public header set.

This is a public interface header (marked MODULE_GLOBAL_INTERFACE) and is imported by downstream modules (rcontrol/mcp/mcpcontroller.h). Other public interface headers in this module (icommanddispatcher.h, imodulecommands.h) are explicitly listed in target_sources, so icommandsregister.h should be added for consistency and to ensure it's included in any header export/install metadata.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/rcommand/CMakeLists.txt` around lines 23 - 35, The public interface
header `icommandsregister.h` is missing from the target_sources list for the
muse_rcommand target in the CMakeLists.txt file. Add `icommandsregister.h` to
the target_sources PRIVATE section alongside the other public interface headers
like `icommanddispatcher.h` and `imodulecommands.h` to ensure consistency with
other public headers and to properly include it in any header export or install
metadata.


#if (MUSE_MODULE_RCOMMAND_TESTS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore/Audacity CLA applies
*
* Copyright (C) 2026 MuseScore/Audacity and others
* Copyright (C) MuseScore/Audacity and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Expand All @@ -19,31 +19,31 @@

#pragma once

#include "ircommanddispatcher.h"
#include "icommanddispatcher.h"

namespace muse::rcommand {
class RCommandable
class Commandable
{
public:
virtual ~RCommandable()
virtual ~Commandable()
{
if (m_dispatcher) {
m_dispatcher->unreg(this);
}
}

inline void setDispatcher(IRCommandDispatcher* dispatcher)
inline void setDispatcher(ICommandDispatcher* dispatcher)
{
m_dispatcher = dispatcher;
}

inline bool isDispatcher(const IRCommandDispatcher* dispatcher) const
inline bool isDispatcher(const ICommandDispatcher* dispatcher) const
{
return m_dispatcher == dispatcher;
}

private:

IRCommandDispatcher* m_dispatcher = nullptr;
ICommandDispatcher* m_dispatcher = nullptr;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore/Audacity CLA applies
*
* Copyright (C) 2026 MuseScore/Audacity and others
* Copyright (C) MuseScore/Audacity and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Expand All @@ -20,13 +20,77 @@
#pragma once

#include <any>
#include <map>
#include <string>

#include "global/types/uri.h"
#include "global/types/ret.h"
#include "global/types/string.h"
#include "global/types/val.h"
#include "global/types/mnemonicstring.h"
#include "global/types/translatablestring.h"
#include "global/types/color.h"
#include "ui/uitypes.h"

namespace muse::rcommand {
constexpr std::string_view COMMAND_SCHEME = "command://";
using Command = Uri;
using CommandQuery = UriQuery;

// Info

enum class DataType {
Undefined = 0,
String,
Integer,
Float,
Boolean,
Object,
Array,
Null
};

struct Arg {
DataType type = DataType::Undefined;
String description;
Val min;
Val max;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};

struct InputSchema {
std::map<std::string, Arg> args;
};

enum class Checkable {
No = 0,
Yes
};

struct Decoration {
ui::IconCode::Code iconCode = ui::IconCode::Code::NONE;
Color iconColor;
Checkable checkable = Checkable::No;

Decoration() = default;
Decoration(ui::IconCode::Code iconCode)
: iconCode(iconCode) {}
Decoration(ui::IconCode::Code iconCode, Color iconColor)
: iconCode(iconCode), iconColor(iconColor) {}
Decoration(ui::IconCode::Code iconCode, Color iconColor, Checkable checkable)
: iconCode(iconCode), iconColor(iconColor), checkable(checkable) {}
};

struct CommandInfo
{
Command command;
MnemonicString title;
TranslatableString description;
InputSchema inputSchema;
Decoration decoration;
};

// Call

using CallId = uint64_t;

struct Request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore/Audacity CLA applies
*
* Copyright (C) 2026 MuseScore/Audacity and others
* Copyright (C) MuseScore/Audacity and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Expand All @@ -23,21 +23,21 @@

#include "global/async/promise.h"

#include "rcommandtypes.h"
#include "commandtypes.h"

namespace muse::rcommand {
class RCommandable;
class IRCommandDispatcher : MODULE_CONTEXT_INTERFACE
class Commandable;
class ICommandDispatcher : MODULE_CONTEXT_INTERFACE
{
INTERFACE_ID(IRCommandDispatcher)
INTERFACE_ID(ICommandDispatcher)
public:
virtual ~IRCommandDispatcher() = default;
virtual ~ICommandDispatcher() = default;

using CallBack = std::function<Response (const Request& request)>;

virtual async::Promise<Response> dispatch(const Request& request) = 0;
virtual void onRequest(RCommandable* client, const Command& command, const CallBack& callback) = 0;
virtual void unreg(RCommandable* client) = 0;
virtual void onRequest(Commandable* client, const Command& command, const CallBack& callback) = 0;
virtual void unreg(Commandable* client) = 0;

async::Promise<Response> dispatch(const CommandQuery& query)
{
Expand Down
37 changes: 37 additions & 0 deletions framework/rcommand/icommandsregister.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore/Audacity CLA applies
*
* Copyright (C) MuseScore/Audacity and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once

#include "modularity/imoduleinterface.h"
#include "imodulecommands.h"

namespace muse::rcommand {
class ICommandsRegister : MODULE_GLOBAL_INTERFACE
{
INTERFACE_ID(ICommandsRegister)
public:
virtual ~ICommandsRegister() = default;

virtual void reg(const IModuleCommandsPtr& module) = 0;
virtual void unreg(const IModuleCommandsPtr& module) = 0;

virtual std::vector<CommandInfo> commandList() const = 0;
};
}
40 changes: 40 additions & 0 deletions framework/rcommand/imodulecommands.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore/Audacity CLA applies
*
* Copyright (C) MuseScore/Audacity and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once

#include <string>
#include <vector>
#include <memory>

#include "commandtypes.h"

namespace muse::rcommand {
class IModuleCommands
{
public:

virtual ~IModuleCommands() = default;

virtual std::string moduleName() const = 0;
virtual const std::vector<CommandInfo>& commandInfos() const = 0;
};

using IModuleCommandsPtr = std::shared_ptr<IModuleCommands>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,31 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "rcommanddispatcher.h"
#include "commanddispatcher.h"

#include "async/promise.h"

#include "../rcommandable.h"
#include "../rcommandtypes.h"
#include "../commandable.h"
#include "../commandtypes.h"

#include "log.h"

using namespace muse;
using namespace muse::rcommand;

async::Promise<Response> RCommandDispatcher::dispatch(const Request& request)
CommandDispatcher::~CommandDispatcher()
{
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
Commandable* client = it->second.client;
if (client) {
client->setDispatcher(nullptr);
}
}

m_clients.clear();
}

async::Promise<Response> CommandDispatcher::dispatch(const Request& request)
{
return async::make_promise<Response>([this, request](auto resolve) {
auto it = m_clients.find(Command(request.query.uri()));
Expand All @@ -41,7 +53,7 @@ async::Promise<Response> RCommandDispatcher::dispatch(const Request& request)
});
}

void RCommandDispatcher::onRequest(RCommandable* client, const Command& command, const CallBack& callback)
void CommandDispatcher::onRequest(Commandable* client, const Command& command, const CallBack& callback)
{
IF_ASSERT_FAILED(m_clients.find(command) == m_clients.end()) {
LOGW() << "command already registered: " << command;
Expand All @@ -52,7 +64,7 @@ void RCommandDispatcher::onRequest(RCommandable* client, const Command& command,
client->setDispatcher(this);
}

void RCommandDispatcher::unreg(RCommandable* client)
void CommandDispatcher::unreg(Commandable* client)
{
if (!client || !client->isDispatcher(this)) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,24 @@
*/
#pragma once

#include "../ircommanddispatcher.h"
#include "../icommanddispatcher.h"

namespace muse::rcommand {
class RCommandDispatcher : public IRCommandDispatcher
class CommandDispatcher : public ICommandDispatcher
{
public:
RCommandDispatcher() = default;
CommandDispatcher() = default;
~CommandDispatcher() override;

async::Promise<Response> dispatch(const Request& request) override;
void onRequest(RCommandable* client, const Command& command, const CallBack& callback) override;
void unreg(RCommandable* client) override;
void onRequest(Commandable* client, const Command& command, const CallBack& callback) override;
void unreg(Commandable* client) override;

private:

struct Client
{
RCommandable* client = nullptr;
Commandable* client = nullptr;
CallBack callback = nullptr;
};

Expand Down
Loading