]> git.lizzy.rs Git - micro.git/commitdiff
Initial commit
authorZachary Yedidia <zyedidia@gmail.com>
Fri, 11 Mar 2016 02:06:06 +0000 (21:06 -0500)
committerZachary Yedidia <zyedidia@gmail.com>
Fri, 11 Mar 2016 02:06:06 +0000 (21:06 -0500)
.gitignore [new file with mode: 0755]
dub.sdl [new file with mode: 0644]
src/buffer.d [new file with mode: 0644]
src/clipboard.d [new file with mode: 0644]
src/cursor.d [new file with mode: 0644]
src/main.d [new file with mode: 0644]
src/statusline.d [new file with mode: 0644]
src/view.d [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100755 (executable)
index 0000000..91fc4cb
--- /dev/null
@@ -0,0 +1,10 @@
+.dub
+docs.json
+__dummy.html
+*.o
+*.obj
+
+dub.selections.json
+micro
+
+.DS_STORE
diff --git a/dub.sdl b/dub.sdl
new file mode 100644 (file)
index 0000000..f3ecfaf
--- /dev/null
+++ b/dub.sdl
@@ -0,0 +1,5 @@
+name "micro"
+description "A minimal D application."
+copyright "Copyright © 2016, zachary"
+authors "zachary"
+dependency "termbox" version="0.0.3"
diff --git a/src/buffer.d b/src/buffer.d
new file mode 100644 (file)
index 0000000..1cd6873
--- /dev/null
@@ -0,0 +1,167 @@
+import std.math;
+import std.stdio;
+import std.utf;
+import std.string;
+import std.conv: to;
+import std.algorithm;
+
+class Buffer {
+    private string value = null;
+    private Buffer left;
+    private Buffer right;
+
+    string name = "";
+    string savedText;
+
+    ulong length;
+
+    int splitLength = 1000;
+    int joinLength = 500;
+    double rebalanceRatio = 1.2;
+
+    this() { }
+
+    this(string str, string name = "") {
+        this.value = str;
+        this.length = str.length;
+        this.name = name;
+        this.savedText = str;
+
+        left = new Buffer();
+        right = new Buffer();
+        left.value = "";
+        right.value = "";
+
+        adjust();
+    }
+
+    void save(string filename = null) {
+        if (filename is null) {
+            filename = name;
+        }
+        if (filename != "") {
+            string bufSrc = this.toString();
+            File f = File(filename, "w");
+            f.write(bufSrc);
+            f.close();
+            savedText = bufSrc;
+        }
+    }
+
+    @property string[] lines() {
+        string str = this.toString();
+        if (str == "") {
+            return [""];
+        } else {
+            return str.split("\n");
+        }
+    }
+
+    void adjust() {
+        if (value !is null) {
+            if (length > splitLength) {
+                auto divide = cast(int) floor(length / 2.0);
+                left = new Buffer(value[0 .. divide]);
+                right = new Buffer(value[divide .. $]);
+            }
+        } else {
+            if (length < joinLength) {
+                value = left.toString() ~ right.toString();
+            }
+        }
+    }
+
+    override
+    string toString() {
+        if (value !is null) {
+            return value;
+        } else {
+            return left.toString ~ right.toString();
+        }
+    }
+
+    void remove(ulong start, ulong end) {
+        if (value !is null) {
+            value = value[0 .. start] ~ value[end .. $];
+            length = value.length;
+        } else {
+            auto leftStart = min(start, left.length);
+            auto leftEnd = min(end, left.length);
+            auto rightStart = max(0, min(start - left.length, right.length));
+            auto rightEnd = max(0, min(end - left.length, right.length));
+            if (leftStart < left.length) {
+                left.remove(leftStart, leftEnd);
+            }
+            if (rightEnd > 0) {
+                right.remove(rightStart, rightEnd);
+            }
+            length = left.length + right.length;
+        }
+
+        adjust();
+    }
+
+    void insert(ulong position, string value) {
+        if (this.value !is null) {
+            this.value = this.value[0 .. position] ~ value ~ this.value[position .. $];
+            length = this.value.length;
+        } else {
+            if (position < left.length) {
+                left.insert(position, value);
+                length = left.length + right.length;
+            } else {
+                right.insert(position - left.length, value);
+            }
+        }
+
+        adjust();
+    }
+
+    void rebuild() {
+        if (value is null) {
+            value = left.toString() ~ right.toString();
+            adjust();
+        }
+    }
+
+    void rebalance() {
+        if (value is null) {
+            if (left.length / right.length > rebalanceRatio ||
+                right.length / left.length > rebalanceRatio) {
+                rebuild();
+            } else {
+                left.rebalance();
+                right.rebalance();
+            }
+        }
+    }
+
+    string substring(ulong start, ulong end = length) {
+        if (value !is null) {
+            return value[start .. end];
+        } else {
+            auto leftStart = min(start, left.length);
+            auto leftEnd = min(end, left.length);
+            auto rightStart = max(0, min(start - left.length, right.length));
+            auto rightEnd = max(0, min(end - left.length, right.length));
+
+            if (leftStart != leftEnd) {
+                if (rightStart != rightEnd) {
+                    return left.substring(leftStart, leftEnd) ~ right.substring(rightStart, rightEnd);
+                } else {
+                    return left.substring(leftStart, leftEnd);
+                }
+            } else {
+                if (rightStart != rightEnd) {
+                    return right.substring(rightStart, rightEnd);
+                } else {
+                    return "";
+                }
+            }
+        }
+    }
+
+    char charAt(ulong pos) {
+        return to!char(substring(pos, pos + 1));
+    }
+}
diff --git a/src/clipboard.d b/src/clipboard.d
new file mode 100644 (file)
index 0000000..9daf9ba
--- /dev/null
@@ -0,0 +1,63 @@
+import std.process: execute, spawnProcess, pipe;
+
+class Clipboard {
+    static bool supported;
+    version(OSX) {
+        static bool init() {
+            return supported = true;
+        }
+
+        static void write(string txt) {
+            auto p = pipe();
+            p.writeEnd.write(txt);
+            spawnProcess("pbcopy", p.readEnd());
+        }
+
+        static string read() {
+            return execute("pbpaste").output;
+        }
+    }
+
+    version(linux) {
+        import std.exception: collectException;
+        string[] copyCmd;
+        string[] pasteCmd;
+
+        static bool init() {
+            if (collectException(execute(["xsel", "-h"]))) {
+                if (collectException(execute(["xclip", "-h"]))) {
+                    return supported = false;
+                } else  {
+                    copyCmd = ["xclip", "-in", "-selection", "clipboard"];
+                    pasteCmd = ["xclip", "-out", "-selection", "clipboard"];
+                    return supported = true;
+                }
+            } else {
+                copyCmd = ["xsel", "--input", "--clipboard"];
+                pasteCmd = ["xsel", "--output", "--clipboard"];
+                return supported = true;
+            }
+        }
+
+        static void write(string txt) {
+            auto p = pipe();
+            p.writeEnd.write(txt);
+            spawnProcess(copyCmd, p.readEnd());
+        }
+
+        static string read() {
+            return execute(pasteCmd).output;
+        }
+    }
+
+    version(Windows) {
+        // No windows support yet
+    }
+}
+
+unittest {
+    string text = "æêáóìëæêî";
+    assert(Clipboard.init());
+    Clipboard.write(text);
+    assert(Clipboard.read() == text);
+}
diff --git a/src/cursor.d b/src/cursor.d
new file mode 100644 (file)
index 0000000..2d72939
--- /dev/null
@@ -0,0 +1,34 @@
+import buffer;
+
+class Cursor {
+    Buffer buf;
+    int x, y;
+
+    int lastX;
+
+    this(Buffer buf) {
+        this.buf = buf;
+    }
+
+    @property int loc() {
+        int loc;
+        foreach (i; 0 .. y) {
+            loc += buf.lines[i].length + 1;
+        }
+        loc += x;
+        return loc;
+    }
+
+    @property void loc(int value) {
+        int loc;
+        foreach (y, l; buf.lines) {
+            if (loc + l.length+1 > value) {
+                this.y = cast(int) y;
+                x = value - loc;
+                return;
+            } else {
+                loc += l.length+1;
+            }
+        }
+    }
+}
diff --git a/src/main.d b/src/main.d
new file mode 100644 (file)
index 0000000..570d11c
--- /dev/null
@@ -0,0 +1,56 @@
+import termbox;
+import view;
+import buffer;
+import cursor;
+import statusline;
+
+import std.regex;
+import core.exception;
+import std.conv;
+import std.file;
+import std.range;
+import std.string;
+import std.stdio;
+
+void main(string[] args) {
+    if (args.length < 2) {
+        return;
+    }
+    string filename = args[1];
+
+    string fileSrc = readText(filename);
+
+    init();
+
+    Buffer buffer = new Buffer(fileSrc, filename);
+    View v = new View(buffer);
+    StatusLine sl = new StatusLine();
+    sl.view = v;
+
+    setInputMode(InputMode.mouse);
+
+    Event e;
+    try {
+        while (e.key != Key.esc) {
+            clear();
+
+            v.display();
+            sl.display();
+
+            flush();
+
+            pollEvent(&e);
+            v.update(e);
+        }
+    } catch (object.Error e) {
+        shutdown();
+        writeln(e);
+        return;
+    } catch (Exception e) {
+        shutdown();
+        writeln(e);
+        return;
+    }
+
+    shutdown();
+}
diff --git a/src/statusline.d b/src/statusline.d
new file mode 100644 (file)
index 0000000..6ab6815
--- /dev/null
@@ -0,0 +1,27 @@
+import termbox;
+import view;
+
+class StatusLine {
+    View view;
+
+    this() { }
+
+    void update() {
+
+    }
+
+    void display() {
+        int y = height() - 2;
+        string file = view.buf.name;
+        if (view.buf.toString != view.buf.savedText) {
+            file ~= " +";
+        }
+        foreach (x; 0 .. width()) {
+            if (x >= 1 && x < 1 + file.length) {
+                setCell(x, y, cast(uint) file[x - 1], Color.white, Color.blue);
+            } else  {
+                setCell(x, y, ' ', Color.white, Color.blue);
+            }
+        }
+    }
+}
diff --git a/src/view.d b/src/view.d
new file mode 100644 (file)
index 0000000..73b4cd1
--- /dev/null
@@ -0,0 +1,157 @@
+import termbox;
+import cursor;
+import buffer;
+import clipboard;
+
+import std.conv: to;
+import std.stdio;
+
+class View {
+    int topline;
+    int xOffset;
+
+    int width;
+    int height;
+
+    Buffer buf;
+    Cursor cursor;
+
+    this(Buffer buf, int topline = 0, int width = termbox.width(), int height = termbox.height()-2) {
+        this.topline = topline;
+        this.width = width;
+        this.height = height;
+
+        this.buf = buf;
+        this.cursor = new Cursor(buf);
+    }
+
+    void cursorUp() {
+        if (cursor.y > 0) {
+            cursor.y--;
+            cursor.x = cursor.lastX;
+            if (cursor.x > buf.lines[cursor.y].length) {
+                cursor.x = cast(int) buf.lines[cursor.y].length;
+            }
+        }
+    }
+
+    void cursorDown() {
+        if (cursor.y < buf.lines.length - 1) {
+            cursor.y++;
+            cursor.x = cursor.lastX;
+            if (cursor.x > buf.lines[cursor.y].length) {
+                cursor.x = cast(int) buf.lines[cursor.y].length;
+            }
+        }
+    }
+
+    void cursorRight() {
+        if (cursor.x < buf.lines[cursor.y].length) {
+            cursor.x++;
+            cursor.lastX = cursor.x;
+        }
+    }
+
+    void cursorLeft() {
+        if (cursor.x > 0) {
+            cursor.x--;
+            cursor.lastX = cursor.x;
+        }
+    }
+
+    void update(Event e) {
+        if (e.key == Key.mouseWheelUp) {
+            if (topline > 0) {
+                topline--;
+            }
+        } else if (e.key == Key.mouseWheelDown) {
+            if (topline < buf.lines.length - height) {
+                topline++;
+            }
+        } else {
+            if (e.key == Key.arrowUp) {
+                cursorUp();
+            } else if (e.key == Key.arrowDown) {
+                cursorDown();
+            } else if (e.key == Key.arrowRight) {
+                cursorRight();
+            } else if (e.key == Key.arrowLeft) {
+                cursorLeft();
+            } else if (e.key == Key.mouseLeft) {
+                cursor.x = e.x - xOffset;
+                if (cursor.x < 0) {
+                    cursor.x = 0;
+                }
+                cursor.y = e.y + topline;
+                cursor.lastX = cursor.x;
+                if (cursor.x > buf.lines[cursor.y].length) {
+                    cursor.x = cast(int) buf.lines[cursor.y].length;
+                }
+            } else if (e.key == Key.ctrl_s) {
+                buf.save();
+            } else if (e.key == Key.ctrl_v) {
+                if (Clipboard.supported) {
+                    buf.insert(cursor.loc, Clipboard.read());
+                }
+            } else {
+                if (e.ch != 0) {
+                    buf.insert(cursor.loc, to!string(to!char(e.ch)));
+                    cursorRight();
+                } else if (e.key == Key.space) {
+                    buf.insert(cursor.loc, " ");
+                    cursorRight();
+                } else if (e.key == Key.enter) {
+                    buf.insert(cursor.loc, "\n");
+                    cursor.loc = cursor.loc + 1;
+                } else if (e.key == Key.backspace2) {
+                    if (cursor.loc != 0) {
+                        cursor.loc = cursor.loc - 1;
+                        buf.remove(cursor.loc, cursor.loc + 1);
+                    }
+                }
+            }
+
+            if (cursor.y < topline) {
+                topline--;
+            }
+
+            if (cursor.y > topline + height-1) {
+                topline++;
+            }
+        }
+    }
+
+    void display() {
+        int x, y;
+        string[] lines;
+        if (topline + height > buf.lines.length) {
+            lines = buf.lines[topline .. $];
+        } else  {
+            lines = buf.lines[topline .. topline + height];
+        }
+        ulong maxLength = to!string(buf.lines.length).length;
+        xOffset = cast(int) maxLength + 1;
+        foreach (i, line; lines) {
+            string lineNum = to!string(i + topline + 1);
+            foreach (_; 0 .. maxLength - lineNum.length) {
+                setCell(cast(int) x++, cast(int) y, ' ', Color.default_, Color.black);
+            }
+            foreach (ch; lineNum) {
+                setCell(cast(int) x++, cast(int) y, ch, Color.default_, Color.black);
+            }
+            setCell(cast(int) x++, cast(int) y, ' ', Color.default_ | Attribute.bold, Color.black);
+
+            foreach (ch; line) {
+                setCell(cast(int) x++, cast(int) y, ch, Color.default_, Color.default_);
+            }
+            y++;
+            x = 0;
+        }
+
+        if (cursor.y - topline < 0 || cursor.y - topline > height) {
+            hideCursor();
+        } else {
+            setCursor(cursor.x + xOffset, cursor.y - topline);
+        }
+    }
+}