Newer
Older
ddhx / src / editor.d
/// Implements a file buffer and file I/O.
/// Copyright: dd86k <dd@dax.moe>
/// License: MIT
/// Authors: $(LINK2 https://github.com/dd86k, dd86k)
module editor;

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;

/// 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.
}

// 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.

enum EditError
{
    none,
    io,
    memory,
    bufferSize,
}

/// Represents a single edit
struct Edit
{
    long position;	/// Absolute offset of edit
    int offset;     /// Offset to byte group in digits
    int value;	/// Payload
    // or ubyte[8]?
}

/// 
struct Editor
{
    private enum DEFAULT_BUFSZ = 4096;
    
    /// 
    //IoMode iomode;
    
    private union
    {
        OSFile       osfile;
        //OSMmFile     mmfile;
        //File         stream;
        //MemoryStream memstream;
    }
    
    struct Edits
    {
        size_t index;
        size_t count;
        Edit[long] list;
        SList!long history;
        const(char)[] name = "ov";
    }
    Edits edits;
    bool insert;
    bool readonly;
    
    /// 
    private ubyte* rdbuf;
    /// 
    private size_t rdbufsz;
    /// File position
    private long position;
    
    int lasterr;
    
    int seterr(int er)
    {
        return (lasterr = er);
    }
    
    int open(string path, bool exists, bool viewonly)
    {
        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;
    }
    
    void close()
    {
        return osfile.close();
    }
    
    //
    //
    //
    
    //TODO: Consider const(char)[] errorMsg()
    //      Clear message buffer on retrieval
    
    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, 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;
    }+/
}