diff --git a/dscanner.ini b/dscanner.ini
index 7004ee1..08fdff0 100644
--- a/dscanner.ini
+++ b/dscanner.ini
@@ -66,7 +66,7 @@
; Checks for asserts that are always true
useless_assert_check="enabled"
; Check for uses of the old-style alias syntax
-alias_syntax_check="enabled"
+alias_syntax_check="disabled"
; Checks for else if that should be else static if
static_if_else_check="enabled"
; Check for unclear lambda syntax
diff --git a/src/ddhx/app.d b/src/ddhx/app.d
index d33dc97..787a3ee 100644
--- a/src/ddhx/app.d
+++ b/src/ddhx/app.d
@@ -20,20 +20,23 @@
// User settings
//
-int printError(A...)(int code, string fmt, A args) {
+int printError(A...)(int code, const(char)[] fmt, A args) {
stderr.write("error: ");
stderr.writefln(fmt, args);
return code;
}
+//TODO: deprecate
int openFile(string path) {
version (Trace) trace("path=%s", path);
return input.openFile(path);
}
+//TODO: deprecate
int openMmfile(string path) {
version (Trace) trace("path=%s", path);
return input.openMmfile(path);
}
+//TODO: deprecate
int openStdin() {
version (Trace) trace("-");
return input.openStdin();
@@ -41,9 +44,6 @@
/// Main app entry point
int appInteractive(long skip = 0) {
- //TODO: Consider changing the buffering strategy
- // e.g., flush+setvbuf/puts+flush
-
//TODO: negative should be starting from end of file (if not stdin)
if (skip < 0)
skip = +skip;
@@ -61,7 +61,7 @@
terminalClear;
version (Trace) trace("conheight");
globals.termHeight = terminalSize.height;
- resizeBuffer(true);
+ resizeDisplayBuffer(true);
input.read();
render();
@@ -79,7 +79,7 @@
L_KEYDOWN:
version (Trace) trace("key=%d", event.key);
- switch (event.key) with (Key) {
+ switch (event.key) with (Key) with (Mod) {
//
// Navigation
@@ -89,12 +89,12 @@
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 | Mod.ctrl: moveStart; break;
- case End: moveAlignEnd; break;
- case End | Mod.ctrl: moveEnd; break;
+ case PageUp: movePageUp; break;
+ case PageDown: movePageDown; break;
+ case Home: moveAlignStart; break;
+ case Home | ctrl: moveStart; break;
+ case End: moveAlignEnd; break;
+ case End | ctrl: moveEnd; break;
//
// Actions/Shortcuts
@@ -114,7 +114,7 @@
break;
case G:
menu("g ");
- displayRenderTop();
+ displayRenderTop;
break;
case I:
msgFileInfo;
@@ -365,7 +365,7 @@
/// Automatically determine new buffer size for display engine from
/// console/terminal window size.
/// Params: skipTerm = Skip terminal size detection and use stored value.
-void resizeBuffer(bool skipTerm = false) {
+void resizeDisplayBuffer(bool skipTerm = false) {
//TODO: Avoid crash when on end of file + resize goes further than file
version (Trace) trace("skip=%s", skipTerm);
@@ -385,7 +385,7 @@
/// 4. Clear the terminal
/// 5. Render
void refresh() {
- resizeBuffer();
+ resizeDisplayBuffer();
input.seek(input.position);
input.read();
terminalClear();
@@ -525,7 +525,7 @@
/// Params:
/// fmt = Format.
/// args = Arguments.
-void msgTop(A...)(string fmt, A args) {
+void msgTop(A...)(const(char)[] fmt, A args) {
terminalPos(0, 0);
msg(fmt, args);
}
@@ -534,12 +534,12 @@
/// Params:
/// fmt = Format.
/// args = Arguments.
-void msgBottom(A...)(string fmt, A args) {
+void msgBottom(A...)(const(char)[] fmt, A args) {
terminalPos(0, terminalSize.height - 1);
msg(fmt, args);
}
-private void msg(A...)(string fmt, A args) {
+private void msg(A...)(const(char)[] fmt, A args) {
import std.format : sformat;
char[256] outbuf = void;
char[] outs = outbuf[].sformat(fmt, args);
diff --git a/src/ddhx/common.d b/src/ddhx/common.d
index 092cb4a..01a5dc2 100644
--- a/src/ddhx/common.d
+++ b/src/ddhx/common.d
@@ -4,7 +4,8 @@
/// Authors: $(LINK2 github.com/dd86k, dd86k)
module ddhx.common;
-import std.stdio : File, stdin, FILE;
+//TODO: Remove imports
+import std.stdio : File, stdin, FILE, fgetpos, fsetpos, fpos_t;
import std.mmfile;
import std.file : getSize;
import std.path : baseName;
@@ -36,6 +37,7 @@
}
///
+//TODO: Deprecate
struct Input {
private union { // Input internals or buffer
File file; /// File input
@@ -51,6 +53,7 @@
}
ulong size; /// file size
long position; /// Absolute position in file/mmfile/buffer
+ private long position2;
uint bufferSize; /// buffer size
string fileName; /// File basename
const(char)[] sizeString; /// Binary file size as string
diff --git a/src/ddhx/error.d b/src/ddhx/error.d
index cfb395c..cc6adfc 100644
--- a/src/ddhx/error.d
+++ b/src/ddhx/error.d
@@ -8,7 +8,6 @@
success,
unknown,
negativeValue,
- exception,
fileEmpty,
inputEmpty,
invalidCommand,
@@ -22,9 +21,12 @@
noLastItem,
eof,
unimplemented,
+
+ exception,
+ os,
}
-private __gshared ErrorCode lastCode;
+__gshared ErrorCode lastError; /// Last error code.
private __gshared string lastMsg;
private __gshared string lastFile;
private __gshared int lastLine;
@@ -33,7 +35,7 @@
version (Trace) trace("%s", code);
lastFile = file;
lastLine = line;
- return (lastCode = code);
+ return (lastError = code);
}
int errorSet(Exception ex) {
@@ -50,12 +52,17 @@
lastFile = ex.file;
lastLine = cast(int)ex.line;
lastMsg = ex.msg;
- return (lastCode = ErrorCode.exception);
+ return (lastError = ErrorCode.exception);
}
-string errorMsg() {
- switch (lastCode) with (ErrorCode) {
+const(char)[] errorMsg() {
+ __gshared char[1024] errMsg;
+
+ switch (lastError) with (ErrorCode) {
case exception: return lastMsg;
+ case os:
+
+ return "os";
case fileEmpty: return "File is empty.";
case inputEmpty: return "Input is empty.";
case invalidCommand: return "Command not found.";
diff --git a/src/ddhx/file.d b/src/ddhx/file.d
new file mode 100644
index 0000000..e6d2298
--- /dev/null
+++ b/src/ddhx/file.d
@@ -0,0 +1,355 @@
+/// File handling.
+///
+/// This exists because 32-bit runtimes suffer from the 32-bit file size limit.
+/// Despite the MS C runtime having _open and _fseeki64, the DM C runtime does
+/// not have these functions. With the OS functions in place, Windows XP is the
+/// minimum version.
+/// Copyright: dd86k
+/// License: MIT
+/// Authors: $(LINK2 github.com/dd86k, dd86k)
+module ddhx.file;
+
+version (Windows) {
+ import core.sys.windows.winnt :
+ DWORD, HANDLE, LARGE_INTEGER,
+ GENERIC_ALL, GENERIC_READ, GENERIC_WRITE;
+ import core.sys.windows.winbase :
+ CreateFileA, CreateFileW, SetFilePointerEx, ReadFile, ReadFileEx, GetFileSizeEx,
+ OPEN_ALWAYS, OPEN_EXISTING, INVALID_HANDLE_VALUE,
+ FILE_BEGIN, FILE_CURRENT, FILE_END;
+
+ private alias OSHANDLE = HANDLE;
+ private alias SEEK_SET = FILE_BEGIN;
+ private alias SEEK_CUR = FILE_CURRENT;
+ private alias SEEK_END = FILE_END;
+} else version (Posix) {
+ import core.sys.posix.unistd;
+ import core.sys.posix.sys.types;
+ import core.sys.posix.sys.stat;
+ import core.sys.posix.sys.ioctl;
+ import core.sys.posix.fcntl;
+ import core.stdc.errno;
+ import core.stdc.stdio : SEEK_SET, SEEK_CUR, SEEK_END;
+
+ version (linux) {
+ import core.sys.linux.fs : BLKGETSIZE64;
+ private alias BLOCKSIZE = BLKGETSIZE64;
+ }
+
+ private alias OSHANDLE = int;
+} else {
+ static assert(0, "Implement file I/O");
+}
+
+import std.mmfile;
+import std.string : toStringz;
+import std.utf : toUTF16z;
+import std.file : getSize;
+import std.path : baseName;
+import std.container.array : Array;
+import std.stdio : File;
+import core.stdc.stdio : FILE;
+import ddhx;
+
+/// Default buffer size.
+private enum DEFAULT_BUFFER_SIZE = 4 * 1024;
+
+/// FileMode for OSFile.
+enum FileMode {
+ file, /// Normal file.
+ mmfile, /// Memory-mapped file.
+ stream, /// Standard streaming I/O.
+ memory, /// Typically from a stream buffered into memory.
+}
+
+/// 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.
+}
+
+/// Improved file I/O.
+//TODO: Share file.
+// By default file isn't shared (at least on Windows) which would allow
+// refreshing view (manually) when a program writes to file.
+//TODO: Virtual change system.
+// For editing/rendering/saving.
+// Array!(Edit) or sorted dictionary?
+struct OSFile {
+ private union {
+ OSHANDLE fileHandle;
+ MmFile mmHandle;
+ File stream;
+ }
+ private union {
+ ubyte[] readBuffer; /// Buffer for file/stream inputs
+ ubyte *mmAddress;
+ }
+
+ long position; /// Current file position.
+ private long position2; /// Saved file position.
+
+ long size; /// Last reported file size.
+ const(char)[] sizeString; /// Binary file size as string
+ string fullPath; /// Original file path.
+ string name; /// Current file name.
+ FileMode mode; /// Current file mode.
+
+ ubyte[] buffer; /// Resulting buffer or slice.
+ uint readSize; /// Desired buffer size.
+
+ int delegate(Seek, long) seek;
+ int delegate() read;
+
+ int openFile(string path/*, bool create*/) {
+ version (Windows) {
+ fileHandle = CreateFileW(
+ //path.toUTF16z, // lpFileName
+ path.tempCStringW, // lpFileName
+ GENERIC_READ/* | GENERIC_WRITE*/, // dwDesiredAccess
+ 0, // dwShareMode
+ null, // lpSecurityAttributes
+ OPEN_EXISTING, // dwCreationDisposition
+ 0, // dwFlagsAndAttributes
+ null, // hTemplateFile
+ );
+ if (fileHandle == INVALID_HANDLE_VALUE)
+ return errorSet(ErrorCode.os);
+ } else version (Posix) {
+ fileHandle = open(path.toStringz, O_RDWR);
+ if (fileHandle == -1)
+ return errorSet(ErrorCode.os);
+ }
+
+ if (refreshSize())
+ return lastError;
+ if (size == 0)
+ return errorSet(ErrorCode.fileEmpty);
+
+ sizeString = getSizeString;
+ setProperties(FileMode.file, path, baseName(path));
+ return 0;
+ }
+
+ int openMmfile(string path/*, bool create*/) {
+ try {
+ /*file.size = getSize(path);
+ if (file.size == 0)
+ return errorSet(ErrorCode.fileEmpty);*/
+ mmHandle = new MmFile(path, MmFile.Mode.read, 0, mmAddress);
+ } catch (Exception ex) {
+ return errorSet(ex);
+ }
+
+ if (refreshSize())
+ return lastError;
+ if (size == 0)
+ return errorSet(ErrorCode.fileEmpty);
+
+ sizeString = getSizeString;
+ setProperties(FileMode.mmfile, path, baseName(path));
+ return 0;
+ }
+
+ int openStream(File file) {
+ setProperties(FileMode.stream, null, "-");
+ return 0;
+ }
+
+ int openMemory(ubyte[] data) {
+ buffer = data;
+ setProperties(FileMode.memory, null, "-");
+ return 0;
+ }
+
+ // Avoids errors where I forget to set basic property members.
+ private void setProperties(FileMode newMode, string path, string baseName) {
+ readSize = DEFAULT_BUFFER_SIZE;
+ mode = newMode;
+ fullPath = path;
+ name = baseName;
+ final switch (newMode) with (FileMode) {
+ case file:
+ read = &readFile;
+ seek = &seekFile;
+ break;
+ case mmfile:
+ read = &readMmfile;
+ seek = &seekMmfile;
+ break;
+ case stream:
+ read = &readStream;
+ seek = &seekStream;
+ break;
+ case memory:
+ read = &readMemory;
+ seek = &seekMemory;
+ break;
+ }
+ }
+
+ private int readFile() {
+ version (Windows) {
+ DWORD r = void;
+ if (ReadFile(fileHandle, readBuffer.ptr, readBuffer.length, &r, null) == FALSE)
+ return errorSet(ErrorCode.os);
+ buffer = readBuffer[0..r];
+ } else version (Posix) {
+ alias mygod = core.sys.posix.unistd.read;
+ ssize_t r = void;
+ if ((r = mygod(fileHandle, readBuffer.ptr, readBuffer.length)) < 0)
+ return errorSet(ErrorCode.os);
+ buffer = readBuffer[0..r];
+ }
+ return 0;
+ }
+ private int readMmfile() {
+ buffer = cast(ubyte[])mmHandle[position..position + readSize];
+ return 0;
+ }
+ private int readStream() {
+ buffer = stream.rawRead(readBuffer);
+ return 0;
+ }
+ private int readMemory() {
+ buffer = readBuffer[position..position + readSize];
+ return 0;
+ }
+
+ private int seekFile(Seek origin, long pos) {
+ version (Windows) {
+ LARGE_INTEGER p = void;
+ p.QuadPart = pos;
+ if (SetFilePointerEx(fileHandle, p, null, 0) == FALSE)
+ return errorSet(ErrorCode.os);
+ } else version (Posix) {
+ if (lseek64(fileHandle, pos, origin) == -1)
+ return errorSet(ErrorCode.os);
+ }
+ return 0;
+ }
+ private int seekMmfile(Seek origin, 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;
+ }
+ }
+ // Acts as a skip regardless of origin (Seek.current)
+ private int seekStream(Seek origin, long pos) {
+ return 0;
+ }
+ private alias seekMemory = seekMmfile;
+
+ void saveState() {
+ }
+ void restoreState() {
+ }
+
+ int refreshSize() {
+ final switch (mode) with (FileMode) {
+ case file:
+ version (Windows) {
+ LARGE_INTEGER li = void;
+ if (GetFileSizeEx(fileHandle, &li) == 0)
+ return errorSet(ErrorCode.os);
+ size = li.QuadPart;
+ return 0;
+ } else version (Posix) {
+ stat_t stats = void;
+ if (fstat(fileHandle, &stats) == -1)
+ return errorSet(ErrorCode.os);
+ // NOTE: fstat(2) sets st_size to 0 on block devices
+ switch (stats.st_mode & S_IFMT) {
+ case S_IFREG: // File
+ case S_IFLNK: // Link
+ size = stats.st_size;
+ return 0;
+ case S_IFBLK: // Block device (like a disk)
+ //TODO: BSD variants
+ return ioctl(fileHandle, BLOCKSIZE, &size) == -1 ?
+ errorSet(ErrorCode.os) : 0;
+ default: return errorSet(ErrorCode.invalidType);
+ }
+ }
+ case mmfile:
+ size = mmHandle.length;
+ return 0;
+ case stream:
+ return 0;
+ case memory:
+ return 0;
+ }
+ }
+
+ const(char)[] getSizeString() {
+ __gshared char[32] b = void;
+ return formatSize(b, size);
+ }
+
+ void resizeBuffer(uint newSize) {
+ readSize = newSize;
+
+ switch (mode) with (FileMode) {
+ case file, stream:
+ readBuffer = new ubyte[newSize];
+ break;
+ default:
+ }
+ }
+
+ //TODO: other types
+ int toMemory(uint skip, long length) {
+ import core.stdc.stdio : fread;
+ import core.stdc.stdlib : malloc, free;
+
+ ubyte[DEFAULT_BUFFER_SIZE] defbuf = void;
+
+ FILE *f = stream.getFP;
+
+ size_t l = void;
+ if (skip) {
+ import std.algorithm.comparison : min;
+
+ L_PRESKIP:
+ int bufSize = min(DEFAULT_BUFFER_SIZE, skip);
+ void *buf = malloc(bufSize);
+ if (buf == null) throw new Error("Out of memory");
+
+ L_SKIP:
+ skip -= fread(buf, 1, bufSize, f);
+ if (skip <= 0) {
+ free(buf);
+ goto L_READ;
+ }
+ if (skip < bufSize) {
+ bufSize = skip;
+ free(buf);
+ goto L_PRESKIP;
+ }
+ goto L_SKIP;
+ }
+
+ L_READ:
+ do {
+ l = fread(defbuf.ptr, 1, DEFAULT_BUFFER_SIZE, f);
+ buffer ~= defbuf[0..l];
+ if (length) {
+ length -= l;
+ if (length < 0) break;
+ }
+ } while (l >= DEFAULT_BUFFER_SIZE);
+
+ size = buffer.length;
+
+ setProperties(FileMode.memory, null, "-");
+ return 0;
+ }
+}
\ No newline at end of file