/// Main module, handling core TUI operations. /// /// Copyright: dd86k <dd@dax.moe> /// License: MIT /// Authors: $(LINK2 https://github.com/dd86k, dd86k) module ddhx; import std.stdio; import core.stdc.stdlib : exit; import os.terminal : Key, Mod; import editor; import display; import transcoder : CharacterSet; import logger; //TODO: On terminal resize, set UHEADER private enum { // Update the cursor position UCURSOR = 1, // Update the current view UVIEW = 1 << 1, // Update the header UHEADER = 1 << 2, // Editing in progress UEDIT = 1 << 3, // Clear the current message UMESSAGE = 1 << 8, UINIT = 0xff, } private __gshared { Editor file; // Current file index (document selector) //int currenfile; /// Total length of the file or data buffer, in bytes long filesize; /// Position of the view, in bytes long viewpos; /// Size of the view, in bytes int viewsize; /// Number of columns desired, one per data group int columns; /// Address padding, in digits int addrpad = 11; /// The type of format to use for rendering offsets int offsetmode; /// The type of format to use for rendering data int datamode; /// The size of one data item, in bytes int groupsize; /// The type of character set to use for rendering text int charset; /// Number of digits per byte, in digits or nibbles int digits; /// Position of the cursor in the file, in bytes long curpos; /// Position of the cursor editing a group of bytes, in digits int editpos; // e.g., hex=nibble, dec=digit, etc. /// int editmode; /// enum EDITBFSZ = 8; /// Value of edit input, as a digit char[EDITBFSZ] editbuffer; /// System status, used in updating certains portions of the screen int status; } int ddhx_start(string path, bool readonly, long skip, long length, int cols, int ucharset) { //TODO: Stream support // With length, build up a buffer if (path == null) { stderr.writeln("todo: Stdin"); return 2; } // Open file if (file.open(path, true, readonly)) { stderr.writeln("error: Could not open file"); return 3; } // if (skip) { viewpos = curpos = skip; } charset = ucharset; filesize = file.size(); // Init display in TUI mode disp_init(true); // Set number of columns, or automatically get column count // from terminal. columns = cols ? cols : disp_hint_cols(); // Get "view" buffer size, in bytes viewsize = disp_hint_view(columns); // Allocate buffer according to desired cols trace("viewsize=%d", viewsize); file.setbuffer( viewsize ); // Initially render everything status = UINIT; Lread: cast(void)update(); int key = disp_readkey(); switch (key) { // Navigation keys case Key.LeftArrow: move_left(); break; case Key.RightArrow: move_right(); break; case Key.DownArrow: move_down(); break; case Key.UpArrow: move_up(); break; case Key.PageDown: move_pg_down(); break; case Key.PageUp: move_pg_up(); break; case Key.Home: move_ln_start(); break; case Key.End: move_ln_end(); break; case Key.Home|Mod.ctrl: move_abs_start(); break; case Key.End |Mod.ctrl: move_abs_end(); break; // Search case Key.W | Mod.ctrl: break; // Reset screen case Key.R | Mod.ctrl: columns = disp_hint_cols(); viewsize = disp_hint_view(columns); file.setbuffer( viewsize ); status = UINIT; break; // case Key.Q | Mod.ctrl: quit(); break; default: // Edit mode /*if (_editkey(datamode, key)) { //TODO: When group size filled, add to edit history trace("EDIT key=%c", cast(char)key); editbuffer[editpos++] = cast(ubyte)key; status |= UEDIT; if (editpos >= digits) { //TODO: add byte+address to edits editpos = 0; _move_rel(1); } goto Lread; }*/ } goto Lread; } string prompt(string text) { throw new Exception("Not implemented"); } private int _editkey(int type, int key) { switch (type) with (Format) { case hex: return (key <= '0' && key <= '9') || (key <= 'A' && key <= 'F') || (key <= 'a' && key <= 'f'); case dec: return key <= '0' && key <= '9'; case oct: return key <= '0' && key <= '7'; default: throw new Exception(__FUNCTION__); } } private int _editval(int type, int key) { switch (type) with (Format) { case hex: if (key <= '0' && key <= '9') return key - '0'; if (key <= 'A' && key <= 'F') return key - 'A' + 0x10; if (key <= 'a' && key <= 'f') return key - 'a' + 0x10; goto default; case dec: if (key <= '0' && key <= '9') return key - '0'; goto default; case oct: if (key <= '0' && key <= '7') return key - '0'; goto default; default: throw new Exception(__FUNCTION__); } } // Move the cursor relative to its position within the file private void _move_rel(long pos) { if (pos == 0) return; long old = curpos; curpos += pos; if (pos > 0 && curpos >= filesize) curpos = filesize; else if (pos < 0 && curpos < 0) curpos = 0; if (old == curpos) return; status |= UCURSOR; _adjust_viewpos(); } // Move the cursor to an absolute file position private void _move_abs(long pos) { long old = curpos; curpos = pos; if (curpos >= filesize) curpos = filesize; else if (curpos < 0) curpos = 0; if (old == curpos) return; status |= UCURSOR; _adjust_viewpos(); } // Adjust the view positon void _adjust_viewpos() { //TODO: Adjust view position algorithmically // Cursor is ahead the view if (curpos >= viewpos + viewsize) { while (curpos >= viewpos + viewsize) { viewpos += columns; if (viewpos >= filesize - viewsize) break; } status |= UVIEW; } // Cursor is behind the view else if (curpos < viewpos) { while (curpos < viewpos) { viewpos -= columns; if (viewpos <= 0) break; } status |= UVIEW; } } void move_left() { if (curpos == 0) return; _move_rel(-1); } void move_right() { if (curpos == filesize) return; _move_rel(1); } void move_up() { if (curpos == 0) return; _move_rel(-columns); } void move_down() { if (curpos == filesize) return; _move_rel(columns); } void move_pg_up() { if (curpos == 0) return; _move_rel(-viewsize); } void move_pg_down() { if (curpos == filesize) return; _move_rel(viewsize); } void move_ln_start() { _move_rel(-curpos % columns); } void move_ln_end() { _move_rel((columns - (curpos % columns)) - 1); } void move_abs_start() { _move_abs(0); } void move_abs_end() { _move_abs(filesize); } // Update all elements on screen depending on status // status global indicates what needs to be updated void update() { // Update header if (status & UHEADER) disp_header(columns); // Update the screen if (status & UVIEW) update_view(); // Update status update_status(); // Update cursor position // NOTE: Should always be updated due to frequent movement // That includes messages, cursor naviation, menu invokes, etc. int curdiff = cast(int)(curpos - viewpos); trace("cur=%d", curdiff); update_edit(curdiff, columns, addrpad); status = 0; } void update_view() { file.seek(viewpos); ubyte[] data = file.read(); trace("addr=%u data.length=%u", viewpos, data.length); disp_update(viewpos, data, columns, Format.hex, Format.hex, '.', charset, 11, 1); } // relative cursor position void update_edit(int curpos, int columns, int addrpadd) { enum hexsize = 3; int row = 1 + (curpos / columns); int col = (addrpadd + 2 + ((curpos % columns) * hexsize)); disp_cursor(row, col); // Editing in progress /*if (editbuf && editsz) { disp_write(editbuf, editsz); }*/ } void update_status() { //TODO: Could limit write length by terminal width? //TODO: check number of edits enum STATBFSZ = 2 * 1024; char[STATBFSZ] statbuf = void; int statlen = snprintf(statbuf.ptr, STATBFSZ, "%s | %s", "test".ptr, "test2".ptr); disp_message(statbuf.ptr, statlen); } void message(const(char)[] msg) { disp_message(msg.ptr, msg.length); status |= UMESSAGE; } void quit() { trace("quit"); exit(0); }