feat: Enhance z3ed networking documentation and CLI capabilities
- Updated NETWORKING.md to reflect the new versioning and provide a comprehensive overview of the z3ed networking, collaboration, and remote access features. - Introduced detailed architecture descriptions, including communication layers and core components for collaboration. - Added new CLI commands for hex and palette manipulation, enhancing the functionality of the z3ed CLI. - Implemented a TODO management system within the CLI for better task tracking and execution planning. - Improved README.md to include new CLI capabilities and enhancements related to the TODO management system.
This commit is contained in:
@@ -457,9 +457,10 @@ else()
|
||||
set(YAZE_RES_FILE "${CMAKE_CURRENT_BINARY_DIR}/yaze.res")
|
||||
|
||||
# Add a custom command to generate the .res file from the .rc and .ico
|
||||
# /I adds include directory so RC compiler can find yaze.ico
|
||||
add_custom_command(
|
||||
OUTPUT ${YAZE_RES_FILE}
|
||||
COMMAND ${CMAKE_RC_COMPILER} /fo ${YAZE_RES_FILE} ${YAZE_RC_FILE}
|
||||
COMMAND ${CMAKE_RC_COMPILER} /fo ${YAZE_RES_FILE} /I "${CMAKE_SOURCE_DIR}/assets" ${YAZE_RC_FILE}
|
||||
DEPENDS ${YAZE_RC_FILE} ${YAZE_ICO_FILE}
|
||||
COMMENT "Generating yaze.res from yaze.rc and yaze.ico"
|
||||
VERBATIM
|
||||
|
||||
@@ -68,6 +68,8 @@ set(YAZE_AGENT_SOURCES
|
||||
cli/service/agent/proposal_executor.cc
|
||||
cli/handlers/agent/tool_commands.cc
|
||||
cli/handlers/agent/todo_commands.cc
|
||||
cli/handlers/agent/hex_commands.cc
|
||||
cli/handlers/agent/palette_commands.cc
|
||||
cli/service/agent/conversational_agent_service.cc
|
||||
cli/service/agent/simple_chat_session.cc
|
||||
cli/service/agent/tool_dispatcher.cc
|
||||
|
||||
@@ -151,6 +151,28 @@ absl::Status Agent::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (subcommand == "simple-chat") {
|
||||
return agent::HandleSimpleChatCommand(subcommand_args, &rom_, absl::GetFlag(FLAGS_quiet));
|
||||
}
|
||||
|
||||
// Hex manipulation commands
|
||||
if (subcommand == "hex-read") {
|
||||
return agent::HandleHexRead(subcommand_args, &rom_);
|
||||
}
|
||||
if (subcommand == "hex-write") {
|
||||
return agent::HandleHexWrite(subcommand_args, &rom_);
|
||||
}
|
||||
if (subcommand == "hex-search") {
|
||||
return agent::HandleHexSearch(subcommand_args, &rom_);
|
||||
}
|
||||
|
||||
// Palette manipulation commands
|
||||
if (subcommand == "palette-get-colors") {
|
||||
return agent::HandlePaletteGetColors(subcommand_args, &rom_);
|
||||
}
|
||||
if (subcommand == "palette-set-color") {
|
||||
return agent::HandlePaletteSetColor(subcommand_args, &rom_);
|
||||
}
|
||||
if (subcommand == "palette-analyze") {
|
||||
return agent::HandlePaletteAnalyze(subcommand_args, &rom_);
|
||||
}
|
||||
|
||||
return absl::InvalidArgumentError(std::string(agent::kUsage));
|
||||
}
|
||||
|
||||
@@ -69,6 +69,22 @@ absl::Status HandleSimpleChatCommand(const std::vector<std::string>&, Rom* rom,
|
||||
absl::Status HandleTestConversationCommand(
|
||||
const std::vector<std::string>& arg_vec);
|
||||
|
||||
// Hex manipulation commands
|
||||
absl::Status HandleHexRead(const std::vector<std::string>& arg_vec,
|
||||
Rom* rom_context = nullptr);
|
||||
absl::Status HandleHexWrite(const std::vector<std::string>& arg_vec,
|
||||
Rom* rom_context = nullptr);
|
||||
absl::Status HandleHexSearch(const std::vector<std::string>& arg_vec,
|
||||
Rom* rom_context = nullptr);
|
||||
|
||||
// Palette manipulation commands
|
||||
absl::Status HandlePaletteGetColors(const std::vector<std::string>& arg_vec,
|
||||
Rom* rom_context = nullptr);
|
||||
absl::Status HandlePaletteSetColor(const std::vector<std::string>& arg_vec,
|
||||
Rom* rom_context = nullptr);
|
||||
absl::Status HandlePaletteAnalyze(const std::vector<std::string>& arg_vec,
|
||||
Rom* rom_context = nullptr);
|
||||
|
||||
} // namespace agent
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
287
src/cli/handlers/agent/hex_commands.cc
Normal file
287
src/cli/handlers/agent/hex_commands.cc
Normal file
@@ -0,0 +1,287 @@
|
||||
#include "cli/handlers/agent/hex_commands.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
namespace agent {
|
||||
|
||||
namespace {
|
||||
|
||||
// Parse hex address from string (supports 0x prefix)
|
||||
absl::StatusOr<uint32_t> ParseHexAddress(const std::string& str) {
|
||||
try {
|
||||
size_t pos;
|
||||
uint32_t addr = std::stoul(str, &pos, 16);
|
||||
if (pos != str.size()) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrFormat("Invalid hex address: %s", str));
|
||||
}
|
||||
return addr;
|
||||
} catch (const std::exception& e) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrFormat("Failed to parse address '%s': %s", str, e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
// Parse hex byte pattern (supports wildcards ??)
|
||||
std::vector<std::pair<uint8_t, bool>> ParseHexPattern(const std::string& pattern) {
|
||||
std::vector<std::pair<uint8_t, bool>> result;
|
||||
std::vector<std::string> bytes = absl::StrSplit(pattern, ' ');
|
||||
|
||||
for (const auto& byte_str : bytes) {
|
||||
if (byte_str.empty()) continue;
|
||||
|
||||
if (byte_str == "??" || byte_str == "?") {
|
||||
result.push_back({0x00, false}); // Wildcard
|
||||
} else {
|
||||
try {
|
||||
uint8_t value = static_cast<uint8_t>(std::stoul(byte_str, nullptr, 16));
|
||||
result.push_back({value, true}); // Exact match
|
||||
} catch (const std::exception&) {
|
||||
std::cerr << "Warning: Invalid byte in pattern: " << byte_str << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Format bytes as hex string
|
||||
std::string FormatHexBytes(const uint8_t* data, size_t length,
|
||||
const std::string& format) {
|
||||
std::ostringstream oss;
|
||||
|
||||
if (format == "hex" || format == "both") {
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
oss << std::hex << std::setw(2) << std::setfill('0')
|
||||
<< static_cast<int>(data[i]);
|
||||
if (i < length - 1) oss << " ";
|
||||
}
|
||||
}
|
||||
|
||||
if (format == "both") {
|
||||
oss << " | ";
|
||||
}
|
||||
|
||||
if (format == "ascii" || format == "both") {
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
char c = static_cast<char>(data[i]);
|
||||
oss << (std::isprint(c) ? c : '.');
|
||||
}
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
absl::Status HandleHexRead(const std::vector<std::string>& args,
|
||||
Rom* rom_context) {
|
||||
if (!rom_context || !rom_context->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
std::string address_str;
|
||||
int length = 16;
|
||||
std::string format = "both";
|
||||
|
||||
for (const auto& arg : args) {
|
||||
if (arg.rfind("--address=", 0) == 0) {
|
||||
address_str = arg.substr(10);
|
||||
} else if (arg.rfind("--length=", 0) == 0) {
|
||||
length = std::stoi(arg.substr(9));
|
||||
} else if (arg.rfind("--format=", 0) == 0) {
|
||||
format = arg.substr(9);
|
||||
}
|
||||
}
|
||||
|
||||
if (address_str.empty()) {
|
||||
return absl::InvalidArgumentError("--address required");
|
||||
}
|
||||
|
||||
// Parse address
|
||||
auto addr_or = ParseHexAddress(address_str);
|
||||
if (!addr_or.ok()) {
|
||||
return addr_or.status();
|
||||
}
|
||||
uint32_t address = addr_or.value();
|
||||
|
||||
// Validate range
|
||||
if (address + length > rom_context->size()) {
|
||||
return absl::OutOfRangeError(
|
||||
absl::StrFormat("Read beyond ROM: 0x%X+%d > %zu",
|
||||
address, length, rom_context->size()));
|
||||
}
|
||||
|
||||
// Read and format data
|
||||
const uint8_t* data = rom_context->data() + address;
|
||||
std::string formatted = FormatHexBytes(data, length, format);
|
||||
|
||||
// Output
|
||||
std::cout << absl::StrFormat("Address 0x%06X [%d bytes]:\n", address, length);
|
||||
std::cout << formatted << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleHexWrite(const std::vector<std::string>& args,
|
||||
Rom* rom_context) {
|
||||
if (!rom_context || !rom_context->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
std::string address_str;
|
||||
std::string data_str;
|
||||
|
||||
for (const auto& arg : args) {
|
||||
if (arg.rfind("--address=", 0) == 0) {
|
||||
address_str = arg.substr(10);
|
||||
} else if (arg.rfind("--data=", 0) == 0) {
|
||||
data_str = arg.substr(7);
|
||||
}
|
||||
}
|
||||
|
||||
if (address_str.empty() || data_str.empty()) {
|
||||
return absl::InvalidArgumentError("--address and --data required");
|
||||
}
|
||||
|
||||
// Parse address
|
||||
auto addr_or = ParseHexAddress(address_str);
|
||||
if (!addr_or.ok()) {
|
||||
return addr_or.status();
|
||||
}
|
||||
uint32_t address = addr_or.value();
|
||||
|
||||
// Parse data bytes
|
||||
std::vector<std::string> byte_strs = absl::StrSplit(data_str, ' ');
|
||||
std::vector<uint8_t> bytes;
|
||||
|
||||
for (const auto& byte_str : byte_strs) {
|
||||
if (byte_str.empty()) continue;
|
||||
try {
|
||||
uint8_t value = static_cast<uint8_t>(std::stoul(byte_str, nullptr, 16));
|
||||
bytes.push_back(value);
|
||||
} catch (const std::exception& e) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrFormat("Invalid byte '%s': %s", byte_str, e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes.empty()) {
|
||||
return absl::InvalidArgumentError("No valid bytes to write");
|
||||
}
|
||||
|
||||
// Validate range
|
||||
if (address + bytes.size() > rom_context->size()) {
|
||||
return absl::OutOfRangeError(
|
||||
absl::StrFormat("Write beyond ROM: 0x%X+%zu > %zu",
|
||||
address, bytes.size(), rom_context->size()));
|
||||
}
|
||||
|
||||
// Write data
|
||||
for (size_t i = 0; i < bytes.size(); ++i) {
|
||||
rom_context->WriteByte(address + i, bytes[i]);
|
||||
}
|
||||
|
||||
// Output confirmation
|
||||
std::cout << absl::StrFormat("✓ Wrote %zu bytes to address 0x%06X\n",
|
||||
bytes.size(), address);
|
||||
std::cout << " Data: " << FormatHexBytes(bytes.data(), bytes.size(), "hex")
|
||||
<< std::endl;
|
||||
|
||||
// Note: In a full implementation, this would create a proposal
|
||||
std::cout << "Note: Changes written directly to ROM (proposal system TBD)\n";
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleHexSearch(const std::vector<std::string>& args,
|
||||
Rom* rom_context) {
|
||||
if (!rom_context || !rom_context->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
std::string pattern_str;
|
||||
uint32_t start_address = 0;
|
||||
uint32_t end_address = rom_context->size();
|
||||
|
||||
for (const auto& arg : args) {
|
||||
if (arg.rfind("--pattern=", 0) == 0) {
|
||||
pattern_str = arg.substr(10);
|
||||
} else if (arg.rfind("--start=", 0) == 0) {
|
||||
auto addr_or = ParseHexAddress(arg.substr(8));
|
||||
if (addr_or.ok()) {
|
||||
start_address = addr_or.value();
|
||||
}
|
||||
} else if (arg.rfind("--end=", 0) == 0) {
|
||||
auto addr_or = ParseHexAddress(arg.substr(6));
|
||||
if (addr_or.ok()) {
|
||||
end_address = addr_or.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pattern_str.empty()) {
|
||||
return absl::InvalidArgumentError("--pattern required");
|
||||
}
|
||||
|
||||
// Parse pattern
|
||||
auto pattern = ParseHexPattern(pattern_str);
|
||||
if (pattern.empty()) {
|
||||
return absl::InvalidArgumentError("Empty or invalid pattern");
|
||||
}
|
||||
|
||||
// Search for pattern
|
||||
std::vector<uint32_t> matches;
|
||||
const uint8_t* rom_data = rom_context->data();
|
||||
|
||||
for (uint32_t i = start_address; i <= end_address - pattern.size(); ++i) {
|
||||
bool match = true;
|
||||
for (size_t j = 0; j < pattern.size(); ++j) {
|
||||
if (pattern[j].second && // If not wildcard
|
||||
rom_data[i + j] != pattern[j].first) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
matches.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Output results
|
||||
std::cout << absl::StrFormat("Pattern: %s\n", pattern_str);
|
||||
std::cout << absl::StrFormat("Search range: 0x%06X - 0x%06X\n",
|
||||
start_address, end_address);
|
||||
std::cout << absl::StrFormat("Found %zu match(es):\n", matches.size());
|
||||
|
||||
for (size_t i = 0; i < matches.size() && i < 100; ++i) { // Limit output
|
||||
uint32_t addr = matches[i];
|
||||
std::string context = FormatHexBytes(rom_data + addr, pattern.size(), "hex");
|
||||
std::cout << absl::StrFormat(" 0x%06X: %s\n", addr, context);
|
||||
}
|
||||
|
||||
if (matches.size() > 100) {
|
||||
std::cout << absl::StrFormat(" ... and %zu more matches\n",
|
||||
matches.size() - 100);
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace agent
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
55
src/cli/handlers/agent/hex_commands.h
Normal file
55
src/cli/handlers/agent/hex_commands.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef YAZE_CLI_HANDLERS_AGENT_HEX_COMMANDS_H_
|
||||
#define YAZE_CLI_HANDLERS_AGENT_HEX_COMMANDS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
|
||||
namespace yaze {
|
||||
class Rom;
|
||||
|
||||
namespace cli {
|
||||
namespace agent {
|
||||
|
||||
/**
|
||||
* @brief Read bytes from ROM at a specific address
|
||||
*
|
||||
* @param args Command arguments: [address, length, format]
|
||||
* @param rom_context ROM instance to read from
|
||||
* @return absl::Status Result of the operation
|
||||
*
|
||||
* Example: hex-read --address=0x1C800 --length=16 --format=both
|
||||
*/
|
||||
absl::Status HandleHexRead(const std::vector<std::string>& args,
|
||||
Rom* rom_context = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Write bytes to ROM at a specific address (creates proposal)
|
||||
*
|
||||
* @param args Command arguments: [address, data]
|
||||
* @param rom_context ROM instance to write to
|
||||
* @return absl::Status Result of the operation
|
||||
*
|
||||
* Example: hex-write --address=0x1C800 --data="FF 00 12 34"
|
||||
*/
|
||||
absl::Status HandleHexWrite(const std::vector<std::string>& args,
|
||||
Rom* rom_context = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Search for a byte pattern in ROM
|
||||
*
|
||||
* @param args Command arguments: [pattern, start_address, end_address]
|
||||
* @param rom_context ROM instance to search in
|
||||
* @return absl::Status Result of the operation
|
||||
*
|
||||
* Example: hex-search --pattern="FF 00 ?? 12" --start=0x00000
|
||||
*/
|
||||
absl::Status HandleHexSearch(const std::vector<std::string>& args,
|
||||
Rom* rom_context = nullptr);
|
||||
|
||||
} // namespace agent
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_CLI_HANDLERS_AGENT_HEX_COMMANDS_H_
|
||||
344
src/cli/handlers/agent/palette_commands.cc
Normal file
344
src/cli/handlers/agent/palette_commands.cc
Normal file
@@ -0,0 +1,344 @@
|
||||
#include "cli/handlers/agent/palette_commands.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
namespace agent {
|
||||
|
||||
namespace {
|
||||
|
||||
// Convert SNES color to RGB
|
||||
struct RGB {
|
||||
uint8_t r, g, b;
|
||||
};
|
||||
|
||||
RGB SnesColorToRGB(uint16_t snes_color) {
|
||||
// SNES color format: 0bbbbbgggggrrrrr (5 bits per channel)
|
||||
uint8_t r = (snes_color & 0x1F) << 3;
|
||||
uint8_t g = ((snes_color >> 5) & 0x1F) << 3;
|
||||
uint8_t b = ((snes_color >> 10) & 0x1F) << 3;
|
||||
return {r, g, b};
|
||||
}
|
||||
|
||||
// Convert RGB to SNES color
|
||||
uint16_t RGBToSnesColor(uint8_t r, uint8_t g, uint8_t b) {
|
||||
return ((r >> 3) & 0x1F) | (((g >> 3) & 0x1F) << 5) | (((b >> 3) & 0x1F) << 10);
|
||||
}
|
||||
|
||||
// Parse hex color (supports #RRGGBB or RRGGBB)
|
||||
absl::StatusOr<RGB> ParseHexColor(const std::string& str) {
|
||||
std::string clean = str;
|
||||
if (!clean.empty() && clean[0] == '#') {
|
||||
clean = clean.substr(1);
|
||||
}
|
||||
|
||||
if (clean.length() != 6) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrFormat("Invalid hex color format: %s (expected 6 hex digits)", str));
|
||||
}
|
||||
|
||||
try {
|
||||
unsigned long value = std::stoul(clean, nullptr, 16);
|
||||
RGB rgb;
|
||||
rgb.r = (value >> 16) & 0xFF;
|
||||
rgb.g = (value >> 8) & 0xFF;
|
||||
rgb.b = value & 0xFF;
|
||||
return rgb;
|
||||
} catch (const std::exception& e) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrFormat("Failed to parse hex color '%s': %s", str, e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
// Format color based on requested format
|
||||
std::string FormatColor(uint16_t snes_color, const std::string& format) {
|
||||
if (format == "snes") {
|
||||
return absl::StrFormat("$%04X", snes_color);
|
||||
}
|
||||
|
||||
RGB rgb = SnesColorToRGB(snes_color);
|
||||
|
||||
if (format == "rgb") {
|
||||
return absl::StrFormat("rgb(%d, %d, %d)", rgb.r, rgb.g, rgb.b);
|
||||
}
|
||||
|
||||
// Default to hex
|
||||
return absl::StrFormat("#%02X%02X%02X", rgb.r, rgb.g, rgb.b);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
absl::Status HandlePaletteGetColors(const std::vector<std::string>& args,
|
||||
Rom* rom_context) {
|
||||
if (!rom_context || !rom_context->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
int group = -1;
|
||||
int palette = -1;
|
||||
std::string format = "hex";
|
||||
|
||||
for (const auto& arg : args) {
|
||||
if (arg.rfind("--group=", 0) == 0) {
|
||||
group = std::stoi(arg.substr(8));
|
||||
} else if (arg.rfind("--palette=", 0) == 0) {
|
||||
palette = std::stoi(arg.substr(10));
|
||||
} else if (arg.rfind("--format=", 0) == 0) {
|
||||
format = arg.substr(9);
|
||||
}
|
||||
}
|
||||
|
||||
if (group < 0 || palette < 0) {
|
||||
return absl::InvalidArgumentError("--group and --palette required");
|
||||
}
|
||||
|
||||
// Validate indices
|
||||
if (palette > 7) {
|
||||
return absl::OutOfRangeError(
|
||||
absl::StrFormat("Palette index %d out of range (0-7)", palette));
|
||||
}
|
||||
|
||||
// Calculate palette address in ROM
|
||||
// ALTTP palettes are stored at different locations depending on type
|
||||
// For now, use a simplified overworld palette calculation
|
||||
constexpr uint32_t kPaletteBase = 0xDE6C8; // Overworld palettes start
|
||||
constexpr uint32_t kColorsPerPalette = 16;
|
||||
constexpr uint32_t kBytesPerColor = 2;
|
||||
|
||||
uint32_t palette_addr = kPaletteBase +
|
||||
(group * 8 * kColorsPerPalette * kBytesPerColor) +
|
||||
(palette * kColorsPerPalette * kBytesPerColor);
|
||||
|
||||
if (palette_addr + (kColorsPerPalette * kBytesPerColor) > rom_context->size()) {
|
||||
return absl::OutOfRangeError(
|
||||
absl::StrFormat("Palette address 0x%X beyond ROM", palette_addr));
|
||||
}
|
||||
|
||||
// Read palette colors
|
||||
std::cout << absl::StrFormat("Palette Group %d, Palette %d:\n", group, palette);
|
||||
std::cout << absl::StrFormat("ROM Address: 0x%06X\n\n", palette_addr);
|
||||
|
||||
for (int i = 0; i < kColorsPerPalette; ++i) {
|
||||
uint32_t color_addr = palette_addr + (i * kBytesPerColor);
|
||||
auto snes_color_or = rom_context->ReadWord(color_addr);
|
||||
if (!snes_color_or.ok()) {
|
||||
return snes_color_or.status();
|
||||
}
|
||||
uint16_t snes_color = snes_color_or.value();
|
||||
|
||||
std::string formatted = FormatColor(snes_color, format);
|
||||
std::cout << absl::StrFormat(" Color %2d: %s", i, formatted);
|
||||
|
||||
// Show all formats for first color as example
|
||||
if (i == 0) {
|
||||
std::cout << absl::StrFormat(" (SNES: $%04X, RGB: %s, HEX: %s)",
|
||||
snes_color,
|
||||
FormatColor(snes_color, "rgb"),
|
||||
FormatColor(snes_color, "hex"));
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandlePaletteSetColor(const std::vector<std::string>& args,
|
||||
Rom* rom_context) {
|
||||
if (!rom_context || !rom_context->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
int group = -1;
|
||||
int palette = -1;
|
||||
int color_index = -1;
|
||||
std::string color_str;
|
||||
|
||||
for (const auto& arg : args) {
|
||||
if (arg.rfind("--group=", 0) == 0) {
|
||||
group = std::stoi(arg.substr(8));
|
||||
} else if (arg.rfind("--palette=", 0) == 0) {
|
||||
palette = std::stoi(arg.substr(10));
|
||||
} else if (arg.rfind("--index=", 0) == 0) {
|
||||
color_index = std::stoi(arg.substr(8));
|
||||
} else if (arg.rfind("--color=", 0) == 0) {
|
||||
color_str = arg.substr(8);
|
||||
}
|
||||
}
|
||||
|
||||
if (group < 0 || palette < 0 || color_index < 0 || color_str.empty()) {
|
||||
return absl::InvalidArgumentError(
|
||||
"--group, --palette, --index, and --color required");
|
||||
}
|
||||
|
||||
// Validate indices
|
||||
if (palette > 7) {
|
||||
return absl::OutOfRangeError(
|
||||
absl::StrFormat("Palette index %d out of range (0-7)", palette));
|
||||
}
|
||||
if (color_index >= 16) {
|
||||
return absl::OutOfRangeError(
|
||||
absl::StrFormat("Color index %d out of range (0-15)", color_index));
|
||||
}
|
||||
|
||||
// Parse color
|
||||
auto rgb_or = ParseHexColor(color_str);
|
||||
if (!rgb_or.ok()) {
|
||||
return rgb_or.status();
|
||||
}
|
||||
RGB rgb = rgb_or.value();
|
||||
uint16_t snes_color = RGBToSnesColor(rgb.r, rgb.g, rgb.b);
|
||||
|
||||
// Calculate address
|
||||
constexpr uint32_t kPaletteBase = 0xDE6C8;
|
||||
constexpr uint32_t kColorsPerPalette = 16;
|
||||
constexpr uint32_t kBytesPerColor = 2;
|
||||
|
||||
uint32_t color_addr = kPaletteBase +
|
||||
(group * 8 * kColorsPerPalette * kBytesPerColor) +
|
||||
(palette * kColorsPerPalette * kBytesPerColor) +
|
||||
(color_index * kBytesPerColor);
|
||||
|
||||
if (color_addr + kBytesPerColor > rom_context->size()) {
|
||||
return absl::OutOfRangeError(
|
||||
absl::StrFormat("Color address 0x%X beyond ROM", color_addr));
|
||||
}
|
||||
|
||||
// Read old value
|
||||
auto old_color_or = rom_context->ReadWord(color_addr);
|
||||
if (!old_color_or.ok()) {
|
||||
return old_color_or.status();
|
||||
}
|
||||
uint16_t old_color = old_color_or.value();
|
||||
|
||||
// Write new value
|
||||
auto write_status = rom_context->WriteWord(color_addr, snes_color);
|
||||
if (!write_status.ok()) {
|
||||
return write_status;
|
||||
}
|
||||
|
||||
// Output confirmation
|
||||
std::cout << absl::StrFormat("✓ Set color in Palette %d/%d, Index %d\n",
|
||||
group, palette, color_index);
|
||||
std::cout << absl::StrFormat(" Address: 0x%06X\n", color_addr);
|
||||
std::cout << absl::StrFormat(" Old: %s\n", FormatColor(old_color, "hex"));
|
||||
std::cout << absl::StrFormat(" New: %s (SNES: $%04X)\n",
|
||||
FormatColor(snes_color, "hex"), snes_color);
|
||||
std::cout << "Note: Changes written directly to ROM (proposal system TBD)\n";
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandlePaletteAnalyze(const std::vector<std::string>& args,
|
||||
Rom* rom_context) {
|
||||
if (!rom_context || !rom_context->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
std::string target_type;
|
||||
std::string target_id;
|
||||
|
||||
for (const auto& arg : args) {
|
||||
if (arg.rfind("--type=", 0) == 0) {
|
||||
target_type = arg.substr(7);
|
||||
} else if (arg.rfind("--id=", 0) == 0) {
|
||||
target_id = arg.substr(5);
|
||||
}
|
||||
}
|
||||
|
||||
if (target_type.empty() || target_id.empty()) {
|
||||
return absl::InvalidArgumentError("--type and --id required");
|
||||
}
|
||||
|
||||
if (target_type == "palette") {
|
||||
// Parse palette ID (assume format "group/palette")
|
||||
size_t slash_pos = target_id.find('/');
|
||||
if (slash_pos == std::string::npos) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Palette ID format should be 'group/palette' (e.g., '0/0')");
|
||||
}
|
||||
|
||||
int group = std::stoi(target_id.substr(0, slash_pos));
|
||||
int palette = std::stoi(target_id.substr(slash_pos + 1));
|
||||
|
||||
// Read palette
|
||||
constexpr uint32_t kPaletteBase = 0xDE6C8;
|
||||
constexpr uint32_t kColorsPerPalette = 16;
|
||||
constexpr uint32_t kBytesPerColor = 2;
|
||||
|
||||
uint32_t palette_addr = kPaletteBase +
|
||||
(group * 8 * kColorsPerPalette * kBytesPerColor) +
|
||||
(palette * kColorsPerPalette * kBytesPerColor);
|
||||
|
||||
// Analyze colors
|
||||
std::map<uint16_t, int> color_usage;
|
||||
int transparent_count = 0;
|
||||
int darkest = 0xFFFF, brightest = 0;
|
||||
|
||||
for (int i = 0; i < kColorsPerPalette; ++i) {
|
||||
uint32_t color_addr = palette_addr + (i * kBytesPerColor);
|
||||
auto snes_color_or = rom_context->ReadWord(color_addr);
|
||||
if (!snes_color_or.ok()) {
|
||||
return snes_color_or.status();
|
||||
}
|
||||
uint16_t snes_color = snes_color_or.value();
|
||||
|
||||
color_usage[snes_color]++;
|
||||
|
||||
if (snes_color == 0) {
|
||||
transparent_count++;
|
||||
}
|
||||
|
||||
// Calculate brightness (simple sum of RGB components)
|
||||
RGB rgb = SnesColorToRGB(snes_color);
|
||||
int brightness = rgb.r + rgb.g + rgb.b;
|
||||
if (brightness < (((darkest & 0x1F) + ((darkest >> 5) & 0x1F) + ((darkest >> 10) & 0x1F)) * 8)) {
|
||||
darkest = snes_color;
|
||||
}
|
||||
if (brightness > (((brightest & 0x1F) + ((brightest >> 5) & 0x1F) + ((brightest >> 10) & 0x1F)) * 8)) {
|
||||
brightest = snes_color;
|
||||
}
|
||||
}
|
||||
|
||||
// Output analysis
|
||||
std::cout << absl::StrFormat("Palette Analysis: Group %d, Palette %d\n", group, palette);
|
||||
std::cout << absl::StrFormat("Address: 0x%06X\n\n", palette_addr);
|
||||
std::cout << absl::StrFormat("Total colors: %d\n", kColorsPerPalette);
|
||||
std::cout << absl::StrFormat("Unique colors: %zu\n", color_usage.size());
|
||||
std::cout << absl::StrFormat("Transparent/black (0): %d\n", transparent_count);
|
||||
std::cout << absl::StrFormat("Darkest color: %s\n", FormatColor(darkest, "hex"));
|
||||
std::cout << absl::StrFormat("Brightest color: %s\n", FormatColor(brightest, "hex"));
|
||||
|
||||
if (color_usage.size() < kColorsPerPalette) {
|
||||
std::cout << "\nDuplicate colors found:\n";
|
||||
for (const auto& [color, count] : color_usage) {
|
||||
if (count > 1) {
|
||||
std::cout << absl::StrFormat(" %s appears %d times\n",
|
||||
FormatColor(color, "hex"), count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
return absl::UnimplementedError(
|
||||
absl::StrFormat("Analysis for type '%s' not yet implemented", target_type));
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace agent
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
55
src/cli/handlers/agent/palette_commands.h
Normal file
55
src/cli/handlers/agent/palette_commands.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef YAZE_CLI_HANDLERS_AGENT_PALETTE_COMMANDS_H_
|
||||
#define YAZE_CLI_HANDLERS_AGENT_PALETTE_COMMANDS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
|
||||
namespace yaze {
|
||||
class Rom;
|
||||
|
||||
namespace cli {
|
||||
namespace agent {
|
||||
|
||||
/**
|
||||
* @brief Get all colors from a specific palette
|
||||
*
|
||||
* @param args Command arguments: [group, palette, format]
|
||||
* @param rom_context ROM instance to read from
|
||||
* @return absl::Status Result of the operation
|
||||
*
|
||||
* Example: palette-get-colors --group=0 --palette=0 --format=hex
|
||||
*/
|
||||
absl::Status HandlePaletteGetColors(const std::vector<std::string>& args,
|
||||
Rom* rom_context = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Set a specific color in a palette (creates proposal)
|
||||
*
|
||||
* @param args Command arguments: [group, palette, color_index, color]
|
||||
* @param rom_context ROM instance to modify
|
||||
* @return absl::Status Result of the operation
|
||||
*
|
||||
* Example: palette-set-color --group=0 --palette=0 --index=5 --color=FF0000
|
||||
*/
|
||||
absl::Status HandlePaletteSetColor(const std::vector<std::string>& args,
|
||||
Rom* rom_context = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Analyze color usage and statistics for a palette or bitmap
|
||||
*
|
||||
* @param args Command arguments: [target_type, target_id]
|
||||
* @param rom_context ROM instance to analyze
|
||||
* @return absl::Status Result of the operation
|
||||
*
|
||||
* Example: palette-analyze --type=palette --id=0
|
||||
*/
|
||||
absl::Status HandlePaletteAnalyze(const std::vector<std::string>& args,
|
||||
Rom* rom_context = nullptr);
|
||||
|
||||
} // namespace agent
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_CLI_HANDLERS_AGENT_PALETTE_COMMANDS_H_
|
||||
452
src/cli/service/agent/vim_mode.cc
Normal file
452
src/cli/service/agent/vim_mode.cc
Normal file
@@ -0,0 +1,452 @@
|
||||
#include "cli/service/agent/vim_mode.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <conio.h>
|
||||
#else
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
namespace agent {
|
||||
|
||||
namespace {
|
||||
|
||||
// Key codes for special keys
|
||||
constexpr int KEY_ESC = 27;
|
||||
constexpr int KEY_ENTER = 10;
|
||||
constexpr int KEY_BACKSPACE = 127;
|
||||
constexpr int KEY_CTRL_P = 16;
|
||||
constexpr int KEY_CTRL_N = 14;
|
||||
constexpr int KEY_TAB = 9;
|
||||
|
||||
// Terminal control sequences
|
||||
const char* CLEAR_LINE = "\033[2K\r";
|
||||
const char* MOVE_CURSOR_HOME = "\r";
|
||||
const char* SAVE_CURSOR = "\033[s";
|
||||
const char* RESTORE_CURSOR = "\033[u";
|
||||
|
||||
#ifndef _WIN32
|
||||
// Set terminal to raw mode (character-by-character input)
|
||||
void SetRawMode(bool enable) {
|
||||
static struct termios orig_termios;
|
||||
static bool has_orig = false;
|
||||
|
||||
if (enable) {
|
||||
if (!has_orig) {
|
||||
tcgetattr(STDIN_FILENO, &orig_termios);
|
||||
has_orig = true;
|
||||
}
|
||||
|
||||
struct termios raw = orig_termios;
|
||||
raw.c_lflag &= ~(ECHO | ICANON); // Disable echo and canonical mode
|
||||
raw.c_cc[VMIN] = 1; // Read at least 1 character
|
||||
raw.c_cc[VTIME] = 0; // No timeout
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
|
||||
} else {
|
||||
if (has_orig) {
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
VimMode::VimMode() {
|
||||
#ifndef _WIN32
|
||||
SetRawMode(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string VimMode::GetModeString() const {
|
||||
switch (mode_) {
|
||||
case VimModeType::NORMAL:
|
||||
return "NORMAL";
|
||||
case VimModeType::INSERT:
|
||||
return "INSERT";
|
||||
case VimModeType::VISUAL:
|
||||
return "VISUAL";
|
||||
case VimModeType::COMMAND_LINE:
|
||||
return "COMMAND";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
void VimMode::Reset() {
|
||||
current_line_.clear();
|
||||
cursor_pos_ = 0;
|
||||
line_complete_ = false;
|
||||
redo_stack_.clear();
|
||||
}
|
||||
|
||||
void VimMode::AddToHistory(const std::string& line) {
|
||||
if (!line.empty()) {
|
||||
history_.push_back(line);
|
||||
history_index_ = -1; // Reset to no history selection
|
||||
}
|
||||
}
|
||||
|
||||
bool VimMode::ProcessKey(int ch) {
|
||||
line_complete_ = false;
|
||||
|
||||
switch (mode_) {
|
||||
case VimModeType::NORMAL:
|
||||
HandleNormalMode(ch);
|
||||
break;
|
||||
case VimModeType::INSERT:
|
||||
HandleInsertMode(ch);
|
||||
break;
|
||||
case VimModeType::VISUAL:
|
||||
// Visual mode not yet implemented
|
||||
SwitchMode(VimModeType::NORMAL);
|
||||
break;
|
||||
case VimModeType::COMMAND_LINE:
|
||||
// Command line mode not yet implemented
|
||||
if (ch == KEY_ESC) {
|
||||
SwitchMode(VimModeType::NORMAL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return line_complete_;
|
||||
}
|
||||
|
||||
void VimMode::SwitchMode(VimModeType new_mode) {
|
||||
if (new_mode != mode_) {
|
||||
mode_ = new_mode;
|
||||
Render(); // Redraw with new mode indicator
|
||||
}
|
||||
}
|
||||
|
||||
void VimMode::HandleNormalMode(int ch) {
|
||||
switch (ch) {
|
||||
// Enter insert mode
|
||||
case 'i':
|
||||
SwitchMode(VimModeType::INSERT);
|
||||
break;
|
||||
case 'a':
|
||||
MoveRight();
|
||||
SwitchMode(VimModeType::INSERT);
|
||||
break;
|
||||
case 'o':
|
||||
// Insert new line below (just append and go to insert mode)
|
||||
MoveToLineEnd();
|
||||
SwitchMode(VimModeType::INSERT);
|
||||
break;
|
||||
case 'O':
|
||||
// Insert new line above (go to beginning and insert mode)
|
||||
MoveToLineStart();
|
||||
SwitchMode(VimModeType::INSERT);
|
||||
break;
|
||||
|
||||
// Movement
|
||||
case 'h':
|
||||
MoveLeft();
|
||||
break;
|
||||
case 'l':
|
||||
MoveRight();
|
||||
break;
|
||||
case 'w':
|
||||
MoveWordForward();
|
||||
break;
|
||||
case 'b':
|
||||
MoveWordBackward();
|
||||
break;
|
||||
case '0':
|
||||
MoveToLineStart();
|
||||
break;
|
||||
case '$':
|
||||
MoveToLineEnd();
|
||||
break;
|
||||
|
||||
// Editing
|
||||
case 'x':
|
||||
DeleteChar();
|
||||
break;
|
||||
case 'd':
|
||||
// Simple implementation: dd deletes line
|
||||
{
|
||||
int next_ch;
|
||||
#ifdef _WIN32
|
||||
next_ch = _getch();
|
||||
#else
|
||||
read(STDIN_FILENO, &next_ch, 1);
|
||||
#endif
|
||||
if (next_ch == 'd') {
|
||||
DeleteLine();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'y':
|
||||
// yy yanks line
|
||||
{
|
||||
int next_ch;
|
||||
#ifdef _WIN32
|
||||
next_ch = _getch();
|
||||
#else
|
||||
read(STDIN_FILENO, &next_ch, 1);
|
||||
#endif
|
||||
if (next_ch == 'y') {
|
||||
YankLine();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
PasteAfter();
|
||||
break;
|
||||
case 'P':
|
||||
PasteBefore();
|
||||
break;
|
||||
case 'u':
|
||||
Undo();
|
||||
break;
|
||||
case KEY_CTRL_P:
|
||||
case 'k':
|
||||
HistoryPrev();
|
||||
break;
|
||||
case KEY_CTRL_N:
|
||||
case 'j':
|
||||
HistoryNext();
|
||||
break;
|
||||
|
||||
// Accept line (Enter in normal mode)
|
||||
case KEY_ENTER:
|
||||
line_complete_ = true;
|
||||
break;
|
||||
|
||||
// Command mode
|
||||
case ':':
|
||||
SwitchMode(VimModeType::COMMAND_LINE);
|
||||
break;
|
||||
}
|
||||
|
||||
Render();
|
||||
}
|
||||
|
||||
void VimMode::HandleInsertMode(int ch) {
|
||||
switch (ch) {
|
||||
case KEY_ESC:
|
||||
SwitchMode(VimModeType::NORMAL);
|
||||
if (cursor_pos_ > 0) {
|
||||
cursor_pos_--; // Vim moves cursor left on ESC
|
||||
}
|
||||
break;
|
||||
case KEY_ENTER:
|
||||
line_complete_ = true;
|
||||
break;
|
||||
case KEY_BACKSPACE:
|
||||
case 8: // Ctrl+H
|
||||
Backspace();
|
||||
break;
|
||||
case KEY_TAB:
|
||||
Complete();
|
||||
break;
|
||||
case KEY_CTRL_P:
|
||||
HistoryPrev();
|
||||
break;
|
||||
case KEY_CTRL_N:
|
||||
HistoryNext();
|
||||
break;
|
||||
default:
|
||||
if (ch >= 32 && ch < 127) { // Printable ASCII
|
||||
InsertChar(static_cast<char>(ch));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!line_complete_) {
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
// Movement implementations
|
||||
void VimMode::MoveLeft() {
|
||||
if (cursor_pos_ > 0) {
|
||||
cursor_pos_--;
|
||||
}
|
||||
}
|
||||
|
||||
void VimMode::MoveRight() {
|
||||
if (cursor_pos_ < static_cast<int>(current_line_.length())) {
|
||||
cursor_pos_++;
|
||||
}
|
||||
}
|
||||
|
||||
void VimMode::MoveWordForward() {
|
||||
while (cursor_pos_ < static_cast<int>(current_line_.length()) &&
|
||||
!std::isspace(current_line_[cursor_pos_])) {
|
||||
cursor_pos_++;
|
||||
}
|
||||
while (cursor_pos_ < static_cast<int>(current_line_.length()) &&
|
||||
std::isspace(current_line_[cursor_pos_])) {
|
||||
cursor_pos_++;
|
||||
}
|
||||
}
|
||||
|
||||
void VimMode::MoveWordBackward() {
|
||||
if (cursor_pos_ > 0) cursor_pos_--;
|
||||
while (cursor_pos_ > 0 && std::isspace(current_line_[cursor_pos_])) {
|
||||
cursor_pos_--;
|
||||
}
|
||||
while (cursor_pos_ > 0 && !std::isspace(current_line_[cursor_pos_ - 1])) {
|
||||
cursor_pos_--;
|
||||
}
|
||||
}
|
||||
|
||||
void VimMode::MoveToLineStart() {
|
||||
cursor_pos_ = 0;
|
||||
}
|
||||
|
||||
void VimMode::MoveToLineEnd() {
|
||||
cursor_pos_ = current_line_.length();
|
||||
}
|
||||
|
||||
// Editing implementations
|
||||
void VimMode::DeleteChar() {
|
||||
if (cursor_pos_ < static_cast<int>(current_line_.length())) {
|
||||
undo_stack_.push_back(current_line_);
|
||||
current_line_.erase(cursor_pos_, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void VimMode::DeleteLine() {
|
||||
undo_stack_.push_back(current_line_);
|
||||
yank_buffer_ = current_line_;
|
||||
current_line_.clear();
|
||||
cursor_pos_ = 0;
|
||||
}
|
||||
|
||||
void VimMode::YankLine() {
|
||||
yank_buffer_ = current_line_;
|
||||
}
|
||||
|
||||
void VimMode::PasteBefore() {
|
||||
if (!yank_buffer_.empty()) {
|
||||
undo_stack_.push_back(current_line_);
|
||||
current_line_.insert(cursor_pos_, yank_buffer_);
|
||||
}
|
||||
}
|
||||
|
||||
void VimMode::PasteAfter() {
|
||||
if (!yank_buffer_.empty()) {
|
||||
undo_stack_.push_back(current_line_);
|
||||
if (cursor_pos_ < static_cast<int>(current_line_.length())) {
|
||||
cursor_pos_++;
|
||||
}
|
||||
current_line_.insert(cursor_pos_, yank_buffer_);
|
||||
cursor_pos_ += yank_buffer_.length() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
void VimMode::Undo() {
|
||||
if (!undo_stack_.empty()) {
|
||||
redo_stack_.push_back(current_line_);
|
||||
current_line_ = undo_stack_.back();
|
||||
undo_stack_.pop_back();
|
||||
cursor_pos_ = std::min(cursor_pos_, static_cast<int>(current_line_.length()));
|
||||
}
|
||||
}
|
||||
|
||||
void VimMode::Redo() {
|
||||
if (!redo_stack_.empty()) {
|
||||
undo_stack_.push_back(current_line_);
|
||||
current_line_ = redo_stack_.back();
|
||||
redo_stack_.pop_back();
|
||||
cursor_pos_ = std::min(cursor_pos_, static_cast<int>(current_line_.length()));
|
||||
}
|
||||
}
|
||||
|
||||
void VimMode::InsertChar(char c) {
|
||||
undo_stack_.push_back(current_line_);
|
||||
current_line_.insert(cursor_pos_, 1, c);
|
||||
cursor_pos_++;
|
||||
}
|
||||
|
||||
void VimMode::Backspace() {
|
||||
if (cursor_pos_ > 0) {
|
||||
undo_stack_.push_back(current_line_);
|
||||
current_line_.erase(cursor_pos_ - 1, 1);
|
||||
cursor_pos_--;
|
||||
}
|
||||
}
|
||||
|
||||
void VimMode::Delete() {
|
||||
DeleteChar();
|
||||
}
|
||||
|
||||
void VimMode::Complete() {
|
||||
if (autocomplete_callback_) {
|
||||
autocomplete_options_ = autocomplete_callback_(current_line_);
|
||||
if (!autocomplete_options_.empty()) {
|
||||
// Simple implementation: insert first suggestion
|
||||
std::string completion = autocomplete_options_[0];
|
||||
undo_stack_.push_back(current_line_);
|
||||
current_line_ = completion;
|
||||
cursor_pos_ = completion.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// History navigation
|
||||
void VimMode::HistoryPrev() {
|
||||
if (history_.empty()) return;
|
||||
|
||||
if (history_index_ == -1) {
|
||||
history_index_ = history_.size() - 1;
|
||||
} else if (history_index_ > 0) {
|
||||
history_index_--;
|
||||
}
|
||||
|
||||
current_line_ = history_[history_index_];
|
||||
cursor_pos_ = current_line_.length();
|
||||
}
|
||||
|
||||
void VimMode::HistoryNext() {
|
||||
if (history_.empty() || history_index_ == -1) return;
|
||||
|
||||
if (history_index_ < static_cast<int>(history_.size()) - 1) {
|
||||
history_index_++;
|
||||
current_line_ = history_[history_index_];
|
||||
} else {
|
||||
history_index_ = -1;
|
||||
current_line_.clear();
|
||||
}
|
||||
|
||||
cursor_pos_ = current_line_.length();
|
||||
}
|
||||
|
||||
void VimMode::Render() const {
|
||||
// Clear line and redraw
|
||||
std::cout << CLEAR_LINE;
|
||||
|
||||
// Show mode indicator
|
||||
if (mode_ == VimModeType::INSERT) {
|
||||
std::cout << "-- INSERT -- ";
|
||||
} else if (mode_ == VimModeType::NORMAL) {
|
||||
std::cout << "-- NORMAL -- ";
|
||||
} else if (mode_ == VimModeType::COMMAND_LINE) {
|
||||
std::cout << ":";
|
||||
}
|
||||
|
||||
// Show prompt and line
|
||||
std::cout << current_line_;
|
||||
|
||||
// Move cursor to correct position
|
||||
int display_offset = (mode_ == VimModeType::INSERT ? 13 : 13); // Length of "-- INSERT -- "
|
||||
std::cout << "\r";
|
||||
for (int i = 0; i < display_offset + cursor_pos_; ++i) {
|
||||
std::cout << "\033[C"; // Move cursor right
|
||||
}
|
||||
|
||||
std::cout.flush();
|
||||
}
|
||||
|
||||
} // namespace agent
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
Reference in New Issue
Block a user