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