diff --git a/dub.sdl b/dub.sdl index f05ae16..9eeb5fc 100644 --- a/dub.sdl +++ b/dub.sdl @@ -5,7 +5,7 @@ license "MIT" # NOTE: Somehow, the dmd package in the Alpine repo does not contain rdmd. -preBuildCommands "dmd -run setup.d version" platform="dmd" +preBuildCommands "rdmd setup.d version" platform="dmd" preBuildCommands "ldmd2 -run setup.d version" platform="ldc" preBuildCommands "gdmd -run setup.d version" platform="gdc" @@ -18,9 +18,9 @@ dflags "--vgc" "--vtls" platform="ldc" } -buildType "trace" { +configuration "trace" { + targetType "executable" versions "Trace" - buildOptions "debugMode" "debugInfo" } # diff --git a/src/ddhx.d b/src/ddhx.d index d4fbc10..de7c4ff 100644 --- a/src/ddhx.d +++ b/src/ddhx.d @@ -68,10 +68,7 @@ return errorPrint; } - initiate; - screen.cursorOffset; - screen.renderOffset; - readRender; + refresh; version (Trace) trace("loop"); TerminalInput event; @@ -190,9 +187,8 @@ version (Trace) trace("%(%s %)", argv); string command = argv[0]; - //TODO: Check length of command string? - switch (command[0]) { + switch (command[0]) { // shortcuts case '/': // Search if (command.length <= 1) return errorSet(ErrorCode.missingArgumentType); @@ -207,109 +203,109 @@ return errorSet(ErrorCode.missingArgumentNeedle); return ddhx.lookup(command[1..$], argv[1], false, true); - default: // Regular - switch (argv[0]) { - case "g", "goto": - if (argc <= 1) - return errorSet(ErrorCode.missingArgumentPosition); - - switch (argv[1]) - { - case "e", "end": - moveEnd; - break; - case "h", "home": - moveStart; - break; - default: - seek(argv[1]); - } - return 0; - case "skip": - ubyte byte_ = void; - if (argc <= 1) { - size_t p = - (editor.cursor.y * setting.width) + - editor.cursor.x; - byte_ = readdata[p]; - } else { - if (argv[1] == "zero") - byte_ = 0; - else if (convertToVal(byte_, argv[1])) - return error.ecode; - } - return skip(byte_); - case "i", "info": - printFileInfo; - return 0; - case "refresh": - refresh; - return 0; - case "q", "quit": - exit; - return 0; - case "about": - enum C = "Written by dd86k. " ~ DDHX_COPYRIGHT; - screenMessage(C); - return 0; - case "version": - screenMessage(DDHX_ABOUT); - return 0; - // - // Settings - // - case "w", "width": - if (argc <= 1) - return errorSet(ErrorCode.missingArgumentWidth); - - if (settingsWidth(argv[1])) - return error.ecode; - - refresh; - return 0; - case "o", "offset": - if (argc <= 1) - return errorSet(ErrorCode.missingArgumentType); - - if (settingsOffset(argv[1])) - return error.ecode; - - render; - return 0; - case "d", "data": - if (argc <= 1) - return errorSet(ErrorCode.missingArgumentType); - - if (settingsData(argv[1])) - return error.ecode; - - render; - return 0; - case "C", "defaultchar": - if (argc <= 1) - return errorSet(ErrorCode.missingArgumentCharacter); - - if (settingsDefaultChar(argv[1])) - return error.ecode; - - render; - return 0; - case "cp", "charset": - if (argc <= 1) - return errorSet(ErrorCode.missingArgumentCharset); - - if (settingsCharset(argv[1])) - return error.ecode; - - render; - return 0; - case "reset": - resetSettings(); - render; - return 0; + default: + } + + switch (argv[0]) { // regular commands + case "g", "goto": + if (argc <= 1) + return errorSet(ErrorCode.missingArgumentPosition); + + switch (argv[1]) + { + case "e", "end": + moveEnd; + break; + case "h", "home": + moveStart; + break; default: - return errorSet(ErrorCode.invalidCommand); + seek(argv[1]); } + return 0; + case "skip": + ubyte byte_ = void; + if (argc <= 1) { + byte_ = readdata[editor.cursor.position]; + } else { + if (argv[1] == "zero") + byte_ = 0; + else if (convertToVal(byte_, argv[1])) + return error.ecode; + } + return skip(byte_); + case "i", "info": + printFileInfo; + return 0; + case "refresh": + refresh; + return 0; + case "q", "quit": + exit; + return 0; + case "about": + enum C = "Written by dd86k. " ~ DDHX_COPYRIGHT; + screenMessage(C); + return 0; + case "version": + screenMessage(DDHX_ABOUT); + return 0; + // + // Settings + // + case "w", "width": + if (argc <= 1) + return errorSet(ErrorCode.missingArgumentWidth); + + if (settingsWidth(argv[1])) + return error.ecode; + + refresh; + return 0; + case "o", "offset": + if (argc <= 1) + return errorSet(ErrorCode.missingArgumentType); + + if (settingsOffset(argv[1])) + return error.ecode; + + screen.cursorOffset; + screen.renderOffset; + render; + return 0; + case "d", "data": + if (argc <= 1) + return errorSet(ErrorCode.missingArgumentType); + + if (settingsData(argv[1])) + return error.ecode; + + render; + return 0; + case "C", "defaultchar": + if (argc <= 1) + return errorSet(ErrorCode.missingArgumentCharacter); + + if (settingsDefaultChar(argv[1])) + return error.ecode; + + render; + return 0; + case "cp", "charset": + if (argc <= 1) + return errorSet(ErrorCode.missingArgumentCharset); + + if (settingsCharset(argv[1])) + return error.ecode; + + render; + return 0; + case "reset": + resetSettings(); + render; + return 0; + default: + return errorSet(ErrorCode.invalidCommand); } } @@ -325,79 +321,71 @@ /// Move the cursor to the start of the data void moveStart() { - editor.cursorFileStart; - readRender; + if (editor.cursorFileStart) + readRender; + updateStatus; + updateCursor; } /// Move the cursor to the end of the data void moveEnd() { - editor.cursorFileEnd; - readRender; + if (editor.cursorFileEnd) + readRender; + updateStatus; + updateCursor; } /// Align cursor to start of row void moveAlignStart() { - //seek(io.position - (io.position % setting.width)); editor.cursorHome; - readRender; + updateStatus; + updateCursor; } /// Align cursor to end of row void moveAlignEnd() { - /*const long n = io.position + - (setting.width - io.position % setting.width); - seek(n + io.readSize <= io.size ? n : io.size - io.readSize);*/ editor.cursorEnd; - readRender; + updateStatus; + updateCursor; } /// Move cursor to one data group to the left (backwards) void moveLeft() { - /*if (io.position - 1 >= 0) // Else already at 0 - seek(io.position - 1);*/ - editor.cursorLeft; - readRender; + if (editor.cursorLeft) + readRender; + updateStatus; + updateCursor; } /// Move cursor to one data group to the right (forwards) void moveRight() { - /*if (io.position + io.readSize + 1 <= io.size) - seek(io.position + 1); - else - seek(io.size - io.readSize);*/ - editor.cursorRight; - readRender; + if (editor.cursorRight) + readRender; + updateStatus; + updateCursor; } /// Move cursor to one row size up (backwards) void moveRowUp() { - /*if (io.position - setting.width >= 0) - seek(io.position - setting.width); - else - seek(0);*/ - editor.cursorUp; - readRender; + if (editor.cursorUp) + readRender; + updateStatus; + updateCursor; } /// Move cursor to one row size down (forwards) void moveRowDown() { - /*if (io.position + io.readSize + setting.width <= io.size) - seek(io.position + setting.width); - else - seek(io.size - io.readSize);*/ - editor.cursorDown; - readRender; + if (editor.cursorDown) + readRender; + updateStatus; + updateCursor; } /// Move cursor to one page size up (backwards) void movePageUp() { - /*if (io.position - cast(long)io.readSize >= 0) - seek(io.position - io.readSize); - else - seek(0);*/ - editor.cursorPageUp; - readRender; + if (editor.cursorPageUp) + readRender; + updateStatus; + updateCursor; } /// Move view to one page size down (forwards) void movePageDown() { - /*if (io.position + (io.readSize << 1) <= io.size) - seek(io.position + io.readSize); - else - seek(io.size - io.readSize);*/ - editor.cursorDown; - readRender; + if (editor.cursorPageDown) + readRender; + updateStatus; + updateCursor; } /// Initiate screen buffer @@ -415,7 +403,7 @@ // read at current position int read() { - version (Trace) trace(""); + version (Trace) trace; editor.seek(editor.position); // if (editor.err) @@ -426,13 +414,14 @@ return 0; } +//TODO: Consider render with multiple parameters to select what to render + /// Render screen (all elements) void render() { - version (Trace) trace(""); + version (Trace) trace; updateContent; updateStatus; - updateCursor; } void updateOffset() { @@ -440,35 +429,34 @@ screen.renderOffset; } -void updateContent(bool cursor = true) { +void updateContent() { screen.cursorContent; screen.renderContent(editor.position, readdata); } -void updateStatus(bool cursor = true) { +void updateStatus() { import std.format : format; - long cpos = editor.position + editor.readSize; + long c = editor.cursorTell + 1; screen.cursorStatusbar; screen.renderStatusBar( - editor.edits.modestr, - //TODO: editor obviously should return current data type - "hex", //numbers[setting.dataType].name, + editor.editModeString, + screen.name, transcoder.name, formatBin(editor.readSize, setting.si), format("%s (%f%%)", - formatBin(cpos, setting.si), - ((cast(float)cpos) / editor.fileSize) * 100)); + formatBin(c, setting.si), + ((cast(double)c) / editor.fileSize) * 100)); } void updateCursor() { version (Trace) with (editor.cursor) - trace("x=%u y=%u n=%u", x, y, nibble); + trace("pos=%u n=%u", position, nibble); with (editor.cursor) - screen.cursor(x, y, nibble); + screen.cursor(position, nibble); } void readRender() { @@ -483,13 +471,14 @@ /// 4. Read buffer /// 5. Render void refresh() { - version (Trace) trace(""); + version (Trace) trace; - screen.screenClear; + screen.clear; initiate; read; updateOffset; render; + updateCursor; } /// Seek to position in data, reads view's worth, and display that. @@ -555,15 +544,15 @@ else if (pos < 0) pos = 0; - editor.cursorTo(pos); + editor.cursorJump(pos, true); } -private enum LAST_BUFFER_SIZE = 128; -private __gshared ubyte[LAST_BUFFER_SIZE] lastItem; -private __gshared size_t lastSize; -private __gshared string lastType; -private __gshared bool lastForward; -private __gshared bool lastAvailable; +enum LAST_BUFFER_SIZE = 128; +__gshared ubyte[LAST_BUFFER_SIZE] lastItem; +__gshared size_t lastSize; +__gshared string lastType; +__gshared bool lastForward; +__gshared bool lastAvailable; /// Search last item. /// Returns: Error code if set. diff --git a/src/dump.d b/src/dump.d index 47f82b7..e6f4c85 100644 --- a/src/dump.d +++ b/src/dump.d @@ -7,7 +7,19 @@ import os.terminal; import error, editor, screen, settings; -/// Dump to stdout, akin to xxd(1). +/*TODO: DumpOutput +// Then add to function as parameter + +// With custom "byte" formatter +// Default formatter has " " as prefix/suffix +// HTML formatter will have "" as prefix and "" as suffix + +enum DumpOutput { + text, + html +}*/ + +/// Dump to stdout, akin to xxd(1) or hexdump(1). /// Params: /// skip = If set, number of bytes to skip. /// length = If set, maximum length to read. diff --git a/src/editor.d b/src/editor.d index 998b7d6..fbe2f25 100644 --- a/src/editor.d +++ b/src/editor.d @@ -80,11 +80,23 @@ }*/ /// Editor editing mode. -//TODO: "get string" with "ins","ovr","rdo" enum EditMode : ushort { - insert, /// Data will be inserted. - overwrite, /// Data will be overwritten. - readOnly, /// Editing data is disallowed by user or permission. + /// Incoming data will be inserted at cursor position. + /// Editing: Enabled + /// Cursor: Enabled + insert, + /// Incoming data will be overwritten at cursor position. + /// Editing: Enabled + /// Cursor: Enabled + overwrite, + /// The file cannot be edited. + /// Editing: Disabled + /// Cursor: Enabled + readOnly, + /// The file can only be viewed. + /// Editing: Disabled + /// Cursor: Disabled + view, } /// Represents a single edit @@ -96,7 +108,7 @@ } private union Source { - OSFile2 osfile; + OSFile osfile; OSMmFile mmfile; File stream; MemoryStream memory; @@ -106,26 +118,39 @@ __gshared const(char)[] fileName; /// File base name. __gshared FileMode fileMode; /// Current file mode. __gshared long position; /// Last known set position. -private __gshared ubyte[] readBuffer; /// For input input. -__gshared size_t readSize; /// For input input. +__gshared size_t readSize; /// For input size. +private __gshared ubyte[] readBuffer; /// For input data. +private __gshared uint vheight; /// // Editing stuff -private struct Editing { - EditMode mode; /// Current editing mode - const(char[]) modestr = "ins"; /// Current editing mode string - SList!Edit history; /// Temporary file edits - size_t count; /// Amount of edits in history - size_t index; /// Current edit position -} -__gshared Editing edits; +private __gshared size_t editIndex; /// Current edit position +private __gshared size_t editCount; /// Amount of edits in history +private __gshared SList!Edit editHistory; /// Temporary file edits +__gshared EditMode editMode; /// Current editing mode -struct cursor_t { - int x; /// Data group column position - int y; /// Data group row position - int nibble; /// Data group nibble position +string editModeString(EditMode mode = editMode) { + final switch (mode) with (EditMode) { + case insert: return "inse"; + case overwrite: return "over"; + case readOnly: return "read"; + case view: return "view"; + } } -__gshared cursor_t cursor; +bool editModeReadOnly(EditMode mode) { + switch (mode) with (EditMode) { + case readOnly, view: return true; + default: return false; + } +} + +private struct cursor_t { + //TODO: Transform into an 1D position system + // 2D is just clumsy... + uint position; /// Screen cursor byte position + uint nibble; /// Data group nibble position +} +__gshared cursor_t cursor; /// Cursor state // View properties @@ -151,7 +176,7 @@ } bool dirty() { - return edits.index == 0; + return editIndex == 0; } // SECTION: File opening @@ -159,7 +184,7 @@ int openFile(string path) { version (Trace) trace("path='%s'", path); - if (source.osfile.open(path)) + if (source.osfile.open(path, editModeReadOnly(editMode))) return errorSet(ErrorCode.os); fileMode = FileMode.file; @@ -167,11 +192,11 @@ return 0; } -int openMmfile(string path/*, bool create*/) { +int openMmfile(string path) { version (Trace) trace("path='%s'", path); try { - source.mmfile = new OSMmFile(path); + source.mmfile = new OSMmFile(path, editModeReadOnly(editMode)); } catch (Exception ex) { return errorSet(ex); } @@ -216,21 +241,15 @@ // SECTION: View position management // -void seek(long pos) { +long seek(long pos) { position = pos; final switch (fileMode) with (FileMode) { - case file: - source.osfile.seek(Seek.start, pos); - return; - case mmfile: - source.mmfile.seek(pos); - return; - case memory: - source.memory.seek(pos); - return; + case file: return source.osfile.seek(Seek.start, pos); + case mmfile: return source.mmfile.seek(pos); + case memory: return source.memory.seek(pos); case stream: source.stream.seek(pos); - return; + return pos; } } @@ -280,7 +299,7 @@ } void keydown(Key key) { - debug assert(edits.mode != EditMode.readOnly, + debug assert(editMode != EditMode.readOnly, "Editor should not be getting edits in read-only mode"); //TODO: Check by panel (binary or text) @@ -290,7 +309,7 @@ /// Append change at current position void appendEdit(ubyte data) { - debug assert(edits.mode != EditMode.readOnly, + debug assert(editMode != EditMode.readOnly, "Editor should not be getting edits in read-only mode"); @@ -310,28 +329,28 @@ // SECTION View position management // - -// Reserve bytes for file allocation -// void reserve(long nsize) ? - -void moveStart() { +bool viewStart() { + bool z = cursor.position > readSize; position = 0; + return z; } -void moveEnd() { +bool viewEnd() { + long old = position; position = fileSize - readSize; + return position != old; } -void moveUp() { - if (position - setting.width >= 0) - position -= setting.width; - else - position = 0; +bool viewUp() { + if (position - setting.width < 0) + return false; + position -= setting.width; + return true; } -void moveDown() { +bool viewDown() { long fsize = fileSize; - if (position + readSize + setting.width <= fsize) - seek(position + setting.width); - else - seek(fsize - readSize); + if (position + readSize > fsize) + return false; + position += setting.width; + return true; } // !SECTION @@ -340,105 +359,123 @@ // SECTION Cursor position management // -long tellPosition() { - return position + (cursor.y * setting.width) + cursor.x; +void cursorBound() { + if (cursor.position < 0) + cursor.position = 0; + else if (cursor.position >= readSize) { + if (cursor.position - setting.width >= readSize) + cursor.position = cast(uint)(readSize - 1); + else + cursor.position -= setting.width; + } } -void cursorFileStart() { - moveStart; - with (cursor) x = y = nibble = 0; +// These return true if view moves. + +bool cursorFileStart() { + viewStart; + with (cursor) position = nibble = 0; + return true; } -void cursorFileEnd() { - moveEnd; +bool cursorFileEnd() { + viewEnd; with (cursor) { - x = setting.width - 1; - y = (cast(int)readSize / setting.width) - 1; + position = cast(uint)readSize - 1; nibble = 0; } + return true; } - -void cursorHome() { - cursor.x = cursor.nibble = 0; -} -void cursorEnd() { - cursor.x = setting.width - 1; +bool cursorHome() { + with (setting) + cursor.position = (cursor.position / width) * width; cursor.nibble = 0; + return false; } -void cursorLeft() { - if (cursor.x == 0) { - if (cursor.y == 0) - return; - - --cursor.y; +bool cursorEnd() { + with (setting) + cursor.position = ((cursor.position / width) * width) + width - 1; + cursor.nibble = 0; + return false; +} +bool cursorLeft() { + if (cursor.position == 0) { + if (position == 0) + return false; cursorEnd; - return; + return viewUp; } - --cursor.x; + --cursor.position; + cursor.nibble = 0; + return false; } -void cursorRight() { - if (cursor.x == setting.width - 1) { - size_t r = (readSize / setting.width) - 1; - if (cursor.y == r) { - moveDown; - cursorHome; - return; - } - - ++cursor.y; +bool cursorRight() { + if (cursorTell >= fileSize - 1) + return false; + + if (cursor.position == readSize - 1) { cursorHome; - return; + return viewDown; } - ++cursor.x; + ++cursor.position; + cursor.nibble = 0; + return false; } -void cursorUp() { - if (cursor.y == 0) { - moveUp; - return; +bool cursorUp() { + if (cursor.position < setting.width) { + return viewUp; } - --cursor.y; + cursor.position -= setting.width; + return false; } -void cursorDown() { - size_t r = (readSize / setting.width) - 1; +bool cursorDown() { + long fs = fileSize; - version (Trace) trace("rsz=%u w=%u r=%u", readSize, setting.width, r); + // last line + bool last = cursor.position > readSize - setting.width; + // should move down + bool force = cursorTell + setting.width >= fs; + bool ok = last && force; - if (cursor.y == r) { - moveDown; - return; + if (cursor.position + setting.width >= readSize) { + bool m = viewDown; + if (force) + goto L_FORCE_CURSOR; + return m; } - ++cursor.y; +L_FORCE_CURSOR: + if (force) { + uint rem = cast(uint)(fs % setting.width); + cursor.position = cast(uint)(readSize - setting.width + rem) - 1; + return false; + } + + cursor.position += setting.width; + return false; } -void cursorPageUp() { +bool cursorPageUp() { + + return false; } -void cursorPageDown() { - size_t r = (readSize / setting.width) - 1; +bool cursorPageDown() { + return false; } /// Get cursor absolute position long cursorTell() { - return position + cursorView; + return position + cursor.position; } -void cursorTo(long m) { // absolute +void cursorJump(long m, bool absolute) { // Per view chunks, then per y chunks, then x //long npos = } -/// Get cursor relative position to view -long cursorView() { - return (cursor.y * setting.width) + cursor.x; -} -void cursorJump(long m) { // relative - - //long npos = - -} // !SECTION diff --git a/src/error.d b/src/error.d index 9508041..4ecff12 100644 --- a/src/error.d +++ b/src/error.d @@ -169,6 +169,10 @@ void traceInit() { log.open("ddhx.log", "w"); } + void trace(string func = __FUNCTION__, int line = __LINE__, A...)() { + log.writefln("TRACE:%s:%u", func, line); + log.flush; + } void trace(string func = __FUNCTION__, int line = __LINE__, A...)(string fmt, A args) { log.writef("TRACE:%s:%u: ", func, line); log.writefln(fmt, args); diff --git a/src/main.d b/src/main.d index 180f1c7..e81cc2a 100644 --- a/src/main.d +++ b/src/main.d @@ -30,7 +30,10 @@ \_/ SECRET"; +immutable string OPT_INSERT = "insert"; +immutable string OPT_OVERWRITE = "overwrite"; immutable string OPT_READONLY = "R|readonly"; +immutable string OPT_VIEW = "view"; immutable string OPT_SI = "si"; immutable string OPT_WIDTH = "w|width"; //TODO: rename to c|columns immutable string OPT_OFFSET = "o|offset"; @@ -46,16 +49,16 @@ } void cliList(string opt) { - writeln("Values available for option ",opt,":"); + writeln("Available values for ",opt,":"); import std.traits : EnumMembers; switch (opt) { case OPT_OFFSET, OPT_DATA: foreach (m; EnumMembers!NumberType) - writeln(m); + writeln("\t=", m); break; case OPT_CHARSET: foreach (m; EnumMembers!CharacterSet) - writeln(m); + writeln("\t=", m); break; default: } @@ -64,8 +67,17 @@ void cliOption(string opt, string val) { final switch (opt) { + case OPT_INSERT: + editor.editMode = EditMode.insert; + return; + case OPT_OVERWRITE: + editor.editMode = EditMode.overwrite; + return; case OPT_READONLY: - editor.edits.mode = EditMode.readOnly; + editor.editMode = EditMode.readOnly; + return; + case OPT_VIEW: + editor.editMode = EditMode.view; return; case OPT_WIDTH: if (settingsWidth(val)) @@ -127,7 +139,10 @@ OPT_DATA, "Set data mode (decimal, hex, or octal)", &cliOption, OPT_DEFAULTCHAR, "Set non-printable replacement character (default='.')", &cliOption, OPT_CHARSET, "Set character translation (default=ascii)", &cliOption, - OPT_READONLY, "Set file mode to read-only", &cliOption, + OPT_INSERT, "Open file in insert editing mode", &cliOption, + OPT_OVERWRITE, "Open file in overwrite editing mode", &cliOption, + OPT_READONLY, "Open file in read-only editing mode", &cliOption, + OPT_VIEW, "Open file in view editing mode", &cliOption, OPT_SI, "Use SI suffixes instead of IEC", &setting.si, "m|mmfile", "Open file as mmfile (memory-mapped)", &cliMmfile, "f|file", "Force opening file as regular", &cliFile, @@ -160,13 +175,17 @@ return 0; } - version (Trace) traceInit; + version (Trace) { + traceInit; + trace(DDHX_ABOUT); + } string cliPath = args.length > 1 ? args[1] : "-"; if (cliStdin == false) cliStdin = args.length <= 1; // Open file + //TODO: Open memory long skip, length; if (cliStdin) { if (editor.openStream(stdin)) diff --git a/src/os/file.d b/src/os/file.d index 9134d6d..8637f0b 100644 --- a/src/os/file.d +++ b/src/os/file.d @@ -85,7 +85,7 @@ // useful when writing all changes to file // Win32: Seek + SetEndOfFile // others: ftruncate -struct OSFile2 { +struct OSFile { private OSHANDLE handle; bool eof, err; @@ -100,7 +100,7 @@ // By default, at least on Windows, files aren't shared. Enabling // sharing would allow refreshing view (manually) when a program // writes to file. - bool open(string path) { + bool open(string path, bool readOnly) { version (Windows) { // NOTE: toUTF16z/tempCStringW // Phobos internally uses tempCStringW from std.internal @@ -108,7 +108,9 @@ // Legacy baggage? handle = CreateFileW( path.toUTF16z, // lpFileName - GENERIC_READ | GENERIC_WRITE, // dwDesiredAccess + readOnly ? // dwDesiredAccess + GENERIC_READ : + GENERIC_READ | GENERIC_WRITE, 0, // dwShareMode null, // lpSecurityAttributes OPEN_EXISTING, // dwCreationDisposition @@ -117,7 +119,7 @@ ); return err = handle == INVALID_HANDLE_VALUE; } else version (Posix) { - handle = .open(path.toStringz, O_RDWR); + handle = .open(path.toStringz, readOnly ? O_RDONLY : O_RDWR); return err = handle == -1; } } diff --git a/src/os/mmfile.d b/src/os/mmfile.d index f563a37..1aee212 100644 --- a/src/os/mmfile.d +++ b/src/os/mmfile.d @@ -11,13 +11,18 @@ // temp public class OSMmFile : MmFile { private void *address; - this(string path) { - super(path, MmFile.Mode.read, 0, address); + this(string path, bool readOnly) { + super(path, + readOnly ? + MmFile.Mode.read : + MmFile.Mode.readWriteNew, + 0, + address); } bool eof, err; private long position; - void seek(long pos) { // only do seek_set for now - position = pos; + long seek(long pos) { // only do seek_set for now + return position = pos; } long tell() { return position; diff --git a/src/os/terminal.d b/src/os/terminal.d index 99774eb..b49d095 100644 --- a/src/os/terminal.d +++ b/src/os/terminal.d @@ -20,7 +20,8 @@ /// private extern (C) int putchar(int); -private import std.stdio : printf, stdin, stdout, _IONBF; +private import std.stdio : _IONBF, _IOLBF, _IOFBF, + printf, stdin, stdout; private import core.stdc.stdlib : system, atexit; version (Windows) { @@ -34,14 +35,25 @@ private __gshared HANDLE hIn, hOut; private __gshared USHORT defaultColor = DEFAULT_COLOR; private __gshared DWORD oldCP; -} -version (Posix) { +} else version (Posix) { private import core.sys.posix.sys.stat; private import core.sys.posix.sys.ioctl; private import core.sys.posix.unistd; private import core.sys.posix.termios; - version (CRuntime_Musl) { + // Bionic depends on the Linux system it's compiled on. + // But Glibc and Musl have the same settings, so does Bionic. + // ...And uClibc, at least on Linux. + // D are missing the following bindings. + version (CRuntime_Musl) + version = IncludeTermiosLinux; + version (CRuntime_Bionic) + version = IncludeTermiosLinux; + version (CRuntime_UClibc) + version = IncludeTermiosLinux; + + version (IncludeTermiosLinux) { + // termios.h, bits/termios.h private alias uint tcflag_t; private alias uint speed_t; private alias char cc_t; @@ -49,7 +61,6 @@ private enum NCCS = 32; private enum ICANON = 2; private enum ECHO = 10; - private enum TIOCGWINSZ = 0x5413; private enum BRKINT = 2; private enum INPCK = 20; private enum ISTRIP = 40; @@ -68,14 +79,16 @@ speed_t __c_ispeed; speed_t __c_ospeed; } + private extern (C) int tcgetattr(int fd, termios *termios_p); + private extern (C) int tcsetattr(int fd, int a, termios *termios_p); + // ioctl.h + private enum TIOCGWINSZ = 0x5413; private struct winsize { ushort ws_row; ushort ws_col; ushort ws_xpixel; ushort ws_ypixel; } - private extern (C) int tcgetattr(int fd, termios *termios_p); - private extern (C) int tcsetattr(int fd, int a, termios *termios_p); private extern (C) int ioctl(int fd, ulong request, ...); } @@ -145,6 +158,7 @@ if (hOut == INVALID_HANDLE_VALUE) throw new WindowsException(GetLastError); + stdout.flush; stdout.windowsHandleOpen(hOut, "wb"); // fixes using write functions SetStdHandle(STD_OUTPUT_HANDLE, hOut); @@ -301,6 +315,28 @@ SetConsoleCursorPosition(hOut, c); } else version (Posix) { // 1-based, so 0,0 needs to be output as 1,1 printf("\033[%d;%dH", ++y, ++x); + stdout.flush; + } +} + +/// Directly write to output. +/// Params: +/// data = Character data. +/// size = Amount in bytes. +/// Returns: Number of bytes written. +size_t terminalOutput(const(void) *data, size_t size) { + version (Windows) { + import core.sys.windows.winbase : STD_OUTPUT_HANDLE, + WriteFile, GetStdHandle; + uint r = void; + assert(WriteFile(hOut, data, cast(uint)size, &r, null)); + return r; + } else version (Posix) { + import core.sys.posix.unistd : write, STDOUT_FILENO; + import core.sys.posix.sys.types : ssize_t; + ssize_t r = write(STDOUT_FILENO, data, size); + assert(r >= 0); + return r; } } diff --git a/src/screen.d b/src/screen.d index 898a1bf..67213a8 100644 --- a/src/screen.d +++ b/src/screen.d @@ -8,9 +8,10 @@ import std.stdio : stdout; // for cwrite family import ddhx; // for setting, NumberType import os.terminal, os.file; +version (Trace) import std.datetime.stopwatch; //TODO: Data grouping (1, 2, 4, 8, 16) -// e.g., cd ab -> abcd +// e.g., cd ab -> abcd, 128 64 -> 192 // cast(uint[]) is probably possible on a ubyte[] range //TODO: Group endianness (when >1) // native (default), little, big @@ -29,8 +30,16 @@ // Rendering engine should be capable to take off whereever it stopped // or be able to specify/toggle seperate regardless of column length. // Probably useful for dump app. +//TODO: Consider buffer strategy full for terminal-altscreen mode +// + manual flushes here +/// Last known terminal size. __gshared TerminalSize termSize; +// Internal buffer filling character. +// For dump, that should be spaces and 0, +// For interactive, that should be spaces and spaces. +//__gshared char binaryFiller; +//__gshared char textFiller; void initiate() { terminalInit(TermFeat.all); @@ -43,23 +52,20 @@ //string screenPrompt(string prompt) /// Update cursor position on the terminal screen -void cursor(int x, int y, int nibble) { +void cursor(uint pos, uint nibble) { //TODO: (x * 3) -> x * datawidth + uint y = pos / setting.width; + uint x = pos % setting.width; terminalPos(13 + (x * 3) + nibble, 1 + y); } -// Called after an editing action (undo/redo/insert/overwrite) -// Not sure... -void screenUpdateDirty() { - //TODO: (w * 3) -> w * datawidth - int x = 11 + (setting.width * 3) + 2; - terminalPos(x, 0); - cwrite(editor.dirty ? '*' : ' '); +/// Clear entire terminal screen +void clear() { + terminalClear; } -/// Clear entire terminal screen -void screenClear() { - terminalClear; +string name() { + return dataFmt.name; } /*void clearStatusBar() { @@ -83,7 +89,7 @@ } private struct NumberFormatter { - immutable(char)[] name; /// Short offset name + string name; /// Short offset name align(2) char fmtchar; /// Format character for printf-like functions uint size; /// Size for formatted byte size_t function(char*,long) offset; /// Function to format offset @@ -315,6 +321,10 @@ import std.typecons : scoped; import std.conv : octal; + version (Trace) { + StopWatch sw = StopWatch(AutoStart.yes); + } + // Setup index formatting //TODO: Consider SingleSpec or "maker" function int dsz = numbers[setting.dataType].size; @@ -338,8 +348,19 @@ outbuf.put(' '); } + version (Trace) { + Duration a = sw.peek; + } + // OutBuffer.toString duplicates it, what a waste! cwriteln(cast(const(char)[])outbuf.toBytes); + + version (Trace) { + Duration b = sw.peek; + trace("gen='%s µs' print='%s µs'", + a.total!"usecs", + (b - a).total!"usecs"); + } } /// @@ -347,29 +368,50 @@ import std.outbuffer : OutBuffer; import std.typecons : scoped; + version (Trace) { + StopWatch sw = StopWatch(AutoStart.yes); + } + + int w = termSize.width; + auto outbuf = scoped!OutBuffer(); - outbuf.reserve(termSize.width); + outbuf.reserve(w); outbuf.put(' '); foreach (item; items) { if (outbuf.offset > 1) outbuf.put(" | "); outbuf.put(item); + if (outbuf.offset >= w) { + + } } // Fill rest by space - outbuf.data[outbuf.offset..termSize.width] = ' '; - outbuf.offset = termSize.width; // used in .toBytes + outbuf.data[outbuf.offset..w] = ' '; + outbuf.offset = w; // used in .toBytes + version (Trace) { + Duration a = sw.peek; + } + +L_WRITE: cwrite(cast(const(char)[])outbuf.toBytes); + + version (Trace) { + sw.stop; + Duration b = sw.peek; + trace("gen='%s µs' print='%s µs'", + a.total!"usecs", + (b - a).total!"usecs"); + } } -//TODO: [0.5] Possibility to only redraw a specific byte. -// renderContentByte(size_t bufpos, ubyte newData) - /// Update display from buffer. /// Returns: Numbers of row written. uint renderContent(long position, ubyte[] data) { - version (Trace) - trace("position=%u data.len=%u cursor=%s", - position, data.length, cursor); + version (Trace) { + trace("position=%u data.len=%u", + position, data.length); + StopWatch swtotal = StopWatch(AutoStart.yes); + } // Setup formatting related stuff prepareView; @@ -382,6 +424,11 @@ ++lines; } + version (Trace) { + swtotal.stop; + trace("totaltime='%s µs'", swtotal.peek.total!"usecs"); + } + return lines; } @@ -400,6 +447,8 @@ private char[] renderRow(ubyte[] chunk, long pos) { import core.stdc.string : memset; + //TODO: Consider realloc on terminal width + // In screen.initiate enum BUFFER_SIZE = 2048; __gshared char[BUFFER_SIZE] buffer; __gshared char *bufferptr = buffer.ptr; @@ -410,6 +459,7 @@ const uint dataLen = (setting.width * (dataFmt.size + 1)); /// data row character count size_t indexChar = indexData + dataLen; // Position for character column + *(cast(ushort*)(bufferptr + indexChar)) = 0x2020; // DATA-CHAR spacer indexChar += 2; // indexChar: indexData + dataLen + spacer @@ -417,8 +467,7 @@ // NOTE: Smaller loops could fit in cache... // And would separate data/text logic foreach (data; chunk) { -// for (size_t i; i < chunk.length; ++i) { -// const ubyte data = chunk[i]; /// byte data + //TODO: Maybe binary data formatter should include space // Data translation bufferptr[indexData++] = ' '; indexData += dataFmt.data(bufferptr + indexData, data); @@ -430,15 +479,22 @@ } else // Invalid character, insert default character bufferptr[indexChar++] = setting.defaultChar; } - // data length < minimum row requirement = pad DATA + + size_t end = indexChar; + + // data length < minimum row requirement = in-fill data and text columns if (chunk.length < setting.width) { - size_t left = - (setting.width - chunk.length) // Bytes left - * (dataFmt.size + 1); // space + 1x data size - memset(bufferptr + indexData, ' ', left); + // In-fill characters: left = Columns - ChunkLength + size_t leftchar = (setting.width - chunk.length); // Bytes left + memset(bufferptr + indexChar, ' ', leftchar); + // In-fill binary data: left = CharactersLeft * (DataSize + 1) + size_t leftdata = leftchar * (dataFmt.size + 1); + memset(bufferptr + indexData, ' ', leftdata); + + end += leftchar; } - return buffer[0..indexChar]; + return buffer[0..end]; } //TODO: More renderRow unittests @@ -447,16 +503,33 @@ // With defaults prepareView; // Offset(hex) 0 1 2 3 4 5 6 7 8 9 a b c d e f - assert(renderRow([ 0 ], 0) == - " 0 00 ."); - assert(renderRow([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf ], 0x10) == - " 10 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................"); + assert(renderRow([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf ], 0) == + " 0 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................"); + assert(renderRow([ 0 ], 0x10) == + " 10 00 . "); } // !SECTION // SECTION Console Write functions +size_t cwrite(char c) { + return terminalOutput(&c, 1); +} +size_t cwrite(const(char)[] _str) { + return terminalOutput(_str.ptr, _str.length); +} +size_t cwriteln(const(char)[] _str) { + return cwrite(_str) + cwrite('\n'); +} +size_t cwritef(A...)(const(char)[] fmt, A args) { + import std.format : sformat; + char[128] buf = void; + return cwrite(sformat(buf, fmt, args)); +} +size_t cwritefln(A...)(const(char)[] fmt, A args) { + return cwritef(fmt, args) + cwrite('\n'); +} size_t cwriteAt(int x, int y, char c) { terminalPos(x, y); return cwrite(c); @@ -477,32 +550,5 @@ terminalPos(x, y); return cwritefln(fmt, args); } -size_t cwrite(char c) { - import std.stdio : stdout; - import core.stdc.stdio : fwrite, FILE; - - return fwrite(&c, 1, 1, stdout.getFP); -} -size_t cwrite(const(char)[] _str) { - import std.stdio : stdout; - import core.stdc.stdio : fwrite, FILE; - - return fwrite(_str.ptr, 1, _str.length, stdout.getFP); -} -size_t cwriteln(const(char)[] _str) { - size_t c = cwrite(_str); - cwrite("\n"); - return ++c; -} -size_t cwritef(A...)(const(char)[] fmt, A args) { - import std.format : sformat; - char[128] buf = void; - return cwrite(sformat(buf, fmt, args)); -} -size_t cwritefln(A...)(const(char)[] fmt, A args) { - size_t c = cwritef(fmt, args); - cwrite("\n"); - return ++c; -} // !SECTION \ No newline at end of file diff --git a/src/utils/memory.d b/src/utils/memory.d index 020b2b6..52b3591 100644 --- a/src/utils/memory.d +++ b/src/utils/memory.d @@ -35,10 +35,10 @@ } - void seek(long pos) { + long seek(long pos) { /*final switch (origin) with (Seek) { case start:*/ - position = pos; + return position = pos; /* return 0; case current: position += pos;