]> git.lizzy.rs Git - micro.git/commitdiff
Switch to go in order to use tcell
authorZachary Yedidia <zyedidia@gmail.com>
Thu, 17 Mar 2016 21:27:57 +0000 (17:27 -0400)
committerZachary Yedidia <zyedidia@gmail.com>
Thu, 17 Mar 2016 21:27:57 +0000 (17:27 -0400)
16 files changed:
.gitignore [changed mode: 0755->0644]
buffer.go [new file with mode: 0644]
cursor.go [new file with mode: 0644]
dub.sdl [deleted file]
micro.go [new file with mode: 0644]
rope.go [new file with mode: 0644]
src/buffer.d [deleted file]
src/clipboard.d [deleted file]
src/cursor.d [deleted file]
src/main.d [deleted file]
src/rope.d [deleted file]
src/statusline.d [deleted file]
src/util.d [deleted file]
src/view.d [deleted file]
util.go [new file with mode: 0644]
view.go [new file with mode: 0644]

old mode 100755 (executable)
new mode 100644 (file)
index 91fc4cb..aaf0cae
@@ -1,10 +1 @@
-.dub
-docs.json
-__dummy.html
-*.o
-*.obj
-
-dub.selections.json
 micro
-
-.DS_STORE
diff --git a/buffer.go b/buffer.go
new file mode 100644 (file)
index 0000000..7e59cf3
--- /dev/null
+++ b/buffer.go
@@ -0,0 +1,61 @@
+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
+}
diff --git a/cursor.go b/cursor.go
new file mode 100644 (file)
index 0000000..d5aa48d
--- /dev/null
+++ b/cursor.go
@@ -0,0 +1,147 @@
+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)
+       }
+}
diff --git a/dub.sdl b/dub.sdl
deleted file mode 100644 (file)
index dad7a22..0000000
--- a/dub.sdl
+++ /dev/null
@@ -1,5 +0,0 @@
-name "micro"
-description "A minimal D application."
-copyright "Copyright © 2016, zachary"
-authors "zachary"
-dependency "termbox" version="0.0.5"
diff --git a/micro.go b/micro.go
new file mode 100644 (file)
index 0000000..0b6e21f
--- /dev/null
+++ b/micro.go
@@ -0,0 +1,76 @@
+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)
+       }
+}
diff --git a/rope.go b/rope.go
new file mode 100644 (file)
index 0000000..2998028
--- /dev/null
+++ b/rope.go
@@ -0,0 +1,112 @@
+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()
+}
diff --git a/src/buffer.d b/src/buffer.d
deleted file mode 100644 (file)
index e191992..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-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);
-    }
-}
diff --git a/src/clipboard.d b/src/clipboard.d
deleted file mode 100644 (file)
index 3af3998..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-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);
-}
diff --git a/src/cursor.d b/src/cursor.d
deleted file mode 100644 (file)
index 3cf02f2..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-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;
-    }
-}
diff --git a/src/main.d b/src/main.d
deleted file mode 100644 (file)
index e32a240..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-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();
-}
diff --git a/src/rope.d b/src/rope.d
deleted file mode 100644 (file)
index 074d221..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-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));
-    }
-}
diff --git a/src/statusline.d b/src/statusline.d
deleted file mode 100644 (file)
index f3bb067..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-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);
-            }
-        }
-    }
-}
diff --git a/src/util.d b/src/util.d
deleted file mode 100644 (file)
index d1754d6..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-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;
-}
diff --git a/src/view.d b/src/view.d
deleted file mode 100644 (file)
index 7465776..0000000
+++ /dev/null
@@ -1,253 +0,0 @@
-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();
-    }
-}
diff --git a/util.go b/util.go
new file mode 100644 (file)
index 0000000..e78561a
--- /dev/null
+++ b/util.go
@@ -0,0 +1,27 @@
+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
+}
diff --git a/view.go b/view.go
new file mode 100644 (file)
index 0000000..67d6cf7
--- /dev/null
+++ b/view.go
@@ -0,0 +1,164 @@
+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++
+       }
+}