From 8072756990ad3aa8167567d4b509f049b7dce5db Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Sat, 12 Mar 2016 15:43:24 -0500 Subject: [PATCH] Begin rewrite --- dub.sdl | 2 +- src/buffer.d | 178 +++++++++++------------------------------------ src/cursor.d | 33 +++------ src/main.d | 33 ++++----- src/rope.d | 137 ++++++++++++++++++++++++++++++++++++ src/statusline.d | 30 -------- src/view.d | 131 +++++++++++++++++----------------- 7 files changed, 266 insertions(+), 278 deletions(-) create mode 100644 src/rope.d delete mode 100644 src/statusline.d diff --git a/dub.sdl b/dub.sdl index f3ecfaf3..9864fd03 100644 --- a/dub.sdl +++ b/dub.sdl @@ -2,4 +2,4 @@ name "micro" description "A minimal D application." copyright "Copyright © 2016, zachary" authors "zachary" -dependency "termbox" version="0.0.3" +dependency "termbox" version="0.0.4" diff --git a/src/buffer.d b/src/buffer.d index e5c4aa7b..6fad0bcf 100644 --- a/src/buffer.d +++ b/src/buffer.d @@ -1,167 +1,67 @@ -import std.math; -import std.stdio; -import std.utf: count; -import std.string; -import std.conv: to; -import std.algorithm: min, max; +import rope; + +import std.string, std.stdio; class Buffer { - private string value = null; - private Buffer left; - private Buffer right; + private Rope text; - string name = ""; + string path; string savedText; - ulong count; + private string value; - int splitLength = 1000; - int joinLength = 500; - double rebalanceRatio = 1.2; + string[] lines; - this() { } - - this(string str, string name = "") { - this.value = str; - this.count = str.count; - this.name = name; - this.savedText = str; - - left = new Buffer(); - right = new Buffer(); - left.value = ""; - right.value = ""; - - adjust(); + this(string txt, string path) { + text = new Rope(txt); + this.path = path; + update(); } - 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; - } + void save() { + saveAs(path); } - @property string[] lines() { - string str = this.toString(); - if (str == "") { - return [""]; - } else { - return str.split("\n"); - } - } - - void adjust() { - if (value !is null) { - if (count > splitLength) { - auto divide = cast(int) floor(count / 2.0); - left = new Buffer(value[0 .. divide]); - right = new Buffer(value[divide .. $]); - } - } else { - if (count < joinLength) { - value = left.toString() ~ right.toString(); - } - } + void saveAs(string filename) { + string bufTxt = text.toString(); + File f = File(filename, "w"); + f.write(bufTxt); + f.close(); + savedText = bufTxt; } override string toString() { - if (value !is null) { - return value; + return value; + } + + void update() { + value = text.toString(); + if (value == "") { + lines = [""]; } else { - return left.toString ~ right.toString(); + lines = value.split("\n"); } } + @property ulong length() { + return text.length; + } + void remove(ulong start, ulong end) { - if (value !is null) { - value = to!string(value.to!dstring[0 .. start] ~ value.to!dstring[end .. $]); - count = value.count; - } else { - auto leftStart = min(start, left.count); - auto leftEnd = min(end, left.count); - auto rightStart = max(0, min(start - left.count, right.count)); - auto rightEnd = max(0, min(end - left.count, right.count)); - if (leftStart < left.count) { - left.remove(leftStart, leftEnd); - } - if (rightEnd > 0) { - right.remove(rightStart, rightEnd); - } - count = left.count + right.count; - } - - adjust(); + text.remove(start, end); } - void insert(ulong position, string value) { - if (this.value !is null) { - this.value = to!string(this.value.to!dstring[0 .. position] ~ value.to!dstring ~ this.value.to!dstring[position .. $]); - count = this.value.count; + text.insert(position, value); + } + string substring(ulong start, ulong end = -1) { + if (end == -1) { + return text.substring(start, text.length); } else { - if (position < left.count) { - left.insert(position, value); - count = left.count + right.count; - } else { - right.insert(position - left.count, value); - } - } - - adjust(); - } - - void rebuild() { - if (value is null) { - value = left.toString() ~ right.toString(); - adjust(); + return text.substring(start, end); } } - - void rebalance() { - if (value is null) { - if (left.count / right.count > rebalanceRatio || - right.count / left.count > rebalanceRatio) { - rebuild(); - } else { - left.rebalance(); - right.rebalance(); - } - } - } - - string substring(ulong start, ulong end = count) { - if (value !is null) { - return value[start .. end]; - } else { - auto leftStart = min(start, left.count); - auto leftEnd = min(end, left.count); - auto rightStart = max(0, min(start - left.count, right.count)); - auto rightEnd = max(0, min(end - left.count, right.count)); - - 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)); + return text.charAt(pos); } } diff --git a/src/cursor.d b/src/cursor.d index 50e3aa86..d1c699e0 100644 --- a/src/cursor.d +++ b/src/cursor.d @@ -1,34 +1,21 @@ -import buffer; +import termbox; class Cursor { - Buffer buf; int x, y; - int lastX; - this(Buffer buf) { - this.buf = buf; + this() {} + + this(int x, int y) { + this.x = x; + this.y = y; } - @property int loc() { - int loc; - foreach (i; 0 .. y) { - loc += buf.lines[i].count + 1; - } - loc += x; - return loc; + void hide() { + x = y = -1; } - @property void loc(int value) { - int loc; - foreach (y, l; buf.lines) { - if (loc + l.count+1 > value) { - this.y = cast(int) y; - x = value - loc; - return; - } else { - loc += l.count+1; - } - } + void display() { + setCursor(x, y); } } diff --git a/src/main.d b/src/main.d index e7fa79d6..f580794f 100644 --- a/src/main.d +++ b/src/main.d @@ -1,44 +1,39 @@ import termbox; -import view; import buffer; import cursor; -import statusline; +import view; -import std.regex; -import core.exception; -import std.conv; -import std.file; -import std.range; -import std.string; import std.stdio; +import std.file; void main(string[] args) { - if (args.length < 2) { - return; - } - string filename = args[1]; + string filename = ""; + string fileTxt = ""; - string fileSrc = readText(filename); + if (args.length > 1) { + filename = args[1]; + fileTxt = readText(filename); + } init(); - Buffer buffer = new Buffer(fileSrc, filename); - View v = new View(buffer); - StatusLine sl = new StatusLine(); - sl.view = v; + Buffer buf = new Buffer(fileTxt, filename); + Cursor cursor = new Cursor(); + View v = new View(buf, cursor); setInputMode(InputMode.mouse); Event e; try { - while (e.key != Key.esc) { + while (e.key != Key.ctrlQ) { clear(); v.display(); - sl.display(); + cursor.display(); flush(); pollEvent(&e); + v.update(e); } } catch (object.Error e) { diff --git a/src/rope.d b/src/rope.d new file mode 100644 index 00000000..3def9f2e --- /dev/null +++ b/src/rope.d @@ -0,0 +1,137 @@ +import std.string, std.stdio; +import std.algorithm: min, max; +import std.conv: to; +import std.math: floor; + +// Rope data structure to store the text in the buffer +class Rope { + private Rope left; + private Rope right; + private string value = null; + + ulong length; + + const int splitLength = 1000; + const int joinLength = 500; + const double rebalanceRatio = 1.2; + + this(string str) { + this.value = str; + this.length = str.count; + + adjust(); + } + + void adjust() { + if (value !is null) { + if (length > splitLength) { + auto divide = cast(int) floor(length / 2.0); + left = new Rope(value[0 .. divide]); + right = new Rope(value[divide .. $]); + value = null; + } + } else { + if (length < joinLength) { + value = left.toString() ~ right.toString(); + left = null; + right = null; + } + } + } + + 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 = to!string(value.to!dstring[0 .. start] ~ value.to!dstring[end .. $]); + length = value.count; + } 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 = to!string(this.value.to!dstring[0 .. position] ~ value.to!dstring ~ this.value.to!dstring[position .. $]); + length = this.value.count; + } 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(); + left = null; + right = null; + 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) { + 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/statusline.d b/src/statusline.d deleted file mode 100644 index 2f718c49..00000000 --- a/src/statusline.d +++ /dev/null @@ -1,30 +0,0 @@ -import termbox; -import view; - -import std.conv: to; - -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 ~= " +"; - } - file ~= " (" ~ to!string(view.cursor.y) ~ "," ~ to!string(view.cursor.x) ~ ")"; - foreach (x; 0 .. width()) { - if (x >= 1 && x < 1 + file.length) { - setCell(x, y, cast(uint) file[x - 1], Color.black, Color.blue); - } else { - setCell(x, y, ' ', Color.black, Color.blue); - } - } - } -} diff --git a/src/view.d b/src/view.d index eeff4fc3..9fbea79c 100644 --- a/src/view.d +++ b/src/view.d @@ -1,30 +1,61 @@ import termbox; -import cursor; import buffer; import clipboard; +import cursor; -import std.array: join; -import std.regex; import std.conv: to; -import std.stdio; +import std.utf: count; class View { - int topline; - int xOffset; + uint topline; - int width; - int height; + uint width; + uint height; Buffer buf; Cursor cursor; - this(Buffer buf, int topline = 0, int width = termbox.width(), int height = termbox.height()-2) { + this(Buffer buf, Cursor cursor, uint topline = 0, uint width = termbox.width(), uint height = termbox.height() - 2) { this.topline = topline; this.width = width; this.height = height; this.buf = buf; - this.cursor = new Cursor(buf); + this.cursor = cursor; + } + + uint toCharNumber(int x, int y) { + int loc; + foreach (i; 0 .. y) { + loc += buf.lines[i].count + 1; + } + loc += x; + return loc; + } + + int[] fromCharNumber(uint value) { + int x, y; + int loc; + foreach (lineNum, l; buf.lines) { + if (loc + l.count+1 > value) { + y = cast(int) lineNum; + x = value - loc; + return [x, y]; + } else { + loc += l.count+1; + } + } + return [-1, -1]; + } + + uint cursorLoc() { + return toCharNumber(cursor.x, cursor.y); + } + + void setCursorLoc(uint charNum) { + int[] xy = fromCharNumber(charNum); + cursor.x = xy[0]; + cursor.y = xy[1]; } void cursorUp() { @@ -62,14 +93,13 @@ class View { } void update(Event e) { + uint cloc = cursorLoc(); if (e.key == Key.mouseWheelUp) { - if (topline > 0) { + if (topline > 0) topline--; - } } else if (e.key == Key.mouseWheelDown) { - if (topline < buf.lines.length - height) { + if (topline < buf.lines.length - height) topline++; - } } else { if (e.key == Key.arrowUp) { cursorUp(); @@ -80,53 +110,54 @@ class View { } 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.x = e.x; + cursor.y = e.y; 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) { + } else if (e.key == Key.ctrlS) { + if (buf.path != "") { + buf.save(); + } + } else if (e.key == Key.ctrlV) { if (Clipboard.supported) { - buf.insert(cursor.loc, Clipboard.read()); + buf.insert(cloc, Clipboard.read()); } } else { if (e.ch != 0) { - buf.insert(cursor.loc, to!string(to!dchar(e.ch))); + buf.insert(cloc, to!string(to!dchar(e.ch))); cursorRight(); } else if (e.key == Key.space) { - buf.insert(cursor.loc, " "); + buf.insert(cursorLoc(), " "); cursorRight(); } else if (e.key == Key.enter) { - buf.insert(cursor.loc, "\n"); - cursor.loc = cursor.loc + 1; + buf.insert(cloc, "\n"); + cursorDown(); + cursor.x = 0; cursor.lastX = cursor.x; } else if (e.key == Key.backspace2) { - if (cursor.loc != 0) { - cursor.loc = cursor.loc - 1; - buf.remove(cursor.loc, cursor.loc + 1); + if (cloc > 0) { + buf.remove(cloc-1, cloc); + setCursorLoc(cloc - 1); cursor.lastX = cursor.x; } } } if (cursor.y < topline) { - topline--; + topline = cursor.y; } if (cursor.y > topline + height-1) { - topline++; + topline = cursor.y - height-1; } + buf.update(); } } void display() { - int x, y; + uint x, y; string[] lines; if (topline + height > buf.lines.length) { @@ -134,45 +165,13 @@ class View { } else { lines = buf.lines[topline .. topline + height]; } - ulong maxLength = to!string(buf.lines.length).length; - xOffset = cast(int) maxLength + 1; - int charNum; - string bufSrc = lines.join("\n"); - auto r = regex("\".*?\""); - auto matches = bufSrc.matchAll(r); - Color[ulong] colors; - foreach (m; matches) { - colors[m.pre.length] = Color.blue; - colors[m.pre.length + m.hit.length] = Color.default_; - } 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 (dchar 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); - - Color c; foreach (dchar ch; line) { - if (charNum in colors) { - c = colors[charNum]; - } - setCell(cast(int) x++, cast(int) y, ch, c, Color.default_); - charNum += to!string(ch).length; + setCell(x++, y, ch, Color.basic, Color.basic); } - charNum++; y++; x = 0; } - - if (cursor.y - topline < 0 || cursor.y - topline > height-1) { - hideCursor(); - } else { - setCursor(cursor.x + xOffset, cursor.y - topline); - } } }