mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-25 18:07:07 +09:00
Initial commit
This commit is contained in:
10
.gitignore
vendored
Executable file
10
.gitignore
vendored
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
.dub
|
||||||
|
docs.json
|
||||||
|
__dummy.html
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
dub.selections.json
|
||||||
|
micro
|
||||||
|
|
||||||
|
.DS_STORE
|
||||||
5
dub.sdl
Normal file
5
dub.sdl
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
name "micro"
|
||||||
|
description "A minimal D application."
|
||||||
|
copyright "Copyright © 2016, zachary"
|
||||||
|
authors "zachary"
|
||||||
|
dependency "termbox" version="0.0.3"
|
||||||
167
src/buffer.d
Normal file
167
src/buffer.d
Normal file
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/clipboard.d
Normal file
63
src/clipboard.d
Normal file
@@ -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);
|
||||||
|
}
|
||||||
34
src/cursor.d
Normal file
34
src/cursor.d
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/main.d
Normal file
56
src/main.d
Normal file
@@ -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();
|
||||||
|
}
|
||||||
27
src/statusline.d
Normal file
27
src/statusline.d
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
157
src/view.d
Normal file
157
src/view.d
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user