diff --git a/LICENSE b/LICENSE
index af5da61..43b140b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) dd86k
2017-2022
+Copyright (c) dd86k 2017-2024
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/dub.sdl b/dub.sdl
index 3bd6ead..7354ffc 100644
--- a/dub.sdl
+++ b/dub.sdl
@@ -1,14 +1,9 @@
name "ddhx"
description "Hexadecimal file viewer"
authors "dd86k"
-copyright "Copyright © 2017-2022 dd86k"
+copyright "Copyright © 2017-2024 dd86k"
license "MIT"
-# NOTE: Somehow, the dmd package in the Alpine repo does not contain rdmd.
-preBuildCommands "rdmd setup.d version" platform="dmd"
-preBuildCommands "ldmd2 -run setup.d version" platform="ldc"
-preBuildCommands "gdmd -run setup.d version" platform="gdc"
-
configuration "default" {
targetType "executable"
mainSourceFile "src/main.d"
diff --git a/setup.d b/setup.d
deleted file mode 100644
index 1ad9763..0000000
--- a/setup.d
+++ /dev/null
@@ -1,27 +0,0 @@
-#!rdmd
-
-import std.process;
-import std.string : stripRight;
-import std.file : write;
-import std.path : dirSeparator;
-
-alias SEP = dirSeparator;
-
-enum GITINFO_PATH = "src" ~ SEP ~ "gitinfo.d";
-
-int main(string[] args)
-{
- final switch (args[1]) {
- case "version":
- auto describe = executeShell("git describe");
- if (describe.status)
- return describe.status;
-
- string ver = stripRight(describe.output);
- write(GITINFO_PATH,
- "// NOTE: This file was automatically generated.\n"~
- "module gitinfo;\n"~
- "enum GIT_DESCRIPTION = \""~ver~"\";");
- return 0;
- }
-}
\ No newline at end of file
diff --git a/src/README.md b/src/README.md
deleted file mode 100644
index 5698eb4..0000000
--- a/src/README.md
+++ /dev/null
@@ -1,21 +0,0 @@
-# Introduction
-
-This document explains the source file structure.
-
-# Structure
-
-| Entry | Description |
-|--------------|-------------|
-| os/ | OS utilities |
-| utils/ | Generic utils |
-| converter.d | Dynamic type converter and conversion routines |
-| ddhx.d | Main interactive application |
-| dump.d | Dump application mode |
-| editor.d | Used by ddhx, represents an editor instance |
-| error.d | Error facility |
-| gitinfo.d | This file is automatically generated. |
-| main.d | Program entry point |
-| screen.d | Screen handling |
-| searcher.d | Search engine |
-| settings.d | User settings (to be removed) |
-| transcoder.d | Character encoding transcoder |
\ No newline at end of file
diff --git a/src/converter.d b/src/converter.d
deleted file mode 100644
index 8dc453c..0000000
--- a/src/converter.d
+++ /dev/null
@@ -1,177 +0,0 @@
-/// Type conversion routines.
-/// Copyright: dd86k
-/// License: MIT
-/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
-module converter;
-
-import std.format : FormatSpec, singleSpec, unformatValue;
-import std.encoding : transcode;
-import error;
-
-// NOTE: template to(T) can turn string values into anything.
-
-//TODO: More types
-// - FILETIME
-// - GUID (little-endian)/UUID (big-endian)
-
-/// Convert data to a raw pointer dynamically.
-/// Params:
-/// data = Data pointer receiver.
-/// len = Data length receiver.
-/// val = Value to parse.
-/// type = Name of expected type.
-/// Returns: Error code.
-int convertToRaw(ref void *data, ref size_t len, string val, string type)
-{
- union TypeData {
- ulong u64;
- long i64;
- uint u32;
- int i32;
- ushort u16;
- short i16;
- ubyte u8;
- byte i8;
- dstring s32;
- wstring s16;
- string s8;
- }
- __gshared TypeData types;
-
- version (Trace) trace("data=%s val=%s type=%s", data, val, type);
-
- //TODO: utf16le and all.
- // bswap all wchars?
-
- int e = void;
- with (types) switch (type) {
- case "s32", "dstring":
- e = convertToVal(s32, val);
- if (e) return e;
- data = cast(void*)s32.ptr;
- len = s32.length * dchar.sizeof;
- break;
- case "s16", "wstring":
- e = convertToVal(s16, val);
- if (e) return e;
- data = cast(void*)s16.ptr;
- len = s16.length * wchar.sizeof;
- break;
- case "s8", "string":
- data = cast(void*)val.ptr;
- len = val.length;
- break;
- case "u64", "ulong":
- e = convertToVal(u64, val);
- if (e) return e;
- data = &u64;
- len = u64.sizeof;
- break;
- case "i64", "long":
- e = convertToVal(i64, val);
- if (e) return e;
- data = &i64;
- len = i64.sizeof;
- break;
- case "u32", "uint":
- e = convertToVal(u32, val);
- if (e) return e;
- data = &u32;
- len = u32.sizeof;
- break;
- case "i32", "int":
- e = convertToVal(i32, val);
- if (e) return e;
- data = &i32;
- len = i32.sizeof;
- break;
- case "u16", "ushort":
- e = convertToVal(u16, val);
- if (e) return e;
- data = &u16;
- len = u16.sizeof;
- break;
- case "i16", "short":
- e = convertToVal(i16, val);
- if (e) return e;
- data = &i16;
- len = i16.sizeof;
- break;
- case "u8", "ubyte":
- e = convertToVal(u8, val);
- if (e) return e;
- data = &u8;
- len = u8.sizeof;
- break;
- case "i8", "byte":
- e = convertToVal(i8, val);
- if (e) return e;
- data = &i8;
- len = i8.sizeof;
- break;
- default:
- return errorSet(ErrorCode.invalidType);
- }
-
- return ErrorCode.success;
-}
-
-/// Convert data depending on the type supplied at compile-time.
-/// Params:
-/// v = Data reference.
-/// val = String value.
-/// Returns: Error code.
-int convertToVal(T)(ref T v, string val)
-{
- import std.conv : ConvException;
- try {
- //TODO: ubyte[] input
- static if (is(T == wstring) || is(T == dstring))
- {
- transcode(val, v);
- } else { // Scalar
- const size_t len = val.length;
- FormatSpec!char fmt = void;
- if (len >= 3 && val[0..2] == "0x")
- {
- fmt = singleSpec("%x");
- val = val[2..$];
- }
- else if (len >= 2 && val[0] == '0')
- {
- fmt = singleSpec("%o");
- val = val[1..$];
- } else {
- fmt = singleSpec("%d");
- }
- v = unformatValue!T(val, fmt);
- }
- }
- catch (Exception ex)
- {
- return errorSet(ex);
- }
-
- return 0;
-}
-
-///
-@system unittest {
- int i;
- assert(convertToVal(i, "256") == ErrorCode.success);
- assert(i == 256);
- assert(convertToVal(i, "0100") == ErrorCode.success);
- assert(i == 64);
- assert(convertToVal(i, "0x100") == ErrorCode.success);
- assert(i == 0x100);
- ulong l;
- assert(convertToVal(l, "1000000000000000") == ErrorCode.success);
- assert(l == 1000000000000000);
- assert(convertToVal(l, "01000000000000000") == ErrorCode.success);
- assert(l == 35184372088832);
- assert(convertToVal(l, "0x1000000000000000") == ErrorCode.success);
- assert(l == 0x1000000000000000);
- wstring w;
- assert(convertToVal(w, "hello") == ErrorCode.success);
- assert(w == "hello"w);
-}
diff --git a/src/ddhx.d b/src/ddhx.d
index 7eb08e5..7af3bd6 100644
--- a/src/ddhx.d
+++ b/src/ddhx.d
@@ -1,696 +1,429 @@
-/// Main application.
+/// Main module, handling core TUI operations.
+///
/// Copyright: dd86k
/// License: MIT
/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
module ddhx;
-//TODO: When searching strings, maybe convert it to current charset?
+import std.stdio;
+import core.stdc.stdlib : exit;
+import os.terminal : Key, Mod;
+import editor;
+import display;
+import transcoder : CharacterSet;
+import logger;
-import gitinfo;
-import os.terminal;
-public import
- editor,
- searcher,
- encoding,
- error,
- converter,
- settings,
- screen,
- utils.args,
- utils.format,
- utils.memory;
+//TODO: On terminal resize, set UHEADER
-/// Copyright string
-enum DDHX_COPYRIGHT = "Copyright (c) 2017-2022 dd86k ";
-private enum DESCRIPTION = GIT_DESCRIPTION[1..$];
-/// App version
-debug enum DDHX_VERSION = DESCRIPTION~"+debug";
-else enum DDHX_VERSION = DESCRIPTION; /// Ditto
-/// Version line
-enum DDHX_ABOUT = "ddhx "~DDHX_VERSION~" (built: "~__TIMESTAMP__~")";
-
-/// Number type to render either for offset or data
-//TODO: NumberType -> OffsetType
-enum NumberType : ubyte {
- hexadecimal,
- decimal,
- octal
-}
-//TODO: enum DataType { hex8, ... } etc.
-
-//TODO: Seprate start functions into their own modules
-
-/// Interactive application.
-/// Params: skip = Seek to file/data position.
-/// Returns: Error code.
-int start(long skip = 0)
+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)
{
- //TODO: negative should be starting from end of file (if not stdin)
- // stdin: use seek
- if (skip < 0)
- return errorPrint(1, "Skip value must be positive");
-
- if (editor.fileMode == FileMode.stream)
- {
- version (Trace) trace("slurp skip=%u", skip);
- if (editor.slurp(skip, 0))
- return errorPrint;
- }
- else if (skip)
- {
- version (Trace) trace("seek skip=%u", skip);
- editor.seek(skip);
- if (editor.err)
- return errorPrint;
- }
+ viewpos = curpos = skip;
}
- if (editor.fileMode == FileMode.stream)
+ 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)
{
- editor.slurp(skip);
- }
+ // 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;
- if (screen.initiate)
- panic;
-
- screen.onResize(&eventResize);
-
- refresh;
-
- inputLoop;
- return 0;
-}
-
-private:
-
-//TODO: rename to "readbuffer"? or "screenData"?
-// screen.setData(...)?
-__gshared ubyte[] readdata;
-
-void inputLoop()
-{
- version (Trace) trace("loop");
- TerminalInput event;
-
-L_INPUT:
- terminalInput(event);
- version (Trace) trace("type=%d", event.type);
-
- switch (event.type) with (InputType) {
- case keyDown: goto L_KEYDOWN;
- default: goto L_INPUT; // unknown
- }
-
- //
- // Keyboard
- //
-
-L_KEYDOWN:
- version (Trace) trace("key=%d", event.key);
-
- switch (event.key) with (Key) with (Mod) {
-
- // Navigation
-
- //TODO: ctrl+(up|down) = move view only
- //TODO: ctrl+(left|right) = move to next word
-
- case UpArrow, K: moveRowUp; break;
- case DownArrow, J: moveRowDown; break;
- case LeftArrow, H: moveLeft; break;
- case RightArrow, L: moveRight; break;
- case PageUp: movePageUp; break;
- case PageDown: movePageDown; break;
- case Home: moveAlignStart; break;
- case Home | ctrl: moveAbsStart; break;
- case End: moveAlignEnd; break;
- case End | ctrl: moveAbsEnd; break;
-
- // Actions/Shortcuts
-
- case '/':
- menu(null, "/");
+ // Search
+ case Key.W | Mod.ctrl:
break;
- case '?':
- menu(null, "?");
+
+ // Reset screen
+ case Key.R | Mod.ctrl:
+ columns = disp_hint_cols();
+ viewsize = disp_hint_view(columns);
+ file.setbuffer( viewsize );
+ status = UINIT;
break;
- case N:
- next;
+
+ //
+ case Key.Q | Mod.ctrl:
+ quit();
break;
- case Escape, Enter, ':':
- menu;
- break;
- case G:
- menu("g ");
- break;
- case I:
- printFileInfo;
- break;
- case R, F5:
- refresh;
- break;
- case A:
- settingsColumns("a");
- refresh;
- break;
- case Q: exit; 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 L_INPUT;
+ goto Lread;
}
-void eventResize()
+string prompt(string text)
{
- screen.updateTermSize();
- refresh; // temp
+ throw new Exception("Not implemented");
}
-//TODO: revamp menu system
-// char mode: character mode (':', '/', '?')
-// string command: command shortcut (e.g., 'g' + ' ' default)
-void menu(string prefix = ":", string prepend = null)
+private
+int _editkey(int type, int key)
{
- import std.stdio : readln;
- import std.string : chomp, strip;
-
- // clear bar and command prepend
- screen.clearOffsetBar;
-
- string line = screen.prompt(prefix, prepend);
-
- scope (exit)
+ switch (type) with (Format)
{
- editor.cursorBound;
- updateCursor;
- }
-
- if (line.length == 0)
- return;
-
- if (command(line))
- screen.message(errorMessage());
-}
-
-int command(string line)
-{
- return command(arguments(line));
-}
-
-int command(string[] argv)
-{
- const size_t argc = argv.length;
- if (argc == 0) return 0;
-
- version (Trace) trace("argv=%(%s %)", argv);
-
- string command = argv[0];
-
- switch (command[0]) { // shortcuts
- case '/': // Search
- if (command.length < 2)
- return errorSet(ErrorCode.missingType);
- if (argc < 2)
- return errorSet(ErrorCode.missingNeedle);
-
- return ddhx.lookup(command[1..$], argv[1], true, true);
- case '?': // Search backwards
- if (command.length < 2)
- return errorSet(ErrorCode.missingType);
- if (argc < 2)
- return errorSet(ErrorCode.missingNeedle);
-
- return ddhx.lookup(command[1..$], argv[1], false, true);
+ 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__);
}
-
- switch (argv[0]) { // regular commands
- case "g", "goto":
- if (argc < 2)
- return errorSet(ErrorCode.missingValue);
-
- switch (argv[1]) {
- case "e", "end":
- moveAbsEnd;
- break;
- case "h", "home":
- moveAbsStart;
- break;
- default:
- 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.errorcode;
- }
- 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;
- screen.message(C);
- return 0;
- case "version":
- screen.message(DDHX_ABOUT);
- return 0;
- case "reset":
- resetSettings();
- render;
- return 0;
- case "set":
- if (argc < 2)
- return errorSet(ErrorCode.missingOption);
-
- int e = settings.set(argv[1..$]);
- if (e == 0) refresh;
- return e;
+}
+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:
- return errorSet(ErrorCode.invalidCommand);
+ throw new Exception(__FUNCTION__);
}
}
-// for more elaborate user inputs (commands invoke this)
-// prompt="save as"
-// "save as: " + user input
-/*private
-string input(string prompt, string include)
+// Move the cursor relative to its position within the file
+private
+void _move_rel(long pos)
{
- terminalPos(0, 0);
- screen.cwriteAt(0, 0, prompt, ": ", include);
- return readln;
-}*/
-
-//TODO: When screen doesn't move: Only render lines affected
-
-/// Move the cursor to the start of the data
-void moveAbsStart()
-{
- if (editor.cursorAbsStart)
- readRender;
- else
- render;
- updateCursor;
-}
-/// Move the cursor to the end of the data
-void moveAbsEnd()
-{
- if (editor.cursorAbsEnd)
- readRender;
- else
- render;
- updateCursor;
-}
-/// Align cursor to start of row
-void moveAlignStart()
-{
- editor.cursorHome;
- render;
- updateCursor;
-}
-/// Align cursor to end of row
-void moveAlignEnd()
-{
- editor.cursorEnd;
- render;
- updateCursor;
-}
-/// Move cursor to one data group to the left (backwards)
-void moveLeft()
-{
- if (editor.cursorLeft)
- readRender;
- else
- render;
- updateCursor;
-}
-/// Move cursor to one data group to the right (forwards)
-void moveRight()
-{
- if (editor.cursorRight)
- readRender;
- else
- render;
- updateCursor;
-}
-/// Move cursor to one row size up (backwards)
-void moveRowUp()
-{
- if (editor.cursorUp)
- readRender;
- else
- render;
- updateCursor;
-}
-/// Move cursor to one row size down (forwards)
-void moveRowDown()
-{
- if (editor.cursorDown)
- readRender;
- else
- render;
- updateCursor;
-}
-/// Move cursor to one page size up (backwards)
-void movePageUp()
-{
- if (editor.cursorPageUp)
- readRender;
- else
- render;
- updateCursor;
-}
-/// Move view to one page size down (forwards)
-void movePageDown()
-{
- if (editor.cursorPageDown)
- readRender;
- updateStatus;
- updateCursor;
-}
-
-/// Initiate screen buffer
-void initiate()
-{
- screen.updateTermSize;
-
- long fsz = editor.fileSize; /// data size
- int ssize = (screen.termSize.height - 2) * setting.columns; /// screen size
- uint nsize = ssize >= fsz ? cast(uint)fsz : ssize;
-
- version (Trace) trace("fsz=%u ssz=%u nsize=%u", fsz, ssize, nsize);
-
- editor.setBuffer(nsize);
-}
-
-// read at current position
-void read()
-{
- editor.seek(editor.position);
- if (editor.err) panic;
-
- version (Trace) trace("editor.position=%u", editor.position);
-
- readdata = editor.read();
- if (editor.err) panic;
-
- version (Trace) trace("readdata.length=%u", readdata.length);
-}
-
-//TODO: Consider render with multiple parameters to select what to render
-
-/// Render screen (all elements)
-void renderAll()
-{
- version (Trace) trace;
-
- updateOffset;
- updateContent;
- updateStatus;
-}
-void render()
-{
- version (Trace) trace;
-
- updateContent;
- updateStatus;
-}
-void readRender()
-{
- read;
- render;
-}
-
-void updateOffset()
-{
- screen.cursorOffset;
- screen.renderOffset;
-}
-
-void updateContent()
-{
- screen.cursorContent; // Set pos for rendering
- const int rows = screen.output(
- editor.position, readdata,
- editor.cursor.position);
-
- if (rows < 0)
- {
- message("Something bad happened");
+ if (pos == 0)
return;
- }
- screen.renderEmpty(rows);
-}
-
-void updateStatus()
-{
- import std.format : format;
+ long old = curpos;
+ curpos += pos;
- long pos = editor.cursorTell;
- char[12] offset = void;
- size_t l = screen.offsetFormatter.offset(offset.ptr, pos);
+ if (pos > 0 && curpos >= filesize)
+ curpos = filesize;
+ else if (pos < 0 && curpos < 0)
+ curpos = 0;
- screen.cursorStatusbar;
- screen.renderStatusBar(
- editor.editModeString,
- screen.binaryFormatter.name,
- transcoder.name,
- formatBin(editor.readSize, setting.si),
- offset[0..l],
- format("%f%%",
- ((cast(double)pos) / editor.fileSize) * 100));
-}
-
-void updateCursor()
-{
- version (Trace)
- with (editor.cursor)
- trace("pos=%u n=%u", position, nibble);
-
- //with (editor.cursor)
- // screen.cursor(position, nibble);
-}
-
-/// Refresh the entire screen by:
-/// 1. Clearing the terminal
-/// 2. Automatically resizing the view buffer
-/// 3. Re-seeking to the current position (failsafe)
-/// 4. Read buffer
-/// 5. Render
-void refresh()
-{
- version (Trace) trace;
-
- screen.clear;
- initiate;
- read;
- updateOffset;
- render;
- updateCursor;
-}
-
-/// Seek to position in data, reads view's worth, and display that.
-/// Ignores bounds checking for performance reasons.
-/// Sets CurrentPosition.
-/// Params: pos = New position
-void seek(long pos)
-{
- version (Trace) trace("pos=%d", pos);
-
- if (editor.readSize >= editor.fileSize)
- {
- screen.message("Navigation disabled, file too small");
+ if (old == curpos)
return;
- }
- //TODO: cursorTo
- editor.seek(pos);
- readRender;
+ status |= UCURSOR;
+ _adjust_viewpos();
}
-
-/// Parses the string as a long and navigates to the file location.
-/// Includes offset checking (+/- notation).
-/// Params: str = String as a number
-void seek(string str)
+// Move the cursor to an absolute file position
+private
+void _move_abs(long pos)
{
- version (Trace) trace("str=%s", str);
-
- const char seekmode = str[0];
- if (seekmode == '+' || seekmode == '-') // relative input.position
- {
- str = str[1..$];
- }
- long newPos = void;
- if (convertToVal(newPos, str))
- {
- screen.message("Could not parse number");
+ long old = curpos;
+ curpos = pos;
+
+ if (curpos >= filesize)
+ curpos = filesize;
+ else if (curpos < 0)
+ curpos = 0;
+
+ if (old == curpos)
return;
- }
- switch (seekmode) {
- case '+': // Relative, add
- safeSeek(editor.position + newPos);
- break;
- case '-': // Relative, substract
- safeSeek(editor.position - newPos);
- break;
- default: // Absolute
- if (newPos < 0)
+
+ 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)
{
- screen.message("Range underflow: %d (0x%x)", newPos, newPos);
+ viewpos += columns;
+ if (viewpos >= filesize - viewsize)
+ break;
}
- else if (newPos >= editor.fileSize - editor.readSize)
+ status |= UVIEW;
+ }
+ // Cursor is behind the view
+ else if (curpos < viewpos)
+ {
+ while (curpos < viewpos)
{
- screen.message("Range overflow: %d (0x%x)", newPos, newPos);
+ viewpos -= columns;
+ if (viewpos <= 0)
+ break;
}
- else
- {
- seek(newPos);
- }
+ status |= UVIEW;
}
}
-/// Goes to the specified position in the file.
-/// Checks bounds and calls Goto.
-/// Params: pos = New position
-void safeSeek(long pos)
+void move_left()
{
- version (Trace) trace("pos=%s", pos);
+ if (curpos == 0)
+ return;
- long fsize = editor.fileSize;
+ _move_rel(-1);
+}
+void move_right()
+{
+ if (curpos == filesize)
+ return;
- if (pos + editor.readSize >= fsize)
- pos = fsize - editor.readSize;
- else if (pos < 0)
- pos = 0;
+ _move_rel(1);
+}
+void move_up()
+{
+ if (curpos == 0)
+ return;
- editor.cursorGoto(pos);
+ _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);
}
-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.
-//TODO: I don't think the return code is ever used...
-int next()
+// Update all elements on screen depending on status
+// status global indicates what needs to be updated
+void update()
{
- if (lastAvailable == false)
+ // 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)
{
- return errorSet(ErrorCode.noLastItem);
- }
-
- long pos = void;
- int e = searchData(pos, lastItem.ptr, lastSize, lastForward);
-
- if (e)
- {
- screen.message("Not found");
- return e;
- }
-
- safeSeek(pos);
- //TODO: Format position found with current offset type
- screen.message("Found at 0x%x", pos);
- return 0;
+ disp_write(editbuf, editsz);
+ }*/
}
-// Search data
-int lookup(string type, string data, bool forward, bool save)
+void update_status()
{
- void *p = void;
- size_t len = void;
- if (convertToRaw(p, len, data, type))
- return error.errorcode;
-
- if (save)
- {
- import core.stdc.string : memcpy;
- lastType = type;
- lastSize = len;
- lastForward = forward;
- //TODO: Check length against LAST_BUFFER_SIZE
- memcpy(lastItem.ptr, p, len);
- lastAvailable = true;
- }
-
- screen.message("Searching for %s...", type);
-
- long pos = void;
- int e = searchData(pos, p, len, forward);
-
- if (e)
- {
- screen.message("Not found");
- return e;
- }
-
- safeSeek(pos);
- //TODO: Format position found with current offset type
- screen.message("Found at 0x%x", pos);
- return 0;
+ //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);
}
-int skip(ubyte data)
+void message(const(char)[] msg)
{
- screen.message("Skipping all 0x%x...", data);
-
- long pos = void;
- const int e = searchSkip(data, pos);
-
- if (e)
- {
- screen.message("End of file reached");
- return e;
- }
-
- safeSeek(pos);
- //TODO: Format position found with current offset type
- screen.message("Found at 0x%x", pos);
- return 0;
+ disp_message(msg.ptr, msg.length);
+ status |= UMESSAGE;
}
-/// Print file information
-void printFileInfo()
+void quit()
{
- screen.message("%11s %s",
- formatBin(editor.fileSize, setting.si),
- editor.fileName);
-}
-
-void exit()
-{
- import core.stdc.stdlib : exit;
- version (Trace) trace();
+ trace("quit");
exit(0);
-}
-
-void panic()
-{
- import std.stdio : stderr;
- import core.stdc.stdlib : exit;
- version (Trace) trace("code=%u", errorcode);
-
- stderr.writeln("error: (", errorcode, ") ", errorMessage(errorcode));
-
- exit(errorcode);
}
\ No newline at end of file
diff --git a/src/display.d b/src/display.d
new file mode 100644
index 0000000..886d645
--- /dev/null
+++ b/src/display.d
@@ -0,0 +1,301 @@
+/// Handles complex terminal operations.
+/// Copyright: dd86k
+/// License: MIT
+/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
+module display;
+
+import std.range : chunks;
+import std.conv : text;
+import core.stdc.string : memset, memcpy;
+import os.terminal;
+import formatter;
+import transcoder;
+import utils.math;
+import logger;
+
+enum Format
+{
+ hex,
+ dec,
+ oct,
+}
+
+int selectFormat(string fmt)
+{
+ switch (fmt) with (Format)
+ {
+ case "hex": return hex;
+ case "dec": return dec;
+ case "oct": return oct;
+ default:
+ throw new Exception(text("Invalid format: ", fmt));
+ }
+}
+
+//TODO: cache rendered (data) lines
+// dedicated char buffers
+
+private enum
+{
+ TUIMODE = 0x1,
+}
+
+private __gshared
+{
+ /// Capabilities
+ int caps;
+}
+
+void disp_init(bool tui)
+{
+ caps |= tui;
+ terminalInit(tui ? TermFeat.altScreen | TermFeat.inputSys : 0);
+}
+
+int disp_readkey()
+{
+Lread:
+ TermInput i = terminalRead();
+ if (i.type != InputType.keyDown) goto Lread;
+ return i.key;
+}
+
+// Given n columns, hint the optimal buffer size for the "view".
+// If 0, calculated automatically
+int disp_hint_cols()
+{
+ enum hexsize = 2;
+ enum decsize = 3;
+ enum octsize = 3;
+ TerminalSize w = terminalSize();
+ return (w.columns - /*gofflen*/ 16) / (hexsize + 2);
+}
+int disp_hint_view(int cols)
+{
+ TerminalSize w = terminalSize();
+
+ enum hexsize = 2;
+ enum decsize = 3;
+ enum octsize = 3;
+
+ // 16 - for text section?
+ return ((w.columns - cols /* 16 */) / (hexsize + 2)) * (w.rows - 2);
+}
+
+void disp_enable_cursor()
+{
+
+}
+
+void disp_disable_cursor()
+{
+
+}
+
+void disp_cursor(int row, int col)
+{
+ terminalPos(col, row);
+}
+void disp_write(char* stuff, size_t sz)
+{
+ terminalWrite(stuff, sz);
+}
+
+///
+void disp_header(int columns,
+ int addrfmt = Format.hex)
+{
+ //int max = int.max;
+
+ if (caps & TUIMODE)
+ {
+ //max = terminalSize().columns;
+ disp_cursor(0, 0);
+ }
+
+ enum BUFSZ = 2048;
+ __gshared char[BUFSZ] buffer;
+
+ static immutable string prefix = "Offset(";
+
+ string soff = void;
+ size_t function(char*, ubyte) format;
+ switch (addrfmt) with (Format)
+ {
+ case hex:
+ soff = "hex";
+ format = &format8hex;
+ break;
+ case dec:
+ soff = "dec";
+ //format = &;
+ break;
+ case oct:
+ soff = "oct";
+ //format = &;
+ break;
+ default:
+ assert(false);
+ }
+
+ memcpy(buffer.ptr, prefix.ptr, prefix.length);
+ memcpy(buffer.ptr + prefix.length, soff.ptr, soff.length);
+
+ size_t i = prefix.length + 3;
+ buffer[i++] = ')';
+ buffer[i++] = ' ';
+
+ for (int col; col < columns; ++col)
+ {
+ buffer[i++] = ' ';
+ i += format(&buffer.ptr[i], cast(ubyte)col);
+ }
+
+ buffer[i++] = '\n';
+
+ terminalWrite(buffer.ptr, i);
+}
+
+///
+void disp_message(const(char)* msg, size_t len)
+{
+ TerminalSize w = terminalSize();
+ disp_cursor(w.rows - 1, 0);
+
+ //TODO: Invert colors for rest of space
+ terminalWrite(msg, min(len, w.columns));
+}
+
+// NOTE: Settings could be passed through a structure
+///
+void disp_update(ulong base, ubyte[] data,
+ int columns,
+ int datafmt = Format.hex, int addrfmt = Format.hex,
+ char defaultchar = '.', int textfmt = CharacterSet.ascii,
+ int addrpadd = 11, int groupsize = 1)
+{
+ assert(columns > 0);
+
+ enum BUFFERSZ = 1024 * 1024;
+ __gshared char[BUFFERSZ] buffer;
+ // Buffer size
+ size_t bufsz = BUFFERSZ;
+ // Buffer index
+ size_t bi = void;
+
+ // Prepare data formatting functions
+ int elemsz = void; // Size of one data element, in characters, plus space
+ size_t function(char*, ubyte) formatdata; // Byte formatter
+ switch (datafmt) with (Format)
+ {
+ case hex:
+ formatdata = &format8hex;
+ elemsz = 3;
+ break;
+ case dec:
+ elemsz = 4;
+ break;
+ case oct:
+ elemsz = 4;
+ break;
+ default:
+ assert(false, "Invalid data format");
+ }
+
+ // Prepare address formatting functions
+ size_t function(char*, ulong) formataddr;
+ switch (addrfmt) with (Format)
+ {
+ case hex:
+ formataddr = &format64hex;
+ break;
+ case dec:
+ break;
+ case oct:
+ break;
+ default:
+ assert(false, "Invalid address format");
+ }
+
+ // Prepare transcoder
+ string function(ubyte) transcode = void;
+ switch (textfmt) with (CharacterSet)
+ {
+ case ascii:
+ transcode = &transcodeASCII;
+ break;
+ case cp437:
+ transcode = &transcodeCP437;
+ break;
+ case ebcdic:
+ transcode = &transcodeEBCDIC;
+ break;
+ case mac:
+ transcode = &transcodeMac;
+ break;
+ default:
+ assert(false, "Invalid character set");
+ }
+
+ // Starting position of text column
+ size_t cstart = addrpadd + 1 + (elemsz * columns) + 2;
+
+ if (caps & TUIMODE) disp_cursor(1, 0);
+
+ foreach (chunk; chunks(data, columns))
+ {
+ // Format address, update address, add space
+ bi = formataddr(buffer.ptr, base);
+ base += columns;
+ buffer[bi++] = ' ';
+
+ // Insert data and text bytes
+ size_t ci = cstart;
+ foreach (b, u8; chunk)
+ {
+ buffer[bi++] = ' ';
+ bi += formatdata(&buffer.ptr[bi], u8);
+
+ // Transcode and insert it into buffer
+ immutable(char)[] units = transcode(u8);
+ if (units.length == 0) // No utf-8 codepoints, insert default char
+ {
+ buffer[ci++] = defaultchar;
+ continue;
+ }
+ foreach (codeunit; units)
+ buffer[ci++] = codeunit;
+ }
+
+ // If row isn't entirely filled by column requirement
+ // NOTE: Text is filled as well to damage the display in TUI mode
+ if (chunk.length < columns)
+ {
+ size_t rem = columns - chunk.length; // remaining bytes
+
+ // Fill empty data space
+ size_t sz = rem * elemsz;
+ memset(&buffer.ptr[bi], ' ', sz); bi += sz;
+
+ // Fill empty text space
+ memset(&buffer.ptr[cstart + chunk.length], ' ', rem);
+ }
+
+ // Add spaces between data and text columns
+ buffer[bi++] = ' ';
+ buffer[bi++] = ' ';
+
+ // Add length of text column and terminate with newline
+ bi += ci - cstart; // text index - text start = text size in bytes
+ buffer[bi++] = '\n';
+
+ trace("out=%d", bi);
+ if (chunk.length < columns)
+ trace("buffer=%(-%02x%)", cast(ubyte[])buffer[0..bi]);
+
+ terminalWrite(buffer.ptr, bi);
+ }
+}
+
+private:
+
diff --git a/src/dump.d b/src/dump.d
deleted file mode 100644
index d78d0f5..0000000
--- a/src/dump.d
+++ /dev/null
@@ -1,89 +0,0 @@
-/// Dumps binary data to stdout.
-/// Copyright: dd86k
-/// License: MIT
-/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
-module dump;
-
-import os.terminal;
-import error, editor, screen, settings;
-
-/*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.
-/// Returns: Error code.
-int start(long skip, long length)
-{
- if (length < 0)
- return errorPrint(1, "Length must be a positive value");
-
- terminalInit(TermFeat.none);
-
- version (Trace) trace("skip=%d length=%d", skip, length);
-
- switch (editor.fileMode) with (FileMode)
-{
- case file, mmfile: // Seekable
- long fsize = editor.fileSize;
-
- // negative skip value: from end of file
- if (skip < 0)
- skip = fsize + skip;
-
- // adjust length if unset
- if (length == 0)
- length = fsize - skip;
- else if (length < 0)
- return errorPrint(1, "Length value must be positive");
-
- // overflow check
- //TODO: This shouldn't error and should take the size we get from file.
- if (skip + length > fsize)
- return errorPrint(1, "Specified length overflows file size");
-
- break;
- case stream: // Unseekable
- if (skip < 0)
- return errorPrint(1, "Skip value must be positive with stream");
- if (length == 0)
- length = long.max;
- break;
- default: // Memory mode is never initiated from CLI
- }
-
- // start skipping
- if (skip)
- editor.seek(skip);
-
- // top bar to stdout
- screen.renderOffset;
-
- // mitigate unaligned reads/renders
- size_t a = setting.columns * 16;
- if (a > length)
- a = cast(size_t)length;
- editor.setBuffer(a);
-
- // read until EOF or length spec
- long r;
- do {
- ubyte[] data = editor.read;
- screen.output(r, data);
- r += data.length;
- //io.position = r;
- } while (editor.eof == false && r < length);
-
- return 0;
-}
\ No newline at end of file
diff --git a/src/dumper.d b/src/dumper.d
new file mode 100644
index 0000000..4ac70cc
--- /dev/null
+++ b/src/dumper.d
@@ -0,0 +1,61 @@
+/// Simple dumper UI, no interactive input.
+/// Copyright: dd86k
+/// License: MIT
+/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
+module dumper;
+
+import std.stdio;
+import std.file;
+import core.stdc.stdlib : malloc;
+import display;
+import transcoder;
+import utils.math;
+
+private enum CHUNKSIZE = 1024 * 1024;
+
+// NOTE: if path is null, then stdin is used
+int dump(string path, int columns,
+ long skip, long length,
+ int charset)
+{
+ scope buffer = new ubyte[CHUNKSIZE];
+
+ // opAssign is bugged on ldc with optimizations
+ File file;
+ if (path)
+ {
+ file = File(path, "rb");
+
+ if (skip) file.seek(skip);
+ }
+ else
+ {
+ file = stdin;
+
+ // Read blocks until length
+ if (skip)
+ {
+ Lskip:
+ size_t rdsz = min(skip, CHUNKSIZE);
+ if (file.rawRead(buffer[0..rdsz]).length == CHUNKSIZE)
+ goto Lskip;
+ }
+ }
+
+ if (columns == 0)
+ columns = 16;
+
+ disp_init(false);
+
+ disp_header(columns);
+
+ ulong address;
+ foreach (chunk; file.byChunk(CHUNKSIZE))
+ {
+ disp_update(address, chunk, columns);
+
+ address += chunk.length;
+ }
+
+ return 0;
+}
diff --git a/src/editor.d b/src/editor.d
index d9937b3..7443f44 100644
--- a/src/editor.d
+++ b/src/editor.d
@@ -1,664 +1,330 @@
+/// Implements a file buffer and file I/O.
+/// Copyright: dd86k
+/// License: MIT
+/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
module editor;
-import std.container.slist;
-import std.stdio : File;
-import std.path : baseName;
-import std.file : getSize;
-import core.stdc.stdio : FILE;
-import settings, error;
-import os.file, os.mmfile, os.terminal;
-import utils.memory;
+private import std.stdio : File;
+private import os.file : OSFile, OFlags, Seek;
+private import std.container.slist;
+private import std.stdio : File;
+private import std.path : baseName;
+private import core.stdc.stdio : FILE;
+private import utils.memory;
+private import std.array : uninitializedArray;
+private import core.stdc.stdlib : malloc, calloc, free;
+private import core.stdc.string : memcmp;
-// NOTE: Cursor management.
-//
-// +-> File position is at 0x10, at the start of the read buffer
-// |
-// | hex 01 02 03 04
-// 00000010 ab cd 11 22 -+
-// 00000014 33 44<55>66 +- Read buffer
-// 00000018 77 88 99 ff -+
-// ^^
-// ++------ Cursor is at position 6 of read buffer
-
-// NOTE: Dependencies
-//
-// If the editor doesn't directly invoke screen handlers, the editor
-// code could potentially be re-used in other projects.
-
-//TODO: Error mechanism
-//TODO: Consider function hooks for basic events
-// Like when the cursor has changed position, camera moved, etc.
-// Trying to find a purpose for this...
-//TODO: [0.5] Virtual change system.
-// For editing/rendering/saving.
-// Array!(Edit) or sorted dictionary?
-// Obviously CTRL+Z for undo, CTRL+Y for redo.
-//TODO: [0.5] ubyte[] view
-// Copy screen buffer, modify depending on changes, send
-// foreach edit
-// if edit.position within current position + screen length
-// modify screen result buffer
-//TODO: File watcher
-//TODO: File lock mechanic
-
-//TODO: Make editor hold more states and settings
-// Lower module has internal function pointers set from received option
-
-/*private struct settings_t {
- /// Bytes per row
- //TODO: Rename to columns
- ushort width = 16;
- /// Current offset view type
- NumberType offsetType;
- /// Current data view type
- NumberType dataType;
- /// Default character to use for non-ascii characters
- char defaultChar = '.';
- /// Use ISO base-10 prefixes over IEC base-2
- bool si;
+/// Editor I/O mode.
+enum IoMode : ushort
+{
+ file, /// Normal file.
+ mmfile, /// Memory-mapped file.
+ stream, /// Standard streaming I/O, often pipes.
+ memory, /// Typically from a stream buffered into memory.
}
-/// Current settings.
-public __gshared settings_t settings2;*/
+// NOTE: Edition mode across files
+// HxD2 keeps the edition mode on a per-file basis.
+// While Notepad++ does not, it may have a dedicated read-only field.
-// File properties
-
-/// FileMode for Io.
-enum FileMode {
- file, /// Normal file.
- mmfile, /// Memory-mapped file.
- stream, /// Standard streaming I/O, often pipes.
- memory, /// Typically from a stream buffered into memory.
-}
-
-/*enum SourceType {
- file,
- pipe,
-}*/
-
-/// Editor editing mode.
-enum EditMode : ushort {
- /// Incoming data will be overwritten at cursor position.
- /// This is the default.
- /// Editing: Enabled
- /// Cursor: Enabled
- overwrite,
- /// Incoming data will be inserted at cursor position.
- /// Editing: Enabled
- /// Cursor: Enabled
- insert,
- /// The file cannot be edited.
- /// Editing: Disabled
- /// Cursor: Enabled
- readOnly,
- /// The file can only be viewed.
- /// Editing: Disabled
- /// Cursor: Disabled
- view,
+enum EditError
+{
+ none,
+ io,
+ memory,
+ bufferSize,
}
/// Represents a single edit
-struct Edit {
- EditMode mode; /// Which mode was used for edit
- long position; /// Absolute offset of edit
- ubyte value; /// Payload
- // or ubyte[]?
-}
-
-private union Source
+struct Edit
{
- OSFile osfile;
- OSMmFile mmfile;
- File stream;
- MemoryStream memory;
+ long position; /// Absolute offset of edit
+ int offset; /// Offset to byte group in digits
+ int value; /// Payload
+ // or ubyte[8]?
}
-private __gshared Source source;
-
-__gshared const(char)[] fileName; /// File base name.
-__gshared FileMode fileMode; /// Current file mode.
-__gshared long position; /// Last known set position.
-//TODO: rename to viewSize
-__gshared size_t readSize; /// For input size.
-private __gshared ubyte[] readBuffer; /// For input data.
-private __gshared uint vheight; ///
-__gshared long fileSize; /// Last size of file
-
-// Editing stuff
-
-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
-
-string editModeString(EditMode mode = editMode)
-{
- final switch (mode) with (EditMode) {
- case overwrite: return "ov";
- case insert: return "in";
- case readOnly: return "rd";
- case view: return "vw";
- }
-}
-
-private struct cursor_t {
- uint position; /// Screen cursor byte position
- uint nibble; /// Data group nibble position
-}
-__gshared cursor_t cursor; /// Cursor state
-
-// View properties
-
-__gshared size_t viewSize; /// ?
-__gshared ubyte[] viewBuffer; /// ?
-
-bool eof()
-{
- final switch (fileMode) {
- case FileMode.file: return source.osfile.eof;
- case FileMode.mmfile: return source.mmfile.eof;
- case FileMode.stream: return source.stream.eof;
- case fileMode.memory: return source.memory.eof;
- }
-}
-
-bool err()
-{
- final switch (fileMode) {
- case FileMode.file: return source.osfile.err;
- case FileMode.mmfile: return source.mmfile.err;
- case FileMode.stream: return source.stream.error;
- case fileMode.memory: return false;
- }
-}
-
-bool dirty()
-{
- return editIndex > 0;
-}
-
-// SECTION: File opening
-
-int openFile(string path)
-{
- version (Trace) trace("path='%s'", path);
-
- if (source.osfile.open(path, OFlags.read | OFlags.exists))
- return errorSetOs;
-
- fileMode = FileMode.file;
- fileName = baseName(path);
- refreshFileSize;
- return 0;
-}
-
-int openMmfile(string path)
-{
- version (Trace) trace("path='%s'", path);
-
- try
- {
- source.mmfile = new OSMmFile(path, MMFlags.read);
- }
- catch (Exception ex)
- {
- return errorSet(ex);
- }
-
- fileMode = FileMode.mmfile;
- fileName = baseName(path);
- refreshFileSize;
- return 0;
-}
-
-int openStream(File file)
-{
- version (Trace) trace();
-
- source.stream = file;
- fileMode = FileMode.stream;
- fileName = null;
- return 0;
-}
-
-int openMemory(ubyte[] data)
-{
- version (Trace) trace();
-
- source.memory.open(data);
- fileMode = FileMode.memory;
- fileName = null;
- refreshFileSize;
- return 0;
-}
-
-// !SECTION
-
-// SECTION: Buffer management
-
-void setBuffer(size_t size)
-{
- readSize = size;
-
- switch (fileMode) with (FileMode) {
- case file, stream:
- readBuffer = new ubyte[size];
- return;
- default:
- }
-}
-
-// !SECTION
-
-//
-// SECTION: View position management
-//
-
-long seek(long pos)
-{
- version (Trace) trace("mode=%s", fileMode);
- position = pos;
- final switch (fileMode) with (FileMode) {
- 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 source.stream.tell;
- }
-}
-
-long tell()
-{
- version (Trace) trace("mode=%s", fileMode);
- final switch (fileMode) with (FileMode) {
- case file: return source.osfile.tell;
- case mmfile: return source.mmfile.tell;
- case stream: return source.stream.tell;
- case memory: return source.memory.tell;
- }
-}
-
-// !SECTION
-
-//
-// SECTION: Reading
-//
-
-ubyte[] read()
-{
- version (Trace) trace("mode=%s", fileMode);
- final switch (fileMode) with (FileMode) {
- case file: return source.osfile.read(readBuffer);
- case mmfile: return source.mmfile.read(readSize);
- case stream: return source.stream.rawRead(readBuffer);
- case memory: return source.memory.read(readSize);
- }
-}
-ubyte[] read(ubyte[] buffer)
-{
- version (Trace) trace("mode=%s", fileMode);
- final switch (fileMode) with (FileMode) {
- case file: return source.osfile.read(buffer);
- case mmfile: return source.mmfile.read(buffer.length);
- case stream: return source.stream.rawRead(buffer);
- case memory: return source.memory.read(buffer.length);
- }
-}
-
-// !SECTION
-
-// SECTION: Editing
///
-ubyte[] peek()
+struct Editor
{
- ubyte[] t = read().dup;
+ private enum DEFAULT_BUFSZ = 4096;
+ ///
+ //IoMode iomode;
-
- return null;
-}
-
-void keydown(Key key)
-{
- debug assert(editMode != EditMode.readOnly,
- "Editor should not be getting edits in read-only mode");
-
- //TODO: Check by panel (binary or text)
- //TODO: nibble-awareness
-
-}
-
-/// Append change at current position
-void appendEdit(ubyte data)
-{
- debug assert(editMode != EditMode.readOnly,
- "Editor should not be getting edits in read-only mode");
-
-
-}
-
-/// Write all changes to file.
-/// Only edits [0..index] will be carried over.
-/// When done, [0..index] edits will be cleared.
-void writeEdits()
-{
-
-}
-
-
-// !SECTION
-
-//
-// SECTION View position management
-//
-
-// NOTE: These return true if the position of the view changed.
-// This is to determine if it's really necessary to update the
-// view on screen, because pointlessly rendering content on-screen
-// is not something I want to waste.
-
-private
-bool viewStart()
-{
- bool z = position != 0;
- position = 0;
- return z;
-}
-private
-bool viewEnd()
-{
- long old = position;
- long npos = (fileSize - readSize) + setting.columns;
- npos -= npos % setting.columns; // re-align to columns
- position = npos;
- return position != old;
-}
-private
-bool viewUp()
-{
- long npos = position - setting.columns;
- if (npos < 0)
- return false;
- position = npos;
- return true;
-}
-private
-bool viewDown()
-{
- long fsize = fileSize;
- if (position + readSize > fsize)
- return false;
- position += setting.columns;
- return true;
-}
-private
-bool viewPageUp()
-{
- long npos = position - readSize;
- bool ok = npos >= 0;
- position = ok ? npos : 0;
- return ok;
-}
-private
-bool viewPageDown()
-{
- long npos = position + readSize;
- bool ok = npos < fileSize;
- if (ok) position = npos;
- return ok;
-}
-
-// !SECTION
-
-//
-// SECTION Cursor position management
-//
-
-void cursorBound()
-{
- // NOTE: It is impossible for the cursor to be at a negative position
- // Because it is unsigned and the cursor is 0-based
-
- long fsize = fileSize;
- bool nok = cursorTell > fsize;
-
- if (nok)
+ private union
{
- int l = cast(int)(cursorTell - fsize);
- cursor.position -= l;
- return;
+ OSFile osfile;
+ //OSMmFile mmfile;
+ //File stream;
+ //MemoryStream memstream;
}
-}
-
-// NOTE: These return true if view has moved.
-
-/// Move cursor at the absolute start of the file.
-/// Returns: True if the view moved.
-bool cursorAbsStart()
-{
- with (cursor) position = nibble = 0;
- return viewStart;
-}
-/// Move cursor at the absolute end of the file.
-/// Returns: True if the view moved.
-bool cursorAbsEnd()
-{
- uint base = cast(uint)(readSize < fileSize ? fileSize : readSize);
- uint rem = cast(uint)(fileSize % setting.columns);
- uint h = cast(uint)(base / setting.columns);
- cursor.position = h + rem;
- return viewEnd;
-}
-/// Move cursor at the start of the row.
-/// Returns: True if the view moved.
-bool cursorHome() // put cursor at the start of row
-{
- cursor.position = cursor.position - (cursor.position % setting.columns);
- cursor.nibble = 0;
- return false;
-}
-/// Move cursor at the end of the row.
-/// Returns: True if the view moved.
-bool cursorEnd() // put cursor at the end of the row
-{
- cursor.position =
- (cursor.position - (cursor.position % setting.columns))
- + setting.columns - 1;
- if (cursorTell > fileSize)
+
+ struct Edits
{
- uint rem = cast(uint)(fileSize % setting.columns);
- cursor.position = (cursor.position + setting.columns - rem);
+ size_t index;
+ size_t count;
+ Edit[long] list;
+ SList!long history;
+ const(char)[] name = "ov";
}
- cursor.nibble = 0;
- return false;
-}
-/// Move cursor up the file by a data group.
-/// Returns: True if the view moved.
-bool cursorLeft()
-{
- if (cursor.position == 0)
+ Edits edits;
+ bool insert;
+ bool readonly;
+
+ ///
+ private ubyte* rdbuf;
+ ///
+ private size_t rdbufsz;
+ /// File position
+ private long position;
+
+ int lasterr;
+
+ int seterr(int er)
{
- if (position == 0)
- return false;
- cursorEnd;
- return viewUp;
+ return (lasterr = er);
}
- --cursor.position;
- cursor.nibble = 0;
- return false;
-}
-/// Move cursor down the file by a data group.
-/// Returns: True if the view moved.
-bool cursorRight()
-{
- if (cursorTell >= fileSize)
- return false;
-
- if (cursor.position == readSize - 1)
+ int open(string path, bool exists, bool viewonly)
{
- cursorHome;
- return viewDown;
+ readonly = viewonly;
+ int flags = readonly ? OFlags.read : OFlags.readWrite;
+ if (exists) flags |= OFlags.exists;
+
+ if (osfile.open(path, flags))
+ return seterr(EditError.io);
+
+ return 0;
}
- ++cursor.position;
- cursor.nibble = 0;
- return false;
-}
-/// Move cursor up the file by the number of columns.
-/// Returns: True if the view moved.
-bool cursorUp()
-{
- if (cursor.position < setting.columns)
+ void close()
{
- return viewUp;
+ return osfile.close();
}
- cursor.position -= setting.columns;
- return false;
-}
-/// Move cursor down the file by the bymber of columns.
-/// Returns: True if the view moved.
-bool cursorDown()
-{
- /// File size
- long fsize = fileSize;
- /// Normalized file size with last row (remaining) trimmed
- long fsizenorm = fsize - (fsize % setting.columns);
- /// Absolute cursor position
- long acpos = cursorTell;
-
- bool bottom = cursor.position + setting.columns >= readSize; // cursor bottom
- bool finalr = acpos >= fsizenorm; /// final row
-
- version (Trace) trace("bottom=%s final=%s", bottom, finalr);
-
- if (finalr)
- return false;
-
- if (bottom)
- return viewDown;
-
- if (acpos + setting.columns > fsize)
- {
- uint rem = cast(uint)(fsize % setting.columns);
- cursor.position = cast(uint)(readSize - setting.columns + rem);
- //cursor.position = cast(uint)((fsize + cursor.position) - fsize);
- return false;
- }
-
- cursor.position += setting.columns;
- return false;
-}
-/// Move cursor by a page up the file.
-/// Returns: True if the view moved.
-bool cursorPageUp()
-{
-// int v = readSize / setting.columns;
- return viewPageUp;
-}
-/// Move cursor by a page down the file.
-/// Returns: True if the view moved.
-bool cursorPageDown()
-{
- bool ok = viewPageDown;
- cursorBound;
- return ok;
-}
-/// Get cursor absolute position within the file.
-/// Returns: Cursor absolute position in file.
-long cursorTell()
-{
- return position + cursor.position;
-}
-/// Make the cursor jump to an absolute position within the file.
-/// This is used for search results.
-/// Returns: True if the view moved.
-void cursorGoto(long m)
-{
- // Per view chunks, then per y chunks, then x
- //long npos =
-
-}
-
-// !SECTION
-
-long refreshFileSize()
-{
- final switch (fileMode) with (FileMode) {
- case file: fileSize = source.osfile.size; break;
- case mmfile: fileSize = source.mmfile.length; break;
- case memory: fileSize = source.memory.size; break;
- case stream: fileSize = source.stream.size; break;
- }
-
- version (Trace) trace("sz=%u", fileSize);
-
- return fileSize;
-}
-
-//TODO: from other types?
-// or implement this via MemoryStream?
-int slurp(long skip = 0, long length = 0)
-{
- import std.array : uninitializedArray;
- import core.stdc.stdio : fread;
- import core.stdc.stdlib : malloc, free;
- import std.algorithm.comparison : min;
- import std.outbuffer : OutBuffer;
- import std.typecons : scoped;
-
- enum READ_SIZE = 4096;
-
- version (Trace) trace("skip=%u length=%u", skip, length);
-
//
- // Skiping
+ //
//
- ubyte *b = cast(ubyte*)malloc(READ_SIZE);
- if (b == null)
- return errorSetOs;
+ //TODO: Consider const(char)[] errorMsg()
+ // Clear message buffer on retrieval
- FILE *_file = source.stream.getFP;
-
- if (skip)
+ bool error()
{
+ return lasterr != 0;
+ }
+ void clearerr()
+ {
+ lasterr = 0;
+ }
+ bool eof()
+ {
+ return osfile.eof;
+ }
+
+ void setbuffer(size_t newsize)
+ {
+ version (Trace) trace("newsize=%u", newsize);
+
+ // If there is an exisiting size, must have been allocated before
+ if (rdbufsz) free(rdbuf);
+
+ // Allocate read buffer
+ rdbuf = cast(ubyte*)malloc(newsize);
+ if (rdbuf == null)
+ {
+ lasterr = EditError.memory;
+ }
+ rdbufsz = newsize;
+ }
+
+ //
+ // Editing
+ //
+
+ bool dirty()
+ {
+ return edits.index > 0;
+ }
+
+ // digit: true value (not a character)
+ int addEdit(long editpos, int digit, int groupmax)
+ {
+ int ndpos; // new digit position
+ edits.list.update(editpos,
+ // edit does not exist, add it to the list
+ // and update history
+ () {
+ Edit nedit; // New edit
+ return nedit;
+ },
+ // edit exists at this position
+ // if so, increase the digit position when able
+ (ref Edit oldedit) {
+ }
+ );
+ return ndpos;
+ }
+
+ void removeEdit(long editpos)
+ {
+ if (readonly)
+ return;
+
+ }
+
+ void removeLastEdit()
+ {
+ if (readonly)
+ return;
+
+ }
+
+ /+void editMode(EditMode emode)
+ {
+ edits.mode = emode;
+ final switch (emode) with (EditMode) {
+ case overwrite: edits.modeString = "ov"; return;
+ case insert: edits.modeString = "in"; return;
+ case readOnly: edits.modeString = "rd"; return;
+ case view: edits.modeString = "vw"; return;
+ }
+ }
+
+ ubyte[] peek()
+ {
+ return null;
+ }+/
+
+ //
+ // File operations
+ //
+
+ bool seek(long pos, Seek seek = Seek.start)
+ {
+ version (Trace) trace("mode=%s", fileMode);
+ position = pos;
+ /*final switch (input.mode) with (FileMode) {
+ case file:
+ position = io.osfile.seek(Seek.start, pos);
+ return io.osfile.err;
+ case mmfile:
+ position = io.mmfile.seek(pos);
+ return io.mmfile.err;
+ case memory:
+ position = io.memory.seek(pos);
+ return io.memory.err;
+ case stream:
+ io.stream.seek(pos);
+ position = io.stream.tell;
+ return io.stream.error;
+ }*/
+ osfile.seek(seek, pos);
+ return osfile.err;
+ }
+
+ long tell()
+ {
+ version (Trace) trace("mode=%s", fileMode);
+ /*final switch (input.mode) with (FileMode) {
+ case file: return io.osfile.tell;
+ case mmfile: return io.mmfile.tell;
+ case stream: return io.stream.tell;
+ case memory: return io.memory.tell;
+ }*/
+ return position;
+ }
+
+ long size()
+ {
+ return osfile.size();
+ }
+
+ ubyte[] read()
+ {
+ version (Trace) trace("mode=%s", fileMode);
+ /*final switch (input.mode) with (FileMode) {
+ case file: return buffer.output = io.osfile.read(buffer.input);
+ case mmfile: return buffer.output = io.mmfile.read(buffer.size);
+ case stream: return buffer.output = io.stream.rawRead(buffer.input);
+ case memory: return buffer.output = io.memory.read(buffer.size);
+ }*/
+ ubyte[] res = osfile.read(rdbuf, rdbufsz);
+ //TODO: Edit res
+ return res;
+ }
+
+ //TODO: Turning into MemoryStream should be optional
+ // e.g., dump -> only read until skip
+ // ddhx -> read all into memory
+ // slurp -> only read skip amount
+ // readAll -> read all into MemoryStream
+ //TODO: Rename to convert or toMemoryStream
+ /// Read stream into memory.
+ /// Params:
+ /// skip = Number of bytes to skip.
+ /// length = Length of data to read into memory.
+ /// Returns: Error code.
+ /+int slurp(long skip = 0, long length = 0)
+ {
+ //NOTE: Can't close File, could be stdin
+ // Let attached Editor close stream if need be
+
+ import core.stdc.stdio : fread;
+ import core.stdc.stdlib : malloc, free;
+ import std.algorithm.comparison : min;
+ import std.outbuffer : OutBuffer;
+
+ enum READ_SIZE = 4096;
+
+ version (Trace) trace("skip=%u length=%u", skip, length);
+
+ // Skiping
+
+ ubyte *tmpbuf = cast(ubyte*)malloc(READ_SIZE);
+ if (tmpbuf == null)
+ {
+ return seterr(EditError.memory);
+ }
+ scope(exit) free(tmpbuf);
+
+ FILE *fp = io.stream.getFP;
+
+ if (skip)
+ {
+ do
+ {
+ size_t bsize = cast(size_t)min(READ_SIZE, skip);
+ skip -= fread(tmpbuf, 1, bsize, fp);
+ } while (skip > 0);
+ }
+
+ // Reading
+
+ scope outbuf = new OutBuffer;
+
+ // If no length set, just read as much as possible.
+ if (length == 0) length = long.max;
+
+ // Loop ends when len (read length) is under the buffer's length
+ // or requested length.
do {
- size_t bsize = cast(size_t)min(READ_SIZE, skip);
- skip -= fread(b, 1, bsize, _file);
- } while (skip > 0);
- }
-
- //
- // Reading
- //
-
- auto outbuf = scoped!OutBuffer;
-
- // If no length set, just read as much as possible.
- if (length == 0) length = long.max;
-
- // Loop ends when len (read length) is under the buffer's length
- // or requested length.
- do {
- size_t bsize = cast(size_t)min(READ_SIZE, length);
- size_t len = fread(b, 1, bsize, _file);
- if (len == 0) break;
- outbuf.put(b[0..len]);
- if (len < bsize) break;
- length -= len;
- } while (length > 0);
-
- free(b);
-
- version (Trace) trace("outbuf.offset=%u", outbuf.offset);
-
- source.memory.open(outbuf.toBytes);
-
- version (Trace) trace("source.memory.size=%u", source.memory.size);
-
- fileMode = FileMode.memory;
- return 0;
+ size_t bsize = cast(size_t)min(READ_SIZE, length);
+ size_t len = fread(tmpbuf, 1, bsize, fp);
+ if (len == 0) break;
+ outbuf.put(b[0..len]);
+ if (len < bsize) break;
+ length -= len;
+ } while (length > 0);
+
+ version (Trace) trace("outbuf.offset=%u", outbuf.offset);
+
+ io.memory.copy(outbuf.toBytes);
+
+ version (Trace) trace("io.memory.size=%u", io.memory.size);
+
+ input.mode = FileMode.memory;
+ }+/
}
\ No newline at end of file
diff --git a/src/error.d b/src/error.d
deleted file mode 100644
index 3492704..0000000
--- a/src/error.d
+++ /dev/null
@@ -1,223 +0,0 @@
-/// Error handling.
-/// Copyright: dd86k
-/// License: MIT
-/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
-module error;
-
-import std.stdio;
-
-/// Error codes
-enum ErrorCode {
- success,
- fileEmpty = 2,
- negativeValue,
- inputEmpty,
- invalidCommand,
- invalidSetting,
- invalidParameter,
- invalidNumber,
- invalidType,
- invalidCharset,
- notFound, // ??
- overflow,
- unparsable,
- noLastItem,
- eof, // ??
- unimplemented,
- insufficientSpace,
- missingOption,
- missingValue,
- missingType,
- missingNeedle,
- screenMinimumRows,
- screenMinimumColumns,
-
- // Settings
-
- settingFileMissing = 100,
- settingColumnsInvalid,
-
- // Special
-
- unknown = 0xf000,
- exception,
-}
-/// Error source
-enum ErrorSource {
- code,
- exception,
- os,
-}
-
-private struct last_t {
- const(char)[] message;
- const(char)[] file;
- int line;
- ErrorSource source;
-}
-private __gshared last_t last;
-__gshared int errorcode; /// Last error code.
-
-/// Get system error message from an error code.
-/// Params: code = System error code.
-/// Returns: Error message.
-const(char)[] systemMessage(int code)
-{
- import std.string : fromStringz;
-
- version (Windows)
- {
- import core.sys.windows.winbase :
- LocalFree, FormatMessageA,
- FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM,
- FORMAT_MESSAGE_IGNORE_INSERTS;
- import core.sys.windows.winnt :
- MAKELANGID, LANG_NEUTRAL, SUBLANG_DEFAULT;
-
- enum LANG = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
-
- char *strerror;
-
- uint r = FormatMessageA(
- FORMAT_MESSAGE_ALLOCATE_BUFFER |
- FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS,
- null,
- code,
- LANG,
- cast(char*)&strerror,
- 0,
- null);
-
- assert(strerror, "FormatMessageA failed");
-
- if (strerror[r - 1] == '\n') --r;
- if (strerror[r - 1] == '\r') --r;
- string msg = strerror[0..r].idup;
- LocalFree(strerror);
- return msg;
- } else {
- import core.stdc.string : strerror;
-
- return strerror(code).fromStringz;
- }
-}
-
-int errorSet(ErrorCode code, string file = __FILE__, int line = __LINE__)
-{
- version (unittest)
- {
- import std.stdio : writefln;
- writefln("%s:%u: %s", file, line, code);
- }
- version (Trace) trace("%s:%u: %s", file, line, code);
- last.file = file;
- last.line = line;
- last.source = ErrorSource.code;
- return (errorcode = code);
-}
-
-int errorSet(Exception ex)
-{
- version (unittest)
- {
- import std.stdio : writeln;
- writeln(ex);
- }
- version (Trace)
- {
- debug trace("%s", ex);
- else trace("%s", ex.msg);
- }
- last.file = ex.file;
- last.line = cast(int)ex.line;
- last.message = ex.msg;
- last.source = ErrorSource.exception;
- return (errorcode = ErrorCode.exception);
-}
-
-int errorSetOs(string file = __FILE__, int line = __LINE__)
-{
- version (Windows)
- {
- import core.sys.windows.winbase : GetLastError;
- errorcode = GetLastError();
- }
- else version (Posix)
- {
- import core.stdc.errno : errno;
- errorcode = errno;
- }
- version (Trace) trace("errorcode=%u line=%s:%u", errorcode, file, line);
- last.file = file;
- last.line = line;
- last.source = ErrorSource.os;
- return errorcode;
-}
-
-const(char)[] errorMessage(int code = errorcode)
-{
- final switch (last.source) with (ErrorSource) {
- case os: return systemMessage(errorcode);
- case exception: return last.message;
- case code:
- }
-
- switch (code) with (ErrorCode) {
- case fileEmpty: return "File is empty.";
- case inputEmpty: return "Input is empty.";
- case invalidCommand: return "Command not found.";
- case invalidSetting: return "Invalid setting property.";
- case invalidParameter: return "Parameter is invalid.";
- case invalidType: return "Invalid type.";
- case eof: return "Unexpected end of file (EOF).";
- case notFound: return "Data not found.";
- case overflow: return "Integer overflow.";
- case unparsable: return "Integer could not be parsed.";
- case noLastItem: return "No previous search items saved.";
- case insufficientSpace: return "Too little space left to search.";
-
- // Settings
-
- case settingFileMissing: return "Settings file does not exist at given path.";
- case settingColumnsInvalid: return "Columns cannot be zero.";
-
- case success: return "No errors occured.";
- default: return "Internal error occured.";
- }
-}
-
-int errorPrint()
-{
- return errorPrint(errorcode, errorMessage());
-}
-int errorPrint(A...)(int code, const(char)[] fmt, A args)
-{
- stderr.write("error: ");
- stderr.writefln(fmt, args);
- return code;
-}
-
-version (Trace)
-{
- public import std.datetime.stopwatch;
-
- private __gshared File log;
-
- void traceInit(string n)
- {
- log.open("ddhx.log", "w");
- trace(n);
- }
- 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);
- log.flush;
- }
-}
diff --git a/src/formatter.d b/src/formatter.d
new file mode 100644
index 0000000..d8392d9
--- /dev/null
+++ b/src/formatter.d
@@ -0,0 +1,511 @@
+/// Handles data formatting.
+/// Copyright: dd86k
+/// License: MIT
+/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
+module formatter;
+
+//TODO: format function for int
+
+size_t format8hex(char *buffer, ubyte v)
+{
+ buffer[1] = hexMap[v & 15];
+ buffer[0] = hexMap[v >> 4];
+ return 2;
+}
+@system unittest
+{
+ char[2] c = void;
+ format02x(c.ptr, 0x01);
+ assert(c[] == "01", c);
+ format02x(c.ptr, 0x20);
+ assert(c[] == "20", c);
+ format02x(c.ptr, 0xff);
+ assert(c[] == "ff", c);
+}
+
+size_t format64hex(char *buffer, ulong v)
+{
+ size_t pos;
+ bool pad = true;
+ for (int shift = 60; shift >= 0; shift -= 4)
+ {
+ const ubyte b = (v >> shift) & 15;
+ if (b == 0)
+ {
+ if (pad && shift >= 44)
+ {
+ continue; // cut
+ }
+ else if (pad && shift >= 4)
+ {
+ buffer[pos++] = pad ? ' ' : '0';
+ continue; // pad
+ }
+ }
+ else // Padding no longer acceptable
+ pad = false;
+ buffer[pos++] = hexMap[b];
+ }
+ return pos;
+}
+@system unittest
+{
+ char[32] b = void;
+ char *p = b.ptr;
+ assert(b[0..format64hex(p, 0)] == " 0");
+ assert(b[0..format64hex(p, 1)] == " 1");
+ assert(b[0..format64hex(p, 0x10)] == " 10");
+ assert(b[0..format64hex(p, 0x100)] == " 100");
+ assert(b[0..format64hex(p, 0x1000)] == " 1000");
+ assert(b[0..format64hex(p, 0x10000)] == " 10000");
+ assert(b[0..format64hex(p, 0x100000)] == " 100000");
+ assert(b[0..format64hex(p, 0x1000000)] == " 1000000");
+ assert(b[0..format64hex(p, 0x10000000)] == " 10000000");
+ assert(b[0..format64hex(p, 0x100000000)] == " 100000000");
+ assert(b[0..format64hex(p, 0x1000000000)] == " 1000000000");
+ assert(b[0..format64hex(p, 0x10000000000)] == "10000000000");
+ assert(b[0..format64hex(p, 0x100000000000)] == "100000000000");
+ assert(b[0..format64hex(p, 0x1000000000000)] == "1000000000000");
+ assert(b[0..format64hex(p, ubyte.max)] == " ff");
+ assert(b[0..format64hex(p, ushort.max)] == " ffff");
+ assert(b[0..format64hex(p, uint.max)] == " ffffffff");
+ assert(b[0..format64hex(p, ulong.max)] == "ffffffffffffffff");
+ assert(b[0..format64hex(p, 0x1010)] == " 1010");
+ assert(b[0..format64hex(p, 0x10101010)] == " 10101010");
+ assert(b[0..format64hex(p, 0x1010101010101010)] == "1010101010101010");
+}
+
+private:
+
+immutable string hexMap = "0123456789abcdef";
+
+size_t format02x(char *buffer, ubyte v)
+{
+ buffer[1] = hexMap[v & 15];
+ buffer[0] = hexMap[v >> 4];
+ return 2;
+}
+@system unittest
+{
+ char[2] c = void;
+ format02x(c.ptr, 0x01);
+ assert(c[] == "01", c);
+ format02x(c.ptr, 0x20);
+ assert(c[] == "20", c);
+ format02x(c.ptr, 0xff);
+ assert(c[] == "ff", c);
+}
+
+size_t format11x(char *buffer, long v)
+{
+ size_t pos;
+ bool pad = true;
+ for (int shift = 60; shift >= 0; shift -= 4)
+ {
+ const ubyte b = (v >> shift) & 15;
+ if (b == 0)
+ {
+ if (pad && shift >= 44)
+ {
+ continue; // cut
+ }
+ else if (pad && shift >= 4)
+ {
+ buffer[pos++] = pad ? ' ' : '0';
+ continue; // pad
+ }
+ } else pad = false;
+ buffer[pos++] = hexMap[b];
+ }
+ return pos;
+}
+///
+@system unittest
+{
+ char[32] b = void;
+ char *p = b.ptr;
+ assert(b[0..format11x(p, 0)] == " 0");
+ assert(b[0..format11x(p, 1)] == " 1");
+ assert(b[0..format11x(p, 0x10)] == " 10");
+ assert(b[0..format11x(p, 0x100)] == " 100");
+ assert(b[0..format11x(p, 0x1000)] == " 1000");
+ assert(b[0..format11x(p, 0x10000)] == " 10000");
+ assert(b[0..format11x(p, 0x100000)] == " 100000");
+ assert(b[0..format11x(p, 0x1000000)] == " 1000000");
+ assert(b[0..format11x(p, 0x10000000)] == " 10000000");
+ assert(b[0..format11x(p, 0x100000000)] == " 100000000");
+ assert(b[0..format11x(p, 0x1000000000)] == " 1000000000");
+ assert(b[0..format11x(p, 0x10000000000)] == "10000000000");
+ assert(b[0..format11x(p, 0x100000000000)] == "100000000000");
+ assert(b[0..format11x(p, 0x1000000000000)] == "1000000000000");
+ assert(b[0..format11x(p, ubyte.max)] == " ff");
+ assert(b[0..format11x(p, ushort.max)] == " ffff");
+ assert(b[0..format11x(p, uint.max)] == " ffffffff");
+ assert(b[0..format11x(p, ulong.max)] == "ffffffffffffffff");
+ assert(b[0..format11x(p, 0x1010)] == " 1010");
+ assert(b[0..format11x(p, 0x10101010)] == " 10101010");
+ assert(b[0..format11x(p, 0x1010101010101010)] == "1010101010101010");
+}
+
+immutable static string decMap = "0123456789";
+size_t format03d(char *buffer, ubyte v)
+{
+ buffer[2] = (v % 10) + '0';
+ buffer[1] = (v / 10 % 10) + '0';
+ buffer[0] = (v / 100 % 10) + '0';
+ return 3;
+}
+@system unittest
+{
+ char[3] c = void;
+ format03d(c.ptr, 1);
+ assert(c[] == "001", c);
+ format03d(c.ptr, 10);
+ assert(c[] == "010", c);
+ format03d(c.ptr, 111);
+ assert(c[] == "111", c);
+}
+
+size_t format11d(char *buffer, long v)
+{
+ debug import std.conv : text;
+ enum ulong I64MAX = 10_000_000_000_000_000_000UL;
+ size_t pos;
+ bool pad = true;
+ for (ulong d = I64MAX; d > 0; d /= 10)
+ {
+ const long r = (v / d) % 10;
+ if (r == 0)
+ {
+ if (pad && d >= 100_000_000_000)
+ {
+ continue; // cut
+ }
+ else if (pad && d >= 10)
+ {
+ buffer[pos++] = pad ? ' ' : '0';
+ continue;
+ }
+ } else pad = false;
+ debug assert(r >= 0 && r < 10, "r="~r.text);
+ buffer[pos++] = decMap[r];
+ }
+ return pos;
+}
+///
+@system unittest
+{
+ char[32] b = void;
+ char *p = b.ptr;
+ assert(b[0..format11d(p, 0)] == " 0");
+ assert(b[0..format11d(p, 1)] == " 1");
+ assert(b[0..format11d(p, 10)] == " 10");
+ assert(b[0..format11d(p, 100)] == " 100");
+ assert(b[0..format11d(p, 1000)] == " 1000");
+ assert(b[0..format11d(p, 10_000)] == " 10000");
+ assert(b[0..format11d(p, 100_000)] == " 100000");
+ assert(b[0..format11d(p, 1000_000)] == " 1000000");
+ assert(b[0..format11d(p, 10_000_000)] == " 10000000");
+ assert(b[0..format11d(p, 100_000_000)] == " 100000000");
+ assert(b[0..format11d(p, 1000_000_000)] == " 1000000000");
+ assert(b[0..format11d(p, 10_000_000_000)] == "10000000000");
+ assert(b[0..format11d(p, 100_000_000_000)] == "100000000000");
+ assert(b[0..format11d(p, 1000_000_000_000)] == "1000000000000");
+ assert(b[0..format11d(p, ubyte.max)] == " 255");
+ assert(b[0..format11d(p, ushort.max)] == " 65535");
+ assert(b[0..format11d(p, uint.max)] == " 4294967295");
+ assert(b[0..format11d(p, ulong.max)] == "18446744073709551615");
+ assert(b[0..format11d(p, 1010)] == " 1010");
+}
+
+size_t format03o(char *buffer, ubyte v)
+{
+ buffer[2] = (v % 8) + '0';
+ buffer[1] = (v / 8 % 8) + '0';
+ buffer[0] = (v / 64 % 8) + '0';
+ return 3;
+}
+@system unittest
+{
+ import std.conv : octal;
+ char[3] c = void;
+ format03o(c.ptr, 1);
+ assert(c[] == "001", c);
+ format03o(c.ptr, octal!20);
+ assert(c[] == "020", c);
+ format03o(c.ptr, octal!133);
+ assert(c[] == "133", c);
+}
+
+size_t format11o(char *buffer, long v)
+{
+ size_t pos;
+ if (v >> 63) buffer[pos++] = '1'; // ulong.max coverage
+ bool pad = true;
+ for (int shift = 60; shift >= 0; shift -= 3)
+ {
+ const ubyte b = (v >> shift) & 7;
+ if (b == 0)
+ {
+ if (pad && shift >= 33)
+ {
+ continue; // cut
+ }
+ else if (pad && shift >= 3)
+ {
+ buffer[pos++] = pad ? ' ' : '0';
+ continue;
+ }
+ } else pad = false;
+ buffer[pos++] = hexMap[b];
+ }
+ return pos;
+}
+///
+@system unittest
+{
+ import std.conv : octal;
+ char[32] b = void;
+ char *p = b.ptr;
+ assert(b[0..format11o(p, 0)] == " 0");
+ assert(b[0..format11o(p, 1)] == " 1");
+ assert(b[0..format11o(p, octal!10)] == " 10");
+ assert(b[0..format11o(p, octal!20)] == " 20");
+ assert(b[0..format11o(p, octal!100)] == " 100");
+ assert(b[0..format11o(p, octal!1000)] == " 1000");
+ assert(b[0..format11o(p, octal!10_000)] == " 10000");
+ assert(b[0..format11o(p, octal!100_000)] == " 100000");
+ assert(b[0..format11o(p, octal!1000_000)] == " 1000000");
+ assert(b[0..format11o(p, octal!10_000_000)] == " 10000000");
+ assert(b[0..format11o(p, octal!100_000_000)] == " 100000000");
+ assert(b[0..format11o(p, octal!1000_000_000)] == " 1000000000");
+ assert(b[0..format11o(p, octal!10_000_000_000)] == "10000000000");
+ assert(b[0..format11o(p, octal!100_000_000_000)] == "100000000000");
+ assert(b[0..format11o(p, ubyte.max)] == " 377");
+ assert(b[0..format11o(p, ushort.max)] == " 177777");
+ assert(b[0..format11o(p, uint.max)] == "37777777777");
+ assert(b[0..format11o(p, ulong.max)] == "1777777777777777777777");
+ assert(b[0..format11o(p, octal!101_010)] == " 101010");
+}
+
+// !SECTION
+
+version (none):
+
+//int outputLine(long base, ubyte[] data, int row, int cursor = -1)
+
+//TODO: Add int param for data at cursor (placeholder)
+/// Render multiple lines on screen with optional cursor.
+/// Params:
+/// base = Offset base.
+/// data = data to render.
+/// cursor = Position of cursor.
+/// Returns: Number of rows printed. Negative numbers indicate error.
+int output(long base, ubyte[] data, int cursor = -1)
+{
+ int crow = void, ccol = void;
+
+ if (data.length == 0)
+ return 0;
+
+ if (cursor < 0)
+ crow = ccol = -1;
+ else
+ {
+ crow = cursor / setting.columns;
+ ccol = cursor % setting.columns;
+ }
+
+ version (Trace)
+ {
+ trace("base=%u D=%u crow=%d ccol=%d",
+ base, data.length, crow, ccol);
+ StopWatch sw = StopWatch(AutoStart.yes);
+ }
+
+ size_t buffersz = // minimum anyway
+ OFFSET_SPACE + 2 + // offset + spacer
+ ((binaryFormatter.size + 1) * setting.columns) + // binary + spacer * cols
+ (1 + (setting.columns * 3)); // spacer + text (utf-8)
+
+ char *buffer = cast(char*)malloc(buffersz);
+ if (buffer == null) return -1;
+
+ int lines;
+ foreach (chunk; chunks(data, setting.columns))
+ {
+ const bool cur_row = lines == crow;
+
+ Row row = makerow(buffer, buffersz, chunk, base, ccol);
+
+ if (cur_row)
+ {
+ version (Trace) trace(
+ "row.length=%u cbi=%u cbl=%u cti=%u ctl=%u bl=%u tl=%u",
+ row.result.length,
+ row.cursorBinaryIndex,
+ row.cursorBinaryLength,
+ row.cursorTextIndex,
+ row.cursorTextLength,
+ row.binaryLength,
+ row.textLength);
+
+ // between binary and text cursors
+ size_t distance = row.cursorTextIndex -
+ row.cursorBinaryIndex -
+ row.cursorBinaryLength;
+
+ char *p = buffer;
+ // offset + pre-cursor binary
+ p += cwrite(p, row.cursorBinaryIndex);
+ // binary cursor
+ terminalInvertColor;
+ p += cwrite(p, row.cursorBinaryLength);
+ terminalResetColor;
+ // post-cursor binary + pre-cursor text (minus spacer)
+ p += cwrite(p, distance);
+ // text cursor
+ terminalHighlight;
+ p += cwrite(p, row.cursorTextLength);
+ terminalResetColor;
+ // post-cursor text
+ size_t rem = row.result.length - (p - buffer);
+ p += cwrite(p, rem);
+
+ version (Trace) trace("d=%u r=%u l=%u", distance, rem, p - buffer);
+ }
+ else
+ cwrite(row.result.ptr, row.result.length);
+
+ cwrite('\n');
+
+ ++lines;
+ base += setting.columns;
+ }
+
+ free(buffer);
+
+ version (Trace)
+ {
+ sw.stop;
+ trace("time='%s µs'", sw.peek.total!"usecs");
+ }
+
+ return lines;
+}
+
+//TODO: Consider moving to this ddhx
+void renderEmpty(uint rows, int w)
+{
+ version (Trace)
+ {
+ trace("lines=%u rows=%u cols=%u", lines, rows, w);
+ StopWatch sw = StopWatch(AutoStart.yes);
+ }
+
+ char *p = cast(char*)malloc(w);
+ assert(p); //TODO: Soft asserts
+ memset(p, ' ', w);
+
+ //TODO: Output to scoped OutBuffer
+ for (int i; i < rows; ++i)
+ cwrite(p, w);
+
+ free(p);
+
+ version (Trace)
+ {
+ sw.stop;
+ trace("time='%s µs'", sw.peek.total!"usecs");
+ }
+}
+
+private
+struct Row
+{
+ char[] result;
+
+ size_t cursorBinaryIndex;
+ size_t cursorBinaryLength;
+ size_t cursorTextIndex;
+ size_t cursorTextLength;
+
+ size_t binaryLength;
+ size_t textLength;
+}
+
+private
+Row makerow(char *buffer, size_t bufferlen,
+ ubyte[] chunk, long pos,
+ int cursor_col)
+{
+ Row row = void;
+
+ // Insert OFFSET
+ size_t indexData = offsetFormatter.offset(buffer, pos);
+ buffer[indexData++] = ' '; // index: OFFSET + space
+
+ const uint dataLen = (setting.columns * (binaryFormatter.size + 1)); /// data row character count
+ size_t indexChar = indexData + dataLen; // Position for character column
+
+ *(cast(ushort*)(buffer + indexChar)) = 0x2020; // DATA-CHAR spacer
+ indexChar += 2; // indexChar: indexData + dataLen + spacer
+
+ // Format DATA and CHAR
+ // NOTE: Smaller loops could fit in cache...
+ // And would separate data/text logic
+ size_t bi0 = indexData, ti0 = indexChar;
+ int currentCol;
+ foreach (data; chunk)
+ {
+ const bool curhit = currentCol == cursor_col; // cursor hit column
+ //TODO: Maybe binary data formatter should include space?
+ // Data translation
+ buffer[indexData++] = ' ';
+ if (curhit)
+ {
+ row.cursorBinaryIndex = indexData;
+ row.cursorBinaryLength = binaryFormatter.size;
+ }
+ indexData += binaryFormatter.data(buffer + indexData, data);
+ // Character translation
+ immutable(char)[] units = transcoder.transform(data);
+ if (curhit)
+ {
+ row.cursorTextIndex = indexChar;
+ row.cursorTextLength = units.length ? units.length : 1;
+ }
+ if (units.length) // Has utf-8 codepoints
+ {
+ foreach (codeunit; units)
+ buffer[indexChar++] = codeunit;
+ } else // Invalid character, insert default character
+ buffer[indexChar++] = setting.defaultChar;
+
+ ++currentCol;
+ }
+
+ row.binaryLength = dataLen - bi0;
+ row.textLength = indexChar - ti0;
+
+ size_t end = indexChar;
+
+ // data length < minimum row requirement = in-fill data and text columns
+ if (chunk.length < setting.columns)
+ {
+ // In-fill characters: left = Columns - ChunkLength
+ size_t leftchar = (setting.columns - chunk.length); // Bytes left
+ memset(buffer + indexChar, ' ', leftchar);
+ row.textLength += leftchar;
+ // In-fill binary data: left = CharactersLeft * (DataSize + 1)
+ size_t leftdata = leftchar * (binaryFormatter.size + 1);
+ memset(buffer + indexData, ' ', leftdata);
+ row.binaryLength += leftdata;
+
+ end += leftchar;
+ }
+
+ row.result = buffer[0..end];
+
+ return row;
+}
\ No newline at end of file
diff --git a/src/logger.d b/src/logger.d
new file mode 100644
index 0000000..e97041a
--- /dev/null
+++ b/src/logger.d
@@ -0,0 +1,30 @@
+/// Basic logger.
+/// Copyright: dd86k
+/// License: MIT
+/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
+module logger;
+
+import std.stdio;
+import std.datetime.stopwatch;
+
+private __gshared File tracefile;
+private __gshared StopWatch sw;
+private __gshared bool tracing;
+
+void traceInit()
+{
+ tracing = true;
+ tracefile = File("ddhx.log", "w");
+ tracefile.setvbuf(0, _IONBF);
+ sw.start();
+}
+
+void trace(A...)(string fmt, A args)
+{
+ if (tracing == false)
+ return;
+
+ double ms = sw.peek().total!"msecs"() / 1_000.0;
+ tracefile.writef("[%08.3f] ", ms);
+ tracefile.writefln(fmt, args);
+}
\ No newline at end of file
diff --git a/src/main.d b/src/main.d
index 42b8028..fb6028f 100644
--- a/src/main.d
+++ b/src/main.d
@@ -1,241 +1,198 @@
/// Command-line interface.
///
-/// Some of these functions are private for linter reasons
/// Copyright: dd86k
/// License: MIT
/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
module main;
+import std.compiler : version_major, version_minor;
import std.stdio, std.mmfile, std.format, std.getopt;
+import std.conv : text;
import core.stdc.stdlib : exit;
-import ddhx, editor, dump, reverser;
+import ddhx, dumper;
+import utils.strings;
+import display : Format, selectFormat;
+import transcoder : CharacterSet;
+import logger;
-//TODO: --only=n
-// text: only display translated text
-// data: only display hex data
-//TODO: --memory
-// read all into memory
-// current seeing no need for this
+private: // Shuts up the linter
-private:
+/// Copyright string
+immutable string DDHX_COPYRIGHT = "Copyright (c) 2017-2024 dd86k ";
+/// App version
+immutable string DDHX_VERSION = "0.5.0";
+/// Version line
+immutable string DDHX_ABOUT = "ddhx "~DDHX_VERSION~" (built: "~__TIMESTAMP__~")";
immutable string SECRET = q"SECRET
- +---------------------------------+
- __ | Heard you need help editing |
- / \ | data, can I help you with that? |
- - - | [ Yes ] [ No ] [ Go away ] |
- O O +-. .-----------------------------+
+ +----------------------------+
+ __ | Heard you need help. |
+ / \ | Can I help you with that? |
+ _ _ | [ Yes ] [ No ] [ Go away ] |
+ O O +-. .------------------------+
|| |/ |/
| V |
\_/
SECRET";
-// CLI command options
+static immutable string COMPILER_VERSION =
+ format("%d.%03d", version_major, version_minor);
+static immutable string PAGE_VERSION =
+ DDHX_ABOUT~"\n"~
+ DDHX_COPYRIGHT~"\n"~
+ "License: MIT \n"~
+ "Homepage: \n"~
+ "Compiler: "~__VENDOR__~" "~COMPILER_VERSION;
-immutable string OPT_COLUMNS = "c|"~COMMAND_COLUMNS;
-immutable string OPT_INSERT = COMMAND_INSERT;
-immutable string OPT_OVERWRITE = COMMAND_OVERWRITE;
-immutable string OPT_READONLY = "R|"~COMMAND_READONLY;
-immutable string OPT_VIEW = COMMAND_VIEW;
-immutable string OPT_SI = COMMAND_SI;
-immutable string OPT_IEC = COMMAND_IEC;
-immutable string OPT_OFFSET = "o|"~COMMAND_OFFSET;
-immutable string OPT_DATA = "d|"~COMMAND_DATA;
-immutable string OPT_FILLER = "F|"~COMMAND_FILLER;
-immutable string OPT_CHARSET = "C|"~COMMAND_CHARSET;
-
-// CLI common options
-
-immutable string OPT_VERSION = "version";
-immutable string OPT_VER = "ver";
-immutable string OPT_SECRET = "assistant";
-
-bool askingHelp(string v) { return v == "help"; }
-
-void cliList(string opt)
+void cliPage(string key)
{
- writeln("Available values for ",opt,":");
- import std.traits : EnumMembers;
- switch (opt) {
- case OPT_OFFSET, OPT_DATA:
- foreach (m; EnumMembers!NumberType)
- writeln("\t=", m);
- break;
- case OPT_CHARSET:
- foreach (m; EnumMembers!CharacterSet)
- writeln("\t=", m);
- break;
- default:
+ final switch (key) {
+ case "ver": key = DDHX_VERSION; break;
+ case "version": key = PAGE_VERSION; break;
+ case "assistant": key = SECRET; break;
}
+ writeln(key);
exit(0);
}
-void cliOption(string opt, string val)
+string argstdin(string filename)
{
- final switch (opt) {
- case OPT_INSERT:
- editor.editMode = EditMode.insert;
- return;
- case OPT_OVERWRITE:
- editor.editMode = EditMode.overwrite;
- return;
- case OPT_READONLY:
- editor.editMode = EditMode.readOnly;
- return;
- case OPT_VIEW:
- editor.editMode = EditMode.view;
- return;
- case OPT_COLUMNS:
- if (settingsColumns(val))
- break;
- return;
- case OPT_OFFSET:
- if (askingHelp(val))
- cliList(opt);
- if (settingsOffset(val))
- break;
- return;
- case OPT_DATA:
- if (askingHelp(val))
- cliList(opt);
- if (settingsData(val))
- break;
- return;
- case OPT_FILLER:
- if (settingsFiller(val))
- break;
- return;
- case OPT_CHARSET:
- if (askingHelp(val))
- cliList(opt);
- if (settingsCharset(val))
- break;
- return;
- }
- errorPrint(1, "Invalid value for %s: %s", opt, val);
- exit(1);
+ return filename == "-" ? null : filename;
+}
+long lparse(string v)
+{
+ if (v[0] != '+')
+ throw new Exception(text("Missing '+' prefix to argument: ", v));
+
+ return cparse(v[1..$]);
}
-void page(string opt)
-{
- import std.compiler : version_major, version_minor;
- static immutable string COMPILER_VERSION = format("%d.%03d", version_major, version_minor);
- static immutable string VERSTR =
- DDHX_ABOUT~"\n"~
- DDHX_COPYRIGHT~"\n"~
- "License: MIT \n"~
- "Homepage: \n"~
- "Compiler: "~__VENDOR__~" "~COMPILER_VERSION;
- final switch (opt) {
- case OPT_VERSION: opt = VERSTR; break;
- case OPT_VER: opt = DDHX_VERSION; break;
- case OPT_SECRET: opt = SECRET; break;
- }
- writeln(opt);
- exit(0);
-}
+debug enum TRACE = true;
+else enum TRACE = false;
int main(string[] args)
{
- bool cliMmfile, cliFile, cliDump, cliStdin;
- bool cliNoRC;
- string cliSeek, cliLength, cliRC, cliReverse;
+ bool otrace = TRACE;
+ bool odump;
+ bool oreadonly;
+ int ofmtdata = Format.hex;
+ int ofmtaddr = Format.hex;
+ int ocolumns = 16;
+ int ocharset = CharacterSet.ascii;
+ long oskip;
+ long olength;
+
GetoptResult res = void;
- try {
- //TODO: Change &cliOption to {}
+ try
+ {
res = args.getopt(config.caseSensitive,
- OPT_COLUMNS, "Set column size (automatic='a', default=16)", &cliOption,
- OPT_OFFSET, "Set offset mode (decimal, hex, or octal)", &cliOption,
- OPT_DATA, "Set binary mode (decimal, hex, or octal)", &cliOption,
- OPT_FILLER, "Set non-printable default character (default='.')", &cliOption,
- OPT_CHARSET, "Set character translation (default=ascii)", &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 binary suffixes instead of IEC", &setting.si,
- "m|mmfile", "Open file as mmfile (memory-mapped)", &cliMmfile,
- "f|file", "Force opening file as regular", &cliFile,
- "stdin", "Open stdin instead of file", &cliStdin,
- "s|seek", "Seek at position", &cliSeek,
- "D|dump", "Dump file non-interactive onto screen", &cliDump,
- "l|length", "Dump: Length of data to read", &cliLength,
- "I|norc", "Use detaults and ignore user configuration files", &cliNoRC,
- "rc", "Use supplied RC file", &cliRC,
- "r|reverse", "Reverse operation: From hex, output binary to this file", &cliReverse,
- OPT_VERSION, "Print the version screen and exit", &page,
- OPT_VER, "Print only the version and exit", &page,
- OPT_SECRET, "", &page
+ "assistant", "", &cliPage,
+ // Editor option
+ "c|columns", "Set column length (default=16, auto=0)", &ocolumns,
+ "o|offset", "Set offset mode ('hex', 'dec', or 'oct')",
+ (string _, string fmt) {
+ ofmtaddr = selectFormat(fmt);
+ },
+ "data", "Set data mode ('hex', 'dec', or 'oct')",
+ (string _, string fmt) {
+ ofmtdata = selectFormat(fmt);
+ },
+ //"filler", "Set non-printable default character (default='.')", &cliOption,
+ "C|charset", "Set character translation (default=ascii)",
+ (string _, string charset) {
+ switch (charset) with (CharacterSet)
+ {
+ case "cp437": ocharset = cp437; break;
+ case "ebcdic": ocharset = ebcdic; break;
+ case "mac": ocharset = mac; break;
+ case "ascii": ocharset = ascii; break;
+ default:
+ throw new Exception(text("Invalid charset: ", charset));
+ }
+ },
+ "r|readonly", "Open file in read-only editing mode", &oreadonly,
+ // Editor input mode
+ // Application options
+ "dump", "Dump file non-interactively", &odump,
+ "s|seek", "Seek at position",
+ (string _, string len) { oskip = cparse(len); },
+ "l|length", "Maximum amount of data to read",
+ (string _, string len) { olength = cparse(len); },
+ //"I|norc", "Use defaults and ignore user configuration files", &cliNoRC,
+ //"rc", "Use supplied RC file", &cliRC,
+ // Pages
+ "version", "Print the version screen and exit", &cliPage,
+ "ver", "Print only the version and exit", &cliPage,
+ "trace", "Enable tracing. Used in debugging", &otrace,
);
}
catch (Exception ex)
{
- return errorPrint(1, ex.msg);
+ stderr.writeln("error: ", ex.msg);
+ return 1;
}
if (res.helpWanted)
{
// Replace default help line
res.options[$-1].help = "Print this help screen and exit";
- writeln("ddhx - Interactive hexadecimal file viewer\n"~
- " Usage: ddhx [OPTIONS] [FILE|--stdin]\n"~
+
+ writeln("ddhx - Hex editor\n"~
+ "USAGE\n"~
+ " ddhx [OPTIONS] [+POSITION] [+LENGTH] [FILE|-]\n"~
+ " ddhx {-h|--help|--version|--ver]\n"~
"\n"~
"OPTIONS");
- foreach (opt; res.options) with (opt)
+ foreach (opt; res.options[1..$]) with (opt)
{
- if (help == "") continue;
if (optShort)
- writefln("%s, %-14s %s", optShort, optLong, help);
+ write(' ', optShort, ',');
else
- writefln(" %-14s %s", optLong, help);
+ write(" ");
+ writefln(" %-14s %s", optLong, help);
}
+
return 0;
}
- version (Trace)
- traceInit(DDHX_ABOUT);
-
- long skip, length;
-
- // Convert skip value
- if (cliSeek)
+ // Positional arguments
+ string filename = void;
+ try switch (args.length)
{
- version (Trace) trace("seek=%s", cliSeek);
-
- if (convertToVal(skip, cliSeek))
- return errorPrint();
-
- if (skip < 0)
- return errorPrint(1, "Skip value must be positive");
+ case 1: // Missing filename, implicit stdin
+ filename = null;
+ break;
+ case 2: // Has filename or -
+ filename = argstdin(args[1]);
+ break;
+ case 3: // Has position and filename
+ oskip = lparse(args[1]);
+ filename = argstdin(args[2]);
+ break;
+ case 4: // Has position, length, and filename
+ oskip = lparse(args[1]);
+ olength = lparse(args[2]);
+ filename = argstdin(args[3]);
+ break;
+ default:
+ throw new Exception("Too many arguments");
+ }
+ catch (Exception ex)
+ {
+ stderr.writeln("error: ", ex.msg);
+ return 2;
}
- // Open file or stream
- //TODO: Open MemoryStream
- if ((args.length <= 1 || cliStdin) && editor.openStream(stdin))
- return errorPrint;
- else if (cliFile ? false : cliMmfile && editor.openMmfile(args[1]))
- return errorPrint;
- else if (args.length > 1 && editor.openFile(args[1]))
- return errorPrint;
-
- // Parse settings
- if (cliNoRC == false && loadSettings(cliRC))
- return errorPrint;
+ if (otrace) traceInit();
+ trace("version=%s args=%u", DDHX_VERSION, args.length);
// App: dump
- if (cliDump)
+ if (odump)
{
- if (cliLength && convertToVal(length, cliLength))
- return errorPrint;
- return dump.start(skip, length);
- }
-
- // App: reverse
- if (cliReverse)
- {
- return reverser.start(cliReverse);
+ //TODO: If args.length < 2 -> open stream
+ return dump(filename, ocolumns, oskip, olength, ocharset);
}
// App: interactive
- return ddhx.start(skip);
+ return ddhx_start(filename, oreadonly, oskip, olength, ocolumns, ocharset);
}
\ No newline at end of file
diff --git a/src/os/file.d b/src/os/file.d
index c2ed9b1..962d74a 100644
--- a/src/os/file.d
+++ b/src/os/file.d
@@ -83,9 +83,9 @@
/// File seek origin.
enum Seek
{
- start = SEEK_SET, /// Seek since start of file.
- current = SEEK_CUR, /// Seek since current position in file.
- end = SEEK_END /// Seek since end of file.
+ start = SEEK_SET, /// Seek since start of file.
+ current = SEEK_CUR, /// Seek since current position in file.
+ end = SEEK_END, /// Seek since end of file.
}
/// Open file flags
@@ -102,7 +102,8 @@
// useful when writing all changes to file
// Win32: Seek + SetEndOfFile
// others: ftruncate
-struct OSFile {
+struct OSFile
+{
private OSHANDLE handle;
bool eof, err;
@@ -130,11 +131,9 @@
{
version (Windows)
{
- uint dwAccess;
- uint dwCreation;
+ uint dwCreation = flags & OFlags.exists ? OPEN_EXISTING : OPEN_ALWAYS;
- if (flags & OFlags.exists) dwCreation |= OPEN_EXISTING;
- else dwCreation |= OPEN_ALWAYS;
+ uint dwAccess;
if (flags & OFlags.read) dwAccess |= GENERIC_READ;
if (flags & OFlags.write) dwAccess |= GENERIC_WRITE;
@@ -156,7 +155,7 @@
else version (Posix)
{
int oflags;
- if (!(flags & OFlags.exists)) oflags |= O_CREAT;
+ if ((flags & OFlags.exists) == 0) oflags |= O_CREAT;
if ((flags & OFlags.readWrite) == OFlags.readWrite)
oflags |= O_RDWR;
else if (flags & OFlags.write)
diff --git a/src/os/mmfile.d b/src/os/mmfile.d
deleted file mode 100644
index 5c4c1c6..0000000
--- a/src/os/mmfile.d
+++ /dev/null
@@ -1,685 +0,0 @@
-/// A re-imagination and updated version of std.mmfile that features
-/// some minor enhancements, such as APIs similar to File.
-/// Copyright: dd86k
-/// License: MIT
-/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
-module os.mmfile;
-
-import std.stdio : File;
-import std.mmfile : MmFile;
-
-enum MMFlags
-{
- exists = 1, /// File must exist.
- read = 1 << 1, /// Read access.
- write = 1 << 2, /// Write access.
-}
-
-// temp
-public class OSMmFile : MmFile
-{
- private void *address;
-
- this(string path, int flags)
- {
- super(path,
- flags & MMFlags.read ? MmFile.Mode.read :
- flags & MMFlags.exists ?
- MmFile.Mode.readWrite : MmFile.Mode.readWriteNew,
- 0,
- address);
- }
-
- bool eof, err;
- private long position;
- long seek(long pos) // only do seek_set for now
- {
- return position = pos;
- }
- long tell()
- {
- return position;
- }
- ubyte[] read(size_t size)
- {
- long sz = cast(long)this.length;
- long p2 = position+size;
- eof = p2 > sz;
- if (eof) p2 = sz;
- return cast(ubyte[])this[position..p2];
- }
- /*void seek(long pos)
- {
- final switch (origin) with (Seek)
- {
- case start:
- position = pos;
- return 0;
- case current:
- position += pos;
- return 0;
- case end:
- position = size - pos;
- return 0;
- }
- }*/
-}
-
-/// The mode the memory mapped file is opened with.
-/+enum MmFileMode {
- read, /// Read existing file
- readWriteNew, /// Delete existing file, write new file (overwrite)
- readWrite, /// Read/Write existing file, create if not existing
- readCopyOnWrite, /// Read/Write existing file, copy on write
-}
-
-/// Memory-mapped file.
-struct OSMmFile2 {
- /// Open a memory-mapped file.
- /// Params:
- /// filename = File path.
- /// mode = mmfile operating mode.
- /// size =
- this(string filename, MmFileMode mode = Mode.read, ulong size = 0,
- void* address = null, size_t window = 0)
-{
-
- version (Windows)
-{
- } else version (linux)
-{
- int oflag;
- int fmode;
-
- final switch (mode) with (MmFileMode)
-{
- case read:
- flags = MAP_SHARED;
- prot = PROT_READ;
- oflag = O_RDONLY;
- fmode = 0;
- break;
- case Mode.readWriteNew:
- assert(size != 0);
- flags = MAP_SHARED;
- prot = PROT_READ | PROT_WRITE;
- oflag = O_CREAT | O_RDWR | O_TRUNC;
- fmode = octal!660;
- break;
- case Mode.readWrite:
- flags = MAP_SHARED;
- prot = PROT_READ | PROT_WRITE;
- oflag = O_CREAT | O_RDWR;
- fmode = octal!660;
- break;
- case Mode.readCopyOnWrite:
- flags = MAP_PRIVATE;
- prot = PROT_READ | PROT_WRITE;
- oflag = O_RDWR;
- fmode = 0;
- break;
- }
-
- fd = fildes;
-
- // Adjust size
- stat_t statbuf = void;
- errnoEnforce(fstat(fd, &statbuf) == 0);
- if (prot & PROT_WRITE && size > statbuf.st_size)
- {
- // Need to make the file size bytes big
- lseek(fd, cast(off_t)(size - 1), SEEK_SET);
- char c = 0;
- core.sys.posix.unistd.write(fd, &c, 1);
- }
- else if (prot & PROT_READ && size == 0)
- size = statbuf.st_size;
- this.size = size;
-
- // Map the file into memory!
- size_t initial_map = (window && 2*window statbuf.st_size)
- {
- // Need to make the file size bytes big
- lseek(fd, cast(off_t)(size - 1), SEEK_SET);
- char c = 0;
- core.sys.posix.unistd.write(fd, &c, 1);
- }
- else if (prot & PROT_READ && size == 0)
- size = statbuf.st_size;
- this.size = size;
-
- // Map the file into memory!
- size_t initial_map = (window && 2*window> 32);
- hFileMap = CreateFileMappingW(hFile, null, flProtect,
- hi, cast(uint) size, null);
- wenforce(hFileMap, "CreateFileMapping");
- scope(failure)
- {
- CloseHandle(hFileMap);
- hFileMap = null;
- }
-
- if (size == 0 && filename != null)
- {
- uint sizehi;
- uint sizelow = GetFileSize(hFile, &sizehi);
- wenforce(sizelow != INVALID_FILE_SIZE || GetLastError() != ERROR_SUCCESS,
- "GetFileSize");
- size = (cast(ulong) sizehi << 32) + sizelow;
- }
- this.size = size;
-
- size_t initial_map = (window && 2*window statbuf.st_size)
- {
- // Need to make the file size bytes big
- .lseek(fd, cast(off_t)(size - 1), SEEK_SET);
- char c = 0;
- core.sys.posix.unistd.write(fd, &c, 1);
- }
- else if (prot & PROT_READ && size == 0)
- size = statbuf.st_size;
- }
- else
- {
- fd = -1;
- flags |= MAP_ANON;
- }
- this.size = size;
- size_t initial_map = (window && 2*window= start && i < start+data.length;
- }
-
- // unmap the current range
- private void unmap()
- {
- debug (MMFILE) printf("MmFile.unmap()\n");
- version (Windows)
- {
- wenforce(!data.ptr || UnmapViewOfFile(data.ptr) != FALSE, "UnmapViewOfFile");
- }
- else
- {
- errnoEnforce(!data.ptr || munmap(cast(void*) data, data.length) == 0,
- "munmap failed");
- }
- data = null;
- }
-
- // map range
- private void map(ulong start, size_t len)
- {
- debug (MMFILE) printf("MmFile.map(%lld, %d)\n", start, len);
- void* p;
- if (start+len > size)
- len = cast(size_t)(size-start);
- version (Windows)
- {
- uint hi = cast(uint)(start >> 32);
- p = MapViewOfFileEx(hFileMap, dwDesiredAccess, hi, cast(uint) start, len, address);
- wenforce(p, "MapViewOfFileEx");
- }
- else
- {
- p = mmap(address, len, prot, flags, fd, cast(off_t) start);
- errnoEnforce(p != MAP_FAILED);
- }
- data = p[0 .. len];
- this.start = start;
- }
-
- // ensure a given position is mapped
- private void ensureMapped(ulong i)
- {
- debug (MMFILE) printf("MmFile.ensureMapped(%lld)\n", i);
- if (!mapped(i))
- {
- unmap();
- if (window == 0)
- {
- map(0,cast(size_t) size);
- }
- else
- {
- ulong block = i/window;
- if (block == 0)
- map(0,2*window);
- else
- map(window*(block-1),3*window);
- }
- }
- }
-
- // ensure a given range is mapped
- private void ensureMapped(ulong i, ulong j)
- {
- debug (MMFILE) printf("MmFile.ensureMapped(%lld, %lld)\n", i, j);
- if (!mapped(i) || !mapped(j-1))
- {
- unmap();
- if (window == 0)
- {
- map(0,cast(size_t) size);
- }
- else
- {
- ulong iblock = i/window;
- ulong jblock = (j-1)/window;
- if (iblock == 0)
- {
- map(0,cast(size_t)(window*(jblock+2)));
- }
- else
- {
- map(window*(iblock-1),cast(size_t)(window*(jblock-iblock+3)));
- }
- }
- }
- }
-
-private:
- string filename;
- void[] data;
- ulong start;
- size_t window;
- ulong size;
- Mode mMode;
- void* address;
- version (linux) File file;
-
- version (Windows)
- {
- HANDLE hFile = INVALID_HANDLE_VALUE;
- HANDLE hFileMap = null;
- uint dwDesiredAccess;
- }
- else version (Posix)
- {
- int fd;
- int prot;
- int flags;
- int fmode;
- } else {
- static assert(0);
- }
-}+/
\ No newline at end of file
diff --git a/src/os/path.d b/src/os/path.d
index a819899..934c02d 100644
--- a/src/os/path.d
+++ b/src/os/path.d
@@ -37,7 +37,13 @@
{
version (Windows)
{
- // 1. SHGetFolderPath
+ // 1. %USERPROFILE%
+ if ("USERPROFILE" in environment)
+ return environment["USERPROFILE"];
+ // 2. %HOMEDRIVE% and %HOMEPATH%
+ if ("HOMEDRIVE" in environment && "HOMEPATH" in environment)
+ return environment["HOMEDRIVE"] ~ environment["HOMEPATH"];
+ // 3. SHGetFolderPath
wchar *buffer = cast(wchar*)malloc(1024);
if (SHGetFolderPathW(null, CSIDL_PROFILE, null, 0, buffer) == S_OK)
{
@@ -47,12 +53,6 @@
return path;
}
free(buffer);
- // 2. %USERPROFILE%
- if ("USERPROFILE" in environment)
- return environment["USERPROFILE"];
- // 3. %HOMEDRIVE% and %HOMEPATH%
- if ("HOMEDRIVE" in environment && "HOMEPATH" in environment)
- return environment["HOMEDRIVE"] ~ environment["HOMEPATH"];
}
else version (Posix)
{
@@ -77,16 +77,19 @@
/// Get the path to the current user data folder.
/// This does not verify if the path exists.
-/// Windows: Typically C:\\Users\\%USERNAME%\\AppData\\Roaming
+/// Windows: Typically C:\\Users\\%USERNAME%\\AppData\\Local
/// Posix: Typically /home/$USERNAME/.config
/// Returns: Path or null on failure.
-string getUserDataFolder()
+string getUserConfigFolder()
{
version (Windows)
{
- // 1. SHGetFolderPath
+ // 1. %LOCALAPPDATA%
+ if ("LOCALAPPDATA" in environment)
+ return environment["LOCALAPPDATA"];
+ // 2. SHGetFolderPath
wchar *buffer = cast(wchar*)malloc(1024);
- if (SHGetFolderPathW(null, CSIDL_APPDATA, null, 0, buffer) == S_OK)
+ if (SHGetFolderPathW(null, CSIDL_LOCAL_APPDATA, null, 0, buffer) == S_OK)
{
string path;
transcode(buffer[0..wcslen(buffer)], path);
@@ -94,15 +97,11 @@
return path;
}
free(buffer);
- // 2. %APPDATA%
- // Is is the exact same with CSIDL_APPDATA but anything can go wrong.
- if ("APPDATA" in environment)
- return environment["APPDATA"];
}
else version (Posix)
{
- if ("XDG_CONFIG_HOME" in environment)
- return environment["XDG_CONFIG_HOME"];
+ if (const(string) *xdg_config_home = "XDG_CONFIG_HOME" in environment)
+ return *xdg_config_home;
}
// Fallback
@@ -148,7 +147,7 @@
/// Returns: Path or null on failure.
string buildUserAppFile(string appname, string filename)
{
- string base = getUserDataFolder;
+ string base = getUserConfigFolder;
if (base is null)
return null;
diff --git a/src/os/terminal.d b/src/os/terminal.d
index 02e1084..2648fca 100644
--- a/src/os/terminal.d
+++ b/src/os/terminal.d
@@ -7,44 +7,43 @@
//TODO: readline
// automatically pause input, stdio.readln, resume input
//TODO: Switch key input depending on $TERM
-// xterm, xterm-color, xterm-256color, linux, vt100
-//TODO: Invert color support
+// xterm (xterm, xterm-color, xterm-256color), linux, vt100
+//TODO: BetterC coverage (using D_BetterC version)
// NOTE: Useful links for escape codes
// https://man7.org/linux/man-pages/man0/termios.h.0p.html
// https://man7.org/linux/man-pages/man3/tcsetattr.3.html
// https://man7.org/linux/man-pages/man4/console_codes.4.html
-///
-private extern (C) int putchar(int);
-
private import std.stdio : _IONBF, _IOLBF, _IOFBF, stdin, stdout;
-version (TestInput) private import std.stdio : printf;
private import core.stdc.stdlib : system, atexit;
+version (unittest)
+{
+ private import core.stdc.stdio : printf;
+ private extern (C) int putchar(int);
+}
version (Windows)
{
- //TODO: To reduce output binary, import only modules necessary
- private import core.sys.windows.windows;
- private import std.windows.syserror : WindowsException;
- private enum ALT_PRESSED = RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED;
- private enum CTRL_PRESSED = RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED;
- private enum DEFAULT_COLOR =
- FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
+ import core.sys.windows.winbase;
+ import core.sys.windows.wincon;
+ import core.sys.windows.windef; // HANDLE, USHORT, DWORD
+ import std.windows.syserror : WindowsException;
private enum CP_UTF8 = 65_001;
private __gshared HANDLE hIn, hOut;
- private __gshared USHORT defaultColor = DEFAULT_COLOR;
private __gshared DWORD oldCP;
private __gshared ushort oldAttr;
}
else version (Posix)
{
- private import core.stdc.stdio : snprintf;
- 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;
- private import core.sys.posix.signal;
+ import core.stdc.stdio : snprintf;
+ import core.sys.posix.sys.stat;
+ import core.sys.posix.sys.ioctl;
+ import core.sys.posix.unistd;
+ import core.sys.posix.termios;
+ import core.sys.posix.signal;
+ import core.sys.posix.unistd : write, STDOUT_FILENO;
+ import core.sys.posix.sys.types : ssize_t;
private enum NULL_SIGACTION = cast(sigaction_t*)0;
private enum SIGWINCH = 28;
@@ -107,67 +106,67 @@
int value;
}
immutable KeyInfo[] keyInputsVTE = [
- // text Key value
- { "\033[A", Key.UpArrow },
- { "\033[1;2A", Key.UpArrow | Mod.shift },
- { "\033[1;3A", Key.UpArrow | Mod.alt },
- { "\033[1;5A", Key.UpArrow | Mod.ctrl },
- { "\033[A:4A", Key.UpArrow | Mod.shift | Mod.alt },
- { "\033[B", Key.DownArrow },
- { "\033[1;2B", Key.DownArrow | Mod.shift },
- { "\033[1;3B", Key.DownArrow | Mod.alt },
- { "\033[1;5B", Key.DownArrow | Mod.ctrl },
- { "\033[A:4B", Key.DownArrow | Mod.shift | Mod.alt },
- { "\033[C", Key.RightArrow },
- { "\033[1;2C", Key.RightArrow | Mod.shift },
- { "\033[1;3C", Key.RightArrow | Mod.alt },
- { "\033[1;5C", Key.RightArrow | Mod.ctrl },
- { "\033[A:4C", Key.RightArrow | Mod.shift | Mod.alt },
- { "\033[D", Key.LeftArrow },
- { "\033[1;2D", Key.LeftArrow | Mod.shift },
- { "\033[1;3D", Key.LeftArrow | Mod.alt },
- { "\033[1;5D", Key.LeftArrow | Mod.ctrl },
- { "\033[A:4D", Key.LeftArrow | Mod.shift | Mod.alt },
+ // text Key value
+ { "\033[A", Key.UpArrow },
+ { "\033[1;2A", Key.UpArrow | Mod.shift },
+ { "\033[1;3A", Key.UpArrow | Mod.alt },
+ { "\033[1;5A", Key.UpArrow | Mod.ctrl },
+ { "\033[A:4A", Key.UpArrow | Mod.shift | Mod.alt },
+ { "\033[B", Key.DownArrow },
+ { "\033[1;2B", Key.DownArrow | Mod.shift },
+ { "\033[1;3B", Key.DownArrow | Mod.alt },
+ { "\033[1;5B", Key.DownArrow | Mod.ctrl },
+ { "\033[A:4B", Key.DownArrow | Mod.shift | Mod.alt },
+ { "\033[C", Key.RightArrow },
+ { "\033[1;2C", Key.RightArrow | Mod.shift },
+ { "\033[1;3C", Key.RightArrow | Mod.alt },
+ { "\033[1;5C", Key.RightArrow | Mod.ctrl },
+ { "\033[A:4C", Key.RightArrow | Mod.shift | Mod.alt },
+ { "\033[D", Key.LeftArrow },
+ { "\033[1;2D", Key.LeftArrow | Mod.shift },
+ { "\033[1;3D", Key.LeftArrow | Mod.alt },
+ { "\033[1;5D", Key.LeftArrow | Mod.ctrl },
+ { "\033[A:4D", Key.LeftArrow | Mod.shift | Mod.alt },
{ "\033[2~", Key.Insert },
- { "\033[2;3~", Key.Insert | Mod.alt },
+ { "\033[2;3~", Key.Insert | Mod.alt },
{ "\033[3~", Key.Delete },
- { "\033[3;5~", Key.Delete | Mod.ctrl },
- { "\033[H", Key.Home },
- { "\033[1;3H", Key.Home | Mod.alt },
- { "\033[1;5H", Key.Home | Mod.ctrl },
- { "\033[F", Key.End },
- { "\033[1;3F", Key.End | Mod.alt },
- { "\033[1;5F", Key.End | Mod.ctrl },
+ { "\033[3;5~", Key.Delete | Mod.ctrl },
+ { "\033[H", Key.Home },
+ { "\033[1;3H", Key.Home | Mod.alt },
+ { "\033[1;5H", Key.Home | Mod.ctrl },
+ { "\033[F", Key.End },
+ { "\033[1;3F", Key.End | Mod.alt },
+ { "\033[1;5F", Key.End | Mod.ctrl },
{ "\033[5~", Key.PageUp },
- { "\033[5;5~", Key.PageUp | Mod.ctrl },
+ { "\033[5;5~", Key.PageUp | Mod.ctrl },
{ "\033[6~", Key.PageDown },
- { "\033[6;5~", Key.PageDown | Mod.ctrl },
- { "\033OP", Key.F1 },
- { "\033[1;2P", Key.F1 | Mod.shift, },
- { "\033[1;3R", Key.F1 | Mod.alt, },
- { "\033[1;5P", Key.F1 | Mod.ctrl, },
- { "\033OQ", Key.F2 },
- { "\033[1;2Q", Key.F2 | Mod.shift },
- { "\033OR", Key.F3 },
- { "\033[1;2R", Key.F3 | Mod.shift },
- { "\033OS", Key.F4 },
- { "\033[1;2S", Key.F4 | Mod.shift },
- { "\033[15~", Key.F5 },
- { "\033[15;2~", Key.F5 | Mod.shift },
- { "\033[17~", Key.F6 },
- { "\033[17;2~", Key.F6 | Mod.shift },
- { "\033[18~", Key.F7 },
- { "\033[18;2~", Key.F7 | Mod.shift },
- { "\033[19~", Key.F8 },
- { "\033[19;2~", Key.F8 | Mod.shift },
- { "\033[20~", Key.F9 },
- { "\033[20;2~", Key.F9 | Mod.shift },
- { "\033[21~", Key.F10 },
- { "\033[21;2~", Key.F10 | Mod.shift },
- { "\033[23~", Key.F11 },
- { "\033[23;2~", Key.F11 | Mod.shift},
- { "\033[24~", Key.F12 },
- { "\033[24;2~", Key.F12 | Mod.shift },
+ { "\033[6;5~", Key.PageDown | Mod.ctrl },
+ { "\033OP", Key.F1 },
+ { "\033[1;2P", Key.F1 | Mod.shift, },
+ { "\033[1;3R", Key.F1 | Mod.alt, },
+ { "\033[1;5P", Key.F1 | Mod.ctrl, },
+ { "\033OQ", Key.F2 },
+ { "\033[1;2Q", Key.F2 | Mod.shift },
+ { "\033OR", Key.F3 },
+ { "\033[1;2R", Key.F3 | Mod.shift },
+ { "\033OS", Key.F4 },
+ { "\033[1;2S", Key.F4 | Mod.shift },
+ { "\033[15~", Key.F5 },
+ { "\033[15;2~", Key.F5 | Mod.shift },
+ { "\033[17~", Key.F6 },
+ { "\033[17;2~", Key.F6 | Mod.shift },
+ { "\033[18~", Key.F7 },
+ { "\033[18;2~", Key.F7 | Mod.shift },
+ { "\033[19~", Key.F8 },
+ { "\033[19;2~", Key.F8 | Mod.shift },
+ { "\033[20~", Key.F9 },
+ { "\033[20;2~", Key.F9 | Mod.shift },
+ { "\033[21~", Key.F10 },
+ { "\033[21;2~", Key.F10 | Mod.shift },
+ { "\033[23~", Key.F11 },
+ { "\033[23;2~", Key.F11 | Mod.shift},
+ { "\033[24~", Key.F12 },
+ { "\033[24;2~", Key.F12 | Mod.shift },
];
private __gshared termios old_ios, new_ios;
@@ -177,23 +176,24 @@
//TODO: captureCtrlC: Block CTRL+C
enum TermFeat : ushort {
/// Initiate only the basic.
- none = 0,
+ none = 0,
/// Initiate the input system.
inputSys = 1,
/// Initiate the alternative screen buffer.
- altScreen = 1 << 1,
+ altScreen = 1 << 1,
/// Initiate everything.
- all = 0xffff,
+ all = 0xffff,
}
-private __gshared TermFeat current_features;
+private __gshared int current_features;
/// Initiate terminal.
/// Params: features = Feature bits to initiate.
/// Throws: (Windows) WindowsException on OS exception
-void terminalInit(TermFeat features)
+void terminalInit(int features)
{
current_features = features;
+
version (Windows)
{
CONSOLE_SCREEN_BUFFER_INFO csbi = void;
@@ -252,7 +252,9 @@
if (SetConsoleActiveScreenBuffer(hOut) == FALSE)
throw new WindowsException(GetLastError);
- } else {
+ }
+ else
+ {
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
}
@@ -322,21 +324,21 @@
private extern (C)
void terminalQuit()
{
- terminalRestore;
+ terminalRestore();
}
/// Restore CP and other settings
void terminalRestore()
{
version (Windows)
-{
+ {
SetConsoleOutputCP(oldCP); // unconditionally
}
else version (Posix)
{
// restore main screen buffer
if (current_features & TermFeat.altScreen)
- terminalOutput2("\033[?1049l");
+ terminalOut("\033[?1049l");
terminalShowCursor;
}
if (current_features & TermFeat.inputSys)
@@ -393,14 +395,15 @@
FillConsoleOutputAttribute(hOut, csbi.wAttributes, size, c, &num) == 0*/)
{
terminalPos(0, 0);
- } else // If that fails, run cls.
+ }
+ else // If that fails, run cls.
system("cls");
}
else version (Posix)
{
// \033c is a Reset
// \033[2J is "Erase whole display"
- terminalOutput2("\033[2J");
+ terminalOut("\033[2J");
} else static assert(0, "Clear: Not implemented");
}
@@ -413,8 +416,8 @@
{
CONSOLE_SCREEN_BUFFER_INFO c = void;
GetConsoleScreenBufferInfo(hOut, &c);
- size.height = c.srWindow.Bottom - c.srWindow.Top + 1;
- size.width = c.srWindow.Right - c.srWindow.Left + 1;
+ size.rows = c.srWindow.Bottom - c.srWindow.Top + 1;
+ size.columns = c.srWindow.Right - c.srWindow.Left + 1;
}
else version (Posix)
{
@@ -424,8 +427,8 @@
// Reply: ESC [ 8 ; ROWS ; COLUMNS t
winsize ws = void;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
- size.height = ws.ws_row;
- size.width = ws.ws_col;
+ size.rows = ws.ws_row;
+ size.columns = ws.ws_col;
} else static assert(0, "terminalSize: Not implemented");
return size;
}
@@ -449,7 +452,7 @@
char[16] b = void;
int r = snprintf(b.ptr, 16, "\033[%d;%dH", ++y, ++x);
assert(r > 0);
- terminalOutput(b.ptr, r);
+ terminalOut(b.ptr, r);
}
}
@@ -465,7 +468,7 @@
}
else version (Posix)
{
- terminalOutput2("\033[?25l");
+ terminalOut("\033[?25l");
}
}
/// Show the terminal cursor.
@@ -480,7 +483,7 @@
}
else version (Posix)
{
- terminalOutput2("\033[?25h");
+ terminalOut("\033[?25h");
}
}
@@ -492,7 +495,7 @@
}
else version (Posix)
{
- terminalOutput2("\033[41m");
+ terminalOut("\033[41m");
}
}
/// Invert color.
@@ -504,7 +507,7 @@
}
else version (Posix)
{
- terminalOutput2("\033[7m");
+ terminalOut("\033[7m");
}
}
/// Underline.
@@ -517,7 +520,7 @@
}
else version (Posix)
{
- terminalOutput2("\033[4m");
+ terminalOut("\033[4m");
}
}
/// Reset color.
@@ -529,13 +532,13 @@
}
else version (Posix)
{
- terminalOutput2("\033[0m");
+ terminalWrite("\033[0m");
}
}
-size_t terminalOutput2(const(void)[] data)
+size_t terminalWrite(const(void)[] data)
{
- return terminalOutput(data.ptr, data.length);
+ return terminalWrite(data.ptr, data.length);
}
/// Directly write to output.
@@ -543,49 +546,56 @@
/// data = Character data.
/// size = Amount in bytes.
/// Returns: Number of bytes written.
-size_t terminalOutput(const(void) *data, size_t size)
+size_t terminalWrite(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;
}
}
-/// Read an input event. This function is blocking.
-/// Params:
-/// event = TerminalInfo struct
-/// Throws: (Windows) WindowsException on OS error
-void terminalInput(ref TerminalInput event)
+// Commented until terminalRead() improves
+/*template CTRL(int c) { enum CTRL = c & 0x1f; }
+unittest
{
+ assert(CTRL!('a') == 0xa);
+}*/
+
+/// Read an input event. This function is blocking.
+/// Throws: (Windows) WindowsException on OS error.
+/// Returns: Terminal input.
+TermInput terminalRead()
+{
+ TermInput event;
+
version (Windows)
{
+ enum ALT_PRESSED = RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED;
+ enum CTRL_PRESSED = RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED;
+
INPUT_RECORD ir = void;
DWORD num = void;
-L_READ:
+Lread:
if (ReadConsoleInputA(hIn, &ir, 1, &num) == 0)
throw new WindowsException(GetLastError);
if (num == 0)
- goto L_READ;
+ goto Lread;
switch (ir.EventType) {
case KEY_EVENT:
if (ir.KeyEvent.bKeyDown == FALSE)
- goto L_READ;
+ goto Lread;
- version (TestInput)
+ version (unittest)
{
printf(
"KeyEvent: AsciiChar=%d wVirtualKeyCode=%d dwControlKeyState=%x\n",
@@ -599,7 +609,7 @@
// Filter out single modifier key events
switch (keycode) {
- case 16, 17, 18: goto L_READ; // shift,ctrl,alt
+ case 16, 17, 18: goto Lread; // shift,ctrl,alt
default:
}
@@ -610,7 +620,7 @@
if (ascii >= 'a' && ascii <= 'z')
{
event.key = ascii - 32;
- return;
+ return event;
}
else if (ascii >= 0x20 && ascii < 0x7f)
{
@@ -621,7 +631,7 @@
if (ascii >= 'A' && ascii <= 'Z')
event.key = Mod.shift;
- return;
+ return event;
}
event.key = keycode;
@@ -629,7 +639,7 @@
if (state & ALT_PRESSED) event.key |= Mod.alt;
if (state & CTRL_PRESSED) event.key |= Mod.ctrl;
if (state & SHIFT_PRESSED) event.key |= Mod.shift;
- return;
+ return event;
/*case MOUSE_EVENT:
if (ir.MouseEvent.dwEventFlags & MOUSE_WHEELED)
{
@@ -640,8 +650,8 @@
case WINDOW_BUFFER_SIZE_EVENT:
if (terminalOnResizeEvent)
terminalOnResizeEvent();
- goto L_READ;
- default: goto L_READ;
+ goto Lread;
+ default: goto Lread;
}
}
else version (Posix)
@@ -684,31 +694,33 @@
enum BLEN = 8;
char[BLEN] b = void;
- L_READ:
+ Lread:
ssize_t r = read(STDIN_FILENO, b.ptr, BLEN);
event.type = InputType.keyDown; // Assuming for now
event.key = 0; // clear as safety measure
switch (r) {
- case -1: assert(0, "read(2) failed");
+ case -1:
+ assert(0, "read(2) failed");
+ goto Lread;
case 0: // How even
- version (TestInput) printf("stdin: empty\n");
- goto L_READ;
+ version (unittest) printf("stdin: empty\n");
+ goto Lread;
case 1:
char c = b[0];
- version (TestInput) printf("stdin: \\0%o (%d)\n", c, c);
+ version (unittest) printf("stdin: \\0%o (%d)\n", c, c);
// Filtering here adjusts the value only if necessary.
switch (c) {
case 0: // Ctrl+Space
event.key = Key.Spacebar | Mod.ctrl;
- return;
+ return event;
case 13:
event.key = Key.Enter;
- return;
+ return event;
case 8, 127: // ^H
event.key = Key.Backspace;
- return;
+ return event;
default:
}
if (c >= 'a' && c <= 'z')
@@ -723,7 +735,7 @@
default:
}
- version (TestInput)
+ version (unittest)
{
printf("stdin:");
for (size_t i; i < r; ++i)
@@ -750,31 +762,60 @@
if (r != ki.text.length) continue;
if (inputString != ki.text) continue;
event.key = ki.value;
- return;
+ return event;
}
// Matched to nothing
- goto L_READ;
+ goto Lread;
} // version posix
}
+
/// Terminal input type.
-enum InputType {
+enum InputType
+{
keyDown,
keyUp,
mouseDown,
mouseUp,
}
+//TODO: Redo input encoding
+// 15: 0 key value (ascii or special key)
+// 23:16 flags
+// 0000 0000
+// |||| ||+-- Alt
+// |||| |+--- Ctrl
+// |||+------ Special key
+// 31:24 input type
+// 0 keydown
+// 1 mousedown
+// 128 keyup
+// 129 mouseup
+
+/*private enum specialbit = 1 << 20;
+enum Special
+{
+ rightArrow = 1 | specialbit,
+ leftArrow = 2 | specialbit,
+ upArrow = 3 | specialbit,
+ Arrow = 4 | specialbit,
+
+
+}*/
+
/// Key modifier
-enum Mod {
- ctrl = 1 << 16,
- shift = 1 << 17,
- alt = 1 << 18,
+enum Mod
+{
+ ctrl = 1 << 24,
+ shift = 1 << 25,
+ alt = 1 << 26,
}
+
/// Key codes map.
//TODO: Consider mapping these to os/ascii-specific
-enum Key {
+enum Key
+{
Undefined = 0,
Backspace = 8,
Tab = 9,
@@ -925,10 +966,13 @@
}
/// Terminal input structure
-struct TerminalInput {
- union {
+struct TermInput
+{
+ union
+ {
int key; /// Keyboard input with possible Mod flags.
- struct {
+ struct
+ {
ushort mouseX; /// Mouse column coord
ushort mouseY; /// Mouse row coord
}
@@ -937,15 +981,10 @@
}
/// Terminal size structure
-struct TerminalSize {
- union {
- /// Terminal width in character columns
- int width;
- int columns; /// Ditto
- }
- union {
- /// Terminal height in character rows
- int height;
- int rows; /// Ditto
- }
+struct TerminalSize
+{
+ /// Terminal width in character columns
+ int columns;
+ /// Terminal height in character rows
+ int rows;
}
diff --git a/src/reverser.d b/src/reverser.d
deleted file mode 100644
index 8b6eecf..0000000
--- a/src/reverser.d
+++ /dev/null
@@ -1,79 +0,0 @@
-module reverser;
-
-import std.stdio;
-import editor;
-import os.file;
-import error;
-
-int start(string outpath)
-{
- // editor: hex input
- // outfile: binary output
- enum BUFSZ = 4096;
- ubyte[BUFSZ] data = void;
-
- OSFile binfd = void;
- if (binfd.open(outpath, OFlags.write))
- {
- stderr.writefln("error: %s", systemMessage(binfd.syscode()));
- return 2;
- }
-
-L_READ:
- ubyte[] r = editor.read(data);
-
- if (editor.err)
- {
- return 3;
- }
-
- foreach (ubyte b; r)
- {
- if (b >= '0' && b <= '9')
- {
- outnibble(binfd, b - 0x30);
- }
- else if (b >= 'a' && b <= 'f')
- {
- outnibble(binfd, b - 0x57);
- }
- else if (b >= 'A' && b <= 'F')
- {
- outnibble(binfd, b - 0x37);
- }
- }
-
- if (editor.eof)
- {
- outfinish(binfd);
- return 0;
- }
-
- goto L_READ;
-}
-
-private:
-
-__gshared bool low;
-__gshared ubyte data;
-
-void outnibble(ref OSFile file, int nibble)
-{
- if (low == false)
- {
- data = cast(ubyte)(nibble << 4);
- low = true;
- return;
- }
-
- low = false;
- ubyte b = cast(ubyte)(data | nibble);
- file.write(&b, 1);
-}
-
-void outfinish(ref OSFile file)
-{
- if (low == false) return;
-
- file.write(&data, 1);
-}
\ No newline at end of file
diff --git a/src/screen.d b/src/screen.d
deleted file mode 100644
index d8edd8c..0000000
--- a/src/screen.d
+++ /dev/null
@@ -1,775 +0,0 @@
-/// Terminal screen handling.
-/// Copyright: dd86k
-/// License: MIT
-/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
-module screen;
-
-import std.range : chunks;
-import ddhx; // for setting, NumberType
-import os.terminal, os.file;
-import core.stdc.string : memset;
-import core.stdc.stdlib : malloc, free;
-import std.outbuffer : OutBuffer;
-
-//TODO: Consider renaming module to render
-// And rename all functions to remove "render" prefix
-//TODO: Data viewer groups
-// hex: h8, h16, h32, h64
-// signed decimal: i8, i16, i32, i64
-// unsigned decimal: u8, u16, u32, u64
-// signed octal: oi8, oi16, oi32, oi64
-// unsigned octal: ou8, ou16, ou32, ou64
-// float: f32, f64
-//TODO: Group endianness (when >1)
-// native (default), little, big
-//TODO: View display mode (data+text, data, text)
-// Currently very low priority
-//TODO: Unaligned rendering.
-// 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.
-
-private
-{
- /// Length of offset space taken on screen
- enum OFFSET_SPACE = 11; // Not currently a setting, but should be
-}
-
-private struct NumberFormatter
-{
- string name; /// Short offset name
- char fmtchar; /// Format character for printf-like functions
- ubyte size; /// Size for formatted byte (excluding space)
- size_t function(char*,long) offset; /// Function to format offset
- size_t function(char*,ubyte) data; /// Function to format data
-}
-
-//TODO: Replace with OffsetFormatter and BinaryFormatter structures
-// XXXFormatter.select()
-
-private immutable NumberFormatter[3] formatters = [
- { "hex", 'x', 2, &format11x, &format02x },
- { "dec", 'u', 3, &format11d, &format03d },
- { "oct", 'o', 3, &format11o, &format03o },
-];
-
-/// Last known terminal size.
-__gshared TerminalSize termSize;
-///
-__gshared uint maxLine = uint.max;
-/// Offset formatter.
-__gshared NumberFormatter offsetFormatter = formatters[0];
-/// Binary data formatter.
-__gshared NumberFormatter binaryFormatter = formatters[0];
-
-int initiate()
-{
- terminalInit(TermFeat.all);
-
- updateTermSize;
-
- if (termSize.height < 3)
- return errorSet(ErrorCode.screenMinimumRows);
- if (termSize.width < 20)
- return errorSet(ErrorCode.screenMinimumColumns);
-
- terminalHideCursor;
-
- return 0;
-}
-
-void updateTermSize()
-{
- termSize = terminalSize;
- maxLine = termSize.height - 2;
-}
-
-void onResize(void function() func)
-{
- terminalOnResize(func);
-}
-
-void setOffsetFormat(NumberType type)
-{
- offsetFormatter = formatters[type];
-}
-void setBinaryFormat(NumberType type)
-{
- binaryFormatter = formatters[type];
-}
-
-/// Clear entire terminal screen
-void clear()
-{
- terminalClear;
-}
-
-/*void clearStatusBar()
-{
- screen.cwritefAt(0,0,"%*s", termSize.width - 1, " ");
-}*/
-
-/// Display a formatted message at the bottom of the screen.
-/// Params:
-/// fmt = Formatting message string.
-/// args = Arguments.
-void message(A...)(const(char)[] fmt, A args)
-{
- //TODO: Consider using a scoped outbuffer + private message(outbuf)
- import std.format : format;
- message(format(fmt, args));
-}
-/// Display a message at the bottom of the screen.
-/// Params: str = Message.
-void message(const(char)[] str)
-{
- terminalPos(0, termSize.height - 1);
- cwritef("%-*s", termSize.width - 1, str);
-}
-
-string prompt(string prefix, string include)
-{
- import std.stdio : readln;
- import std.string : chomp;
-
- scope (exit)
- {
- cursorOffset;
- renderOffset;
- }
-
- clearOffsetBar;
-
- terminalPos(0, 0);
-
- cwrite(prefix);
- if (include) cwrite(include);
-
- terminalShowCursor;
- terminalPauseInput;
-
- string line = include ~ chomp(readln());
-
- terminalHideCursor;
- terminalResumeInput;
-
- return line;
-}
-
-//
-// SECTION Rendering
-//
-
-//TODO: Move formatting stuff to module format.
-
-private immutable string hexMap = "0123456789abcdef";
-
-private
-size_t format02x(char *buffer, ubyte v)
-{
- buffer[1] = hexMap[v & 15];
- buffer[0] = hexMap[v >> 4];
- return 2;
-}
-@system unittest {
- char[2] c = void;
- format02x(c.ptr, 0x01);
- assert(c[] == "01", c);
- format02x(c.ptr, 0x20);
- assert(c[] == "20", c);
- format02x(c.ptr, 0xff);
- assert(c[] == "ff", c);
-}
-private
-size_t format11x(char *buffer, long v)
-{
- size_t pos;
- bool pad = true;
- for (int shift = 60; shift >= 0; shift -= 4)
- {
- const ubyte b = (v >> shift) & 15;
- if (b == 0)
- {
- if (pad && shift >= 44)
- {
- continue; // cut
- }
- else if (pad && shift >= 4)
- {
- buffer[pos++] = pad ? ' ' : '0';
- continue; // pad
- }
- } else pad = false;
- buffer[pos++] = hexMap[b];
- }
- return pos;
-}
-///
-@system unittest {
- char[32] b = void;
- char *p = b.ptr;
- assert(b[0..format11x(p, 0)] == " 0");
- assert(b[0..format11x(p, 1)] == " 1");
- assert(b[0..format11x(p, 0x10)] == " 10");
- assert(b[0..format11x(p, 0x100)] == " 100");
- assert(b[0..format11x(p, 0x1000)] == " 1000");
- assert(b[0..format11x(p, 0x10000)] == " 10000");
- assert(b[0..format11x(p, 0x100000)] == " 100000");
- assert(b[0..format11x(p, 0x1000000)] == " 1000000");
- assert(b[0..format11x(p, 0x10000000)] == " 10000000");
- assert(b[0..format11x(p, 0x100000000)] == " 100000000");
- assert(b[0..format11x(p, 0x1000000000)] == " 1000000000");
- assert(b[0..format11x(p, 0x10000000000)] == "10000000000");
- assert(b[0..format11x(p, 0x100000000000)] == "100000000000");
- assert(b[0..format11x(p, 0x1000000000000)] == "1000000000000");
- assert(b[0..format11x(p, ubyte.max)] == " ff");
- assert(b[0..format11x(p, ushort.max)] == " ffff");
- assert(b[0..format11x(p, uint.max)] == " ffffffff");
- assert(b[0..format11x(p, ulong.max)] == "ffffffffffffffff");
- assert(b[0..format11x(p, 0x1010)] == " 1010");
- assert(b[0..format11x(p, 0x10101010)] == " 10101010");
- assert(b[0..format11x(p, 0x1010101010101010)] == "1010101010101010");
-}
-
-private immutable static string decMap = "0123456789";
-private
-size_t format03d(char *buffer, ubyte v)
-{
- buffer[2] = (v % 10) + '0';
- buffer[1] = (v / 10 % 10) + '0';
- buffer[0] = (v / 100 % 10) + '0';
- return 3;
-}
-@system unittest {
- char[3] c = void;
- format03d(c.ptr, 1);
- assert(c[] == "001", c);
- format03d(c.ptr, 10);
- assert(c[] == "010", c);
- format03d(c.ptr, 111);
- assert(c[] == "111", c);
-}
-private
-size_t format11d(char *buffer, long v)
-{
- debug import std.conv : text;
- enum ulong I64MAX = 10_000_000_000_000_000_000UL;
- size_t pos;
- bool pad = true;
- for (ulong d = I64MAX; d > 0; d /= 10)
- {
- const long r = (v / d) % 10;
- if (r == 0)
- {
- if (pad && d >= 100_000_000_000)
- {
- continue; // cut
- }
- else if (pad && d >= 10)
- {
- buffer[pos++] = pad ? ' ' : '0';
- continue;
- }
- } else pad = false;
- debug assert(r >= 0 && r < 10, "r="~r.text);
- buffer[pos++] = decMap[r];
- }
- return pos;
-}
-///
-@system unittest {
- char[32] b = void;
- char *p = b.ptr;
- assert(b[0..format11d(p, 0)] == " 0");
- assert(b[0..format11d(p, 1)] == " 1");
- assert(b[0..format11d(p, 10)] == " 10");
- assert(b[0..format11d(p, 100)] == " 100");
- assert(b[0..format11d(p, 1000)] == " 1000");
- assert(b[0..format11d(p, 10_000)] == " 10000");
- assert(b[0..format11d(p, 100_000)] == " 100000");
- assert(b[0..format11d(p, 1000_000)] == " 1000000");
- assert(b[0..format11d(p, 10_000_000)] == " 10000000");
- assert(b[0..format11d(p, 100_000_000)] == " 100000000");
- assert(b[0..format11d(p, 1000_000_000)] == " 1000000000");
- assert(b[0..format11d(p, 10_000_000_000)] == "10000000000");
- assert(b[0..format11d(p, 100_000_000_000)] == "100000000000");
- assert(b[0..format11d(p, 1000_000_000_000)] == "1000000000000");
- assert(b[0..format11d(p, ubyte.max)] == " 255");
- assert(b[0..format11d(p, ushort.max)] == " 65535");
- assert(b[0..format11d(p, uint.max)] == " 4294967295");
- assert(b[0..format11d(p, ulong.max)] == "18446744073709551615");
- assert(b[0..format11d(p, 1010)] == " 1010");
-}
-
-private
-size_t format03o(char *buffer, ubyte v)
-{
- buffer[2] = (v % 8) + '0';
- buffer[1] = (v / 8 % 8) + '0';
- buffer[0] = (v / 64 % 8) + '0';
- return 3;
-}
-@system unittest {
- import std.conv : octal;
- char[3] c = void;
- format03o(c.ptr, 1);
- assert(c[] == "001", c);
- format03o(c.ptr, octal!20);
- assert(c[] == "020", c);
- format03o(c.ptr, octal!133);
- assert(c[] == "133", c);
-}
-private
-size_t format11o(char *buffer, long v)
-{
- size_t pos;
- if (v >> 63) buffer[pos++] = '1'; // ulong.max coverage
- bool pad = true;
- for (int shift = 60; shift >= 0; shift -= 3)
- {
- const ubyte b = (v >> shift) & 7;
- if (b == 0)
- {
- if (pad && shift >= 33)
- {
- continue; // cut
- }
- else if (pad && shift >= 3)
- {
- buffer[pos++] = pad ? ' ' : '0';
- continue;
- }
- } else pad = false;
- buffer[pos++] = hexMap[b];
- }
- return pos;
-}
-///
-@system unittest {
- import std.conv : octal;
- char[32] b = void;
- char *p = b.ptr;
- assert(b[0..format11o(p, 0)] == " 0");
- assert(b[0..format11o(p, 1)] == " 1");
- assert(b[0..format11o(p, octal!10)] == " 10");
- assert(b[0..format11o(p, octal!20)] == " 20");
- assert(b[0..format11o(p, octal!100)] == " 100");
- assert(b[0..format11o(p, octal!1000)] == " 1000");
- assert(b[0..format11o(p, octal!10_000)] == " 10000");
- assert(b[0..format11o(p, octal!100_000)] == " 100000");
- assert(b[0..format11o(p, octal!1000_000)] == " 1000000");
- assert(b[0..format11o(p, octal!10_000_000)] == " 10000000");
- assert(b[0..format11o(p, octal!100_000_000)] == " 100000000");
- assert(b[0..format11o(p, octal!1000_000_000)] == " 1000000000");
- assert(b[0..format11o(p, octal!10_000_000_000)] == "10000000000");
- assert(b[0..format11o(p, octal!100_000_000_000)] == "100000000000");
- assert(b[0..format11o(p, ubyte.max)] == " 377");
- assert(b[0..format11o(p, ushort.max)] == " 177777");
- assert(b[0..format11o(p, uint.max)] == "37777777777");
- assert(b[0..format11o(p, ulong.max)] == "1777777777777777777777");
- assert(b[0..format11o(p, octal!101_010)] == " 101010");
-}
-
-// !SECTION
-
-//
-// SECTION Rendering
-//
-
-void cursorOffset()
-{
- terminalPos(0, 0);
-}
-void cursorContent()
-{
- terminalPos(0, 1);
-}
-void cursorStatusbar()
-{
- terminalPos(0, termSize.height - 1);
-}
-
-void clearOffsetBar()
-{
- screen.cwritefAt(0, 0, "%*s", termSize.width - 1, " ");
-}
-///
-//TODO: Add "edited" or '*' to end if file edited
-void renderOffset()
-{
- import std.conv : octal;
-
- version (Trace)
- {
- StopWatch sw = StopWatch(AutoStart.yes);
- }
-
- // Setup index formatting
- int datasize = binaryFormatter.size;
- __gshared char[4] offsetFmt = " %__";
- offsetFmt[2] = cast(char)(datasize + '0');
- offsetFmt[3] = formatters[setting.offsetType].fmtchar;
-
- scope outbuf = new OutBuffer();
- outbuf.reserve(16 + (setting.columns * datasize));
- outbuf.write("Offset(");
- outbuf.write(formatters[setting.offsetType].name);
- outbuf.write(") ");
-
- // Add offsets
- uint i;
- for (; i < setting.columns; ++i)
- outbuf.writef(offsetFmt, i);
- // Fill rest of terminal width if in interactive mode
- if (termSize.width)
- {
- for (i = cast(uint)outbuf.offset; i < termSize.width; ++i)
- 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");
- }
-}
-
-///
-void renderStatusBar(const(char)[][] items ...)
-{
- version (Trace)
- {
- StopWatch sw = StopWatch(AutoStart.yes);
- }
-
- int w = termSize.width;
- bool done;
-
- scope outbuf = new OutBuffer();
- outbuf.reserve(w);
- outbuf.put(' ');
- foreach (item; items)
- {
- if (outbuf.offset > 1) outbuf.put(" | ");
- if (outbuf.offset + item.length >= w)
- {
- size_t r = outbuf.offset + item.length - w;
- outbuf.put(item[0..r]);
- done = true;
- break;
- }
- outbuf.put(item);
- }
-
- if (done == false)
- {
- // Fill rest by space
- outbuf.data[outbuf.offset..w] = ' ';
- outbuf.offset = w; // used in .toBytes
- }
-
- version (Trace)
- {
- Duration a = sw.peek;
- }
-
- 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");
- }
-}
-
-//int outputLine(long base, ubyte[] data, int row, int cursor = -1)
-
-/// Render multiple lines on screen with optional cursor.
-/// Params:
-/// base = Offset base.
-/// data = data to render.
-/// cursor = Position of cursor.
-/// Returns: Number of rows printed. Negative numbers indicate error.
-int output(long base, ubyte[] data, int cursor = -1)
-{
- uint crow = void, ccol = void;
-
- if (data.length == 0)
- return 0;
-
- if (cursor < 0)
- crow = ccol = -1;
- else
- {
- crow = cursor / setting.columns;
- ccol = cursor % setting.columns;
- }
-
- version (Trace)
- {
- trace("P=%u D=%u R=%u C=%u",
- position, data.length, crow, ccol);
- StopWatch sw = StopWatch(AutoStart.yes);
- }
-
- size_t buffersz = // minimum anyway
- OFFSET_SPACE + 2 + // offset + spacer
- ((binaryFormatter.size + 1) * setting.columns) + // binary + spacer * cols
- (1 + (setting.columns * 3)); // spacer + text (utf-8)
-
- char *buffer = cast(char*)malloc(buffersz);
- if (buffer == null) return -1;
-
- int lines;
- foreach (chunk; chunks(data, setting.columns))
- {
- const bool cur_row = lines == crow;
-
- Row row = makerow(buffer, buffersz, chunk, base, ccol);
-
- if (cur_row)
- {
- version (Trace) trace("row.length=%u cbi=%u cbl=%u cti=%u ctl=%u bl=%u tl=%u",
- row.result.length,
- row.cursorBinaryIndex,
- row.cursorBinaryLength,
- row.cursorTextIndex,
- row.cursorTextLength,
- row.binaryLength,
- row.textLength);
-
- // between binary and text cursors
- size_t distance = row.cursorTextIndex -
- row.cursorBinaryIndex -
- row.cursorBinaryLength;
-
- char *p = buffer;
- // offset + pre-cursor binary
- p += cwrite(p, row.cursorBinaryIndex);
- // binary cursor
- terminalInvertColor;
- p += cwrite(p, row.cursorBinaryLength);
- terminalResetColor;
- // post-cursor binary + pre-cursor text (minus spacer)
- p += cwrite(p, distance);
- // text cursor
- terminalHighlight;
- p += cwrite(p, row.cursorTextLength);
- terminalResetColor;
- // post-cursor text
- size_t rem = row.result.length - (p - buffer);
- p += cwrite(p, rem);
-
- version (Trace) trace("d=%u r=%u l=%u", distance, rem, p - buffer);
- }
- else
- with (row) cwrite(result.ptr, result.length);
-
- cwrite('\n');
-
- ++lines;
- base += setting.columns;
- }
-
- free(buffer);
-
- version (Trace)
- {
- sw.stop;
- trace("time='%s µs'", sw.peek.total!"usecs");
- }
-
- return lines;
-}
-
-void renderEmpty(uint rows)
-{
- debug assert(termSize.rows);
- debug assert(termSize.columns);
-
- uint lines = maxLine - rows;
-
- version (Trace)
- {
- trace("lines=%u rows=%u cols=%u", lines, termSize.rows, termSize.columns);
- StopWatch sw = StopWatch(AutoStart.yes);
- }
-
- char *p = cast(char*)malloc(termSize.columns);
- assert(p); //TODO: Soft asserts
- int w = termSize.columns;
- memset(p, ' ', w);
-
- //TODO: Output to scoped OutBuffer
- for (int i; i < lines; ++i)
- cwrite(p, w);
-
- free(p);
-
- version (Trace)
- {
- sw.stop;
- trace("time='%s µs'", sw.peek.total!"usecs");
- }
-}
-
-private
-struct Row
-{
- char[] result;
-
- size_t cursorBinaryIndex;
- size_t cursorBinaryLength;
- size_t cursorTextIndex;
- size_t cursorTextLength;
-
- size_t binaryLength;
- size_t textLength;
-}
-
-private
-Row makerow(char *buffer, size_t bufferlen,
- ubyte[] chunk, long pos,
- int cursor_col)
-{
- Row row = void;
-
- // Insert OFFSET
- size_t indexData = offsetFormatter.offset(buffer, pos);
- buffer[indexData++] = ' '; // index: OFFSET + space
-
- const uint dataLen = (setting.columns * (binaryFormatter.size + 1)); /// data row character count
- size_t indexChar = indexData + dataLen; // Position for character column
-
- *(cast(ushort*)(buffer + indexChar)) = 0x2020; // DATA-CHAR spacer
- indexChar += 2; // indexChar: indexData + dataLen + spacer
-
- // Format DATA and CHAR
- // NOTE: Smaller loops could fit in cache...
- // And would separate data/text logic
- size_t bi0 = indexData, ti0 = indexChar;
- int currentCol;
- foreach (data; chunk)
- {
- const bool curhit = currentCol == cursor_col; // cursor hit column
- //TODO: Maybe binary data formatter should include space?
- // Data translation
- buffer[indexData++] = ' ';
- if (curhit)
- {
- row.cursorBinaryIndex = indexData;
- row.cursorBinaryLength = binaryFormatter.size;
- }
- indexData += binaryFormatter.data(buffer + indexData, data);
- // Character translation
- immutable(char)[] units = transcoder.transform(data);
- if (curhit)
- {
- row.cursorTextIndex = indexChar;
- row.cursorTextLength = units.length ? units.length : 1;
- }
- if (units.length) // Has utf-8 codepoints
- {
- foreach (codeunit; units)
- buffer[indexChar++] = codeunit;
- } else // Invalid character, insert default character
- buffer[indexChar++] = setting.defaultChar;
-
- ++currentCol;
- }
-
- row.binaryLength = dataLen - bi0;
- row.textLength = indexChar - ti0;
-
- size_t end = indexChar;
-
- // data length < minimum row requirement = in-fill data and text columns
- if (chunk.length < setting.columns)
- {
- // In-fill characters: left = Columns - ChunkLength
- size_t leftchar = (setting.columns - chunk.length); // Bytes left
- memset(buffer + indexChar, ' ', leftchar);
- row.textLength += leftchar;
- // In-fill binary data: left = CharactersLeft * (DataSize + 1)
- size_t leftdata = leftchar * (binaryFormatter.size + 1);
- memset(buffer + indexData, ' ', leftdata);
- row.binaryLength += leftdata;
-
- end += leftchar;
- }
-
- row.result = buffer[0..end];
-
- return row;
-}
-
-// !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 cwrite(char *_str, size_t size)
-{
- return terminalOutput(_str, size);
-}
-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[256] 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);
-}
-size_t cwriteAt(int x, int y, const(char)[] str)
-{
- terminalPos(x, y);
- return cwrite(str);
-}
-size_t cwritelnAt(int x, int y, const(char)[] str)
-{
- terminalPos(x, y);
- return cwriteln(str);
-}
-size_t cwritefAt(A...)(int x, int y, const(char)[] fmt, A args)
-{
- terminalPos(x, y);
- return cwritef(fmt, args);
-}
-size_t cwriteflnAt(A...)(int x, int y, const(char)[] fmt, A args)
-{
- terminalPos(x, y);
- return cwritefln(fmt, args);
-}
-
-// !SECTION
\ No newline at end of file
diff --git a/src/searcher.d b/src/searcher.d
deleted file mode 100644
index 06e1cfc..0000000
--- a/src/searcher.d
+++ /dev/null
@@ -1,255 +0,0 @@
-/// Search module.
-/// Copyright: dd86k
-/// License: MIT
-/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
-module searcher;
-
-import std.array : uninitializedArray;
-import core.stdc.string : memcmp;
-import editor, error;
-
-alias compare = memcmp; // avoids confusion with memcpy/memcmp
-
-/// Search buffer size.
-private enum BUFFER_SIZE = 4 * 1024;
-
-/*int data(ref Editor editor, out long pos, const(void) *data, size_t len, bool dir)
-{
-}*/
-
-/// Binary search.
-/// Params:
-/// pos = Found position.
-/// data = Needle pointer.
-/// len = Needle length.
-/// dir = Search direction. If set, forwards. If unset, backwards.
-/// Returns: Error code if set.
-int searchData(out long pos, const(void) *data, size_t len, bool dir)
-{
- int function(out long, const(void)*, size_t) F = dir ? &forward : &backward;
- return F(pos, data, len);
-}
-
-/// Search for binary data forward.
-/// Params:
-/// pos = Found position in file.
-/// data = Data pointer.
-/// len = Data length.
-/// Returns: Error code if set.
-private
-int forward(out long newPos, const(void) *data, size_t len)
-{
- version (Trace) trace("data=%s len=%u", data, len);
-
- ubyte *needle = cast(ubyte*)data;
- /// First byte for data to compare with haystack.
- const ubyte firstByte = needle[0];
- const bool firstByteOnly = len == 1;
- /// File buffer.
- ubyte[] fileBuffer = uninitializedArray!(ubyte[])(BUFFER_SIZE);
- /// Allocated if size is higher than one.
- /// Used to read file data to compare with needle if needle extends
- /// across haystack chunks.
- ubyte[] needleBuffer;
-
- if (firstByteOnly == false)
- needleBuffer = uninitializedArray!(ubyte[])(len);
-
- // Setup
- const long oldPos = editor.position;
- long pos = oldPos + 1; /// New haystack position
- editor.seek(pos);
-
- version (Trace) trace("start=%u", pos);
-
- size_t haystackIndex = void; /// haystack chunk index
-
-L_CONTINUE:
- ubyte[] haystack = editor.read(fileBuffer);
- const size_t haystackLen = haystack.length;
-
- // For every byte
- for (haystackIndex = 0; haystackIndex < haystackLen; ++haystackIndex)
- {
- // Check first byte
- if (haystack[haystackIndex] != firstByte) continue;
-
- // If first byte is indifferent and length is of 1, then
- // we're done.
- if (firstByteOnly) goto L_FOUND;
-
- // In-haystack or out-haystack check
- // Determined if needle fits within haystack (chunk)
- if (haystackIndex + len < haystackLen) // fits inside haystack
- {
- if (compare(haystack.ptr + haystackIndex, needle, len) == 0)
- goto L_FOUND;
- } else { // needle spans across haystacks
- const long t = pos + haystackIndex; // temporary seek
- editor.seek(t); // Go at chunk start + index
- ubyte[] tc = editor.read(needleBuffer); // Read needle length
- if (editor.eof) // Already hit past the end with needle length
- goto L_NOTFOUND; // No more chunks
- if (compare(tc.ptr, needle, len) == 0)
- goto L_FOUND;
- editor.seek(pos); // Negative, return to chunk start
- }
- }
-
- // Increase (search) position with chunk length.
- pos += haystackLen;
-
- // Check if last haystack.
- if (editor.eof == false) goto L_CONTINUE;
-
- // Not found
-L_NOTFOUND:
- editor.seek(oldPos);
- return errorSet(ErrorCode.notFound);
-
-L_FOUND: // Found
- newPos = pos + haystackIndex; // Position + Chunk index = Found position
- return 0;
-}
-
-/// Search for binary data backward.
-/// Params:
-/// pos = Found position in file.
-/// data = Data pointer.
-/// len = Data length.
-/// Returns: Error code if set.
-private
-int backward(out long newPos, const(void) *data, size_t len)
-{
- version (Trace) trace("data=%s len=%u", data, len);
-
- if (editor.position < 2)
- return errorSet(ErrorCode.insufficientSpace);
-
- ubyte *needle = cast(ubyte*)data;
- /// First byte for data to compare with haystack.
- const ubyte lastByte = needle[len - 1];
- const bool lastByteOnly = len == 1;
- /// File buffer.
- ubyte[] fileBuffer = uninitializedArray!(ubyte[])(BUFFER_SIZE);
- /// Allocated if size is higher than one.
- /// Used to read file data to compare with needle if needle extends
- /// across haystack chunks.
- ubyte[] needleBuffer;
-
- if (lastByteOnly == false) // No need for buffer if needle is a byte
- needleBuffer = uninitializedArray!(ubyte[])(len);
-
- // Setup
- const long oldPos = editor.position;
- long pos = oldPos - 1; /// Chunk position
- editor.seek(pos);
-
- version (Trace) trace("start=%u", pos);
-
- size_t haystackIndex = void;
- size_t haystackLen = BUFFER_SIZE;
- ptrdiff_t diff = void;
-
-L_CONTINUE:
- pos -= haystackLen;
-
- // Adjusts buffer size to read chunk if it goes past start of haystack.
- if (pos < 0)
- {
- haystackLen += pos;
- fileBuffer = uninitializedArray!(ubyte[])(haystackLen);
- pos = 0;
- }
-
- editor.seek(pos);
- ubyte[] haystack = editor.read;
-
- // For every byte
- for (haystackIndex = haystackLen; haystackIndex-- > 0;)
- {
- // Check last byte
- if (haystack[haystackIndex] != lastByte) continue;
-
- // Fix when needle is one byte
- diff = 0;
-
- // If first byte is indifferent and length is of 1, then
- // we're done.
- if (lastByteOnly) goto L_FOUND;
-
- // Go at needle
- diff = haystackIndex - len + 1; // Go at needle[0]
-
- // In-haystack or out-haystack check
- // Determined if needle fits within haystack (chunk)
- if (diff >= 0) // fits inside haystack
- {
- if (compare(haystack.ptr + diff, needle, len) == 0)
- goto L_FOUND;
- } else { // needle spans across haystacks
- editor.seek(pos + diff); // temporary seek
- ubyte[] tc = editor.read(needleBuffer); // Read needle length
- if (compare(tc.ptr, needle, len) == 0)
- goto L_FOUND;
- editor.seek(pos); // Go back we where last
- }
- }
-
- // Acts like EOF.
- if (pos > 0) goto L_CONTINUE;
-
- // Not found
- editor.seek(oldPos);
- return errorSet(ErrorCode.notFound);
-
-L_FOUND: // Found
- newPos = pos + diff; // Position + Chunk index = Found position
- return 0;
-}
-
-/// Finds the next position indifferent to specified byte.
-/// Params:
-/// data = Byte.
-/// newPos = Found position.
-/// Returns: Error code.
-int searchSkip(ubyte data, out long newPos)
-{
- version (Trace) trace("data=0x%x", data);
-
- /// File buffer.
- ubyte[] fileBuffer = uninitializedArray!(ubyte[])(BUFFER_SIZE);
- size_t haystackIndex = void;
-
- const long oldPos = editor.position;
- long pos = oldPos + 1;
-
-L_CONTINUE:
- editor.seek(pos);
- ubyte[] haystack = editor.read(fileBuffer);
- const size_t haystackLen = haystack.length;
-
- // For every byte
- for (haystackIndex = 0; haystackIndex < haystackLen; ++haystackIndex)
- {
- if (haystack[haystackIndex] != data)
- goto L_FOUND;
- }
-
- // Increase (search) position with chunk length.
- pos += haystackLen;
-
- // Check if last haystack.
- // If haystack length is lower than the default size,
- // this simply means it's the last haystack since it reached
- // OEF (by having read less data).
- if (haystackLen == BUFFER_SIZE) goto L_CONTINUE;
-
- // Not found
- editor.seek(oldPos);
- return errorSet(ErrorCode.notFound);
-
-L_FOUND: // Found
- newPos = pos + haystackIndex; // Position + Chunk index = Found position
- return 0;
-}
diff --git a/src/settings.d b/src/settings.d
deleted file mode 100644
index 8d491f8..0000000
--- a/src/settings.d
+++ /dev/null
@@ -1,309 +0,0 @@
-/// Settings handler.
-/// Copyright: dd86k
-/// License: MIT
-/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
-module settings;
-
-import ddhx; // for NumberType
-import os.terminal : TerminalSize, terminalSize;
-
-//TODO: Save config file on parameter change?
-
-private
-enum MAX_STATUS_ITEMS = 10;
-
-enum StatusItem : ubyte {
- /// Empty.
- empty,
- /// Shows editing mode, like insertion.
- editMode,
- /// Shows binary data display formatting type.
- dataMode,
- /// Shows current character translation.
- charMode,
- /// Shows size of the view buffer in size.
- viewSize,
- /// Shows absolute file position following offset setting.
- absolutePosition,
- /// Shows absolute file position that its type can be changed.
- absolutePositionAlt,
- /// Shows absolute file position relative to file size in percentage.
- absolutePercentage,
-}
-
-immutable string COMMAND_COLUMNS = "columns";
-immutable string COMMAND_OFFSET = "offset";
-immutable string COMMAND_DATA = "data";
-immutable string COMMAND_FILLER = "filler";
-immutable string COMMAND_SI = "si";
-immutable string COMMAND_IEC = "iec";
-immutable string COMMAND_CHARSET = "charset";
-// Editing modes
-immutable string COMMAND_INSERT = "insert";
-immutable string COMMAND_OVERWRITE = "overwrite";
-immutable string COMMAND_READONLY = "readonly";
-immutable string COMMAND_VIEW = "view";
-
-//TODO: Consider having statusbar offset type seperate offset
-
-private struct settings_t {
- /// Bytes per row.
- /// Default: 16
- int columns = 16;
- /// Offset number number formatting type.
- /// Default: hexadecimal
- NumberType offsetType;
- /// Binary data number formatting type.
- /// Default: hexadecimal
- NumberType dataType;
- // Number formatting type for absolute offset in statusbar.
- // Default: hexadecimal
-// NumberType statusType;
- /// Default character to use for non-ascii characters
- /// Default: Period ('.')
- char defaultChar = '.';
- /// Use ISO base-10 prefixes over IEC base-2
- /// Default: false
- bool si;
- ///
- /// Default: As presented
- StatusItem[MAX_STATUS_ITEMS] statusItems = [
- StatusItem.editMode,
- StatusItem.dataMode,
- StatusItem.charMode,
- StatusItem.viewSize,
- StatusItem.absolutePosition,
- StatusItem.empty,
- StatusItem.empty,
- StatusItem.empty,
- StatusItem.empty,
- StatusItem.empty,
- ];
-}
-
-/// Current settings.
-public __gshared settings_t setting;
-
-/// Reset all settings to default.
-void resetSettings()
-{
- setting = setting.init;
- transcoderSelect(CharacterSet.ascii);
-}
-
-/// Determines the optimal column width given terminal width.
-int optimalWidth()
-{
- TerminalSize termsize = terminalSize;
- int dataSize = void;
- final switch (setting.dataType) with (NumberType)
- {
- case hexadecimal: dataSize = 2; break;
- case decimal, octal: dataSize = 3; break;
- }
- dataSize += 2; // Account for space
- //TODO: +groups
- //width = cast(ushort)((w - 12) / 4);
- return (termsize.width - 16) / dataSize;
-}
-
-int settingsColumns(string val)
-{
- if (val == null || val.length == 0)
- return errorSet(ErrorCode.invalidParameter);
-
- version (Trace) trace("value='%s'", val);
-
- switch (val[0]) {
- case 'a': // Automatic (fit terminal width)
- setting.columns = optimalWidth;
- break;
- case 'd': // Default
- setting.columns = 16;
- break;
- default:
- ushort l = void;
- if (convertToVal(l, val))
- return errorSet(ErrorCode.invalidNumber);
- if (l == 0)
- return errorSet(ErrorCode.settingColumnsInvalid);
- setting.columns = l;
- }
- return 0;
-}
-
-int settingsOffset(string val)
-{
- if (val == null || val.length == 0)
- return errorSet(ErrorCode.invalidParameter);
-
- version (Trace) trace("value='%s'", val);
-
- switch (val[0]) {
- case 'o','O': setting.offsetType = NumberType.octal; break;
- case 'd','D': setting.offsetType = NumberType.decimal; break;
- case 'h','H': setting.offsetType = NumberType.hexadecimal; break;
- default: return errorSet(ErrorCode.invalidParameter);
- }
- screen.setOffsetFormat(setting.offsetType);
- return 0;
-}
-
-int settingsData(string val)
-{
- if (val == null || val.length == 0)
- return errorSet(ErrorCode.invalidParameter);
-
- version (Trace) trace("value='%s'", val);
-
- switch (val[0]) {
- case 'o','O': setting.dataType = NumberType.octal; break;
- case 'd','D': setting.dataType = NumberType.decimal; break;
- case 'h','H': setting.dataType = NumberType.hexadecimal; break;
- default: return errorSet(ErrorCode.invalidParameter);
- }
- screen.setBinaryFormat(setting.dataType);
- return 0;
-}
-
-int settingsFiller(string val)
-{
- if (val == null || val.length == 0)
- return errorSet(ErrorCode.invalidParameter);
-
- version (Trace) trace("value='%s'", val);
-
- switch (val) { // aliases
- case "space": setting.defaultChar = ' '; break;
- case "dot": setting.defaultChar = '.'; break;
- default: setting.defaultChar = val[0];
- }
- return 0;
-}
-
-int settingsCharset(string val)
-{
- if (val == null || val.length == 0)
- return errorSet(ErrorCode.invalidParameter);
-
- version (Trace) trace("value='%s'", val);
-
- switch (val) {
- case "ascii": transcoderSelect(CharacterSet.ascii); break;
- case "cp437": transcoderSelect(CharacterSet.cp437); break;
- case "ebcdic": transcoderSelect(CharacterSet.ebcdic); break;
- case "mac": transcoderSelect(CharacterSet.mac); break;
- default: return errorSet(ErrorCode.invalidCharset);
- }
- return 0;
-}
-
-//TODO: Consider doing an AA with string[]... functions
-// or enum size_t HASH_FILLER = "filler".hashOf();
-// Low priority
-
-int set(string[] args)
-{
- const size_t argc = args.length;
-
- if (argc == 0)
- return errorSet(ErrorCode.missingOption);
-
- switch (args[0]) {
- case COMMAND_FILLER:
- if (argc < 2)
- return errorSet(ErrorCode.missingValue);
- return settingsFiller(args[1]);
- case COMMAND_COLUMNS:
- if (argc < 2)
- return errorSet(ErrorCode.missingValue);
- return settingsColumns(args[1]);
- case COMMAND_OFFSET:
- if (argc < 2)
- return errorSet(ErrorCode.missingValue);
- return settingsOffset(args[1]);
- case COMMAND_DATA:
- if (argc < 2)
- return errorSet(ErrorCode.missingValue);
- return settingsData(args[1]);
- case COMMAND_SI:
- setting.si = true;
- return 0;
- case COMMAND_IEC:
- setting.si = false;
- return 0;
- case COMMAND_CHARSET:
- if (argc < 2)
- return errorSet(ErrorCode.missingValue);
- return settingsCharset(args[1]);
- // Editing modes
- case COMMAND_INSERT:
-
- return 0;
- case COMMAND_OVERWRITE:
-
- return 0;
- case COMMAND_READONLY:
-
- return 0;
- case COMMAND_VIEW:
-
- return 0;
- default:
- }
-
- return errorSet(ErrorCode.invalidSetting);
-}
-
-int loadSettings(string rc)
-{
- import std.stdio : File;
- import std.file : exists;
- import os.path : buildUserFile, buildUserAppFile;
- import std.format.read : formattedRead;
- import std.string : chomp, strip;
- import utils.args : arguments;
-
- static immutable string cfgname = ".ddhxrc";
-
- if (rc == null)
- {
- rc = buildUserFile(cfgname);
-
- if (rc is null)
- goto L_APPCONFIG;
- if (rc.exists)
- goto L_SELECTED;
-
-L_APPCONFIG:
- rc = buildUserAppFile("ddhx", cfgname);
-
- if (rc is null)
- return 0;
- if (rc.exists == false)
- return 0;
- } else {
- if (exists(rc) == false)
- return errorSet(ErrorCode.settingFileMissing);
- }
-
-L_SELECTED:
- version (Trace) trace("rc='%s'", rc);
-
- File file;
- file.open(rc);
- int linenum;
- foreach (line; file.byLine())
- {
- ++linenum;
- if (line.length == 0) continue;
- if (line[0] == '#') continue;
-
- string[] args = arguments(cast(string)line.chomp);
-
- if (set(args))
- return errorcode;
- }
-
- return 0;
-}
\ No newline at end of file
diff --git a/src/transcoder.d b/src/transcoder.d
index 8cca392..549ca86 100644
--- a/src/transcoder.d
+++ b/src/transcoder.d
@@ -1,21 +1,48 @@
/// Transcoder module.
///
-/// Translates single-byte characters into utf-8.
+/// Translates single-byte characters into utf-8 strings.
/// Copyright: dd86k
/// License: MIT
/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
-module encoding;
+module transcoder;
import std.encoding : codeUnits, CodeUnits;
/// Character set.
-enum CharacterSet : ubyte {
+enum CharacterSet
+{
ascii, /// 7-bit US-ASCII
cp437, /// IBM PC CP-437
- ebcdic, /// IBM EBCDIC Code Page 37
+ ebcdic, /// IBM EBCDIC Code Page 37 (CCSID 37)
mac, /// Mac OS Roman (Windows 10000)
-// t61, /// ITU T.61
-// gsm, /// GSM 03.38
+ //t61, /// ITU T.61
+ //gsm, /// GSM 03.38
+}
+
+string charsetName(int charset)
+{
+ switch (charset) with (CharacterSet)
+ {
+ case ascii: return "ascii";
+ case cp437: return "cp437";
+ case ebcdic: return "ascii";
+ case mac: return "ascii";
+ default:
+ assert(false);
+ }
+}
+
+string charsetFullname(int charset)
+{
+ switch (charset) with (CharacterSet)
+ {
+ case ascii: return "ASCII";
+ case cp437: return "IBM PC Code Page 437";
+ case ebcdic: return "IBM EBCDIC Code Page 37";
+ case mac: return "Mac OS Roman (Windows 10000)";
+ default:
+ assert(false);
+ }
}
//TODO: Consider registering encoders to EncodingScheme
@@ -39,47 +66,21 @@
// - GSM 03.38 "gsm" (technically multibyte)
// https://www.unicode.org/Public/MAPPINGS/ETSI/GSM0338.TXT
-private struct Transcoder {
- alias transform this;
- string name = "ascii";
- immutable(char)[] function(ubyte) transform = &transcodeASCII;
-}
-
-private immutable(char)[] emptychar = [];
-
-private immutable Transcoder[4] transcoders = [
- { "ascii", &transcodeASCII },
- { "cp437", &transcodeCP437 },
- { "ebcdic", &transcodeEBCDIC },
- { "mac", &transcodeMac },
-];
-
-/// Current transcoder
-public __gshared Transcoder transcoder;
-
-/// Select a new transcoder.
-/// Params: charset = Character set.
-void transcoderSelect(CharacterSet charset)
-{
- debug assert(charset <= CharacterSet.max);
- transcoder = transcoders[charset];
-}
-
private alias U = char[];
private template C(dchar c) { enum C = cast(immutable)codeUnits!char(c).s; }
+private immutable immutable(char)[] emptychar;
-private
-immutable(char)[] transcodeASCII(ubyte data)
+immutable(char)[] transcodeASCII(ubyte data) @trusted
{
- __gshared immutable(char)[] empty;
__gshared char[1] c;
if (data > 0x7E || data < 0x20)
- return empty;
+ return emptychar;
c[0] = data;
return c;
}
-unittest {
+@trusted unittest
+{
assert(transcodeASCII(0) == []);
assert(transcodeASCII('a') == [ 'a' ]);
assert(transcodeASCII(0x7f) == []);
@@ -87,45 +88,45 @@
private immutable U[256] mapCP437 = [
// 0 1 2 3 4 5 6 7
-/*00*/ [], C!'☺', C!'☻', C!'♥', C!'♦', C!'♣', C!'♠', C!'•',
-/*08*/ C!'◘', C!'○', C!'◙', C!'♂', C!'♀', C!'♪', C!'♫', C!'☼',
-/*10*/ C!'►', C!'◄', C!'↕', C!'‼', C!'¶', C!'§', C!'▬', C!'↨',
-/*18*/ C!'↑', C!'↓', C!'→', C!'←', C!'∟', C!'↔', C!'▲', C!'▼',
-/*20*/ C!' ', C!'!', C!'"', C!'#', C!'$', C!'%', C!'&',C!'\'',
-/*28*/ C!'(', C!')', C!'*', C!'+', C!',', C!'-', C!'.', C!'/',
-/*30*/ C!'0', C!'1', C!'2', C!'3', C!'4', C!'5', C!'6', C!'7',
-/*38*/ C!'8', C!'9', C!':', C!';', C!'<', C!'>', C!'=', C!'?',
-/*40*/ C!'@', C!'A', C!'B', C!'C', C!'D', C!'E', C!'F', C!'G',
-/*48*/ C!'H', C!'I', C!'J', C!'K', C!'M', C!'N', C!'L', C!'O',
-/*50*/ C!'P', C!'Q', C!'R', C!'S', C!'T', C!'U', C!'V', C!'W',
-/*58*/ C!'X', C!'Y', C!'Z', C!'[',C!'\\', C!']', C!'^', C!'_',
-/*60*/ C!'`', C!'a', C!'b', C!'c', C!'d', C!'e', C!'f', C!'g',
-/*68*/ C!'h', C!'i', C!'j', C!'k', C!'l', C!'m', C!'n', C!'o',
-/*70*/ C!'p', C!'q', C!'r', C!'s', C!'t', C!'u', C!'v', C!'w',
-/*78*/ C!'x', C!'y', C!'z', C!'{', C!'|', C!'}', C!'~', C!'⌂',
-/*80*/ C!'Ç', C!'ü', C!'é', C!'â', C!'ä', C!'à', C!'å', C!'ç',
-/*88*/ C!'ê', C!'ë', C!'è', C!'ï', C!'î', C!'ì', C!'Ä', C!'Å',
-/*90*/ C!'É', C!'æ', C!'Æ', C!'ô', C!'ö', C!'ò', C!'û', C!'ù',
-/*98*/ C!'ÿ', C!'Ö', C!'Ü', C!'¢', C!'£', C!'¥', C!'₧', C!'ƒ',
-/*a0*/ C!'á', C!'í', C!'ó', C!'ú', C!'ñ', C!'Ñ', C!'ª', C!'º',
-/*a8*/ C!'¿', C!'⌐', C!'¬', C!'½', C!'¼', C!'¡', C!'«', C!'»',
-/*b0*/ C!'░', C!'▒', C!'▓', C!'│', C!'┤', C!'╡', C!'╢', C!'╖',
-/*b8*/ C!'╕', C!'╣', C!'║', C!'╗', C!'╝', C!'╜', C!'╛', C!'┐',
-/*c0*/ C!'└', C!'┴', C!'┬', C!'├', C!'─', C!'┼', C!'╞', C!'╟',
-/*c8*/ C!'╚', C!'╔', C!'╩', C!'╦', C!'╠', C!'═', C!'╬', C!'╧',
-/*d0*/ C!'╨', C!'╤', C!'╥', C!'╙', C!'╘', C!'╒', C!'╓', C!'╫',
-/*d8*/ C!'╪', C!'┘', C!'┌', C!'█', C!'▄', C!'▌', C!'▐', C!'▀',
-/*e0*/ C!'α', C!'β', C!'Γ', C!'π', C!'Σ', C!'σ', C!'µ', C!'τ',
-/*e8*/ C!'Φ', C!'Θ', C!'Ω', C!'δ', C!'∞', C!'φ', C!'ε', C!'∩',
-/*f0*/ C!'≡', C!'±', C!'≥', C!'≤', C!'⌠', C!'⌡', C!'÷', C!'≈',
-/*f8*/ C!'°', C!'∙', C!'·', C!'√', C!'ⁿ', C!'²', C!'■', C!' '
+/*00*/ [], C!'☺', C!'☻', C!'♥', C!'♦', C!'♣', C!'♠', C!'•',
+/*08*/ C!'◘', C!'○', C!'◙', C!'♂', C!'♀', C!'♪', C!'♫', C!'☼',
+/*10*/ C!'►', C!'◄', C!'↕', C!'‼', C!'¶', C!'§', C!'▬', C!'↨',
+/*18*/ C!'↑', C!'↓', C!'→', C!'←', C!'∟', C!'↔', C!'▲', C!'▼',
+/*20*/ C!' ', C!'!', C!'"', C!'#', C!'$', C!'%', C!'&',C!'\'',
+/*28*/ C!'(', C!')', C!'*', C!'+', C!',', C!'-', C!'.', C!'/',
+/*30*/ C!'0', C!'1', C!'2', C!'3', C!'4', C!'5', C!'6', C!'7',
+/*38*/ C!'8', C!'9', C!':', C!';', C!'<', C!'>', C!'=', C!'?',
+/*40*/ C!'@', C!'A', C!'B', C!'C', C!'D', C!'E', C!'F', C!'G',
+/*48*/ C!'H', C!'I', C!'J', C!'K', C!'M', C!'N', C!'L', C!'O',
+/*50*/ C!'P', C!'Q', C!'R', C!'S', C!'T', C!'U', C!'V', C!'W',
+/*58*/ C!'X', C!'Y', C!'Z', C!'[',C!'\\', C!']', C!'^', C!'_',
+/*60*/ C!'`', C!'a', C!'b', C!'c', C!'d', C!'e', C!'f', C!'g',
+/*68*/ C!'h', C!'i', C!'j', C!'k', C!'l', C!'m', C!'n', C!'o',
+/*70*/ C!'p', C!'q', C!'r', C!'s', C!'t', C!'u', C!'v', C!'w',
+/*78*/ C!'x', C!'y', C!'z', C!'{', C!'|', C!'}', C!'~', C!'⌂',
+/*80*/ C!'Ç', C!'ü', C!'é', C!'â', C!'ä', C!'à', C!'å', C!'ç',
+/*88*/ C!'ê', C!'ë', C!'è', C!'ï', C!'î', C!'ì', C!'Ä', C!'Å',
+/*90*/ C!'É', C!'æ', C!'Æ', C!'ô', C!'ö', C!'ò', C!'û', C!'ù',
+/*98*/ C!'ÿ', C!'Ö', C!'Ü', C!'¢', C!'£', C!'¥', C!'₧', C!'ƒ',
+/*a0*/ C!'á', C!'í', C!'ó', C!'ú', C!'ñ', C!'Ñ', C!'ª', C!'º',
+/*a8*/ C!'¿', C!'⌐', C!'¬', C!'½', C!'¼', C!'¡', C!'«', C!'»',
+/*b0*/ C!'░', C!'▒', C!'▓', C!'│', C!'┤', C!'╡', C!'╢', C!'╖',
+/*b8*/ C!'╕', C!'╣', C!'║', C!'╗', C!'╝', C!'╜', C!'╛', C!'┐',
+/*c0*/ C!'└', C!'┴', C!'┬', C!'├', C!'─', C!'┼', C!'╞', C!'╟',
+/*c8*/ C!'╚', C!'╔', C!'╩', C!'╦', C!'╠', C!'═', C!'╬', C!'╧',
+/*d0*/ C!'╨', C!'╤', C!'╥', C!'╙', C!'╘', C!'╒', C!'╓', C!'╫',
+/*d8*/ C!'╪', C!'┘', C!'┌', C!'█', C!'▄', C!'▌', C!'▐', C!'▀',
+/*e0*/ C!'α', C!'β', C!'Γ', C!'π', C!'Σ', C!'σ', C!'µ', C!'τ',
+/*e8*/ C!'Φ', C!'Θ', C!'Ω', C!'δ', C!'∞', C!'φ', C!'ε', C!'∩',
+/*f0*/ C!'≡', C!'±', C!'≥', C!'≤', C!'⌠', C!'⌡', C!'÷', C!'≈',
+/*f8*/ C!'°', C!'∙', C!'·', C!'√', C!'ⁿ', C!'²', C!'■', C!' '
];
-private
immutable(char)[] transcodeCP437(ubyte data)
{
return mapCP437[data];
}
-unittest {
+unittest
+{
assert(transcodeCP437(0) == []);
assert(transcodeCP437('r') == [ 'r' ]);
assert(transcodeCP437(1) == [ '\xe2', '\x98', '\xba' ]);
@@ -133,37 +134,37 @@
private immutable U[192] mapEBCDIC = [ // 256 - 64 (0x40) just unprintable
// 0 1 2 3 4 5 6 7
-/*40*/ C!' ', C!' ', C!'â', C!'ä', C!'à', C!'á', C!'ã', C!'å',
-/*48*/ C!'ç', C!'ñ', C!'¢', C!'.', C!'<', C!'(', C!'+', C!'|',
-/*50*/ C!'&', C!'é', C!'ê', C!'ë', C!'è', C!'í', C!'î', C!'ï',
-/*58*/ C!'ì', C!'ß', C!'!', C!'$', C!'*', C!')', C!';', C!'¬',
-/*60*/ C!'-', C!'/', C!'Â', C!'Ä', C!'À', C!'Á', C!'Ã', C!'Å',
-/*68*/ C!'Ç', C!'Ñ', C!'¦', C!',', C!'%', C!'_', C!'>', C!'?',
-/*70*/ C!'ø', C!'É', C!'Ê', C!'Ë', C!'È', C!'Í', C!'Î', C!'Ï',
-/*78*/ C!'Ì', C!'`', C!':', C!'#', C!'@', C!'\'',C!'=', C!'"',
-/*80*/ C!'Ø', C!'a', C!'b', C!'c', C!'d', C!'e', C!'f', C!'g',
-/*88*/ C!'h', C!'i', C!'«', C!'»', C!'ð', C!'ý', C!'þ', C!'±',
-/*90*/ C!'°', C!'j', C!'k', C!'l', C!'m', C!'n', C!'o', C!'p',
-/*98*/ C!'q', C!'r', C!'ª', C!'º', C!'æ', C!'¸', C!'Æ', C!'¤',
-/*a0*/ C!'µ', C!'~', C!'s', C!'t', C!'u', C!'v', C!'w', C!'x',
-/*a8*/ C!'y', C!'z', C!'¡', C!'¿', C!'Ð', C!'Ý', C!'Þ', C!'®',
-/*b0*/ C!'^', C!'£', C!'¥', C!'·', C!'©', C!'§', C!'¶', C!'¼',
-/*b8*/ C!'½', C!'¾', C!'[', C!']', C!'¯', C!'¨', C!'´', C!'×',
-/*c0*/ C!'{', C!'A', C!'B', C!'C', C!'D', C!'E', C!'F', C!'G',
-/*c8*/ C!'H', C!'I', [], C!'ô', C!'ö', C!'ò', C!'ó', C!'õ',
-/*d0*/ C!'}', C!'J', C!'K', C!'L', C!'M', C!'N', C!'O', C!'P',
-/*d8*/ C!'Q', C!'R', C!'¹', C!'û', C!'ü', C!'ù', C!'ú', C!'ÿ',
-/*e0*/ C!'\\',C!'÷', C!'S', C!'T', C!'U', C!'V', C!'W', C!'X',
-/*e8*/ C!'Y', C!'Z', C!'²', C!'Ô', C!'Ö', C!'Ò', C!'Ó', C!'Õ',
-/*f0*/ C!'0', C!'1', C!'2', C!'3', C!'4', C!'5', C!'6', C!'7',
-/*f8*/ C!'8', C!'9', C!'³', C!'Û', C!'Ü', C!'Ù', C!'Ú', []
+/*40*/ C!' ', C!' ', C!'â', C!'ä', C!'à', C!'á', C!'ã', C!'å',
+/*48*/ C!'ç', C!'ñ', C!'¢', C!'.', C!'<', C!'(', C!'+', C!'|',
+/*50*/ C!'&', C!'é', C!'ê', C!'ë', C!'è', C!'í', C!'î', C!'ï',
+/*58*/ C!'ì', C!'ß', C!'!', C!'$', C!'*', C!')', C!';', C!'¬',
+/*60*/ C!'-', C!'/', C!'Â', C!'Ä', C!'À', C!'Á', C!'Ã', C!'Å',
+/*68*/ C!'Ç', C!'Ñ', C!'¦', C!',', C!'%', C!'_', C!'>', C!'?',
+/*70*/ C!'ø', C!'É', C!'Ê', C!'Ë', C!'È', C!'Í', C!'Î', C!'Ï',
+/*78*/ C!'Ì', C!'`', C!':', C!'#', C!'@', C!'\'',C!'=', C!'"',
+/*80*/ C!'Ø', C!'a', C!'b', C!'c', C!'d', C!'e', C!'f', C!'g',
+/*88*/ C!'h', C!'i', C!'«', C!'»', C!'ð', C!'ý', C!'þ', C!'±',
+/*90*/ C!'°', C!'j', C!'k', C!'l', C!'m', C!'n', C!'o', C!'p',
+/*98*/ C!'q', C!'r', C!'ª', C!'º', C!'æ', C!'¸', C!'Æ', C!'¤',
+/*a0*/ C!'µ', C!'~', C!'s', C!'t', C!'u', C!'v', C!'w', C!'x',
+/*a8*/ C!'y', C!'z', C!'¡', C!'¿', C!'Ð', C!'Ý', C!'Þ', C!'®',
+/*b0*/ C!'^', C!'£', C!'¥', C!'·', C!'©', C!'§', C!'¶', C!'¼',
+/*b8*/ C!'½', C!'¾', C!'[', C!']', C!'¯', C!'¨', C!'´', C!'×',
+/*c0*/ C!'{', C!'A', C!'B', C!'C', C!'D', C!'E', C!'F', C!'G',
+/*c8*/ C!'H', C!'I', [], C!'ô', C!'ö', C!'ò', C!'ó', C!'õ',
+/*d0*/ C!'}', C!'J', C!'K', C!'L', C!'M', C!'N', C!'O', C!'P',
+/*d8*/ C!'Q', C!'R', C!'¹', C!'û', C!'ü', C!'ù', C!'ú', C!'ÿ',
+/*e0*/ C!'\\',C!'÷', C!'S', C!'T', C!'U', C!'V', C!'W', C!'X',
+/*e8*/ C!'Y', C!'Z', C!'²', C!'Ô', C!'Ö', C!'Ò', C!'Ó', C!'Õ',
+/*f0*/ C!'0', C!'1', C!'2', C!'3', C!'4', C!'5', C!'6', C!'7',
+/*f8*/ C!'8', C!'9', C!'³', C!'Û', C!'Ü', C!'Ù', C!'Ú', []
];
-private
immutable(char)[] transcodeEBCDIC(ubyte data)
{
return data >= 0x40 ? mapEBCDIC[data-0x40] : emptychar;
}
-unittest {
+unittest
+{
assert(transcodeEBCDIC(0) == [ ]);
assert(transcodeEBCDIC(0x42) == [ '\xc3', '\xa2' ]);
assert(transcodeEBCDIC(0x7c) == [ '@' ]);
@@ -174,41 +175,41 @@
// NOTE: 0xF0 is the apple logo and that's obviously not in Unicode
private immutable U[224] mapMac = [ // 256 - 32 (0x20)
// 0 1 2 3 4 5 6 7
-/*20*/ C!' ', C!'!', C!'"', C!'#', C!'$', C!'%', C!'&',C!'\'',
-/*28*/ C!'(', C!')', C!'*', C!'+', C!',', C!'-', C!'.', C!'/',
-/*30*/ C!'0', C!'1', C!'2', C!'3', C!'4', C!'5', C!'6', C!'7',
-/*38*/ C!'8', C!'9', C!':', C!';', C!'<', C!'=', C!'>', C!'?',
-/*40*/ C!'@', C!'A', C!'B', C!'C', C!'D', C!'E', C!'F', C!'G',
-/*48*/ C!'H', C!'I', C!'J', C!'K', C!'L', C!'M', C!'N', C!'O',
-/*50*/ C!'P', C!'Q', C!'R', C!'S', C!'T', C!'U', C!'V', C!'W',
-/*58*/ C!'X', C!'Y', C!'Z', C!'[',C!'\\', C!']', C!'^', C!'_',
-/*60*/ C!'`', C!'a', C!'b', C!'c', C!'d', C!'e', C!'f', C!'g',
-/*68*/ C!'h', C!'i', C!'j', C!'k', C!'l', C!'m', C!'n', C!'o',
-/*70*/ C!'p', C!'q', C!'r', C!'s', C!'t', C!'u', C!'v', C!'w',
-/*78*/ C!'x', C!'y', C!'z', C!'{', C!'|', C!'}', C!'~', [],
-/*80*/ C!'Ä', C!'Å', C!'Ç', C!'É', C!'Ñ', C!'Ö', C!'Ü', C!'á',
-/*88*/ C!'à', C!'â', C!'ä', C!'ã', C!'å', C!'ç', C!'é', C!'è',
-/*90*/ C!'ê', C!'ë', C!'í', C!'ì', C!'î', C!'ï', C!'ñ', C!'ó',
-/*98*/ C!'ò', C!'ô', C!'ö', C!'õ', C!'ú', C!'ù', C!'û', C!'ü',
-/*a0*/ C!'†', C!'°', C!'¢', C!'£', C!'§', C!'•', C!'¶', C!'ß',
-/*a8*/ C!'®', C!'©', C!'™', C!'´', C!'¨', C!'≠', C!'Æ', C!'Ø',
-/*b0*/ C!'∞', C!'±', C!'≤', C!'≥', C!'¥', C!'µ', C!'∂', C!'∑',
-/*b8*/ C!'∏', C!'π', C!'∫', C!'ª', C!'º', C!'Ω', C!'æ', C!'ø',
-/*c0*/ C!'¿', C!'¡', C!'¬', C!'√', C!'ƒ', C!'≈', C!'∆', C!'«',
-/*c8*/ C!'»', C!'…', C!' ', C!'À', C!'Ã', C!'Õ', C!'Œ', C!'œ',
-/*d0*/ C!'–', C!'—', C!'“', C!'”', C!'‘', C!'’', C!'÷', C!'◊',
-/*d8*/ C!'ÿ', C!'Ÿ', C!'⁄', C!'€', C!'‹', C!'›', C!'fi', C!'fl',
-/*e0*/ C!'‡', C!'·', C!'‚', C!'„', C!'‰', C!'Â', C!'Ê', C!'Á',
-/*e8*/ C!'Ë', C!'È', C!'Í', C!'Î', C!'Ï', C!'Ì', C!'Ó', C!'Ô',
-/*f0*/ [], C!'Ò', C!'Ú', C!'Û', C!'Ù', C!'ı', C!'ˆ', C!'˜',
-/*f8*/ C!'¯', C!'˘', C!'˙', C!'˚', C!'¸', C!'˝', C!'˛', C!'ˇ',
+/*20*/ C!' ', C!'!', C!'"', C!'#', C!'$', C!'%', C!'&',C!'\'',
+/*28*/ C!'(', C!')', C!'*', C!'+', C!',', C!'-', C!'.', C!'/',
+/*30*/ C!'0', C!'1', C!'2', C!'3', C!'4', C!'5', C!'6', C!'7',
+/*38*/ C!'8', C!'9', C!':', C!';', C!'<', C!'=', C!'>', C!'?',
+/*40*/ C!'@', C!'A', C!'B', C!'C', C!'D', C!'E', C!'F', C!'G',
+/*48*/ C!'H', C!'I', C!'J', C!'K', C!'L', C!'M', C!'N', C!'O',
+/*50*/ C!'P', C!'Q', C!'R', C!'S', C!'T', C!'U', C!'V', C!'W',
+/*58*/ C!'X', C!'Y', C!'Z', C!'[',C!'\\', C!']', C!'^', C!'_',
+/*60*/ C!'`', C!'a', C!'b', C!'c', C!'d', C!'e', C!'f', C!'g',
+/*68*/ C!'h', C!'i', C!'j', C!'k', C!'l', C!'m', C!'n', C!'o',
+/*70*/ C!'p', C!'q', C!'r', C!'s', C!'t', C!'u', C!'v', C!'w',
+/*78*/ C!'x', C!'y', C!'z', C!'{', C!'|', C!'}', C!'~', [],
+/*80*/ C!'Ä', C!'Å', C!'Ç', C!'É', C!'Ñ', C!'Ö', C!'Ü', C!'á',
+/*88*/ C!'à', C!'â', C!'ä', C!'ã', C!'å', C!'ç', C!'é', C!'è',
+/*90*/ C!'ê', C!'ë', C!'í', C!'ì', C!'î', C!'ï', C!'ñ', C!'ó',
+/*98*/ C!'ò', C!'ô', C!'ö', C!'õ', C!'ú', C!'ù', C!'û', C!'ü',
+/*a0*/ C!'†', C!'°', C!'¢', C!'£', C!'§', C!'•', C!'¶', C!'ß',
+/*a8*/ C!'®', C!'©', C!'™', C!'´', C!'¨', C!'≠', C!'Æ', C!'Ø',
+/*b0*/ C!'∞', C!'±', C!'≤', C!'≥', C!'¥', C!'µ', C!'∂', C!'∑',
+/*b8*/ C!'∏', C!'π', C!'∫', C!'ª', C!'º', C!'Ω', C!'æ', C!'ø',
+/*c0*/ C!'¿', C!'¡', C!'¬', C!'√', C!'ƒ', C!'≈', C!'∆', C!'«',
+/*c8*/ C!'»', C!'…', C!' ', C!'À', C!'Ã', C!'Õ', C!'Œ', C!'œ',
+/*d0*/ C!'–', C!'—', C!'“', C!'”', C!'‘', C!'’', C!'÷', C!'◊',
+/*d8*/ C!'ÿ', C!'Ÿ', C!'⁄', C!'€', C!'‹', C!'›', C!'fi', C!'fl',
+/*e0*/ C!'‡', C!'·', C!'‚', C!'„', C!'‰', C!'Â', C!'Ê', C!'Á',
+/*e8*/ C!'Ë', C!'È', C!'Í', C!'Î', C!'Ï', C!'Ì', C!'Ó', C!'Ô',
+/*f0*/ [], C!'Ò', C!'Ú', C!'Û', C!'Ù', C!'ı', C!'ˆ', C!'˜',
+/*f8*/ C!'¯', C!'˘', C!'˙', C!'˚', C!'¸', C!'˝', C!'˛', C!'ˇ',
];
-private
immutable(char)[] transcodeMac(ubyte data)
{
return data >= 0x20 ? mapMac[data-0x20] : emptychar;
}
-unittest {
+unittest
+{
assert(transcodeMac(0) == [ ]);
assert(transcodeMac(0x20) == [ ' ' ]);
assert(transcodeMac(0x22) == [ '"' ]);
diff --git a/src/utils/args.d b/src/utils/args.d
index b6cebb2..b1aa877 100644
--- a/src/utils/args.d
+++ b/src/utils/args.d
@@ -1,10 +1,13 @@
+/// Utilities to handle arguments.
+/// Copyright: dd86k
+/// License: MIT
+/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
module utils.args;
-
/// Separate buffer into arguments (akin to argv).
/// Params: buffer = String buffer.
/// Returns: Argv-like array.
-string[] arguments(string buffer)
+string[] arguments(const(char)[] buffer)
{
import std.string : strip;
import std.ascii : isControl, isWhite;
@@ -38,7 +41,7 @@
break;
}
- results ~= buffer[start..(index++)];
+ results ~= cast(string)buffer[start..(index++)];
break;
default:
for (start = index, ++index; index < buflen; ++index)
@@ -48,15 +51,14 @@
break;
}
- results ~= buffer[start..index];
+ results ~= cast(string)buffer[start..index];
}
}
return results;
}
-
-///
-@system unittest {
+@system unittest
+{
//TODO: Test embedded string quotes
assert(arguments("") == []);
assert(arguments("\n") == []);
@@ -74,4 +76,6 @@
assert(arguments(`a "b c" d`) == [ "a", "b c", "d" ]);
assert(arguments(`/type 'yes string'`) == [ "/type", "yes string" ]);
assert(arguments(`/type "yes string"`) == [ "/type", "yes string" ]);
+ assert(arguments(`A B`) == [ "A", "B" ]);
+ //assert(arguments(`tab\tmoment`) == [ "tab", "moment" ]);
}
diff --git a/src/utils/format.d b/src/utils/format.d
deleted file mode 100644
index d96b382..0000000
--- a/src/utils/format.d
+++ /dev/null
@@ -1,83 +0,0 @@
-/// Bit utilities.
-/// Copyright: dd86k
-/// License: MIT
-/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
-module utils.format;
-
-private enum : float {
- // SI (base-10)
- SI = 1000, /// SI base
- kB = SI, /// Represents one KiloByte
- MB = kB * SI, /// Represents one MegaByte
- GB = MB * SI, /// Represents one GigaByte
- TB = GB * SI, /// Represents one TeraByte
- PB = TB * SI, /// Represents one PetaByte
- EB = PB * SI, /// Represents one ExaByte
- // IEC (base-2)
- IEC = 1024, /// IEC base
- KiB = IEC, /// Represents one KibiByte (1024)
- MiB = KiB * IEC, /// Represents one MebiByte (1024²)
- GiB = MiB * IEC, /// Represents one GibiByte (1024³)
- TiB = GiB * IEC, /// Represents one TebiByte (1024⁴)
- PiB = TiB * IEC, /// Represents one PebiByte (1024⁵)
- EiB = PiB * IEC, /// Represents one PebiByte (1024⁶)
-}
-
-/// Format byte size.
-/// Params:
-/// size = Binary number.
-/// b10 = Use SI suffixes instead of IEC suffixes.
-/// Returns: Character slice using sformat
-const(char)[] formatBin(long size, bool b10 = false)
-{
- import std.format : format;
-
- // NOTE: ulong.max = (2^64)-1 Bytes = 16 EiB - 1 = 16 * 1024⁵
-
- //TODO: Pretty this up with some clever math
- // Not great, not terrible...
-
- if (b10) // base 1000
- {
- if (size >= EB)
- return format("%0.2f EB", size / EB);
- if (size >= PB)
- return format("%0.2f TB", size / PB);
- if (size >= TB)
- return format("%0.2f TB", size / TB);
- if (size >= GB)
- return format("%0.2f GB", size / GB);
- if (size >= MB)
- return format("%0.1f MB", size / MB);
- if (size >= kB)
- return format("%0.1f kB", size / kB);
- }
- else // base 1024
- {
- if (size >= EiB)
- return format("%0.2f EiB", size / EiB);
- if (size >= PiB)
- return format("%0.2f TiB", size / PiB);
- if (size >= TiB)
- return format("%0.2f TiB", size / TiB);
- if (size >= GiB)
- return format("%0.2f GiB", size / GiB);
- if (size >= MiB)
- return format("%0.1f MiB", size / MiB);
- if (size >= KiB)
- return format("%0.1f KiB", size / KiB);
- }
-
- return format("%u B", size);
-}
-
-unittest {
- assert(formatBin(0) == "0 B");
- assert(formatBin(1) == "1 B");
- assert(formatBin(1023) == "1023 B");
- assert(formatBin(1024) == "1.0 KiB");
- // Wouldn't this more exactly 7.99 EiB? Precision limitation?
- assert(formatBin(long.max) == "8.00 EiB");
- assert(formatBin(999, true) == "999 B");
- assert(formatBin(1000, true) == "1.0 kB");
-}
diff --git a/src/utils/math.d b/src/utils/math.d
new file mode 100644
index 0000000..38539b7
--- /dev/null
+++ b/src/utils/math.d
@@ -0,0 +1,27 @@
+/// Mathematic utilities.
+/// Copyright: dd86k
+/// License: MIT
+/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
+module utils.math;
+
+T min(T)(T a, T b) pure @safe
+{
+ return a < b ? a : b;
+}
+@safe unittest
+{
+ assert(min(1, 2) == 1);
+ assert(min(2, 2) == 2);
+ assert(min(3, 2) == 2);
+}
+
+T max(T)(T a, T b) pure @safe
+{
+ return a > b ? a : b;
+}
+@safe unittest
+{
+ assert(max(1, 2) == 2);
+ assert(max(2, 2) == 2);
+ assert(max(3, 2) == 3);
+}
\ No newline at end of file
diff --git a/src/utils/memory.d b/src/utils/memory.d
index 876fb68..4ec5155 100644
--- a/src/utils/memory.d
+++ b/src/utils/memory.d
@@ -1,10 +1,15 @@
+/// Memory utilities.
+/// Copyright: dd86k
+/// License: MIT
+/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
module utils.memory;
import std.stdio : File;
import std.container.array;
//TODO: Use OutBuffer or Array!ubyte when writing changes?
-struct MemoryStream {
+struct MemoryStream
+{
private ubyte[] buffer;
private long position;
@@ -19,16 +24,12 @@
err = false;
}
- // read file into memory
- /*void open(string path)
- {
- }*/
- void open(ubyte[] data)
+ void copy(ubyte[] data)
{
buffer = new ubyte[data.length];
buffer[0..$] = data[0..$];
}
- void open(File stream)
+ void copy(File stream)
{
buffer = buffer.init;
//TODO: use OutBuffer+reserve (if possible to get filesize)
@@ -36,7 +37,6 @@
{
buffer ~= a;
}
-
}
long seek(long pos)
diff --git a/src/utils/strings.d b/src/utils/strings.d
new file mode 100644
index 0000000..72064cf
--- /dev/null
+++ b/src/utils/strings.d
@@ -0,0 +1,135 @@
+/// Formatting utilities.
+/// Copyright: dd86k
+/// License: MIT
+/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
+module utils.strings;
+
+import core.stdc.stdio : sscanf;
+import core.stdc.string : memcpy;
+import std.string : toStringz;
+import std.conv : text;
+import utils.math;
+
+private enum : double
+{
+ // SI (base-10)
+ SI = 1000, /// SI base
+ kB = SI, /// Represents one KiloByte (1000)
+ MB = kB * SI, /// Represents one MegaByte (1000²)
+ GB = MB * SI, /// Represents one GigaByte (1000³)
+ TB = GB * SI, /// Represents one TeraByte (1000⁴)
+ PB = TB * SI, /// Represents one PetaByte (1000⁵)
+ EB = PB * SI, /// Represents one ExaByte (1000⁶)
+ // IEC (base-2)
+ IEC = 1024, /// IEC base
+ KiB = IEC, /// Represents one KibiByte (1024)
+ MiB = KiB * IEC, /// Represents one MebiByte (1024²)
+ GiB = MiB * IEC, /// Represents one GibiByte (1024³)
+ TiB = GiB * IEC, /// Represents one TebiByte (1024⁴)
+ PiB = TiB * IEC, /// Represents one PebiByte (1024⁵)
+ EiB = PiB * IEC, /// Represents one PebiByte (1024⁶)
+}
+
+/// Format byte size.
+/// Params:
+/// size = Binary number.
+/// b10 = Use SI suffixes instead of IEC suffixes.
+/// Returns: Character slice using sformat
+const(char)[] formatBin(long size, bool b10 = false) @safe
+{
+ import std.format : format;
+
+ // NOTE: ulong.max = (2^64)-1 Bytes = 16 EiB - 1 = 16 * 1024⁵
+
+ //TODO: Consider table+index
+
+ static immutable string[] formatsIEC = [
+ "%0.0f B",
+ "%0.1f KiB",
+ "%0.1f MiB",
+ "%0.2f GiB",
+ "%0.2f TiB",
+ "%0.2f PiB",
+ "%0.2f EiB",
+ ];
+ static immutable string[] formatsSI = [
+ "%0.0f B",
+ "%0.1f kB",
+ "%0.1f MB",
+ "%0.2f GB",
+ "%0.2f TB",
+ "%0.2f PB",
+ "%0.2f EB",
+ ];
+
+ size_t i;
+ double base = void;
+
+ if (b10) // base 1000
+ {
+ base = 1000.0;
+ if (size >= EB) i = 6;
+ else if (size >= PB) i = 5;
+ else if (size >= TB) i = 4;
+ else if (size >= GB) i = 3;
+ else if (size >= MB) i = 2;
+ else if (size >= kB) i = 1;
+ }
+ else // base 1024
+ {
+ base = 1024.0;
+ if (size >= EiB) i = 6;
+ else if (size >= PiB) i = 5;
+ else if (size >= TiB) i = 4;
+ else if (size >= GiB) i = 3;
+ else if (size >= MiB) i = 2;
+ else if (size >= KiB) i = 1;
+ }
+
+ return format(b10 ? formatsSI[i] : formatsIEC[i], size / (base ^^ i));
+}
+@safe unittest
+{
+ assert(formatBin(0) == "0 B");
+ assert(formatBin(1) == "1 B");
+ assert(formatBin(1023) == "1023 B");
+ assert(formatBin(1024) == "1.0 KiB");
+ // Wouldn't this more exactly 7.99 EiB? Precision limitation?
+ assert(formatBin(long.max) == "8.00 EiB");
+ assert(formatBin(999, true) == "999 B");
+ assert(formatBin(1000, true) == "1.0 kB");
+ assert(formatBin(1_000_000, true) == "1.0 MB");
+}
+
+long cparse(string arg) @trusted
+{
+ // NOTE: Since toStringz can allocate, and I use this function a lot,
+ // a regular static buffer is used instead.
+ enum BUFSZ = 64;
+ char[BUFSZ] buf = void;
+ size_t sz = min(arg.length+1, BUFSZ-1); // Account for null terminator
+ memcpy(buf.ptr, arg.ptr, sz);
+ buf[sz] = 0;
+
+ long r = void;
+ if (sscanf(arg.toStringz, "%lli", &r) != 1)
+ throw new Exception("Could not parse: ".text(arg));
+ return r;
+}
+@safe unittest
+{
+ import std.conv : octal;
+ assert(cparse("0") == 0);
+ assert(cparse("1") == 1);
+ assert(cparse("10") == 10);
+ assert(cparse("20") == 20);
+ assert(cparse("0x1") == 0x1);
+ assert(cparse("0x10") == 0x10);
+ assert(cparse("0x20") == 0x20);
+ // NOTE: Signed numbers cannot be over 0x8000_0000_0000_000
+ assert(cparse("0x1bcd1234ffffaaaa") == 0x1bcd_1234_ffff_aaaa);
+ assert(cparse("0") == 0);
+ assert(cparse("01") == 1);
+ assert(cparse("010") == octal!"010");
+ assert(cparse("020") == octal!"020");
+}