Skip to content

Commit

Permalink
Another attempt to fix limited length of paste.
Browse files Browse the repository at this point in the history
I humbly submit this patch as another attempt to address the issue that tcell
will only paste up to 11 characters at a time. The problem is caused by the
fact that events (key, mouse, etc) constructed from the stream of raw input
characters are sent to the consuming application over a length-10 channel via
screen.PostEvent() which, if the channel is full, will drop what can't be sent
immediately. If the input stream grows rapidly e.g. because the user pasted a
large section of text into the running tcell application, then more than 10
events will likely be built from the chunk of input read by scanInput().

A blocking channel send is not used (i.e. PostEventWait() instead of
PostEvent()) because the channel send is issued from a call stack in which the
screen struct's lock is held. If the receiving application is not consuming
events, then callers to other screen APIs will block waiting for the screen's
lock. If the receiving application needs to call another screen API before
reading from the channel, a deadlock may occur if that required screen API
tries to take the screen's lock.

This patch collects events extracted from the input stream into a slice while
the lock is held, then after releasing the screen lock, writes them in order
to the event channel with PostEventWait(). I chose the blocking API to ensure
events aren't dropped, since sending the events outside of the lock-held scope
should remove the risk of a deadlock (unless I've missed something important!)

This patch is similar in spirit to that submitted by @soyking:
soyking@9addd5b.

I have not adjusted the windows cmd console screen because the paste problem
does not seem to be an issue in practice on that platform, at least according
to my testing.
  • Loading branch information
gcla authored and gdamore committed Jun 9, 2019
1 parent b5d0c1a commit 12658f0
Showing 1 changed file with 36 additions and 24 deletions.
60 changes: 36 additions & 24 deletions tscreen.go
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,10 @@ func (t *tScreen) clip(x, y int) (int, int) {
return x, y
}

func (t *tScreen) postMouseEvent(x, y, btn int) {
// buildMouseEvent returns an event based on the supplied coordinates and button
// state. Note that the screen's mouse button state is updated based on the
// input to this function (i.e. it mutates the receiver).
func (t *tScreen) buildMouseEvent(x, y, btn int) *EventMouse {

// XTerm mouse events only report at most one button at a time,
// which may include a wheel button. Wheel motion events are
Expand Down Expand Up @@ -942,16 +945,15 @@ func (t *tScreen) postMouseEvent(x, y, btn int) {
// to the screen in that case.
x, y = t.clip(x, y)

ev := NewEventMouse(x, y, button, mod)
t.PostEvent(ev)
return NewEventMouse(x, y, button, mod)
}

// parseSgrMouse attempts to locate an SGR mouse record at the start of the
// buffer. It returns true, true if it found one, and the associated bytes
// be removed from the buffer. It returns true, false if the buffer might
// contain such an event, but more bytes are necessary (partial match), and
// false, false if the content is definitely *not* an SGR mouse record.
func (t *tScreen) parseSgrMouse(buf *bytes.Buffer) (bool, bool) {
func (t *tScreen) parseSgrMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) {

b := buf.Bytes()

Expand Down Expand Up @@ -1059,7 +1061,7 @@ func (t *tScreen) parseSgrMouse(buf *bytes.Buffer) (bool, bool) {
buf.ReadByte()
i--
}
t.postMouseEvent(x, y, btn)
*evs = append(*evs, t.buildMouseEvent(x, y, btn))
return true, true
}
}
Expand All @@ -1070,7 +1072,7 @@ func (t *tScreen) parseSgrMouse(buf *bytes.Buffer) (bool, bool) {

// parseXtermMouse is like parseSgrMouse, but it parses a legacy
// X11 mouse record.
func (t *tScreen) parseXtermMouse(buf *bytes.Buffer) (bool, bool) {
func (t *tScreen) parseXtermMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) {

b := buf.Bytes()

Expand Down Expand Up @@ -1112,14 +1114,14 @@ func (t *tScreen) parseXtermMouse(buf *bytes.Buffer) (bool, bool) {
buf.ReadByte()
i--
}
t.postMouseEvent(x, y, btn)
*evs = append(*evs, t.buildMouseEvent(x, y, btn))
return true, true
}
}
return true, false
}

func (t *tScreen) parseFunctionKey(buf *bytes.Buffer) (bool, bool) {
func (t *tScreen) parseFunctionKey(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
b := buf.Bytes()
partial := false
for e, k := range t.keycodes {
Expand All @@ -1138,8 +1140,7 @@ func (t *tScreen) parseFunctionKey(buf *bytes.Buffer) (bool, bool) {
mod |= ModAlt
t.escaped = false
}
ev := NewEventKey(k.key, r, mod)
t.PostEvent(ev)
*evs = append(*evs, NewEventKey(k.key, r, mod))
for i := 0; i < len(esc); i++ {
buf.ReadByte()
}
Expand All @@ -1152,7 +1153,7 @@ func (t *tScreen) parseFunctionKey(buf *bytes.Buffer) (bool, bool) {
return partial, false
}

func (t *tScreen) parseRune(buf *bytes.Buffer) (bool, bool) {
func (t *tScreen) parseRune(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
b := buf.Bytes()
if b[0] >= ' ' && b[0] <= 0x7F {
// printable ASCII easy to deal with -- no encodings
Expand All @@ -1161,8 +1162,7 @@ func (t *tScreen) parseRune(buf *bytes.Buffer) (bool, bool) {
mod = ModAlt
t.escaped = false
}
ev := NewEventKey(KeyRune, rune(b[0]), mod)
t.PostEvent(ev)
*evs = append(*evs, NewEventKey(KeyRune, rune(b[0]), mod))
buf.ReadByte()
return true, true
}
Expand All @@ -1187,8 +1187,7 @@ func (t *tScreen) parseRune(buf *bytes.Buffer) (bool, bool) {
mod = ModAlt
t.escaped = false
}
ev := NewEventKey(KeyRune, r, mod)
t.PostEvent(ev)
*evs = append(*evs, NewEventKey(KeyRune, r, mod))
}
for nin > 0 {
buf.ReadByte()
Expand All @@ -1202,6 +1201,19 @@ func (t *tScreen) parseRune(buf *bytes.Buffer) (bool, bool) {
}

func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) {
evs := t.collectEventsFromInput(buf, expire)

for _, ev := range evs {
t.PostEventWait(ev)
}
}

// Return an array of Events extracted from the supplied buffer. This is done
// while holding the screen's lock - the events can then be queued for
// application processing with the lock released.
func (t *tScreen) collectEventsFromInput(buf *bytes.Buffer, expire bool) []Event {

res := make([]Event, 0, 20)

t.Lock()
defer t.Unlock()
Expand All @@ -1210,18 +1222,18 @@ func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) {
b := buf.Bytes()
if len(b) == 0 {
buf.Reset()
return
return res
}

partials := 0

if part, comp := t.parseRune(buf); comp {
if part, comp := t.parseRune(buf, &res); comp {
continue
} else if part {
partials++
}

if part, comp := t.parseFunctionKey(buf); comp {
if part, comp := t.parseFunctionKey(buf, &res); comp {
continue
} else if part {
partials++
Expand All @@ -1231,13 +1243,13 @@ func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) {
// mouse support

if t.ti.Mouse != "" {
if part, comp := t.parseXtermMouse(buf); comp {
if part, comp := t.parseXtermMouse(buf, &res); comp {
continue
} else if part {
partials++
}

if part, comp := t.parseSgrMouse(buf); comp {
if part, comp := t.parseSgrMouse(buf, &res); comp {
continue
} else if part {
partials++
Expand All @@ -1247,8 +1259,7 @@ func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) {
if partials == 0 || expire {
if b[0] == '\x1b' {
if len(b) == 1 {
ev := NewEventKey(KeyEsc, 0, ModNone)
t.PostEvent(ev)
res = append(res, NewEventKey(KeyEsc, 0, ModNone))
t.escaped = false
} else {
t.escaped = true
Expand All @@ -1266,15 +1277,16 @@ func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) {
t.escaped = false
mod = ModAlt
}
ev := NewEventKey(KeyRune, rune(by), mod)
t.PostEvent(ev)
res = append(res, NewEventKey(KeyRune, rune(by), mod))
continue
}

// well we have some partial data, wait until we get
// some more
break
}

return res
}

func (t *tScreen) mainLoop() {
Expand Down

0 comments on commit 12658f0

Please sign in to comment.