From 256cedfd839fd996b3a8351784ff34f35df0b7b0 Mon Sep 17 00:00:00 2001 From: danipiza Date: Mon, 11 May 2026 09:39:34 +0200 Subject: [PATCH 1/3] [#24495] Initial approach Signed-off-by: danipiza --- fastddsspy_tool/src/cpp/tool/Controller.cpp | 76 ++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/fastddsspy_tool/src/cpp/tool/Controller.cpp b/fastddsspy_tool/src/cpp/tool/Controller.cpp index 03d6bdf5..ef816294 100644 --- a/fastddsspy_tool/src/cpp/tool/Controller.cpp +++ b/fastddsspy_tool/src/cpp/tool/Controller.cpp @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include +#include +#include + #include #include #include @@ -134,7 +138,77 @@ Controller::Controller( , model_(backend_.model()) , configuration_(configuration) { - // Do nothing + // Tab completion: suggest topic names when completing arguments of an echo command. + input_.stdin_handler().set_tab_completion_callback( + [this](const std::string& line) -> std::vector + { + // ---------------------------------------------------------------- + // -- Tokenize the current line on whitespace --------------------- + std::vector tokens; + { + std::istringstream iss(line); + std::string tok; + while (iss >> tok) + { + tokens.push_back(tok); + } + } + if (tokens.empty()) + { + return {}; + } + + // ---------------------------------------------------------------- + // Only suggest topics for the echo command + static const std::set echo_aliases = + {"echo", "print", "show", "s", "S"}; + if (echo_aliases.find(tokens[0]) == echo_aliases.end()) + { + return {}; + } + + // ---------------------------------------------------------------- + // Determine the topic-name prefix being completed: + // 1. "echo " -> suggest all topics (empty prefix) + // 2. "echo foo" -> suggest topics starting with "foo" + // 3. Anything past the topic argument is not completed + + const bool trailing_space = !line.empty() && + line[line.size() - 1] == ' '; + + std::string prefix; + if (tokens.size() == 1 && trailing_space) // 1. + { + prefix = ""; + } + else if (tokens.size() == 2 && !trailing_space) // 2. + { + prefix = tokens[1]; + } + else // 3. + { + return {}; + } + + // ---------------------------------------------------------------- + // Reuse the same topic-listing logic as the "topics" command + ddspipe::core::types::WildcardDdsFilterTopic filter_topic; + filter_topic.topic_name = prefix + "*"; + std::set topics = + participants::ModelParser::get_topics(*model_, filter_topic); + + std::vector ret; + ret.reserve(topics.size()); + for (const auto& topic : topics) + { + ret.push_back(topic.m_topic_name); + } + // sort alphabetical + std::sort(ret.begin(), ret.end()); + // remove duplicates + ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); + return ret; + }); } void Controller::run() From 49ff1f44d33b4503fff8e58f8fdcc2c77a815f22 Mon Sep 17 00:00:00 2001 From: danipiza Date: Mon, 11 May 2026 10:49:24 +0200 Subject: [PATCH 2/3] [#24495] Added topics command autocompletation Signed-off-by: danipiza --- fastddsspy_tool/src/cpp/tool/Controller.cpp | 39 +++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/fastddsspy_tool/src/cpp/tool/Controller.cpp b/fastddsspy_tool/src/cpp/tool/Controller.cpp index ef816294..6a2b7077 100644 --- a/fastddsspy_tool/src/cpp/tool/Controller.cpp +++ b/fastddsspy_tool/src/cpp/tool/Controller.cpp @@ -160,9 +160,36 @@ Controller::Controller( // ---------------------------------------------------------------- // Only suggest topics for the echo command + + int cmd_flag = 0; static const std::set echo_aliases = - {"echo", "print", "show", "s", "S"}; - if (echo_aliases.find(tokens[0]) == echo_aliases.end()) + {"echo", "print", "show", "s", "S"}; // 1 + static const std::set topic_aliases = + {"topic", "topics", "t", "T", "filter set topic"}; // 2 + static const std::set writer_aliases = + {"datawriter", "datawriters", "w", "W", + "writer", "writers", "publication", "publications"}; // 3. + static const std::set reader_aliases = + {"datareader", "datareaders", "r", "R", + "reader", "readers", "subscription", "subscriptions"}; // 4. + + if (echo_aliases.find(tokens[0]) != echo_aliases.end()) + { + cmd_flag = 1; + } + else if (topic_aliases.find(tokens[0]) != topic_aliases.end()) + { + cmd_flag = 2; + } + /*else if (writer_aliases.find(tokens[0]) != writer_aliases.end()) + { + cmd_flag = 3; + } + else if (reader_aliases.find(tokens[0]) != reader_aliases.end()) + { + cmd_flag = 4; + }*/ + else { return {}; } @@ -196,6 +223,14 @@ Controller::Controller( filter_topic.topic_name = prefix + "*"; std::set topics = participants::ModelParser::get_topics(*model_, filter_topic); + + std::set readers_set; + const auto readers_v = participants::ModelParser::readers(*model_); + /*for (const auto& r: readers_v) + { + readers_set. + readers_set.insert(r.guid); + }*/ std::vector ret; ret.reserve(topics.size()); From 2eb2d1de84f24618736f930821caee713b700563 Mon Sep 17 00:00:00 2001 From: danipiza Date: Tue, 12 May 2026 10:14:29 +0200 Subject: [PATCH 3/3] [#24495] Added participant/writer/reader suggestions Signed-off-by: danipiza --- fastddsspy_tool/src/cpp/tool/Controller.cpp | 184 +++++++++++++------- 1 file changed, 124 insertions(+), 60 deletions(-) diff --git a/fastddsspy_tool/src/cpp/tool/Controller.cpp b/fastddsspy_tool/src/cpp/tool/Controller.cpp index 6a2b7077..d285d072 100644 --- a/fastddsspy_tool/src/cpp/tool/Controller.cpp +++ b/fastddsspy_tool/src/cpp/tool/Controller.cpp @@ -138,7 +138,7 @@ Controller::Controller( , model_(backend_.model()) , configuration_(configuration) { - // Tab completion: suggest topic names when completing arguments of an echo command. + // Tab completion: suggest topics or GUIDs for supported command arguments input_.stdin_handler().set_tab_completion_callback( [this](const std::string& line) -> std::vector { @@ -159,51 +159,121 @@ Controller::Controller( } // ---------------------------------------------------------------- - // Only suggest topics for the echo command - - int cmd_flag = 0; - static const std::set echo_aliases = - {"echo", "print", "show", "s", "S"}; // 1 - static const std::set topic_aliases = - {"topic", "topics", "t", "T", "filter set topic"}; // 2 - static const std::set writer_aliases = - {"datawriter", "datawriters", "w", "W", - "writer", "writers", "publication", "publications"}; // 3. - static const std::set reader_aliases = - {"datareader", "datareaders", "r", "R", - "reader", "readers", "subscription", "subscriptions"}; // 4. - - if (echo_aliases.find(tokens[0]) != echo_aliases.end()) - { - cmd_flag = 1; - } - else if (topic_aliases.find(tokens[0]) != topic_aliases.end()) - { - cmd_flag = 2; - } - /*else if (writer_aliases.find(tokens[0]) != writer_aliases.end()) + // Suggest command arguments for supported commands + static const std::set participant_aliases = { + "participant", "participants", "p", "P" + }; + static const std::set echo_aliases = { + "echo", "print", "show", "s", "S" + }; + static const std::set topic_aliases = { + "topic", "topics", "t", "T" + }; + static const std::set filter_set_topic_aliases = { + "filter", "f", "F" + }; + static const std::set writer_aliases = { + "datawriter", "datawriters", "w", "W", "writer", "writers", + "publication", "publications" + }; + static const std::set reader_aliases = { + "datareader", "datareaders", "r", "R", + "reader", "readers", "subscription", "subscriptions" + }; + + std::string prefix; + + const bool is_participant_cmd = participant_aliases.find(tokens[0]) != participant_aliases.end(); + const bool is_topic_cmd = echo_aliases.find(tokens[0]) != echo_aliases.end() || + topic_aliases.find(tokens[0]) != topic_aliases.end(); + const bool is_filter_set_topic_command = + filter_set_topic_aliases.find(tokens[0]) != filter_set_topic_aliases.end() && + tokens.size() >= 3 && tokens[1] == "set" && tokens[2] == "topic"; + const bool is_writer_cmd = writer_aliases.find(tokens[0]) != writer_aliases.end(); + const bool is_reader_cmd = reader_aliases.find(tokens[0]) != reader_aliases.end(); + + if (!is_participant_cmd && !is_topic_cmd + && !is_filter_set_topic_command && !is_writer_cmd && !is_reader_cmd) { - cmd_flag = 3; + return {}; } - else if (reader_aliases.find(tokens[0]) != reader_aliases.end()) + + // Check if the last char is an empty space + const bool trailing_space = !line.empty() && line[line.size() - 1] == ' '; + + // ---------------------------------------------------------------- + // -- Helpers ----------------------------------------------------- + auto get_topic_suggestions = + [this](const std::string& prefix) -> std::vector { - cmd_flag = 4; - }*/ - else + // Reuse the same topic-listing logic as the "topics" command + ddspipe::core::types::WildcardDdsFilterTopic filter_topic; + filter_topic.topic_name = prefix + "*"; + std::set topics = + participants::ModelParser::get_topics(*model_, filter_topic); + + std::vector ret; + ret.reserve(topics.size()); + for (const auto& topic : topics) + { + ret.push_back(topic.m_topic_name); + } + + std::sort(ret.begin(), ret.end()); + ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); + return ret; + }; + + auto append_matching_guid_suggestions = + [this](const auto& entities, const std::string& prefix) -> std::vector { - return {}; - } + std::vector ret; + + ret.reserve(ret.size() + entities.size()); + for (const auto& entity : entities) + { + std::ostringstream guid_ss; + guid_ss << entity.guid; + const std::string guid = guid_ss.str(); + if (guid.rfind(prefix, 0) == 0) + { + ret.push_back(guid); + } + } + + std::sort(ret.begin(), ret.end()); + ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); + return ret; + }; // ---------------------------------------------------------------- - // Determine the topic-name prefix being completed: - // 1. "echo " -> suggest all topics (empty prefix) - // 2. "echo foo" -> suggest topics starting with "foo" - // 3. Anything past the topic argument is not completed + // -- Start arguments suggestions --------------------------------- - const bool trailing_space = !line.empty() && - line[line.size() - 1] == ' '; + // Complete the topic-name argument in the command: + // filter/f set topic + if (is_filter_set_topic_command) + { + // No chars in the argument, suggest all topics + if (tokens.size() == 3 && trailing_space) + { + return get_topic_suggestions(""); + } + // The topic argument has atleast one char, + // suggest the topics with those chars + else if (tokens.size() == 4 && !trailing_space) + { + return get_topic_suggestions(tokens[3]); + } + else + { + return {}; + } + } - std::string prefix; + // Determine the first-argument prefix being completed: + // 1. " " -> suggest all matches (empty prefix) + // 2. " foo" -> suggest matches starting with "foo" + // 3. Anything past the first argument is not completed if (tokens.size() == 1 && trailing_space) // 1. { prefix = ""; @@ -217,31 +287,25 @@ Controller::Controller( return {}; } - // ---------------------------------------------------------------- - // Reuse the same topic-listing logic as the "topics" command - ddspipe::core::types::WildcardDdsFilterTopic filter_topic; - filter_topic.topic_name = prefix + "*"; - std::set topics = - participants::ModelParser::get_topics(*model_, filter_topic); - - std::set readers_set; - const auto readers_v = participants::ModelParser::readers(*model_); - /*for (const auto& r: readers_v) - { - readers_set. - readers_set.insert(r.guid); - }*/ - std::vector ret; - ret.reserve(topics.size()); - for (const auto& topic : topics) + + if (is_participant_cmd) { - ret.push_back(topic.m_topic_name); + ret = append_matching_guid_suggestions(participants::ModelParser::participants(*model_), prefix); } - // sort alphabetical - std::sort(ret.begin(), ret.end()); - // remove duplicates - ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); + else if (is_topic_cmd) + { + ret = get_topic_suggestions(prefix); + } + else if (is_writer_cmd) + { + ret = append_matching_guid_suggestions(participants::ModelParser::writers(*model_), prefix); + } + else if (is_reader_cmd) + { + ret = append_matching_guid_suggestions(participants::ModelParser::readers(*model_), prefix); + } + return ret; }); }