Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sixel patch #1

Merged
merged 44 commits into from
Jul 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9a28db4
new struct type sixel
horriblename May 20, 2022
5b10351
add sixel detection logic in printReg
horriblename May 20, 2022
93edee3
new ShowSixels that prints sixels to the screen
horriblename May 20, 2022
c8c5b64
placeholder function UnshowSixels
horriblename May 20, 2022
d782c58
run `ui.ShowSixels` after `ui.screen.Sync`
horriblename May 20, 2022
3642375
fix sixel placed on wrong coordinates
horriblename May 20, 2022
d8c1df2
change sixel termination sequence to prevent other escape sequence be…
horriblename May 20, 2022
4f68fd3
process multi-line sixel sequence
horriblename May 21, 2022
934e589
fixed bug where string before sixel image is not printed
horriblename May 21, 2022
e53e632
add sixelDimPx and pxToCells
horriblename May 25, 2022
eadab5e
add getTermPixels for unix
horriblename May 25, 2022
3483813
remove unused method UnshowSixels
horriblename May 26, 2022
d977957
fix: remove unneeded ShowSixels
horriblename May 26, 2022
1d93785
add new fields sixel.wPx,hPx and reg.sixels
horriblename May 26, 2022
ad88770
modify sixel processing logic
horriblename May 26, 2022
2cf0a36
modify sixel processing logic
horriblename May 26, 2022
9e9b806
reset sixels buffer at the start of `draw`
horriblename May 26, 2022
5487a63
rename `ui.ShowSixels` to `showSixels`
horriblename May 26, 2022
3ba360b
add constants `gSixelBegin` and `gSixelTerminate`
horriblename May 28, 2022
fe8c1d5
add check to prevent arbitrary escape code being passed to stdin
horriblename Jun 2, 2022
c87c639
fix cursor out of place in command line mode
horriblename Jun 2, 2022
16f8e5b
fix bug where sixel in drawn at old location after horizontal resize
horriblename Jun 14, 2022
18efee8
add ui.wPx and ui.hPx
horriblename Jun 14, 2022
c37274d
buffer sixel sequences before printing
horriblename Jun 14, 2022
150dd79
add check for valid terminal size(px) before previewing sixel
horriblename Jun 17, 2022
a6bfeae
clean up
horriblename Jun 17, 2022
3cccb83
placeholder function getTermPixels for windows
horriblename Jul 1, 2022
f8bfef0
function sixelDimPx now considers image size given in the optional ra…
horriblename Jul 2, 2022
2eddca9
change sixel image alignment to emulate behavior of a terminal
horriblename Jul 4, 2022
43fb37f
fix bug where raster attributes are wrongly parsed in sixelDimPx
horriblename Jul 6, 2022
3a6f807
prevent drawing sixels while menu is active
horriblename Jul 6, 2022
6cce131
introduce sixelScreen struct and refactor screen width,height in px t…
horriblename Jul 8, 2022
5929846
fix prevent nested sixel sequences
horriblename Jul 12, 2022
2e0199d
fix bug where rejected sixels cause indexing error
horriblename Jul 12, 2022
22883aa
add "alternating filler" to trick tcell into redrawing when switching…
horriblename Jul 12, 2022
412176e
fix filler string wrongly indented
horriblename Jul 12, 2022
1150515
add comment
horriblename Jul 12, 2022
91efd8d
replace pxToCells() with sixelScreen.pxToCells()
horriblename Jul 13, 2022
af92f4c
add trim sixel height during preview
horriblename Jul 13, 2022
cef82a0
add tests for trimSixelHeight
horriblename Jul 13, 2022
18d4d42
prevent sixel redrawing during input prompts
horriblename Jul 13, 2022
0e7f5ee
change sixel filler to braille space
horriblename Jul 13, 2022
4925d87
clean up
horriblename Jul 13, 2022
6dfd656
use strings.Index instead of regex for simple search
horriblename Jul 13, 2022
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
1 change: 1 addition & 0 deletions eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,7 @@ func (e *callExpr) eval(app *app, args []string) {
if !app.nav.init {
return
}
app.ui.sxScreen.updateSizes(app.ui.screen.Size())
app.ui.renew()
app.ui.screen.Sync()
if app.nav.height != app.ui.wins[0].h {
Expand Down
138 changes: 138 additions & 0 deletions misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,144 @@ func mod(a, b int) int {
return (a%b + b) % b
}

var reNumber = regexp.MustCompile(`^[0-9]+`)

// needs some testing
func sixelDimPx(s string) (w int, h int) {
// TODO maybe take into account pixel aspect ratio

// General sixel sequence:
// DCS <P1>;<P2>;<P3>; q [" <raster_attributes>]; <main_body> ST
// DCS is "ESC P"
// We are not interested in P1~P3
// the optional raster attributes may contain the image size in pixels
// ST is the terminating string "ESC \"
i := strings.Index(s, "q") + 1
if i == 0 {
// syntax error
return -1, -1
}

// Start of (optional) Raster Attributes
// " Pan ; Pad; Ph; Pv
// pixel aspect ratio = Pan/Pad
// We are only interested in Ph and Pv (horizontal and vertical size in px)
if s[i] == '"' {
i++
b := strings.Index(s[i:], ";")
// pan := strconv.Atoi(s[a:b])
i += b + 1
b = strings.Index(s[i:], ";")
// pad := strconv.Atoi(s[a:b])

i += b + 1
b = strings.Index(s[i:], ";")
ph, err1 := strconv.Atoi(s[i : i+b])

i += b + 1
b = strings.Index(s[i:], "#")
pv, err2 := strconv.Atoi(s[i : i+b])
i += b

if err1 != nil || err2 != nil {
goto main_body // keep trying
}

// TODO
// ph and pv are more like suggestions, it's still possible to go over the
// reported size, so we might need to parse the entire main body anyway
return ph, pv
}

main_body:
var wi int
for ; i < len(s)-2; i++ {
c := s[i]
switch {
case '?' <= c && c <= '~':
wi++
case c == '-':
w = max(w, wi)
wi = 0
h++
case c == '$':
w = max(w, wi)
wi = 0
case c == '!':
m := reNumber.FindString(s[i+1:])
if m == "" {
// syntax error
return -1, -1
}
if s[i+1+len(m)] < '?' || s[i+1+len(m)] > '~' {
// syntax error
return -1, -1
}
n, _ := strconv.Atoi(m)
wi += n - 1
default:
}
}
if s[len(s)-3] != '-' {
w = max(w, wi)
h++ // add newline on last row
}
return w, h * 6
}

// maybe merge with sixelDimPx()
func trimSixelHeight(s string, maxh int) (string, int) {
var h int
maxh = maxh - (maxh % 6)

i := strings.Index(s, "q") + 1
if i == 0 {
// syntax error
return "", -1
}

if s[i] == '"' {
i++
for j := 0; j < 3; j++ {
b := strings.Index(s[i:], ";")
i += b + 1
}
b := strings.Index(s[i:], "#")
pv, err := strconv.Atoi(s[i : i+b])

if err == nil && pv > maxh {
mh := strconv.Itoa(maxh)
s = s[:i] + mh + s[i+b:]
i += len(mh)
} else {
i += b
}
}

for h < maxh {
k := strings.IndexRune(s[i+1:], '-')
if k == -1 {
if s[len(s)-3] != '-' {
h += 6
i = len(s) - 3
}
break
}
i += k + 1
h += 6
}

if i == 0 {
return s, 6
}

if len(s) > i+3 {
return s[:i+1] + "\x1b\\", h
}

return s, h
}

// We don't need no generic code
// We don't need no type control
// No dark templates in compiler
Expand Down
45 changes: 45 additions & 0 deletions misc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,48 @@ func TestNaturalLess(t *testing.T) {
}
}
}

func TestTrimSixelHeight(t *testing.T) {
tests := []struct {
si string
hi int
so string
ho int
}{
{
"\x1bPq\"1;1;1;12#0;2;97;97;97#1;2;75;75;75#0B$#1{\x1b\\", 12,
"\x1bPq\"1;1;1;12#0;2;97;97;97#1;2;75;75;75#0B$#1{\x1b\\", 6,
},
{
"\x1bPq\"1;1;1;12#0;2;97;97;97#1;2;75;75;75#0B$#1{-#0o$#1N\x1b\\", 30,
"\x1bPq\"1;1;1;12#0;2;97;97;97#1;2;75;75;75#0B$#1{-#0o$#1N\x1b\\", 12,
},
{
"\x1bPq\"1;1;1;12#0;2;97;97;97#1;2;75;75;75#0B$#1{-#0o$#1N\x1b\\", 12,
"\x1bPq\"1;1;1;12#0;2;97;97;97#1;2;75;75;75#0B$#1{-#0o$#1N\x1b\\", 12,
},
{
"\x1bPq\"1;1;1;12#0;2;97;97;97#1;2;75;75;75#0B$#1{-#0o$#1N\x1b\\", 11,
"\x1bPq\"1;1;1;6#0;2;97;97;97#1;2;75;75;75#0B$#1{-\x1b\\", 6,
},
{
"\x1bPq\"1;1;1;12#0;2;97;97;97#1;2;75;75;75#0B$#1{-#0o$#1N-\x1b\\", 30,
"\x1bPq\"1;1;1;12#0;2;97;97;97#1;2;75;75;75#0B$#1{-#0o$#1N-\x1b\\", 12,
},
{
"\x1bPq\"1;1;1;12#0;2;97;97;97#1;2;75;75;75#0B$#1{-#0o$#1N-\x1b\\", 12,
"\x1bPq\"1;1;1;12#0;2;97;97;97#1;2;75;75;75#0B$#1{-#0o$#1N-\x1b\\", 12,
},
{
"\x1bPq\"1;1;1;12#0;2;97;97;97#1;2;75;75;75#0B$#1{-#0o$#1N-\x1b\\", 11,
"\x1bPq\"1;1;1;6#0;2;97;97;97#1;2;75;75;75#0B$#1{-\x1b\\", 6,
},
}

for i, test := range tests {
if got1, got2 := trimSixelHeight(test.si, test.hi); got1 != test.so || got2 != test.ho {
t.Errorf("test #%d expected height %d, got %d and string %s", i, test.ho, got2, got1[1:])
}
}

}
86 changes: 82 additions & 4 deletions nav.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ import (
times "gopkg.in/djherbis/times.v1"
)

const (
gSixelBegin = "\033P"
gSixelTerminate = "\033\\"
)

var (
gSixelFiller = '\u2800'
)

type linkState byte

const (
Expand Down Expand Up @@ -623,7 +632,7 @@ func (nav *nav) previewLoop(ui *ui) {
}
if len(path) != 0 {
win := ui.wins[len(ui.wins)-1]
nav.preview(path, win)
nav.preview(path, &ui.sxScreen, win)
prev = path
}
}
Expand All @@ -644,7 +653,7 @@ func matchPattern(pattern, name, path string) bool {
return matched
}

func (nav *nav) preview(path string, win *win) {
func (nav *nav) preview(path string, sxScreen *sixelScreen, win *win) {
reg := &reg{loadTime: time.Now(), path: path}
defer func() { nav.regChan <- reg }()

Expand Down Expand Up @@ -698,14 +707,83 @@ func (nav *nav) preview(path string, win *win) {

buf := bufio.NewScanner(reader)

for i := 0; i < win.h && buf.Scan(); i++ {
var sixelBuffer []string
processingSixel := false
for i := 0; (processingSixel || len(reg.lines) < win.h) && buf.Scan(); i++ {
text := buf.Text()

for _, r := range buf.Text() {
if r == 0 {
reg.lines = []string{"\033[7mbinary\033[0m"}
return
}
}
reg.lines = append(reg.lines, buf.Text())
if sxScreen.wpx > 0 && sxScreen.hpx > 0 {
if a := strings.Index(text, gSixelBegin); !processingSixel && a >= 0 {
reg.lines = append(reg.lines, text[:a])
text = text[a:]
processingSixel = true
}

if processingSixel {
var lookFrom int
if text[:2] == gSixelBegin {
lookFrom = 2
}
if b := strings.IndexByte(text[lookFrom:], gEscapeCode); b >= 0 {
b += lookFrom
if len(text) > b && text[b+1] == '\\' {
sixelBuffer = append(sixelBuffer, text[:b+2])
sx := strings.Join(sixelBuffer, "")

xoff := runeSliceWidth([]rune(reg.lines[len(reg.lines)-1])) + 2
yoff := len(reg.lines) - 1
maxh := (win.h - yoff) * sxScreen.fonth
w, h := sixelDimPx(sx)
if w < 0 || h < 0 {
goto discard_sixel
}
sx, h = trimSixelHeight(sx, maxh)
wc, hc := sxScreen.pxToCells(w, h)

reg.sixels = append(reg.sixels, sixel{xoff, yoff, w, h, sx})
fill := sxScreen.filler(path, wc)
paddedfill := strings.Repeat(" ", xoff-2) + fill
reg.lines[len(reg.lines)-1] = reg.lines[len(reg.lines)-1] + fill
for j := 1; j < hc; j++ {
reg.lines = append(reg.lines, paddedfill)
}

reg.lines = append(reg.lines, text[b+2:])
processingSixel = false
continue
} else { // deal with unexpected control sequence
goto discard_sixel
}
}
sixelBuffer = append(sixelBuffer, text)
continue

discard_sixel:
emptyLines := min(win.h-len(reg.lines), len(sixelBuffer)-1)
reg.lines[len(reg.lines)-1] = reg.lines[len(reg.lines)-1] + sixelBuffer[0]
if emptyLines > 0 {
reg.lines = append(reg.lines, sixelBuffer[1:emptyLines+1]...)
}
reg.lines = append(reg.lines, text)
processingSixel = false
continue
}
}
reg.lines = append(reg.lines, text)
}

if processingSixel && len(sixelBuffer) > 0 {
emptyLines := min(win.h-len(reg.lines), len(sixelBuffer)-1)
reg.lines[len(reg.lines)-1] = reg.lines[len(reg.lines)-1] + sixelBuffer[0]
if emptyLines > 0 {
reg.lines = append(reg.lines, sixelBuffer[1:emptyLines+1]...)
}
}

if buf.Err() != nil {
Expand Down
9 changes: 9 additions & 0 deletions os.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,12 @@ func exportFiles(f string, fs []string, pwd string) {
os.Setenv("fx", envFiles)
}
}

func getTermPixels() (w, h int, err error) {
fd := int(os.Stdin.Fd())
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
if err != nil {
return -1, -1, err
}
return int(ws.Xpixel), int(ws.Ypixel), nil
}
4 changes: 4 additions & 0 deletions os_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,7 @@ func exportFiles(f string, fs []string, pwd string) {
os.Setenv("fx", envFiles)
}
}

func getTermPixels() (w, h int, err error) {
return -1, -1, nil
}
Loading