Switch to go in order to use tcell

This commit is contained in:
Zachary Yedidia 2016-03-17 17:27:57 -04:00
parent e9e25d0c85
commit 1781766e21
16 changed files with 587 additions and 678 deletions

9
.gitignore vendored Executable file → Normal file
View file

@ -1,10 +1 @@
.dub
docs.json
__dummy.html
*.o
*.obj
dub.selections.json
micro
.DS_STORE

61
buffer.go Normal file
View file

@ -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
}

147
cursor.go Normal file
View file

@ -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)
}
}

View file

@ -1,5 +0,0 @@
name "micro"
description "A minimal D application."
copyright "Copyright © 2016, zachary"
authors "zachary"
dependency "termbox" version="0.0.5"

76
micro.go Normal file
View file

@ -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)
}
}

112
rope.go Normal file
View file

@ -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()
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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));
}
}

View file

@ -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);
}
}
}
}

View file

@ -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;
}

View file

@ -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();
}
}

27
util.go Normal file
View file

@ -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
}

164
view.go Normal file
View file

@ -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++
}
}