This commit is contained in:
Aetnaeus 2020-12-25 05:26:10 -05:00
parent 0d80fcf494
commit a233c26e00
3 changed files with 138 additions and 53 deletions

17
tt.go
View File

@ -9,6 +9,7 @@ import (
"path/filepath"
"regexp"
"strings"
"time"
"github.com/gdamore/tcell"
"github.com/mattn/go-isatty"
@ -57,10 +58,10 @@ func exit() {
}
func showReport(scr tcell.Screen, cpm, wpm int, accuracy float64) {
report := fmt.Sprintf("WPM: %d\nCPM: %d\nAccuracy: %.2f%%\n", wpm, cpm, accuracy)
report := fmt.Sprintf("WPM: %d\nCPM: %d\nAccuracy: %.2f%%", wpm, cpm, accuracy)
scr.Clear()
drawCellsAtCenter(scr, stringToCells(report), -1)
drawStringAtCenter(scr, report, tcell.StyleDefault)
scr.HideCursor()
scr.Show()
@ -79,15 +80,17 @@ func main() {
var oneShotMode bool
var wrapSz int
var noSkip bool
var timeout int
var err error
flag.IntVar(&n, "n", 50, "The number of random words which constitute the test.")
flag.IntVar(&wrapSz, "w", 80, "Wraps the input text at the given number of columns (ignored if -raw is present)")
flag.IntVar(&wrapSz, "w", 80, "Wraps the input text at the given number of columns (ignored if -raw is present).")
flag.IntVar(&timeout, "t", -1, "Terminate the test after the given number of seconds.")
flag.BoolVar(&noSkip, "noskip", false, "Disable word skipping when space is pressed.")
flag.BoolVar(&csvMode, "csv", false, "Print the test results to stdout in the form <wpm>,<cpm>,<accuracy>.")
flag.BoolVar(&rawMode, "raw", false, "Don't reflow text or show one paragraph at a time.")
flag.BoolVar(&oneShotMode, "o", false, "Automatically exit after a single run.")
flag.BoolVar(&oneShotMode, "o", false, "Automatically exit after a single run (useful for scripts).")
flag.Usage = func() {
fmt.Println(`Usage: tt [options]
@ -177,10 +180,12 @@ Options:`)
if noSkip {
typer.SkipWord = false
}
if timeout != -1 {
timeout *= 1E9
}
for {
scr.Clear()
nerrs, ncorrect, t, exitKey := typer.Start(contentFn())
nerrs, ncorrect, t, exitKey := typer.Start(contentFn(), time.Duration(timeout))
switch exitKey {
case 0:

118
typer.go
View File

@ -2,6 +2,7 @@ package main
import (
"fmt"
"strconv"
"time"
"github.com/gdamore/tcell"
@ -51,39 +52,49 @@ func (t *typer) highlight(text []cell, idx int, currentWordStyle, nextWordStyle
}
}
func (t *typer) Start(text []string) (nerrs, ncorrect int, tim time.Duration, exitKey tcell.Key) {
var startTime time.Time
func (t *typer) Start(text []string, timeout time.Duration) (nerrs, ncorrect int, duration time.Duration, exitKey tcell.Key) {
timeLeft := timeout
for i, p := range text {
startImmediately := true
var d time.Duration
var e, c int
var fn func() = nil
if i == 0 {
fn = func() {
startTime = time.Now()
}
startImmediately = false
}
e, c, exitKey = t.start(p, fn)
e, c, exitKey, d = t.start(p, timeLeft, startImmediately)
nerrs += e
ncorrect += c
duration += d
if timeout != -1 {
timeLeft -= d
if timeLeft <= 0 {
return
}
}
if exitKey != 0 {
tim = time.Now().Sub(startTime)
return
}
}
tim = time.Now().Sub(startTime)
exitKey = 0
return
}
func (t *typer) start(s string, onStart func()) (nerrs int, ncorrect int, exitKey tcell.Key) {
started := false
func (t *typer) start(s string, timeLimit time.Duration, startImmediately bool) (nerrs int, ncorrect int, exitKey tcell.Key, duration time.Duration) {
var startTime time.Time
text := stringToCells(s)
nc, nr := calcStringDimensions(s)
sw, sh := scr.Size()
x := (sw - nc) / 2
y := (sh - nr) / 2
for i, _ := range text {
text[i].style = t.backgroundStyle
}
@ -98,11 +109,33 @@ func (t *typer) start(s string, onStart func()) (nerrs int, ncorrect int, exitKe
idx := 0
redraw := func() {
if timeLimit != -1 && !startTime.IsZero() {
remaining := timeLimit - time.Now().Sub(startTime)
drawString(t.Scr, x+nc/2, y+nr+1, strconv.Itoa(int(remaining/1E9)), -1, t.backgroundStyle)
}
//Potentially inefficient, but seems to be good enough
drawCellsAtCenter(t.Scr, text, idx)
drawCells(t.Scr, x, y, text, idx)
t.Scr.Show()
}
calcStats := func() {
nerrs = 0
ncorrect = 0
for _, c := range text {
if c.style == t.incorrectStyle || c.style == t.incorrectSpaceStyle {
nerrs++
} else if c.style == t.correctStyle {
ncorrect++
}
}
exitKey = 0
duration = time.Now().Sub(startTime)
}
deleteWord := func() {
t.highlight(text, idx, t.backgroundStyle, t.backgroundStyle)
@ -129,6 +162,30 @@ func (t *typer) start(s string, onStart func()) (nerrs int, ncorrect int, exitKe
t.highlight(text, idx, t.currentWordStyle, t.nextWordStyle)
}
tickerCloser := make(chan bool)
//Inject nil events into the main event loop at regular invervals to force an update
ticker := func() {
for {
select {
case <-tickerCloser:
return
default:
}
time.Sleep(time.Duration(1E8))
t.Scr.PostEventWait(nil)
}
}
go ticker()
defer close(tickerCloser)
if startImmediately {
startTime = time.Now()
}
t.Scr.Clear()
for {
t.highlight(text, idx, t.currentWordStyle, t.nextWordStyle)
redraw()
@ -139,13 +196,14 @@ func (t *typer) start(s string, onStart func()) (nerrs int, ncorrect int, exitKe
case *tcell.EventResize:
t.Scr.Sync()
t.Scr.Clear()
case *tcell.EventKey:
if !started {
if onStart != nil {
onStart()
}
started = true
nc, nr = calcStringDimensions(s)
sw, sh = scr.Size()
x = (sw - nc) / 2
y = (sh - nr) / 2
case *tcell.EventKey:
if startTime.IsZero() {
startTime = time.Now()
}
switch key := ev.Key(); key {
@ -218,21 +276,17 @@ func (t *typer) start(s string, onStart func()) (nerrs int, ncorrect int, exitKe
}
if idx == len(text) {
nerrs = 0
for _, c := range text {
if c.style == t.incorrectStyle || c.style == t.incorrectSpaceStyle {
nerrs++
}
}
ncorrect = len(text) - nerrs
exitKey = 0
t.Scr.Clear()
calcStats()
return
}
}
default: //tick
if timeLimit != -1 && !startTime.IsZero() && timeLimit <= time.Now().Sub(startTime) {
calcStats()
return
}
redraw()
}
}
}

56
util.go
View File

@ -280,6 +280,28 @@ func stringToCells(s string) []cell {
return a[:len]
}
func drawString(scr tcell.Screen, x, y int, s string, cursorIdx int, style tcell.Style) {
sx := x
for i, c := range s {
if c == '\n' {
y++
x = sx
} else {
scr.SetContent(x, y, c, nil, style)
if i == cursorIdx {
scr.ShowCursor(x, y)
}
x++
}
}
if cursorIdx == len(s) {
scr.ShowCursor(x, y)
}
}
func drawCells(scr tcell.Screen, x, y int, s []cell, cursorIdx int) {
sx := x
@ -302,16 +324,24 @@ func drawCells(scr tcell.Screen, x, y int, s []cell, cursorIdx int) {
}
}
func drawCellsAtCenter(scr tcell.Screen, s []cell, cursorIdx int) {
rows := 0
cols := 0
func drawStringAtCenter(scr tcell.Screen, s string, style tcell.Style) {
nc, nr := calcStringDimensions(s)
sw, sh := scr.Size()
x := (sw - nc) / 2
y := (sh - nr) / 2
drawString(scr, x, y, s, -1, style)
}
func calcStringDimensions(s string) (nc, nr int) {
c := 0
for _, x := range s {
if x.c == '\n' {
rows++
if c > cols {
cols = c
if x == '\n' {
nr++
if c > nc {
nc = c
}
c = 0
} else {
@ -319,16 +349,12 @@ func drawCellsAtCenter(scr tcell.Screen, s []cell, cursorIdx int) {
}
}
rows++
if c > cols {
cols = c
nr++
if c > nc {
nc = c
}
w, h := scr.Size()
x := (w - cols) / 2
y := (h - rows) / 2
drawCells(scr, x, y, s, cursorIdx)
return
}
func newTcellColor(s string) tcell.Color {