/************************************************************************************** # 076 License Copyright (c) 2026 テクニカル諏訪子 Permission is hereby granted to any person obtaining a copy of the software Hexagon (the "Software") to use, modify, merge, copy, publish, distribute, sublicense, and/or sell copies of the Software, subject to the following conditions: 1. **Origin Attribution**: - You must not misrepresent the origin of the Software; you must not claim you created the original Software. - If the Software is used in a product, you must either: a. Provide clear attribution in the product's documentation, user interface, or other visible areas, **OR** b. Pay the original developers a fee they specify in writing. 2. **Usage Restriction**: - The Software, or any derivative works, dependencies, or libraries incorporating it, must not be used for censorship or to suppress freedom of speech, expression, or creativity. Prohibited uses include, but are not limited to: - Censorship of so-called "hate speech", visuals, non-mainstream opinions, ideas, or objective reality. - Tools or systems designed to restrict access to information or artistic works. 3. **Notice Preservation**: - This license and the above copyright notice must remain intact in all copies of the source code. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. **************************************************************************************/ #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #endif #include "hexeditor.hh" #ifdef _WIN32 static BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType) { if (dwCtrlType == CTRL_C_EVENT) return TRUE; return FALSE; } #endif const std::vector HexEditor::signatures = { {{ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, "PNG"}, {{ 0xFF, 0xD8 }, "JPEG"}, {{ 0x7F, 0x45, 0x4C, 0x46 }, "ELF"}, {{ 0x4D, 0x5A }, "PE"}, {{ 0x25, 0x50, 0x44, 0x46 }, "PDF"}, {{ 0x67, 0x69, 0x6D, 0x70, 0x20, 0x78, 0x63, 0x66 }, "GIMP XCF"}, {{ 0x1A, 0x45, 0xDF, 0xA3, 0x9F, 0x42, 0x86, 0x81, 0x01, 0x42, 0xF7, 0x81, 0x01, 0x42, 0xF2, 0x81, 0x04, 0x42, 0xF3, 0x81, 0x08, 0x42, 0x82, 0x84, 0x77, 0x65, 0x62, 0x6D, 0x42, 0x87, 0x81, 0x04, 0x42, 0x85, 0x81, 0x02, 0x18, 0x53, 0x80, 0x67, 0x01, 0x00, 0x00, 0x00, 0x01 }, "WEBM"}, {{ 0x1A, 0x45, 0xDF, 0xA3, 0xA3, 0x42, 0x86, 0x81, 0x01, 0x42, 0xF7, 0x81, 0x01, 0x42, 0xF2, 0x81, 0x04, 0x42, 0xF3, 0x81, 0x08, 0x42, 0x82, 0x88, 0x6D, 0x61, 0x74, 0x72, 0x6F, 0x73, 0x6B, 0x61, 0x42, 0x87, 0x81, 0x04, 0x42, 0x85, 0x81, 0x02, 0x18, 0x53, 0x80, 0x67, 0x01, 0x00, 0x00, 0x00}, "Matroska"}, {{ 0x52, 0x49, 0x46, 0x46 }, "Waveform"}, }; HexEditor::HexEditor(const std::string &filename) : curPos(0), dpOffset(0), bpr(16), statusMode(Status_Normal), modified(false), running(true), lastSearchDir(Direction_Forward), headerLen(0), headerType("") { // SIGINT (CTRL + C)を無効に #ifdef _WIN32 SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); #else std::signal(SIGINT, SIG_IGN); #endif setlocale(LC_ALL, ""); // ncurses initscr(); clearok(stdscr, TRUE); cbreak(); noecho(); keypad(stdscr, TRUE); raw(); start_color(); // マウス対応 #ifdef _WIN32 mousemask(ALL_MOUSE_EVENTS, nullptr); mouse_set(ALL_MOUSE_EVENTS); request_mouse_pos(); #else mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, nullptr); #endif isMouse = true; init_pair(1, COLOR_BLACK, COLOR_WHITE); // カーソル init_pair(2, COLOR_BLACK, COLOR_GREEN); // 変更 init_pair(3, COLOR_BLACK, COLOR_MAGENTA); // 普通 init_pair(4, COLOR_BLACK, COLOR_CYAN); // コマンド init_pair(5, COLOR_BLACK, COLOR_YELLOW); // 検索 init_pair(6, COLOR_BLACK, COLOR_YELLOW); // 検索ハイライト init_pair(7, COLOR_BLACK, COLOR_RED); // エラー init_pair(8, COLOR_WHITE, COLOR_BLACK); // デフォルト init_pair(9, COLOR_CYAN, COLOR_BLACK); // ファイルヘッダー refresh(); // ファイルをバッファーに読み込む std::ifstream file(filename, std::ios::binary); if (!file) { endwin(); throw std::runtime_error("ファイルを開くに失敗"); } buf.assign((std::istreambuf_iterator(file)), {}); file.close(); if (buf.empty()) { endwin(); throw std::runtime_error("ファイルが空です"); } fname = filename; // ファイルヘッダー for (const auto &sig : signatures) { if (buf.size() >= sig.signature.size()) { bool match = true; for (size_t i = 0; i < sig.signature.size(); ++i) { if (buf[i] != sig.signature[i]) { match = false; break; } } if (match) { headerLen = sig.signature.size(); headerType = sig.type; break; } } } // ウィンドウを作成する getmaxyx(stdscr, rows, cols); if (rows < 4 || cols < 20) { endwin(); throw std::runtime_error("ターミナルが小さ過ぎます"); } bpr = std::min(16, (cols / 2 - 10) / 4); hexPanel = newwin(rows - 3, cols / 2, 1, 0); asciiPanel = newwin(rows - 3, cols / 2, 1, cols / 2); scrollok(hexPanel, TRUE); scrollok(asciiPanel, TRUE); wattrset(hexPanel, COLOR_PAIR(8)); wattrset(asciiPanel, COLOR_PAIR(8)); } HexEditor::~HexEditor() { delwin(hexPanel); delwin(asciiPanel); endwin(); } void HexEditor::highlightcol(size_t i, size_t row, uint8_t byte) { wattron(hexPanel, COLOR_PAIR(1)); mvwprintw(hexPanel, row, 10 + i * 3, "%02x", byte); wattroff(hexPanel, COLOR_PAIR(1)); wattron(asciiPanel, COLOR_PAIR(1)); mvwaddch(asciiPanel, row, i, std::isprint(byte) ? byte : '.'); wattroff(asciiPanel, COLOR_PAIR(1)); } void HexEditor::topbar() { int colorPair = 3; std::string status = "Hexagon 1.2.0"; if (!headerType.empty()) status += " | FILETYPE: " + headerType; std::wstring wstatus(status.begin(), status.end()); wattron(stdscr, COLOR_PAIR(colorPair)); mvprintw(0, 0, "%s", status.c_str()); for (int i = status.length(); i < cols; ++i) { mvaddch(0, i, ' '); } wattroff(stdscr, COLOR_PAIR(colorPair)); } void HexEditor::statusbar() { int colorPair; switch (statusMode) { case Status_Replace: colorPair = 2; break; case Status_Normal: colorPair = 3; break; case Status_Command: colorPair = 4; break; case Status_Search: colorPair = 5; break; case Status_Error: colorPair = 7; break; } std::string status; if (statusMode == Status_Normal || statusMode == Status_Error) { status = "FILE: " + fname; status += " | OFFSET: 0x" + std::to_string(curPos); status += " | FILE SIZE: " + std::to_string(buf.size()) + " B"; if (!lastSearch.empty()) { status += " | SEARCH: " + lastSearch; } else if (!lastHexSearch.empty()) { std::ostringstream hexSearch; for (size_t i = 0; i < lastHexSearch.size(); ++i) { if (i > 0) hexSearch << " "; hexSearch << std::hex << std::setfill('0') << std::setw(2) << (int)lastHexSearch[i]; } status += " | SEARCH: " + hexSearch.str(); } status += (modified ? " [+]" : ""); if (statusMode == Status_Error) status += " | ERROR: " + statusText; else status += !statusText.empty() ? " | " + statusText : ""; } else if (statusMode == Status_Command) { status = ":" + statusText; } else if (statusMode == Status_Search) { status = (lastSearchDir == Direction_Forward ? "/" : "?") + statusText; } else if (statusMode == Status_Replace) { status = "-- REPLACE --"; } std::wstring wstatus(status.begin(), status.end()); wattron(stdscr, COLOR_PAIR(colorPair)); mvprintw(rows - 1, 0, "%s", status.c_str()); for (int i = status.length(); i < cols; ++i) { mvaddch(rows - 1, i, ' '); } wattroff(stdscr, COLOR_PAIR(colorPair)); } void HexEditor::render() { werase(hexPanel); werase(asciiPanel); getmaxyx(stdscr, rows, cols); size_t maxRows = rows - 3; topbar(); std::set matchBytes; if (!lastSearch.empty()) { for (size_t i = 0; i <= buf.size() - lastSearch.size(); ++i) { bool match = true; for (size_t j = 0; j < lastSearch.size(); ++j) { if (buf[i + j] != static_cast(lastSearch[j])) { match = false; break; } } if (match) { for (size_t j = 0; j < lastSearch.size(); ++j) { matchBytes.insert(i + j); } } } } else if (!lastHexSearch.empty()) { for (size_t i = 0; i <= buf.size() - lastHexSearch.size(); ++i) { bool match = true; for (size_t j = 0; j < lastHexSearch.size(); ++j) { if (buf[i + j] != lastHexSearch[j]) { match = false; break; } } if (match) { for (size_t j = 0; j < lastHexSearch.size(); ++j) { matchBytes.insert(i + j); } } } } // HEXとASCIIの表示 for (size_t row = 0; row < maxRows; ++row) { size_t offset = dpOffset + row * bpr; if (offset >= buf.size()) break; std::ostringstream hexLine, asciiLine; hexLine << std::hex << std::setfill('0') << std::setw(8) << offset << ": "; mvwprintw(hexPanel, row, 0, "%s", hexLine.str().c_str()); // HEXとASCIIのデータ for (size_t i = 0; i < bpr && (offset + i) < buf.size(); ++i) { uint8_t byte = buf[offset + i]; bool isMatch = matchBytes.count(offset + i) > 0; bool isHeader = (offset + i) < headerLen; if (offset + i == curPos) { highlightcol(i, row, byte); } else if (isHeader) { wattron(hexPanel, COLOR_PAIR(9)); mvwprintw(hexPanel, row, 10 + i * 3, "%02x", byte); wattroff(hexPanel, COLOR_PAIR(9)); wattron(asciiPanel, COLOR_PAIR(9)); mvwaddch(asciiPanel, row, i, std::isprint(byte) ? byte : '.'); wattroff(asciiPanel, COLOR_PAIR(9)); } else if (isMatch) { wattron(hexPanel, COLOR_PAIR(6)); mvwprintw(hexPanel, row, 10 + i * 3, "%02x", byte); wattroff(hexPanel, COLOR_PAIR(6)); wattron(asciiPanel, COLOR_PAIR(6)); mvwaddch(asciiPanel, row, i, std::isprint(byte) ? byte : '.'); wattroff(asciiPanel, COLOR_PAIR(6)); } else { wattron(hexPanel, COLOR_PAIR(8)); mvwprintw(hexPanel, row, 10 + i * 3, "%02x", byte); wattroff(hexPanel, COLOR_PAIR(8)); wattron(asciiPanel, COLOR_PAIR(8)); mvwaddch(asciiPanel, row, i, std::isprint(byte) ? byte : '.'); wattroff(asciiPanel, COLOR_PAIR(8)); } } } statusbar(); redrawwin(hexPanel); redrawwin(asciiPanel); wrefresh(hexPanel); wrefresh(asciiPanel); refresh(); } void HexEditor::findNextMatch() { if (lastSearch.empty() && lastHexSearch.empty()) return; size_t searchSize = lastSearch.empty() ? lastHexSearch.size() : lastSearch.size(); size_t startPos = curPos + 1; for (size_t i = startPos; i <= buf.size() - searchSize; ++i) { bool match = true; if (!lastSearch.empty()) { for (size_t j = 0; j < lastSearch.size(); ++j) { if (buf[i + j] != static_cast(lastSearch[j])) { match = false; break; } } } else { for (size_t j = 0; j < lastHexSearch.size(); ++j) { if (buf[i + j] != lastHexSearch[j]) { match = false; break; } } } if (match) { curPos = i; dpOffset = curPos - (curPos % bpr); render(); return; } } for (size_t i = 0; i < startPos && i <= buf.size() - searchSize; ++i) { bool match = true; if (!lastSearch.empty()) { for (size_t j = 0; j < lastSearch.size(); ++j) { if (buf[i + j] != static_cast(lastSearch[j])) { match = false; break; } } } else { for (size_t j = 0; j < lastHexSearch.size(); ++j) { if (buf[i + j] != lastHexSearch[j]) { match = false; break; } } } if (match) { curPos = i; dpOffset = curPos - (curPos % bpr); render(); return; } } } void HexEditor::findPrevMatch() { if (lastSearch.empty() && lastHexSearch.empty()) return; size_t searchSize = lastSearch.empty() ? lastHexSearch.size() : lastSearch.size(); size_t startPos = curPos > 0 ? curPos - 1 : buf.size() - searchSize; for (size_t i = startPos + 1; i > 0; --i) { size_t pos = i - 1; if (pos > buf.size() - searchSize) continue; bool match = true; if (!lastSearch.empty()) { for (size_t j = 0; j < lastSearch.size(); ++j) { if (buf[pos + j] != static_cast(lastSearch[j])) { match = false; break; } } } else { for (size_t j = 0; j < lastHexSearch.size(); ++j) { if (buf[pos + j] != lastHexSearch[j]) { match = false; break; } } } if (match) { curPos = pos; dpOffset = curPos - (curPos % bpr); render(); return; } } for (size_t i = buf.size() - searchSize; i > startPos; --i) { bool match = true; if (!lastSearch.empty()) { for (size_t j = 0; j < lastSearch.size(); ++j) { if (buf[i + j] != static_cast(lastSearch[j])) { match = false; break; } } } else { for (size_t j = 0; j < lastHexSearch.size(); ++j) { if (buf[i + j] != lastHexSearch[j]) { match = false; break; } } } if (match) { curPos = i; dpOffset = curPos - (curPos % bpr); render(); return; } } } void HexEditor::handleCommand() { statusMode = Status_Command; statusText.clear(); render(); int ch; while ((ch = getch()) != '\n' && ch != 27) { #ifdef _WIN32 if (ch == KEY_BACKSPACE || ch == 8) { #else if (ch == KEY_BACKSPACE || ch == 127) { #endif if (!statusText.empty()) { statusText.pop_back(); } } else if (ch >= 32 && ch <= 126) { // 書き込める文字 statusText += ch; } render(); } if (ch == 27) { // Esc statusMode = Status_Normal; statusText.clear(); render(); return; } bool isCommand = false; if (statusText == "w") { handleSave(); isCommand = true; } else if (statusText == "q") { handleQuit(false); isCommand = true; } else if (statusText == "wq") { handleSave(); handleQuit(false); isCommand = true; } else if (statusText == "q!") { handleQuit(true); isCommand = true; } else if (statusText == "wq!") { handleSave(); handleQuit(true); isCommand = true; } else if (statusText == "noh") { lastSearch = ""; isCommand = true; } else { statusMode = Status_Error; statusText.clear(); } if (isCommand) { statusMode = Status_Normal; statusText.clear(); } render(); } void HexEditor::handleQuit(bool force) { if (force || (!force && !modified)) { running = false; } else { statusMode = Status_Error; statusText.clear(); render(); } } void HexEditor::handleSave() { std::ofstream file(fname, std::ios::binary); if (file) { file.write(reinterpret_cast(buf.data()), buf.size()); file.close(); modified = false; statusText = "保存済み"; } } void HexEditor::handleSearch() { statusMode = Status_Search; lastSearchDir = Direction_Forward; statusText.clear(); render(); int ch; while ((ch = getch()) != '\n' && ch != 27) { #ifdef _WIN32 if (ch == KEY_BACKSPACE || ch == 8) { #else if (ch == KEY_BACKSPACE || ch == 127) { #endif if (!statusText.empty()) statusText.pop_back(); } else if (ch >= 32 && ch <= 126) { // 書き込める文字 statusText += ch; } render(); } if (ch == 27) { // Esc statusMode = Status_Normal; statusText.clear(); render(); return; } lastSearch.clear(); lastHexSearch.clear(); matchOff.clear(); std::istringstream iss(statusText); std::string hexByte; bool isHexSearch = true; std::vector hexSearch; while (iss >> hexByte) { if (hexByte.size() != 2 || !std::all_of(hexByte.begin(), hexByte.end(), [](char c) { return std::isxdigit(c); })) { isHexSearch = false; break; } try { hexSearch.push_back(static_cast(std::stoul(hexByte, nullptr, 16))); } catch (...) { isHexSearch = false; break; } } if (isHexSearch && !hexSearch.empty()) { // HEX検索 lastHexSearch = hexSearch; for (size_t i = curPos + 1; i <= buf.size() - lastHexSearch.size(); ++i) { bool match = true; for (size_t j = 0; j < lastHexSearch.size(); ++j) { if (buf[i + j] != lastHexSearch[j]) { match = false; break; } } if (match) { matchOff.push_back(i); curPos = i; dpOffset = curPos - (curPos % bpr); break; } } } else { // ASCII検索 lastSearch = statusText; for (size_t i = curPos + 1; i <= buf.size() - lastSearch.size(); ++i) { bool match = true; for (size_t j = 0; j < lastSearch.size(); ++j) { if (buf[i + j] != static_cast(lastSearch[j])) { match = false; break; } } if (match) { matchOff.push_back(i); curPos = i; dpOffset = curPos - (curPos % bpr); break; } } } statusMode = Status_Normal; statusText.clear(); render(); } void HexEditor::handleReverseSearch() { statusMode = Status_Search; lastSearchDir = Direction_Reverse; statusText.clear(); render(); int ch; while ((ch = getch()) != '\n' && ch != 27) { #ifdef _WIN32 if (ch == KEY_BACKSPACE || ch == 8) { #else if (ch == KEY_BACKSPACE || ch == 127) { #endif if (!statusText.empty()) statusText.pop_back(); } else if (ch >= 32 && ch <= 126) { statusText += ch; } render(); } if (ch == 27) { // Esc statusMode = Status_Normal; statusText.clear(); render(); return; } lastSearch.clear(); lastHexSearch.clear(); matchOff.clear(); std::istringstream iss(statusText); std::string hexByte; bool isHexSearch = true; std::vector hexSearch; while (iss >> hexByte) { if (hexByte.size() != 2 || !std::all_of(hexByte.begin(), hexByte.end(), [](char c) { return std::isxdigit(c); })) { isHexSearch = false; break; } try { hexSearch.push_back(static_cast(std::stoul(hexByte, nullptr, 16))); } catch (...) { isHexSearch = false; break; } } if (isHexSearch && !hexSearch.empty()) { lastHexSearch = hexSearch; size_t startPos = curPos > 0 ? curPos - 1 : buf.size() - lastHexSearch.size(); for (size_t i = startPos + 1; i > 0; --i) { size_t pos = i - 1; if (pos > buf.size() - lastHexSearch.size()) continue; bool match = true; for (size_t j = 0; j < lastHexSearch.size(); ++j) { if (buf[pos + j] != lastHexSearch[j]) { match = false; break; } } if (match) { matchOff.push_back(pos); curPos = pos; dpOffset = curPos - (curPos % bpr); break; } } } else { lastSearch = statusText; size_t startPos = curPos > 0 ? curPos - 1 : buf.size() - lastSearch.size(); for (size_t i = startPos + 1; i > 0; --i) { size_t pos = i - 1; if (pos > buf.size() - lastSearch.size()) continue; bool match = true; for (size_t j = 0; j < lastSearch.size(); ++j) { if (buf[pos + j] != static_cast(lastSearch[j])) { match = false; break; } } if (match) { matchOff.push_back(pos); curPos = pos; dpOffset = curPos - (curPos % bpr); break; } } } statusMode = Status_Normal; statusText.clear(); render(); } void HexEditor::handleReplace() { statusMode = Status_Replace; statusText.clear(); render(); std::string hexInput; int ch; while ((ch = getch()) != 27) { if (std::isxdigit(ch) && hexInput.size() < 2) { hexInput += ch; statusText = "REPLACE: " + hexInput; render(); } if (hexInput.size() == 2) { uint8_t byte = std::stoul(hexInput, nullptr, 16); if (curPos < buf.size()) { undoStack.push_back({curPos, buf[curPos], byte}); redoStack.clear(); buf[curPos] = byte; modified = true; curPos = std::min(curPos + 1, buf.size() - 1); } hexInput.clear(); statusText.clear(); render(); } } statusMode = Status_Normal; statusText.clear(); render(); } void HexEditor::handleMouse() { #ifdef _WIN32 request_mouse_pos(); int mx = Mouse_status.x; int my = Mouse_status.y; bool click = false; for (int i = 0; i < 3; ++i) { if ((Mouse_status.button[i] & BUTTON1_PRESSED) || (Mouse_status.button[i] & BUTTON1_CLICKED)) { click = true; break; } } if (!click) return; #else MEVENT event; if (getmouse(&event) != OK) return; if (!(event.bstate & (BUTTON1_PRESSED | BUTTON1_CLICKED))) return; int mx = event.x; int my = event.y; #endif if (my == 0 || my == rows - 1) return; if (my >= 1 && my < rows - 2 && mx < cols / 2) { // HEXパネル int panelRow = my - 1; int panelCol = mx; if (panelCol < 10) return; int i = (panelCol - 10) / 3; size_t offset = dpOffset + panelRow * bpr + i; if (offset < buf.size()) { curPos = offset; render(); } } else if (my >= 1 && my < rows - 2 && mx >= cols / 2) { // ASCIIパネル int panelRow = my - 1; int i = mx - cols / 2; size_t offset = dpOffset + panelRow * bpr + i; if (offset < buf.size()) { curPos = offset; render(); } } } void HexEditor::undo() { if (undoStack.empty()) { statusMode = Status_Error; statusText = "既に一番古い変更です"; render(); return; } Edit edit = undoStack.back(); undoStack.pop_back(); redoStack.push_back({edit.offset, edit.newByte, edit.oldByte}); buf[edit.offset] = edit.oldByte; if (undoStack.empty()) modified = false; else modified = true; curPos = edit.offset; dpOffset = curPos - (curPos % bpr); render(); } void HexEditor::redo() { if (redoStack.empty()) { statusMode = Status_Error; statusText = "既に一番新しい変更です"; render(); return; } Edit edit = redoStack.back(); redoStack.pop_back(); undoStack.push_back({edit.offset, edit.newByte, edit.oldByte}); buf[edit.offset] = edit.newByte; modified = true; curPos = edit.offset; dpOffset = curPos - (curPos % bpr); render(); } void HexEditor::input() { int ch; while (running) { if (statusMode != Status_Normal && statusMode != Status_Error) continue; ch = getch(); if ((ch == 'j' || ch == KEY_DOWN) && curPos + bpr < buf.size()) { curPos += bpr; // 下 if (statusMode == Status_Error) statusMode = Status_Normal; } else if ((ch == 'k' || ch == KEY_UP) && curPos >= bpr) { curPos -= bpr; // 上 if (statusMode == Status_Error) statusMode = Status_Normal; } else if ((ch == 'l' || ch == KEY_RIGHT) && curPos + 1 < buf.size()) { curPos += 1; // 右 if (statusMode == Status_Error) statusMode = Status_Normal; } else if ((ch == 'h' || ch == KEY_LEFT) && curPos > 0) { curPos -= 1; // 左 if (statusMode == Status_Error) statusMode = Status_Normal; } else if ((ch == 'J' || ch == KEY_PPAGE) && curPos >= bpr) { size_t pageSize = (rows - 3) * bpr; if (curPos >= pageSize) curPos -= pageSize; else curPos = 0; if (statusMode == Status_Error) statusMode = Status_Normal; } else if ((ch == 'K' || ch == KEY_NPAGE) && curPos + bpr < buf.size()) { size_t pageSize = (rows - 3) * bpr; if (curPos + pageSize < buf.size()) { curPos += pageSize; dpOffset = curPos - ((rows - 4) * bpr); } else { curPos = buf.size() - 1; dpOffset = (buf.size() - 1) - ((rows - 4) * bpr); if (dpOffset > buf.size()) dpOffset = 0; } if (statusMode == Status_Error) statusMode = Status_Normal; } else if (ch == '^' || ch == '0' || ch == KEY_HOME) { curPos = (curPos / bpr) * bpr; if (statusMode == Status_Error) statusMode = Status_Normal; } else if (ch == '$' || ch == KEY_END) { size_t rowStart = (curPos / bpr) * bpr; curPos = std::min(rowStart + bpr - 1, buf.size() - 1); if (statusMode == Status_Error) statusMode = Status_Normal; } else if (ch == 'i') { if (curPos - 1 > 0) curPos--; handleReplace(); } else if (ch == 'r') { handleReplace(); } else if (ch == 'a') { if (curPos + 1 < buf.size()) curPos++; handleReplace(); } else if (ch == ':') { handleCommand(); } else if (ch == '/') { handleSearch(); } else if (ch == '?') { handleReverseSearch(); } else if (ch == 'n') { if (lastSearchDir == Direction_Forward) findNextMatch(); else findPrevMatch(); } else if (ch == 'N') { if (lastSearchDir == Direction_Forward) findPrevMatch(); else findNextMatch(); } else if (ch == 'u') { undo(); } else if (ch == 'R') { redo(); } else if (ch == 'g') { if (getch() == 'g') { curPos = 0; // ファイルの一番上 if (statusMode == Status_Error) statusMode = Status_Normal; } } else if (ch == 'G') { curPos = buf.size() - 1; // ファイルの一番下 if (statusMode == Status_Error) statusMode = Status_Normal; } else if (ch == 'Z') { int next = getch(); if (next == 'Q') { handleQuit(true); break; } else if (next == 'Z') { handleSave(); handleQuit(true); break; } else if (next == 'S') { handleSave(); if (statusMode == Status_Error) statusMode = Status_Normal; } } else if (ch == KEY_MOUSE) { handleMouse(); continue; } // 画面の動き int rows, cols; getmaxyx(stdscr, rows, cols); if (curPos < dpOffset) { dpOffset = curPos - (curPos % bpr); } else if (curPos >= dpOffset + (rows - 3) * bpr) { dpOffset = curPos - ((rows - 4) * bpr); } render(); } } void HexEditor::run() { render(); input(); }