Newer
Older
ddhx / src / ddhx.d
@dd86k dd86k on 11 Aug 2020 8 KB Fixes
/**
 * Main rendering engine.
 */
module ddhx;

import std.stdio : write, writeln, writef, writefln;
import std.mmfile;
import core.stdc.stdio : printf;
import menu, ddcon;
import utils : formatsize, unformat;

/// Copyright string
enum COPYRIGHT = "Copyright (c) dd86k 2017-2020";

/// App version
enum APP_VERSION = "0.2.0";

/// Offset type (hex, dec, etc.)
enum OffsetType : size_t {
	Hexadecimal, Decimal, Octal
}

/// 
enum DisplayMode : ubyte {
	Default, Text, Data
}

/// Default character for non-displayable characters
enum DEFAULT_CHAR = '.';

/// For header
private __gshared const char[] offsetTable = [
	'h', 'd', 'o'
];
/// For formatting
private __gshared const char[] formatTable = [
	'X', 'u', 'o'
];

//
// User settings
//

/// Bytes shown per row
__gshared ushort BytesPerRow = 16;
/// Current offset view type
__gshared OffsetType CurrentOffsetType = void;
/// Current display view type
__gshared DisplayMode CurrentDisplayMode = void;

//
// Internal
//

__gshared MmFile CFile = void;	/// Current file
__gshared ubyte* mmbuf = void;	/// mmfile buffer address
__gshared uint screenl = void;	/// screen size in bytes, 1 dimensional buffer

__gshared string fname = void;	/// filename
__gshared long fpos = void;	/// Current file position
__gshared long fsize = void;	/// File size

private __gshared char[32] tfsizebuf;	/// total formatted size buffer
private __gshared char[] tfsize;	/// total formatted size (slice)

/// Main app entry point
/// Params: pos = File position to start with
void ddhx_main(long pos) {
	import settings : HandleWidth;

	fpos = pos;
	tfsize = formatsize(tfsizebuf, fsize);
	coninit;
	ddhx_prep;
	conclear;
	ddhx_update_offsetbar;
	if (ddhx_render_raw < conheight - 2)
		ddhx_update_infobar;
	else
		ddhx_update_infobar_raw;

	InputInfo k = void;
KEY:
	coninput(k);
	switch (k.value) {

	//
	// Navigation
	//

	case Key.UpArrow, Key.K:
		if (fpos - BytesPerRow >= 0)
			ddhx_seek_unsafe(fpos - BytesPerRow);
		else
			ddhx_seek_unsafe(0);
		break;
	case Key.DownArrow, Key.J:
		if (fpos + screenl + BytesPerRow <= fsize)
			ddhx_seek_unsafe(fpos + BytesPerRow);
		else
			ddhx_seek_unsafe(fsize - screenl);
		break;
	case Key.LeftArrow, Key.H:
		if (fpos - 1 >= 0) // Else already at 0
			ddhx_seek_unsafe(fpos - 1);
		break;
	case Key.RightArrow, Key.L:
		if (fpos + screenl + 1 <= fsize)
			ddhx_seek_unsafe(fpos + 1);
		else
			ddhx_seek_unsafe(fsize - screenl);
		break;
	case Key.PageUp, Mouse.ScrollUp:
		if (fpos - cast(long)screenl >= 0)
			ddhx_seek_unsafe(fpos - screenl);
		else
			ddhx_seek_unsafe(0);
		break;
	case Key.PageDown, Mouse.ScrollDown:
		if (fpos + screenl + screenl <= fsize)
			ddhx_seek_unsafe(fpos + screenl);
		else
			ddhx_seek_unsafe(fsize - screenl);
		break;
	case Key.Home:
		ddhx_seek_unsafe(k.key.ctrl ? 0 : fpos - (fpos % BytesPerRow));
		break;
	case Key.End:
		if (k.key.ctrl) {
			ddhx_seek_unsafe(fsize - screenl);
		} else {
			const long np = fpos +
				(BytesPerRow - fpos % BytesPerRow);
			ddhx_seek_unsafe(np + screenl <= fsize ? np : fsize - screenl);
		}
		break;

	//
	// Actions/Shortcuts
	//

	case Key.Escape, Key.Enter, Key.Colon:
		hxmenu;
		break;
	case Key.G:
		hxmenu("g ");
		ddhx_update_offsetbar();
		break;
	case Key.I:
		ddhx_fileinfo;
		break;
	case Key.R, Key.F5:
		ddhx_refresh;
		break;
	case Key.A:
		HandleWidth("a");
		ddhx_refresh;
		break;
	case Key.Q: ddhx_exit; break;
	default:
	}
	goto KEY;
}

/// Refresh the entire screen
void ddhx_refresh() {
	ddhx_prep;
	conclear;
	ddhx_update_offsetbar;
	if (ddhx_render_raw < conheight - 2)
		ddhx_update_infobar;
	else
		ddhx_update_infobar_raw;
}

/**
 * Update the upper offset bar.
 */
void ddhx_update_offsetbar() {
	char [8]format = cast(char[8])" %02X"; // default
	format[4] = formatTable[CurrentOffsetType];
	conpos(0, 0);
	printf("Offset %c ", offsetTable[CurrentOffsetType]);
	for (ushort i; i < BytesPerRow; ++i)
		printf(cast(char*)format, i);
	putchar('\n');
}

/// Update the bottom current information bar.
void ddhx_update_infobar() {
	conpos(0, conheight - 1);
	ddhx_update_infobar_raw;
}

/// Updates information bar without cursor position call.
void ddhx_update_infobar_raw() {
	char[32] bl = void, cp = void;
	writef(" %*s | %*s/%*s | %7.3f%%",
		7,  formatsize(bl, screenl), // Buffer size
		10, formatsize(cp, fpos), // Formatted position
		10, tfsize, // Total file size
		((cast(float)fpos + screenl) / fsize) * 100 // Pos/filesize%
	);
}

/// Determine screensize
void ddhx_prep() {
	const int bufs = (conheight - 2) * BytesPerRow; // Proposed buffer size
	screenl = fsize >= bufs ? bufs : cast(uint)fsize;
}

/**
 * Goes to the specified position in the file.
 * Ignores bounds checking for performance reasons.
 * Sets CurrentPosition.
 * Params: pos = New position
 */
void ddhx_seek_unsafe(long pos) {
	if (screenl < fsize) {
		fpos = pos;
		if (ddhx_render < conheight - 2)
			ddhx_update_infobar;
		else
			ddhx_update_infobar_raw;
	} else
		ddhx_msglow("Navigation disabled, buffer too small");
}

/**
 * Goes to the specified position in the file.
 * Checks bounds and calls Goto.
 * Params: pos = New position
 */
void ddhx_seek(long pos) {
	if (pos + screenl > fsize)
		ddhx_seek_unsafe(fsize - screenl);
	else
		ddhx_seek_unsafe(pos);
}

/**
 * Parses the string as a long and navigates to the file location.
 * Includes offset checking (+/- notation).
 * Params: str = String as a number
 */
void ddhx_seek(string str) {
	byte rel = void; // Lazy code
	if (str[0] == '+') { // relative position
		rel = 1;
		str = str[1..$];
	} else if (str[0] == '-') {
		rel = 2;
		str = str[1..$];
	}
	long l = void;
	if (unformat(str, l) == false) {
		ddhx_msglow("Could not parse number");
		return;
	}
	switch (rel) {
	case 1:
		if (fpos + l - screenl < fsize)
			ddhx_seek_unsafe(fpos + l);
		break;
	case 2:
		if (fpos - l >= 0)
			ddhx_seek_unsafe(fpos - l);
		break;
	default:
		if (l >= 0 && l < fsize - screenl) {
			ddhx_seek_unsafe(l);
		} else {
			import std.format : format;
			ddhx_msglow(format("Range too far or negative: %d (%XH)", l, l));
		}
	}
}

/// Update display from buffer
uint ddhx_render() {
	conpos(0, 1);
	return ddhx_render_raw;
}

/// Update display from buffer without setting cursor
/// Returns: The number of lines printed on screen
uint ddhx_render_raw() {
	__gshared char[] hexTable = [
		'0', '1', '2', '3', '4', '5', '6', '7',
		'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
	];
	
	int brow = BytesPerRow; /// bytes per row
	int minw = cast(int)brow * 3;

	char[1024] a = void, d = void;
	a[brow] = d[minw] = '\0';

	size_t p = cast(size_t)fpos, wlen = p + screenl; /// window length
	const ubyte[] fbuf = cast(ubyte[])CFile[p..wlen];

	char[18] bytef = cast(char[18])"%8zX %.*s  %.*s\n";
	bytef[3] = formatTable[CurrentOffsetType];

	//TODO: Rework loop
	uint ld; /// number of lines printed
	bool over = void;
	ubyte b = void;
	for (size_t bi; p < wlen; p += brow) {
		over = p + brow > fsize;

		if (over) {
			brow = cast(uint)(fsize - p);
			minw = brow * 3;
		}

		size_t ai;
		for (size_t di; ai < brow; ++ai) {
			b = fbuf[bi++];
			d[di++] = ' ';
			d[di++] = hexTable[b >> 4];
			d[di++] = hexTable[b & 15];
			a[ai] = b > 0x7E || b < 0x20 ? DEFAULT_CHAR : b;
		}

		//TODO: Row remainder

		printf(cast(char*)bytef, p, minw, cast(char*)d, brow, cast(char*)a);

		++ld;

		if (over) break;
	}
	return ld;
}

/**
 * Message once (upper bar)
 * Params: msg = Message string
 */
void ddhx_msgtop(string msg) {
	conpos(0, 0);
	writef("%s%*s", msg, (conwidth - 1) - msg.length, " ");
}

/**
 * Message once (bottom bar)
 * Params: msg = Message string
 */
void ddhx_msglow(string msg) {
	conpos(0, conheight - 1);
	writef("%s%*s", msg, (conwidth - 1) - msg.length, " ");
}

/**
 * Bottom bar message.
 * Params:
 *   f = Format
 *   arg = String argument
 */
void ddhx_msglow(string f, string arg) {
	//TODO: (string format, ...)
	import std.format : format;
	ddhx_msglow(format(f, arg));
}

/// Print some file information at the bottom bar
void ddhx_fileinfo() {
	import std.format : sformat;
	import std.path : baseName;
	char[256] b = void;
	ddhx_msglow(cast(string)b.sformat!"%s  %s"(tfsize, fname.baseName));
}

/// Exits ddhx
void ddhx_exit() {
	import core.stdc.stdlib : exit;
	conclear;
	exit(0);
}