-.dub
-docs.json
-__dummy.html
-*.o
-*.obj
-
-dub.selections.json
micro
-
-.DS_STORE
--- /dev/null
+package main
+
+import (
+ "io/ioutil"
+ "strings"
+)
+
+type Buffer struct {
+ r *Rope
+
+ // Path to the file on disk
+ path string
+ // Name of the buffer on the status line
+ name string
+
+ // This is the text stored everytime the buffer is saved to check if the buffer is modified
+ savedText string
+
+ text string
+ lines []string
+}
+
+func newBuffer(txt, path string) *Buffer {
+ b := new(Buffer)
+ b.r = newRope(txt)
+ b.path = path
+ b.name = path
+ b.savedText = txt
+
+ b.update()
+
+ return b
+}
+
+func (b *Buffer) update() {
+ b.text = b.r.toString()
+ b.lines = strings.Split(b.text, "\n")
+}
+
+func (b *Buffer) save() error {
+ return b.saveAs(b.path)
+}
+
+func (b *Buffer) saveAs(filename string) error {
+ err := ioutil.WriteFile(filename, []byte(b.text), 0644)
+ return err
+}
+
+func (b *Buffer) insert(idx int, value string) {
+ b.r.insert(idx, value)
+ b.update()
+}
+
+func (b *Buffer) remove(start, end int) {
+ b.r.remove(start, end)
+ b.update()
+}
+
+func (b *Buffer) length() int {
+ return b.r.len
+}
--- /dev/null
+package main
+
+import (
+ "strings"
+)
+
+// Cursor stores the location of the cursor in the view
+type Cursor struct {
+ v *View
+
+ x int
+ y int
+ loc int
+
+ selectionStart int
+ selectionEnd int
+}
+
+func (c *Cursor) resetSelection() {
+ c.selectionStart = 0
+ c.selectionEnd = 0
+}
+
+func (c *Cursor) hasSelection() bool {
+ return (c.selectionEnd - c.selectionStart) > 0
+}
+
+func (c *Cursor) deleteSelected() {
+ // TODO: Implement this
+}
+
+func (c *Cursor) up() {
+ if c.y > 0 {
+ c.loc -= count(c.v.buf.lines[c.y][:c.x])
+ // Count the newline
+ c.loc--
+ c.y--
+
+ if c.x > count(c.v.buf.lines[c.y]) {
+ c.x = count(c.v.buf.lines[c.y])
+ }
+
+ c.loc -= count(c.v.buf.lines[c.y][c.x:])
+ }
+}
+func (c *Cursor) down() {
+ if c.y < len(c.v.buf.lines)-1 {
+ c.loc += count(c.v.buf.lines[c.y][c.x:])
+ // Count the newline
+ c.loc++
+ c.y++
+
+ if c.x > count(c.v.buf.lines[c.y]) {
+ c.x = count(c.v.buf.lines[c.y])
+ }
+
+ c.loc += count(c.v.buf.lines[c.y][:c.x])
+ }
+}
+func (c *Cursor) left() {
+ if c.x > 0 {
+ c.loc--
+ c.x--
+ } else {
+ c.up()
+ c.end()
+ }
+}
+func (c *Cursor) right() {
+ if c.x < count(c.v.buf.lines[c.y]) {
+ c.loc++
+ c.x++
+ } else {
+ c.down()
+ c.start()
+ }
+}
+
+func (c *Cursor) end() {
+ c.loc += count(c.v.buf.lines[c.y][c.x:])
+ c.x = count(c.v.buf.lines[c.y])
+}
+
+func (c *Cursor) start() {
+ c.loc -= count(c.v.buf.lines[c.y][:c.x])
+ c.x = 0
+}
+
+func (c *Cursor) getCharPos(lineNum, visualPos int) int {
+ visualLine := strings.Replace(c.v.buf.lines[lineNum], "\t", "\t"+emptyString(tabSize-1), -1)
+ if visualPos > count(visualLine) {
+ visualPos = count(visualLine)
+ }
+ numTabs := numOccurences(visualLine[:visualPos], '\t')
+ return visualPos - (tabSize-1)*numTabs
+}
+
+func (c *Cursor) distance(x, y int) int {
+ // Same line
+ if y == c.y {
+ return x - c.x
+ }
+
+ var distance int
+ if y > c.y {
+ distance += count(c.v.buf.lines[c.y][c.x:])
+ // Newline
+ distance++
+ i := 1
+ for y != c.y+i {
+ distance += count(c.v.buf.lines[c.y+i])
+ // Newline
+ distance++
+ i++
+ }
+ if x < count(c.v.buf.lines[y]) {
+ distance += count(c.v.buf.lines[y][:x])
+ } else {
+ distance += count(c.v.buf.lines[y])
+ }
+ return distance
+ }
+
+ distance -= count(c.v.buf.lines[c.y][:c.x])
+ // Newline
+ distance--
+ i := 1
+ for y != c.y-i {
+ distance -= count(c.v.buf.lines[c.y-i])
+ // Newline
+ distance--
+ i++
+ }
+ if x > 0 {
+ distance -= count(c.v.buf.lines[y][x:])
+ }
+ return distance
+}
+
+func (c *Cursor) display() {
+ if c.y-c.v.topline < 0 || c.y-c.v.topline > c.v.linesN-1 {
+ c.v.s.HideCursor()
+ } else {
+ voffset := numOccurences(c.v.buf.lines[c.y][:c.x], '\t') * (tabSize - 1)
+ c.v.s.ShowCursor(c.x+voffset, c.y-c.v.topline)
+ }
+}
+++ /dev/null
-name "micro"
-description "A minimal D application."
-copyright "Copyright © 2016, zachary"
-authors "zachary"
-dependency "termbox" version="0.0.5"
--- /dev/null
+package main
+
+import (
+ "fmt"
+ "github.com/mattn/go-isatty"
+ "io/ioutil"
+ "os"
+
+ "github.com/gdamore/tcell"
+)
+
+const (
+ tabSize = 4
+)
+
+func main() {
+ var input []byte
+ var filename string
+
+ if len(os.Args) > 1 {
+ filename = os.Args[1]
+ var err error
+ input, err = ioutil.ReadFile(filename)
+
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ } else if !isatty.IsTerminal(os.Stdin.Fd()) {
+ bytes, err := ioutil.ReadAll(os.Stdin)
+ if err != nil {
+ fmt.Println("Error reading stdin")
+ os.Exit(1)
+ }
+ input = bytes
+ }
+
+ s, e := tcell.NewScreen()
+ if e != nil {
+ fmt.Fprintf(os.Stderr, "%v\n", e)
+ os.Exit(1)
+ }
+ if e := s.Init(); e != nil {
+ fmt.Fprintf(os.Stderr, "%v\n", e)
+ os.Exit(1)
+ }
+ s.EnableMouse()
+
+ v := newViewFromBuffer(newBuffer(string(input), filename), s)
+
+ // Initially everything needs to be drawn
+ redraw := 2
+ for {
+ if redraw == 2 {
+ s.Clear()
+ v.display()
+ v.cursor.display()
+ s.Show()
+ } else if redraw == 1 {
+ v.cursor.display()
+ s.Show()
+ }
+
+ event := s.PollEvent()
+
+ switch e := event.(type) {
+ case *tcell.EventKey:
+ if e.Key() == tcell.KeyCtrlQ {
+ s.Fini()
+ os.Exit(0)
+ }
+ }
+
+ redraw = v.handleEvent(event)
+ }
+}
--- /dev/null
+package main
+
+import (
+ // "fmt"
+ "math"
+ "unicode/utf8"
+)
+
+const (
+ ropeSplitLength = 1000
+ ropeJoinLength = 500
+ ropeRebalanceRatio = 1.2
+)
+
+func min(a, b int) int {
+ if a > b {
+ return b
+ }
+ return a
+}
+
+func max(a, b int) int {
+ if a > b {
+ return a
+ }
+ return b
+}
+
+type Rope struct {
+ left *Rope
+ right *Rope
+ value string
+ valueNil bool
+
+ len int
+}
+
+func newRope(str string) *Rope {
+ r := new(Rope)
+ r.value = str
+ r.valueNil = false
+ r.len = utf8.RuneCountInString(r.value)
+
+ r.adjust()
+
+ return r
+}
+
+func (r *Rope) adjust() {
+ if !r.valueNil {
+ if r.len > ropeSplitLength {
+ divide := int(math.Floor(float64(r.len) / 2))
+ r.left = newRope(r.value[:divide])
+ r.right = newRope(r.value[divide:])
+ r.valueNil = true
+ }
+ } else {
+ if r.len < ropeJoinLength {
+ r.value = r.left.toString() + r.right.toString()
+ r.valueNil = false
+ r.left = nil
+ r.right = nil
+ }
+ }
+}
+
+func (r *Rope) toString() string {
+ if !r.valueNil {
+ return r.value
+ }
+ return r.left.toString() + r.right.toString()
+}
+
+func (r *Rope) remove(start, end int) {
+ if !r.valueNil {
+ r.value = string(append([]rune(r.value)[:start], []rune(r.value)[end:]...))
+ r.valueNil = false
+ r.len = utf8.RuneCountInString(r.value)
+ } else {
+ leftStart := min(start, r.left.len)
+ leftEnd := min(end, r.left.len)
+ rightStart := max(0, min(start-r.left.len, r.right.len))
+ rightEnd := max(0, min(end-r.left.len, r.right.len))
+ if leftStart < r.left.len {
+ r.left.remove(leftStart, leftEnd)
+ }
+ if rightEnd > 0 {
+ r.right.remove(rightStart, rightEnd)
+ }
+ r.len = r.left.len + r.right.len
+ }
+
+ r.adjust()
+}
+
+func (r *Rope) insert(pos int, value string) {
+ if !r.valueNil {
+ first := append([]rune(r.value)[:pos], []rune(value)...)
+ r.value = string(append(first, []rune(r.value)[pos:]...))
+ r.valueNil = false
+ r.len = utf8.RuneCountInString(r.value)
+ } else {
+ if pos < r.left.len {
+ r.left.insert(pos, value)
+ r.len = r.left.len + r.right.len
+ } else {
+ r.right.insert(pos-r.left.len, value)
+ }
+ }
+
+ r.adjust()
+}
+++ /dev/null
-import rope;
-
-import std.string: split;
-import std.stdio;
-
-class Buffer {
- private Rope text;
-
- string path;
- string savedText;
-
- private string value;
-
- string[] lines;
-
- this(string txt, string path) {
- text = new Rope(txt);
- savedText = txt;
- this.path = path;
- update();
- }
-
- void save() {
- saveAs(path);
- }
-
- void saveAs(string filename) {
- string bufTxt = text.toString();
- File f = File(filename, "w");
- f.write(bufTxt);
- f.close();
- savedText = bufTxt;
- }
-
- override
- string toString() {
- return value;
- }
-
- void update() {
- value = text.toString();
- if (value == "") {
- lines = [""];
- } else {
- lines = value.split("\n");
- }
- }
-
- @property ulong length() {
- return text.length;
- }
-
- void remove(ulong start, ulong end) {
- text.remove(start, end);
- update();
- }
- void insert(ulong position, string value) {
- text.insert(position, value);
- update();
- }
- string substring(ulong start, ulong end = -1) {
- if (end == -1) {
- update();
- return text.substring(start, text.length);
- } else {
- update();
- return text.substring(start, end);
- }
- }
- char charAt(ulong pos) {
- update();
- return text.charAt(pos);
- }
-}
+++ /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;
- static string[] copyCmd;
- static 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 termbox;
-
-class Cursor {
- int x, y;
- int lastX;
-
- uint selectionStart;
- uint selectionEnd;
-
- this() {}
-
- this(int x, int y) {
- this.x = x;
- this.y = y;
- }
-
- void hide() {
- x = y = -1;
- }
-}
+++ /dev/null
-import termbox;
-import buffer;
-import cursor;
-import view;
-import clipboard;
-
-import std.stdio;
-import std.file: readText, exists, isDir;
-
-extern(C) int isatty(int);
-
-void main(string[] args) {
- string filename = "";
- string fileTxt = "";
-
- if (args.length > 1) {
- filename = args[1];
- if (exists(filename)) {
- if (isDir(filename)) {
- writeln(filename, " is a directory");
- return;
- }
- fileTxt = readText(filename);
- if (fileTxt is null) {
- fileTxt = "";
- }
- }
- } else {
- if (!isatty(0)) {
- foreach (line; stdin.byLine()) {
- fileTxt ~= line ~ "\n";
- }
- }
- }
- Clipboard.init();
-
- Buffer buf = new Buffer(fileTxt, filename);
- init();
-
- Cursor cursor = new Cursor();
- View v = new View(buf, cursor);
-
- setInputMode(InputMode.mouse);
-
- Event e;
- try {
- while (e.key != Key.ctrlQ) {
- clear();
-
- v.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 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 .. $]);
- if (value is null) {
- value = "";
- }
- 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));
- }
-}
+++ /dev/null
-import termbox;
-import view;
-
-class StatusLine {
- View view;
-
- this(View v) {
- this.view = v;
- }
-
- void display() {
- int y = view.height;
- string file = view.buf.path;
- if (file == "") {
- file = "untitled";
- }
- if (view.buf.toString != view.buf.savedText) {
- file ~= " +";
- }
- file ~= " (" ~ to!string(view.cursor.y + 1) ~ "," ~ to!string(view.cursor.x + 1) ~ ")";
- foreach (x; 0 .. view.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);
- }
- }
- }
-}
+++ /dev/null
-string emptyString(int size) {
- string str;
- foreach (i; 0 .. size) {
- str ~= " ";
- }
- return str;
-}
-
-int numOccurences(string str, char c) {
- int n;
- foreach (letter; str) {
- if (letter == c) {
- n++;
- }
- }
- return n;
-}
+++ /dev/null
-import termbox;
-import buffer;
-import clipboard;
-import cursor;
-import statusline;
-import util;
-
-import std.regex: regex, replaceAll;
-import std.conv: to;
-import std.utf: count;
-
-enum tabSize = 4;
-
-class View {
- uint topline;
- uint xOffset;
-
- uint width;
- uint height;
-
- Buffer buf;
- Cursor cursor;
- StatusLine sl;
-
- 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 = cursor;
- this.sl = new StatusLine(this);
- }
-
- 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];
- }
-
- int getCharPosition(int lineNum, int visualPosition) {
- string visualLine = buf.lines[lineNum].replaceAll(regex("\t"), "\t" ~ emptyString(tabSize-1));
- if (visualPosition > visualLine.length) {
- visualPosition = cast(int) visualLine.length;
- }
- int numTabs = numOccurences(visualLine[0 .. visualPosition], '\t');
- return visualPosition - (tabSize-1) * numTabs;
- }
-
- 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) {
- if (buf.lines[cursor.y][cursor.x] == '\t') {
- cursor.x++;
- } else {
- cursor.x++;
- }
- cursor.lastX = cursor.x;
- }
- }
-
- void cursorLeft() {
- if (cursor.x > 0) {
- if (buf.lines[cursor.y][cursor.x-1] == '\t') {
- cursor.x--;
- } else {
- cursor.x--;
- }
- cursor.lastX = cursor.x;
- }
- }
-
- void update(Event e) {
- uint cloc = cursorLoc();
- if (e.key == Key.mouseWheelUp) {
- if (topline > 0)
- topline--;
- } else if (e.key == Key.mouseWheelDown) {
- if (buf.lines.length > height && 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.y = e.y + topline;
- if (cursor.y - topline > height-1) {
- cursor.y = height + topline-1;
- }
- if (cursor.y > buf.lines.length) {
- cursor.y = cast(int) buf.lines.length-1;
- }
- cursor.x = getCharPosition(cursor.y, e.x - xOffset);
- cursor.lastX = cursor.x;
-
- cursor.selectionStart = 0;
- cursor.selectionEnd = 0;
- } else if (e.key == Key.mouseRelease) {
- auto y = e.y + topline;
- if (y - topline > height-1) {
- y = height + topline-1;
- }
- if (y > buf.lines.length) {
- y = cast(int) buf.lines.length-1;
- }
- auto x = getCharPosition(y, e.x - xOffset);
-
- cursor.selectionStart = toCharNumber(cursor.x, cursor.y);
- cursor.selectionEnd = toCharNumber(x, y);
- } else if (e.key == Key.ctrlS) {
- if (buf.path != "") {
- buf.save();
- }
- } else if (e.key == Key.ctrlV) {
- if (Clipboard.supported) {
- buf.insert(cloc, Clipboard.read());
- }
- } else {
- if (e.ch != 0) {
- buf.insert(cloc, to!string(to!dchar(e.ch)));
- cursorRight();
- } else if (e.key == Key.space) {
- buf.insert(cursorLoc(), " ");
- cursorRight();
- } else if (e.key == Key.enter) {
- buf.insert(cloc, "\n");
- cursorDown();
- cursor.x = 0;
- cursor.lastX = cursor.x;
- } else if (e.key == Key.tab) {
- buf.insert(cloc, "\t");
- cursorRight();
- } else if (e.key == Key.backspace2) {
- if (cloc > 0) {
- buf.remove(cloc-1, cloc);
- setCursorLoc(cloc - 1);
- cursor.lastX = cursor.x;
- }
- }
- }
-
- if (cursor.y < topline) {
- topline = cursor.y;
- }
-
- if (cursor.y > topline + height-1) {
- topline = cursor.y - height+1;
- }
-
- }
- }
-
- void display() {
- uint 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;
-
- int chNum;
- foreach (i, line; lines) {
- // Write the line number
- string lineNum = to!string(i + topline + 1);
- foreach (_; 0 .. maxLength - lineNum.length) {
- setCell(cast(int) x++, cast(int) y, ' ', Color.basic, Color.basic);
- }
- foreach (dchar ch; lineNum) {
- setCell(cast(int) x++, cast(int) y, ch, Color.basic, Color.basic);
- }
- setCell(cast(int) x++, cast(int) y, ' ', Color.basic, Color.basic);
-
- // Write the line
- foreach (dchar ch; line.replaceAll(regex("\t"), emptyString(tabSize))) {
- auto color = Color.basic;
- if (chNum > cursor.selectionStart && chNum < cursor.selectionEnd) {
- color = cast(Color) (Color.basic | Attribute.reverse);
- }
- setCell(x++, y, ch, color, color);
- chNum++;
- }
- y++;
- x = 0;
- chNum++;
- }
-
- if (cursor.y - topline < 0 || cursor.y - topline > height-1) {
- hideCursor();
- } else {
- auto voffset = buf.lines[cursor.y][0 .. cursor.x].numOccurences('\t') * (tabSize-1);
- setCursor(cursor.x + xOffset + voffset, cursor.y - topline);
- }
-
- sl.display();
- }
-}
--- /dev/null
+package main
+
+import (
+ "unicode/utf8"
+)
+
+func count(s string) int {
+ return utf8.RuneCountInString(s)
+}
+
+func numOccurences(s string, c byte) int {
+ var n int
+ for i := 0; i < len(s); i++ {
+ if s[i] == c {
+ n++
+ }
+ }
+ return n
+}
+
+func emptyString(n int) string {
+ var str string
+ for i := 0; i < n; i++ {
+ str += " "
+ }
+ return str
+}
--- /dev/null
+package main
+
+import (
+ "github.com/gdamore/tcell"
+ "strings"
+)
+
+type View struct {
+ cursor Cursor
+ topline int
+ linesN int
+ colsN int
+
+ buf *Buffer
+ mouseReleased bool
+
+ s tcell.Screen
+}
+
+func newViewFromBuffer(buf *Buffer, s tcell.Screen) *View {
+ v := new(View)
+
+ v.buf = buf
+ v.s = s
+ w, h := s.Size()
+
+ v.topline = 0
+ v.linesN = h
+ v.colsN = w
+ v.cursor = Cursor{
+ x: 0,
+ y: 0,
+ loc: 0,
+ v: v,
+ }
+
+ return v
+}
+
+// Returns an int describing how the screen needs to be redrawn
+// 0: Screen does not need to be redrawn
+// 1: Only the cursor needs to be redrawn
+// 2: Everything needs to be redrawn
+func (v *View) handleEvent(event tcell.Event) int {
+ var ret int
+ switch e := event.(type) {
+ case *tcell.EventKey:
+ switch e.Key() {
+ case tcell.KeyUp:
+ v.cursor.up()
+ ret = 1
+ case tcell.KeyDown:
+ v.cursor.down()
+ ret = 1
+ case tcell.KeyLeft:
+ v.cursor.left()
+ ret = 1
+ case tcell.KeyRight:
+ v.cursor.right()
+ ret = 1
+ case tcell.KeyEnter:
+ v.buf.insert(v.cursor.loc, "\n")
+ v.cursor.right()
+ ret = 2
+ case tcell.KeyBackspace2:
+ if v.cursor.loc > 0 {
+ v.cursor.left()
+ v.buf.remove(v.cursor.loc, v.cursor.loc+1)
+ ret = 2
+ }
+ case tcell.KeyTab:
+ v.buf.insert(v.cursor.loc, "\t")
+ v.cursor.right()
+ ret = 2
+ case tcell.KeyRune:
+ v.buf.insert(v.cursor.loc, string(e.Rune()))
+ v.cursor.right()
+ ret = 2
+ }
+ case *tcell.EventMouse:
+ x, y := e.Position()
+ y += v.topline
+ // Position always seems to be off by one
+ x--
+ y--
+
+ button := e.Buttons()
+
+ switch button {
+ case tcell.Button1:
+ if y-v.topline > v.linesN-1 {
+ y = v.linesN + v.topline - 1
+ }
+ if y > len(v.buf.lines) {
+ y = len(v.buf.lines) - 1
+ }
+ if x > count(v.buf.lines[y]) {
+ x = count(v.buf.lines[y])
+ }
+
+ x = v.cursor.getCharPos(y, x)
+ d := v.cursor.distance(x, y)
+ v.cursor.loc += d
+ v.cursor.x = x
+ v.cursor.y = y
+
+ if v.mouseReleased {
+ v.cursor.selectionStart = v.cursor.loc
+ }
+ v.cursor.selectionEnd = v.cursor.loc
+ v.mouseReleased = false
+ ret = 2
+ case tcell.ButtonNone:
+ v.mouseReleased = true
+ case tcell.WheelUp:
+ if v.topline > 0 {
+ v.topline--
+ return 2
+ } else {
+ return 0
+ }
+ case tcell.WheelDown:
+ if v.topline < len(v.buf.lines)-v.linesN {
+ v.topline++
+ return 2
+ } else {
+ return 0
+ }
+ }
+ }
+
+ cy := v.cursor.y
+ if cy < v.topline {
+ v.topline = cy
+ ret = 2
+ }
+ if cy > v.topline+v.linesN-1 {
+ v.topline = cy - v.linesN + 1
+ ret = 2
+ }
+
+ return ret
+}
+
+func (v *View) display() {
+
+ var charNum int
+ for lineN := 0; lineN < v.linesN; lineN++ {
+ if lineN+v.topline >= len(v.buf.lines) {
+ break
+ }
+ line := strings.Replace(v.buf.lines[lineN+v.topline], "\t", emptyString(tabSize), -1)
+ for colN, ch := range line {
+ st := tcell.StyleDefault
+ if v.cursor.hasSelection() && charNum >= v.cursor.selectionStart && charNum <= v.cursor.selectionEnd {
+ st = st.Reverse(true)
+ }
+
+ v.s.SetContent(colN, lineN, ch, nil, st)
+ charNum++
+ }
+ charNum++
+ }
+}