detached 61dc249

perf(screen): write each printable cell once

putPrintable blanked every target cell before writing it, interning the
default style twice per character. Only the orphaned wide-pair halves
outside the written span are blanked now, continuation checks read the
stored flag without materializing a Cell, and RuneWidth returns early for
printable ASCII. The differential oracle stays byte-identical.

ScreenPrintableASCII 15.8 -> 44.2 MiB/s, ScrollbackFlood/history-0
15.7 -> 36.4 MiB/s, history-10000 4.9 -> 13.0 MiB/s
commit
61dc24930674f861fd4db8642d43ad23e397a8ef
author
brice · · 2026-10-07 16:26 UTC
parent
92e0c36
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +16 -6
M core/frame.go +6 -0
@@ -267,6 +267,12 @@ func (f Frame) Cell(x, y int) Cell {
267267 }
268268 func (f Frame) At(x, y int) Cell { return f.Cell(x, y) }
269269
270+// IsContinuation reports whether (x,y) is the right half of a wide pair
271+// without materializing the cell.
272+func (f Frame) IsContinuation(x, y int) bool {
273+ return f.page.cells[f.offset(x, y)].flags&continuationFlag != 0
274+}
275+
270276 func (f Frame) Set(x, y int, cell Cell) {
271277 index := f.offset(x, y)
272278 payloadID := f.internPayload(cell.Payload)
@@ -267,6 +267,12 @@ func (f Frame) Cell(x, y int) Cell {
267 }267 }
268 func (f Frame) At(x, y int) Cell { return f.Cell(x, y) }268 func (f Frame) At(x, y int) Cell { return f.Cell(x, y) }
269 269
270+// IsContinuation reports whether (x,y) is the right half of a wide pair
271+// without materializing the cell.
272+func (f Frame) IsContinuation(x, y int) bool {
273+ return f.page.cells[f.offset(x, y)].flags&continuationFlag != 0
274+}
275+
270 func (f Frame) Set(x, y int, cell Cell) {276 func (f Frame) Set(x, y int, cell Cell) {
271 index := f.offset(x, y)277 index := f.offset(x, y)
272 payloadID := f.internPayload(cell.Payload)278 payloadID := f.internPayload(cell.Payload)
M core/rune_width.go +3 -0
@@ -13,6 +13,9 @@ import "unicode"
1313 // sequences are out of scope: combining marks report width 0 (current
1414 // behavior), ZWJ sequences are not coalesced (each rune is measured on its own).
1515 func RuneWidth(r rune) int {
16+ if r >= 0x20 && r < 0x7F { // printable ASCII, the common case
17+ return 1
18+ }
1619 switch {
1720 case r < 0x20 || r == 0x7F || (r >= 0x80 && r <= 0x9F):
1821 return 0
@@ -13,6 +13,9 @@ import "unicode"
13 // sequences are out of scope: combining marks report width 0 (current13 // sequences are out of scope: combining marks report width 0 (current
14 // behavior), ZWJ sequences are not coalesced (each rune is measured on its own).14 // behavior), ZWJ sequences are not coalesced (each rune is measured on its own).
15 func RuneWidth(r rune) int {15 func RuneWidth(r rune) int {
16+ if r >= 0x20 && r < 0x7F { // printable ASCII, the common case
17+ return 1
18+ }
16 switch {19 switch {
17 case r < 0x20 || r == 0x7F || (r >= 0x80 && r <= 0x9F):20 case r < 0x20 || r == 0x7F || (r >= 0x80 && r <= 0x9F):
18 return 021 return 0
M screen_write.go +7 -6
@@ -107,7 +107,7 @@ func (s *Screen) putPrintable(r rune) {
107107 insertDamageX := s.Col
108108 insertDamageWidth := 0
109109 if s.insertMode {
110- leftSplit := s.Col > 0 && s.frame.At(s.Col, s.Row).Continuation
110+ leftSplit := s.Col > 0 && s.frame.IsContinuation(s.Col, s.Row)
111111 s.frame.CopyRow(s.Row, s.Col+w, s.Col, s.frame.Width-s.Col-w)
112112 s.frame.FillRow(s.Row, s.Col, s.Col+w, renderer.BlankCell())
113113 s.repairRow(s.Row)
@@ -120,15 +120,16 @@ func (s *Screen) putPrintable(r rune) {
120120
121121 // Determine the range of cells actually modified, extending over any wide
122122 // pair the write lands on so no orphaned half is left behind.
123+ // Cells inside [Col, Col+w) are overwritten below, so only the orphaned
124+ // halves just outside it need blanking.
123125 lo, hi := s.Col, s.Col+w-1
124- if s.frame.At(s.Col, s.Row).Continuation {
126+ if s.frame.IsContinuation(s.Col, s.Row) {
125127 lo = s.Col - 1
128+ s.frame.Set(lo, s.Row, renderer.BlankCell())
126129 }
127- if right := s.Col + w; right < s.frame.Width && s.frame.At(right, s.Row).Continuation {
130+ if right := s.Col + w; right < s.frame.Width && s.frame.IsContinuation(right, s.Row) {
128131 hi = right
129- }
130- for x := lo; x <= hi; x++ {
131- s.frame.Set(x, s.Row, renderer.BlankCell())
132+ s.frame.Set(right, s.Row, renderer.BlankCell())
132133 }
133134 s.frame.Set(s.Col, s.Row, renderer.Cell{Rune: r, Style: s.Style})
134135 s.buffer.content(s.Row, s.Col+w)
@@ -107,7 +107,7 @@ func (s *Screen) putPrintable(r rune) {
107 insertDamageX := s.Col107 insertDamageX := s.Col
108 insertDamageWidth := 0108 insertDamageWidth := 0
109 if s.insertMode {109 if s.insertMode {
110- leftSplit := s.Col > 0 && s.frame.At(s.Col, s.Row).Continuation110+ leftSplit := s.Col > 0 && s.frame.IsContinuation(s.Col, s.Row)
111 s.frame.CopyRow(s.Row, s.Col+w, s.Col, s.frame.Width-s.Col-w)111 s.frame.CopyRow(s.Row, s.Col+w, s.Col, s.frame.Width-s.Col-w)
112 s.frame.FillRow(s.Row, s.Col, s.Col+w, renderer.BlankCell())112 s.frame.FillRow(s.Row, s.Col, s.Col+w, renderer.BlankCell())
113 s.repairRow(s.Row)113 s.repairRow(s.Row)
@@ -120,15 +120,16 @@ func (s *Screen) putPrintable(r rune) {
120 120
121 // Determine the range of cells actually modified, extending over any wide121 // Determine the range of cells actually modified, extending over any wide
122 // pair the write lands on so no orphaned half is left behind.122 // pair the write lands on so no orphaned half is left behind.
123+ // Cells inside [Col, Col+w) are overwritten below, so only the orphaned
124+ // halves just outside it need blanking.
123 lo, hi := s.Col, s.Col+w-1125 lo, hi := s.Col, s.Col+w-1
124- if s.frame.At(s.Col, s.Row).Continuation {126+ if s.frame.IsContinuation(s.Col, s.Row) {
125 lo = s.Col - 1127 lo = s.Col - 1
128+ s.frame.Set(lo, s.Row, renderer.BlankCell())
126 }129 }
127- if right := s.Col + w; right < s.frame.Width && s.frame.At(right, s.Row).Continuation {130+ if right := s.Col + w; right < s.frame.Width && s.frame.IsContinuation(right, s.Row) {
128 hi = right131 hi = right
129- }132+ s.frame.Set(right, s.Row, renderer.BlankCell())
130- for x := lo; x <= hi; x++ {
131- s.frame.Set(x, s.Row, renderer.BlankCell())
132 }133 }
133 s.frame.Set(s.Col, s.Row, renderer.Cell{Rune: r, Style: s.Style})134 s.frame.Set(s.Col, s.Row, renderer.Cell{Rune: r, Style: s.Style})
134 s.buffer.content(s.Row, s.Col+w)135 s.buffer.content(s.Row, s.Col+w)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 13

↑↓ moveenter openesc close