--- /dev/null
+.dub
+docs.json
+__dummy.html
+*.o
+*.obj
+
+dub.selections.json
+micro
+
+.DS_STORE
--- /dev/null
+name "micro"
+description "A minimal D application."
+copyright "Copyright © 2016, zachary"
+authors "zachary"
+dependency "termbox" version="0.0.3"
--- /dev/null
+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));
+ }
+}
--- /dev/null
+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);
+}
--- /dev/null
+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;
+ }
+ }
+ }
+}
--- /dev/null
+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();
+}
--- /dev/null
+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);
+ }
+ }
+ }
+}
--- /dev/null
+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);
+ }
+ }
+}