Skip to content

Commit

Permalink
Merge pull request #13 from denandz/newTextPrimitives
Browse files Browse the repository at this point in the history
New text primitives
  • Loading branch information
denandz committed Feb 27, 2022
2 parents e2dff1b + c7e165e commit 2754aca
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 25 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ require (
github.com/fsnotify/fsnotify v1.5.1
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1
github.com/google/martian/v3 v3.2.1
github.com/rivo/tview v0.0.0-20211129142845-821b2667c414
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 // indirect
github.com/rivo/tview v0.0.0-20220216162559-96063d6082f3
golang.org/x/net v0.0.0-20220105145211-5b0dc2dfae98 // indirect
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/tview v0.0.0-20211129142845-821b2667c414 h1:8pLxYvjWizid9rNUDyWv9D4gti+/w+TK7P10eXnh+xA=
github.com/rivo/tview v0.0.0-20211129142845-821b2667c414/go.mod h1:WIfMkQNY+oq/mWwtsjOYHIZBuwthioY2srOmljJkTnk=
github.com/rivo/tview v0.0.0-20220216162559-96063d6082f3 h1:crs4rrYnQqsZpz/EtjezHGCu13e+3W9eqj0MzIYXir0=
github.com/rivo/tview v0.0.0-20220216162559-96063d6082f3/go.mod h1:WIfMkQNY+oq/mWwtsjOYHIZBuwthioY2srOmljJkTnk=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand All @@ -62,8 +62,8 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 h1:0qxwC5n+ttVOINCBeRHO0nq9X7uy8SDsPoi5OaCdIEI=
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220105145211-5b0dc2dfae98 h1:+6WJMRLHlD7X7frgp7TUZ36RnQzSf9wVVTNakEp+nqY=
golang.org/x/net v0.0.0-20220105145211-5b0dc2dfae98/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
14 changes: 4 additions & 10 deletions views/proxyview.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import (
type ProxyView struct {
Layout *tview.Pages // The main replay view, all others should be underneath Layout
Table *tview.Table // the proxy history table
requestBox *tview.TextView // request text box
responseBox *tview.TextView // response text box
requestBox *TextPrimitive // request text box
responseBox *TextPrimitive // response text box
Logger *modifier.Logger // the Martian logger

filter ViewFilter // filter for the proxy view
Expand Down Expand Up @@ -77,7 +77,7 @@ func (view *ProxyView) Init(app *tview.Application, replayview *ReplayView, logg
view.Table.SetCell(0, 7, tview.NewTableCell("Method").SetTextColor(tcell.ColorMediumPurple).SetSelectable(false))

reqRespFlexView := tview.NewFlex()
view.requestBox = tview.NewTextView().SetWrap(false).SetDynamicColors(true)
view.requestBox = NewTextPrimitive()
view.requestBox.SetBorder(true)
view.requestBox.SetTitle("Request")
view.requestBox.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
Expand Down Expand Up @@ -113,7 +113,7 @@ func (view *ProxyView) Init(app *tview.Application, replayview *ReplayView, logg
return event
})

view.responseBox = tview.NewTextView().SetWrap(false).SetDynamicColors(true)
view.responseBox = NewTextPrimitive()
view.responseBox.SetBorder(true)
view.responseBox.SetTitle("Response")
view.responseBox.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
Expand Down Expand Up @@ -216,13 +216,7 @@ func (view *ProxyView) Init(app *tview.Application, replayview *ReplayView, logg
id = view.Table.GetCell(row, 1).Text
if entry := view.Logger.GetEntry(id); entry != nil {
if entry.Request != nil {
// Appending a UTF8 braille pattern blank (U+2800)
// to deal with the partial-trailing-utf8-rune logic
// in tview (textview.go)

// this technique seems to make weird artifecats happen depending on the terminal
// some sensible mechanism forResponse escaping data would probably be better...
//fmt.Fprint(view.requestBox, "\u2800")
view.writeRequest(entry.Request)
view.requestBox.ScrollToBeginning()
}
Expand Down
18 changes: 9 additions & 9 deletions views/replayview.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import (

// ReplayView - struct that holds the main replayview elements
type ReplayView struct {
Layout *tview.Pages // The main replay view, all others should be underneath Layout
Table *tview.Table // the main table that lists all the replay items
request *tview.TextView // http request box
response *tview.TextView // http response box
responseMeta *tview.Table // metadata for size recieved and time taken
goButton *tview.Button // send button
Layout *tview.Pages // The main replay view, all others should be underneath Layout
Table *tview.Table // the main table that lists all the replay items
request *TextPrimitive // http request box
response *TextPrimitive // http response box
responseMeta *tview.Table // metadata for size recieved and time taken
goButton *tview.Button // send button

host *tview.InputField // host field input
port *tview.InputField // port input
Expand Down Expand Up @@ -216,8 +216,8 @@ func (view *ReplayView) Init(app *tview.Application) {
mainLayout := tview.NewFlex()

replayFlexView := tview.NewFlex()
view.request = tview.NewTextView()
view.request.SetWrap(false).SetBorder(true).SetTitle("Request")
view.request = NewTextPrimitive()
view.request.SetBorder(true).SetTitle("Request")

// go and cancel buttons
view.goButton = tview.NewButton("Go")
Expand Down Expand Up @@ -265,7 +265,7 @@ func (view *ReplayView) Init(app *tview.Application) {
view.autoSend.SetLabelColor(tcell.ColorMediumPurple)
view.autoSend.SetLabel("AutoSend")

view.response = tview.NewTextView().SetWrap(false)
view.response = NewTextPrimitive()
view.response.SetBorder(true).SetTitle("Response")

view.goButton.SetSelectedFunc(func() {
Expand Down
272 changes: 272 additions & 0 deletions views/textprimitive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
package views

import (
"bytes"
"regexp"
"sync"

"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)

// TextPrimitive is a basic, line wrapped text view that is designed to replicate a
// severely cut down version of tview's TextView, removing color support, grapheme cluster
// handling, regions and other functionality with the aim of increasing performance
// and being able to handle megabytes of data with wrapping.
// You probably don't want to use this.
// See: https://github.com/rivo/tview/issues/686
type TextPrimitive struct {
sync.Mutex
*tview.Box

buffer []string
lineOffset int // the line offset for view scrolling
fitsAll bool // whether or not the entire content of buffer from the lineOffset onwards fits on the screen
}

var (
newLineRegex = regexp.MustCompile(`\r?\n`)
TabSize = 4
)

func NewTextPrimitive() *TextPrimitive {
var buffer []string

return &TextPrimitive{
Box: tview.NewBox(),
buffer: buffer,
lineOffset: 0,
}
}

// Write lets us implement the io.Writer interface. Tab characters will be
// replaced with TabSize space characters. A "\n" or "\r\n" will be interpreted
// as a new line.
func (t *TextPrimitive) Write(p []byte) (n int, err error) {
t.Lock()
defer t.Unlock()

newBytes := bytes.Replace(p, []byte{'\t'}, bytes.Repeat([]byte{' '}, TabSize), -1)
for index, line := range newLineRegex.Split(string(newBytes), -1) {
if index == 0 {
if len(t.buffer) == 0 {
t.buffer = []string{line}
} else {
t.buffer[len(t.buffer)-1] += line
}
} else {
t.buffer = append(t.buffer, line)
}
}

return len(p), nil
}

func (t *TextPrimitive) Clear() {
t.Lock()
defer t.Unlock()
t.buffer = nil
t.lineOffset = 0
}

func (t *TextPrimitive) ScrollToBeginning() {
t.Lock()
defer t.Unlock()
t.lineOffset = 0
}

func (t *TextPrimitive) Draw(screen tcell.Screen) {
t.Lock()
defer t.Unlock()

t.Box.DrawForSubclass(screen, t)
t.fitsAll = true
x, y, width, height := t.GetInnerRect()

// loop each str and print
index, offsetindex := 0, 0
for _, str := range t.buffer {
if index >= height {
t.fitsAll = false
break
}

if len(str) == 0 { // blank line
if offsetindex < t.lineOffset {
offsetindex++
} else {
index++
}
}

runes := []rune(str)
for len(runes) > 0 {
var extract []rune
if index >= height {
t.fitsAll = false
break
}

if len(runes) > width {
extract = runes[:width]
} else {
extract = runes
}

w := len(extract)
for len(string(extract)) > width {
w-- // string width is greater than rune count, yank one out
extract = runes[:w]
}

runes = runes[len(extract):]
if offsetindex < t.lineOffset {
offsetindex++
continue
} else {
tview.PrintSimple(screen, string(extract), x, y+index)
index++
}
}
}
}

func (t *TextPrimitive) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {

_, _, width, height := t.GetInnerRect()
switch event.Key() {

case tcell.KeyRune:
switch event.Rune() {
case 'g': // back to the beginning
t.lineOffset = 0
case 'G': // end
end := 0
for _, str := range t.buffer {
length := len(str)
end = end + (length / width)
if length%width > 0 {
end++
}
}
t.lineOffset = end - height
case 'j':
if !t.fitsAll {
max := 0
for _, str := range t.buffer {
length := len(str)
max = max + (length / width)
if length%width > 0 {
max++
}
if t.lineOffset < max {
t.lineOffset++
break
}
}
}
case 'k':
if t.lineOffset > 0 {
t.lineOffset--
}
}

case tcell.KeyHome:
t.lineOffset = 0 // back to the beginning
case tcell.KeyEnd:
end := 0
for _, str := range t.buffer {
length := len(str)
end = end + (length / width)
if length%width > 0 {
end++
}
}
t.lineOffset = end - height
case tcell.KeyUp:
if t.lineOffset > 0 {
t.lineOffset--
}
case tcell.KeyDown:
if !t.fitsAll {
max := 0
for _, str := range t.buffer {
length := len(str)
max = max + (length / width)
if length%width > 0 {
max++
}
if t.lineOffset < max {
t.lineOffset++
break
}
}
}
case tcell.KeyPgUp, tcell.KeyCtrlB:
if t.lineOffset > 0 {
t.lineOffset = t.lineOffset - height
if t.lineOffset < 0 {
t.lineOffset = 0
}
}

case tcell.KeyPgDn, tcell.KeyCtrlF:
if !t.fitsAll {
max := 0
for _, str := range t.buffer {
length := len(str)
max = max + (length / width)
if length%width > 0 {
max++
}
if t.lineOffset < max {
t.lineOffset = t.lineOffset + height
break
}
}
}
}

})
}

func (t *TextPrimitive) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
return t.WrapMouseHandler(func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
x, y := event.Position()
if !t.InRect(x, y) {
return false, nil
}

_, _, width, _ := t.GetInnerRect()

switch action {
case tview.MouseLeftClick:
setFocus(t)
consumed = true
case tview.MouseScrollUp:
if t.lineOffset > 0 {
t.lineOffset--
}
consumed = true
case tview.MouseScrollDown:
if !t.fitsAll {
max := 0
for _, str := range t.buffer {
length := len(str)
max = max + (length / width)
if length%width > 0 {
max++
}
if t.lineOffset < max {
t.lineOffset++
break
}
}
}
consumed = true
}

return
})
}

0 comments on commit 2754aca

Please sign in to comment.