mirror of
https://github.com/zyedidia/micro.git
synced 2026-02-05 22:50:21 +09:00
Begin rewrite
This commit is contained in:
2
dub.sdl
2
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"
|
||||
|
||||
178
src/buffer.d
178
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);
|
||||
}
|
||||
}
|
||||
|
||||
33
src/cursor.d
33
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);
|
||||
}
|
||||
}
|
||||
|
||||
33
src/main.d
33
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) {
|
||||
|
||||
137
src/rope.d
Normal file
137
src/rope.d
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
131
src/view.d
131
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user