From 24ce1d6b42262a08d2ba1ad9a2b28d2087f90e0a Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Thu, 10 Mar 2016 21:06:06 -0500 Subject: [PATCH] Initial commit --- .gitignore | 10 +++ dub.sdl | 5 ++ src/buffer.d | 167 +++++++++++++++++++++++++++++++++++++++++++++++ src/clipboard.d | 63 ++++++++++++++++++ src/cursor.d | 34 ++++++++++ src/main.d | 56 ++++++++++++++++ src/statusline.d | 27 ++++++++ src/view.d | 157 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 519 insertions(+) create mode 100755 .gitignore create mode 100644 dub.sdl create mode 100644 src/buffer.d create mode 100644 src/clipboard.d create mode 100644 src/cursor.d create mode 100644 src/main.d create mode 100644 src/statusline.d create mode 100644 src/view.d diff --git a/.gitignore b/.gitignore new file mode 100755 index 00000000..91fc4cb3 --- /dev/null +++ b/.gitignore @@ -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 index 00000000..f3ecfaf3 --- /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 index 00000000..1cd6873b --- /dev/null +++ b/src/buffer.d @@ -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 index 00000000..9daf9ba6 --- /dev/null +++ b/src/clipboard.d @@ -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 index 00000000..2d729392 --- /dev/null +++ b/src/cursor.d @@ -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 index 00000000..570d11cc --- /dev/null +++ b/src/main.d @@ -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 index 00000000..6ab68156 --- /dev/null +++ b/src/statusline.d @@ -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 index 00000000..73b4cd1e --- /dev/null +++ b/src/view.d @@ -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); + } + } +} -- 2.44.0