Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 69 additions & 2 deletions cmd/sst/mosaic/multiplexer/multiplexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ type Multiplexer struct {
main *views.ViewPort
stack *views.BoxLayout

dragging bool
click *tcell.EventMouse
dragging bool
click *tcell.EventMouse
scrollDirection int // -1 for up, 0 for none, 1 for down
scrollStop chan struct{}
lastMouseX int
}

func New() (*Multiplexer, error) {
Expand Down Expand Up @@ -102,6 +105,21 @@ func (s *Multiplexer) Start() {
shouldBreak = true
return

case *EventScrollTick:
if s.dragging && s.scrollDirection != 0 {
if selected != nil && selected.scrollable() {
if s.scrollDirection < 0 {
selected.scrollUp(1)
selected.vt.SelectEnd(s.lastMouseX, 0)
} else {
selected.scrollDown(1)
selected.vt.SelectEnd(s.lastMouseX, s.height-1)
}
s.draw()
}
}
return

case *EventProcess:
for _, p := range s.processes {
if p.key == evt.Key {
Expand Down Expand Up @@ -154,6 +172,7 @@ func (s *Multiplexer) Start() {
s.copy()
}
s.dragging = false
s.stopScrollTimer()
return
}
if evt.Buttons()&tcell.ButtonPrimary != 0 {
Expand Down Expand Up @@ -193,8 +212,18 @@ func (s *Multiplexer) Start() {
}
s.click = evt
offsetX := x - SIDEBAR_WIDTH - 1
s.lastMouseX = offsetX
if s.dragging {
selected.vt.SelectEnd(offsetX, y)
if y <= 0 && selected.scrollable() {
selected.scrollUp(1)
s.startScrollTimer(-1)
} else if y >= s.height-1 {
selected.scrollDown(1)
s.startScrollTimer(1)
} else {
s.stopScrollTimer()
}
}
if !s.dragging {
s.dragging = true
Expand Down Expand Up @@ -335,10 +364,48 @@ func (e *EventExit) When() time.Time {
return e.when
}

type EventScrollTick struct {
when time.Time
}

func (e *EventScrollTick) When() time.Time {
return e.when
}

func (s *Multiplexer) Exit() {
s.screen.PostEvent(&EventExit{})
}

func (s *Multiplexer) startScrollTimer(direction int) {
if s.scrollDirection == direction && s.scrollStop != nil {
return
}
s.stopScrollTimer()
s.scrollDirection = direction
s.scrollStop = make(chan struct{})

go func() {
ticker := time.NewTicker(50 * time.Millisecond) // ~20 scrolls per second
defer ticker.Stop()
for {
select {
case <-s.scrollStop:
return
case <-ticker.C:
s.screen.PostEvent(&EventScrollTick{when: time.Now()})
}
}
}()
}

func (s *Multiplexer) stopScrollTimer() {
if s.scrollStop != nil {
close(s.scrollStop)
s.scrollStop = nil
}
s.scrollDirection = 0
}

func (s *Multiplexer) scrollDown(n int) {
selected := s.selectedProcess()
if selected == nil {
Expand Down
83 changes: 70 additions & 13 deletions cmd/sst/mosaic/multiplexer/tcell-term/vt.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,10 @@ type VT struct {
}

type selection struct {
content strings.Builder
startX int
startY int
endX int
endY int
startX int
startY int
endX int
endY int
}

type cursorState struct {
Expand Down Expand Up @@ -523,7 +522,6 @@ func (vt *VT) Draw() {
return
}
offset := 0
vt.selection.content = strings.Builder{}
if vt.IsScrolling() {
for x := vt.scroll; x < len(vt.primaryScrollback); x += 1 {
if offset >= vt.height() {
Expand All @@ -547,7 +545,6 @@ func (vt *VT) drawRow(row int, cols []cell) {
if vt.scroll != -1 {
scrollOffset = vt.scroll
}
builder := strings.Builder{}
for col := 0; col < len(cols); {
cell := cols[col]
w := cell.width
Expand All @@ -559,18 +556,13 @@ func (vt *VT) drawRow(row int, cols []cell) {
style := cell.attrs
if vt.selection != nil && isCellSelected(col, row+scrollOffset, vt.selection.startX, vt.selection.startY, vt.selection.endX, vt.selection.endY) {
style = style.Reverse(true)
builder.WriteRune(content)
}
vt.surface.SetContent(col, row, content, cell.combining, style)
if w == 0 {
w = 1
}
col += 1
}
if builder.Len() > 0 {
vt.selection.content.WriteString(strings.TrimRight(builder.String(), " "))
vt.selection.content.WriteRune('\n')
}
}

func (vt *VT) HandleEvent(e tcell.Event) bool {
Expand Down Expand Up @@ -603,7 +595,72 @@ func (vt *VT) Clear() {
}

func (vt *VT) Copy() string {
return strings.TrimRightFunc(vt.selection.content.String(), unicode.IsSpace)
if !vt.HasSelection() {
return ""
}

// Normalize selection coordinates
startX, startY, endX, endY := vt.selection.startX, vt.selection.startY, vt.selection.endX, vt.selection.endY
if endY < startY || (endY == startY && endX < startX) {
startX, startY, endX, endY = endX, endY, startX, startY
}

var builder strings.Builder

// Determine which rows to iterate through
totalScrollback := len(vt.primaryScrollback)

for y := startY; y <= endY; y++ {
var row []cell

// Get the row from either scrollback or active screen
if y < totalScrollback {
row = vt.primaryScrollback[y]
} else {
screenY := y - totalScrollback
if screenY < len(vt.activeScreen) {
row = vt.activeScreen[screenY]
} else {
continue
}
}

// Determine the column range for this row
colStart := 0
colEnd := len(row) - 1

if y == startY {
colStart = startX
}
if y == endY {
colEnd = endX
}

// Extract text from this row
rowBuilder := strings.Builder{}
for x := colStart; x <= colEnd && x < len(row); x++ {
cell := row[x]
content := cell.content
if content == '\x00' {
content = ' '
}
rowBuilder.WriteRune(content)
for _, comb := range cell.combining {
rowBuilder.WriteRune(comb)
}
}

// Add the row text, trimming trailing spaces
rowText := strings.TrimRight(rowBuilder.String(), " ")
if rowText != "" || y < endY {
builder.WriteString(rowText)
if y < endY {
builder.WriteRune('\n')
}
}
}

return strings.TrimRightFunc(builder.String(), unicode.IsSpace)
}

func (vt *VT) SelectStart(x int, y int) {
Expand Down