charmbracelet / glow

Render markdown on the CLI, with pizzazz! πŸ’…πŸ»
MIT License
16.34k stars 358 forks source link

When output is redirected, terminal capabilities are ignored and colors are illegible #654

Open AndydeCleyre opened 4 weeks ago

AndydeCleyre commented 4 weeks ago

Describe the bug When output is redirected/piped, terminal capabilities are ignored and colors are illegible.

Setup

To Reproduce

  1. Given a markdown README.md . . .
  2. Run:
$ CLICOLOR_FORCE=1 glow --style dark README.md | head

Source Code

Sample README

Expected behavior

The colors should be the same as they are when | head is omitted, and the header titles should have enough contrast to be easily read.

Screenshots

image

Additional context

See discussion at https://github.com/charmbracelet/glow/issues/440#issuecomment-2358748595

meowgorithm commented 4 weeks ago

Thanks for the excellent report.Β As mentioned, this looks like a regression. Off the cuff I'd guess it's downsampling to 4-bit ANSI (i.e. the 16 user-definable terminal colors; the first two rows in the following chart).

image
folliehiyuki commented 3 weeks ago

It's the behavior of termenv, which glow uses. See https://github.com/muesli/termenv/blob/82936c5ea257b458deb5238e6093773b42c43773/termenv.go#L104.

In short, when being piped, it detects that no terminal capabilities are available, hence no color profile will be chosen. If you force the color output with CLICOLOR_FORCE=1 when no color profile is detected, it will fallback to 4-color ANSI.

meowgorithm commented 3 weeks ago

Aha, there we have it. Technically speaking CLICOLOR_FORCE=1 COLORTERM=truecolor glow-s dark filename.md | head should work, but it looks like it may have regressed upstream.

Alternatively, we could introduce a flag like glow --force-color-profile.

glow --force-color-profile=truecolor
glow --force-color-profile=ansi256
glow --force-color-profile=ansi
glow --force-color-profile=notty
Chaitanyabsprip commented 3 weeks ago

instead of the --force-color-profile flag, just use whatever value COLORTERM env var contains

meowgorithm commented 3 weeks ago

That’s fine by me, but termenv will need to be corrected upstream.

nikelborm commented 4 days ago

https://github.com/charmbracelet/glow/issues/21#issuecomment-2467020125

script -q -c "glow README.md" | tail -n +2
nikelborm commented 4 days ago

After some digging, I found something interesting.

bash -c "echo date_before_script: \$(date); script -q -c 'echo date_before_glow: \$(date); glow README.md; echo date_after_glow: \$(date)' > temp; cat temp; echo date_after_script: \$(date)" > temp2

Instead of > temp2 in the end you can use | sed '3d' to remove bad line and

with following README.md:

# header

1. _something_
2. *bold*

temp2 looks like this:

date_before_script: Mon Nov 11 04:22:13 MSK 2024
date_before_glow: Mon Nov 11 04:22:13 MSK 2024
]10;?\]11;?\]11;?\
   header                                                                         
                                                                                  
  1. something                                                                    
  2. bold                                                                         

date_after_glow: Mon Nov 11 04:22:28 MSK 2024
date_after_script: Mon Nov 11 04:22:28 MSK 2024

Pairs of dates are exactly the same. Long execution is glow's or some other internally used library's responsibility. And for me it's always exactly 15 f***ing seconds. TTY is allocated immediately. And first line, that breaks output is printed by glow too.

@eric-saintetienne (pinging because I think you'll be interested about it too because you did the same stuff here: https://github.com/charmbracelet/glow/issues/21#issuecomment-2360703369)

I tried tracing the program. I created following script:

#!/bin/bash

/bin/bash -c "echo date_before_script: \$(/sbin/date); /sbin/script -q -c 'echo date_before_glow: \$(/sbin/date); SHLVL=1 COLUMNS=180 LINES=49 /sbin/glow README.md; echo date_after_glow: \$(/sbin/date)' > temp; /sbin/cat temp; echo date_after_script: \$(/sbin/date)" > temp2

And then I'm running it

sudo strace ./sh

What I get

trace log ```plaintext execve("./sh", ["./sh"], 0x7ffcd70817c0 /* 17 vars */) = 0 brk(NULL) = 0x5ae528cfd000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=194599, ...}) = 0 mmap(NULL, 194599, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a28493000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/libreadline.so.8", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=343064, ...}) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x758a28491000 mmap(NULL, 350904, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x758a2843b000 mmap(0x758a28450000, 188416, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15000) = 0x758a28450000 mmap(0x758a2847e000, 36864, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x43000) = 0x758a2847e000 mmap(0x758a28487000, 36864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4b000) = 0x758a28487000 mmap(0x758a28490000, 2744, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x758a28490000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340_\2\0\0\0\0\0"..., 832) = 832 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784 fstat(3, {st_mode=S_IFREG|0755, st_size=2014520, ...}) = 0 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784 mmap(NULL, 2034616, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x758a2824a000 mmap(0x758a2826e000, 1511424, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x24000) = 0x758a2826e000 mmap(0x758a283df000, 319488, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x195000) = 0x758a283df000 mmap(0x758a2842d000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e3000) = 0x758a2842d000 mmap(0x758a28433000, 31672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x758a28433000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/libncursesw.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=453896, ...}) = 0 mmap(NULL, 453856, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x758a281db000 mmap(0x758a281e8000, 290816, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xd000) = 0x758a281e8000 mmap(0x758a2822f000, 90112, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x54000) = 0x758a2822f000 mmap(0x758a28245000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6a000) = 0x758a28245000 close(3) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x758a281d9000 arch_prctl(ARCH_SET_FS, 0x758a281d9b80) = 0 set_tid_address(0x758a281d9e50) = 338133 set_robust_list(0x758a281d9e60, 24) = 0 rseq(0x758a281da4a0, 0x20, 0, 0x53053053) = 0 mprotect(0x758a2842d000, 16384, PROT_READ) = 0 mprotect(0x758a28245000, 16384, PROT_READ) = 0 mprotect(0x758a28487000, 12288, PROT_READ) = 0 mprotect(0x5ae513c4d000, 12288, PROT_READ) = 0 mprotect(0x758a284fd000, 8192, PROT_READ) = 0 prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0 munmap(0x758a28493000, 194599) = 0 openat(AT_FDCWD, "/dev/tty", O_RDWR|O_NONBLOCK) = 3 close(3) = 0 getrandom("\x9f\x6e\x12\xb3\x3b\xe2\x4e\x66", 8, GRND_NONBLOCK) = 8 brk(NULL) = 0x5ae528cfd000 brk(0x5ae528d1e000) = 0x5ae528d1e000 openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=3055776, ...}) = 0 mmap(NULL, 3055776, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a27e00000 close(3) = 0 openat(AT_FDCWD, "/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=2998, ...}) = 0 read(3, "# Locale name alias data base.\n#"..., 4096) = 2998 read(3, "", 4096) = 0 close(3) = 0 openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_IDENTIFICATION", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_IDENTIFICATION", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=258, ...}) = 0 mmap(NULL, 258, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a284c2000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/gconv/gconv-modules.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=27010, ...}) = 0 mmap(NULL, 27010, PROT_READ, MAP_SHARED, 3, 0) = 0x758a284bb000 close(3) = 0 futex(0x758a2843272c, FUTEX_WAKE_PRIVATE, 2147483647) = 0 openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_MEASUREMENT", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_MEASUREMENT", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=23, ...}) = 0 mmap(NULL, 23, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a284ba000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_TELEPHONE", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_TELEPHONE", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=47, ...}) = 0 mmap(NULL, 47, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a284b9000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_ADDRESS", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_ADDRESS", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=127, ...}) = 0 mmap(NULL, 127, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a284b8000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_NAME", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_NAME", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=62, ...}) = 0 mmap(NULL, 62, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a284b7000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_PAPER", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_PAPER", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=34, ...}) = 0 mmap(NULL, 34, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a284b6000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_MESSAGES", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFDIR|0755, st_size=30, ...}) = 0 close(3) = 0 openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_MESSAGES/SYS_LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=48, ...}) = 0 mmap(NULL, 48, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a284b5000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_MONETARY", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_MONETARY", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=270, ...}) = 0 mmap(NULL, 270, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a284b4000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_COLLATE", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_COLLATE", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=1406, ...}) = 0 mmap(NULL, 1406, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a284b3000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_TIME", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_TIME", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=3360, ...}) = 0 mmap(NULL, 3360, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a284b2000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_NUMERIC", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_NUMERIC", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=50, ...}) = 0 mmap(NULL, 50, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a284b1000 close(3) = 0 openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_CTYPE", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/locale/C.utf8/LC_CTYPE", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=360460, ...}) = 0 mmap(NULL, 360460, PROT_READ, MAP_PRIVATE, 3, 0) = 0x758a28180000 close(3) = 0 getuid() = 0 getgid() = 0 geteuid() = 0 getegid() = 0 rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 rt_sigaction(SIGCHLD, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 rt_sigaction(SIGCHLD, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x758a282871d0}, 8) = 0 rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, 8) = 0 rt_sigaction(SIGQUIT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 rt_sigaction(SIGQUIT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, 8) = 0 rt_sigaction(SIGTSTP, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 rt_sigaction(SIGTSTP, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, 8) = 0 rt_sigaction(SIGTTIN, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 rt_sigaction(SIGTTIN, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, 8) = 0 rt_sigaction(SIGTTOU, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 rt_sigaction(SIGTTOU, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, 8) = 0 rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 rt_sigaction(SIGQUIT, {sa_handler=SIG_IGN, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, 8) = 0 uname({sysname="Linux", nodename="archhost", ...}) = 0 getcwd("/tmp/tmp.zGlsz5be5c", 4096) = 20 getpid() = 338133 getppid() = 338130 getpid() = 338133 getppid() = 338130 getpid() = 338133 getppid() = 338130 getpgrp() = 338130 ioctl(2, TIOCGPGRP, [338130]) = 0 rt_sigaction(SIGCHLD, {sa_handler=0x5ae513b93a60, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x758a282871d0}, 8) = 0 prlimit64(0, RLIMIT_NPROC, NULL, {rlim_cur=54025, rlim_max=54025}) = 0 rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 openat(AT_FDCWD, "./sh", O_RDONLY) = 3 newfstatat(AT_FDCWD, "./sh", {st_mode=S_IFREG|0755, st_size=289, ...}, 0) = 0 ioctl(3, TCGETS, 0x7ffd50ed22c0) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = 0 read(3, "#!/bin/bash\n\n\n/bin/bash -c \"echo"..., 80) = 80 lseek(3, 0, SEEK_SET) = 0 prlimit64(0, RLIMIT_NOFILE, NULL, {rlim_cur=1024, rlim_max=512*1024}) = 0 fcntl(255, F_GETFD) = -1 EBADF (Bad file descriptor) dup2(3, 255) = 255 close(3) = 0 fcntl(255, F_SETFD, FD_CLOEXEC) = 0 fcntl(255, F_GETFL) = 0x8000 (flags O_RDONLY|O_LARGEFILE) fstat(255, {st_mode=S_IFREG|0755, st_size=289, ...}) = 0 lseek(255, 0, SEEK_CUR) = 0 read(255, "#!/bin/bash\n\n\n/bin/bash -c \"echo"..., 289) = 289 rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 rt_sigprocmask(SIG_BLOCK, [INT TERM CHLD], [], 8) = 0 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x758a281d9e50) = 338134 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGINT, {sa_handler=0x5ae513b961e0, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, 8) = 0 wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 338134 rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=0x5ae513b961e0, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, 8) = 0 ioctl(2, TIOCGWINSZ, {ws_row=78, ws_col=288, ws_xpixel=3456, ws_ypixel=2106}) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=338134, si_uid=0, si_status=0, si_utime=0, si_stime=0} --- wait4(-1, 0x7ffd50ed1210, WNOHANG, NULL) = -1 ECHILD (No child processes) rt_sigreturn({mask=[]}) = 0 read(255, "", 289) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 exit_group(0) = ? +++ exited with 0 +++ ```

The most important part is where and how it hangs:

rt_sigaction(SIGINT, {sa_handler=0x5ae513b961e0, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x758a282871d0}, 8) = 0
wait4(-1,

it hangs in this state with uncompleted line for 15 seconds. Here's the most interesting part. remember the trash line? Its three equal parts (]11;?\) are being printed with equal noticeable time intervals one by one.

That's how stuff appears in a temp file:

  1. immediately after start file becomes:
    date_before_glow: Mon Nov 11 05:19:21 MSK 2024
    ]10;?\
  2. exactly after 5 second hanging file becomes:
    date_before_glow: Mon Nov 11 05:25:08 MSK 2024
    ]10;?\]11;?\
  3. exactly after another 5 second hanging file becomes:
    date_before_glow: Mon Nov 11 05:25:08 MSK 2024
    ]10;?\]11;?\]11;?\
  4. exactly after YET ANOTHER 5 second hanging file becomes:
    
    date_before_glow: Mon Nov 11 05:28:21 MSK 2024
    ]10;?\]11;?\]11;?\
       header                                                                         
                                                                                      
      1. something                                                                    
      2. bold                                                                         

date_after_glow: Mon Nov 11 05:28:36 MSK 2024



Based on that, I conclude that it hangs each time AFTER it printed `]10;?\`. I don't know what's the f\*\*k is going on. May be some kind of awaiting before output buffer filled, and flushing it when timeout expires? But why the f\*\*k doesn't this shit appear in other case we run glow? πŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈ

Also interesting observation:
I'm 99.99% sure that `glow` uses something similar with `gh-cli` under the hood to beatify Markdown. I went ahead and run the same test, but with a different command: `gh repo view`.
Guess how many `]11;?\` in it's output?

- 1

Guess how long it executes? `06:30:36 -- 06:30:42` (6 sec)

- 5 (seconds) * 1 (`]11;?\`) + 1 (second -- overhead to make a call to github API)

I tried to decode this ANSI code using this [cheatsheet](https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797) and this [character inspector](https://apps.timwhitlock.info/unicode/inspect?s=%1B%5D11%3B%3F%1B%5C%1B%5B6n).
In the end this seems like a command, to make some kind of cursor shift and then to request cursor position (`ESC[6n`).

And probably this artifitial TTY takes too long to respond and is being cutoff on timeout? πŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈ

Also I have a hypothesis about what's up with all the trash symbols being triggered by printing `]11;?\` -- I have no idea how ANSI works, but maybe that's the response with cursor position that was asked by `ESC[6n`? πŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈπŸ€·πŸ»β€β™€οΈ

# Ideas?
nikelborm commented 3 days ago

Until the fix is made somewhere, you can just use bat Prints more-less acceptable colored .md file.

bat --style=plain --color=always "$file"
nikelborm commented 3 days ago

If only there was a way to find and disable this cursor shift with following cursor position request... πŸ˜‰πŸ˜‰

Would be nice if somebody from the team will help identify libraries or pieces of code playing the role here and how to disable them, so we will have a quite nice workaround πŸ˜‰πŸ˜‰

Here are common libraries between GitHub CLI and Glow. I assume the stuff we're looking is inside one of them:

(click to expand) - https://github.com/alecthomas/chroma - https://github.com/aymanbagabas/go-osc52 - https://github.com/aymerick/douceur - https://github.com/charmbracelet/glamour - https://github.com/charmbracelet/lipgloss - https://github.com/dlclark/regexp2 - https://github.com/fsnotify/fsnotify - https://github.com/gorilla/css - https://github.com/hashicorp/hcl - https://github.com/inconshreveable/mousetrap - https://github.com/lucasb-eyer/go-colorful - https://github.com/magiconair/properties - https://github.com/mattn/go-isatty - https://github.com/mattn/go-runewidth - https://github.com/microcosm-cc/bluemonday - https://github.com/mitchellh/go-homedir - https://github.com/mitchellh/mapstructure - https://github.com/muesli/reflow - https://github.com/muesli/termenv - https://github.com/pelletier/go-toml - https://github.com/rivo/uniseg - https://github.com/spf13/afero - https://github.com/spf13/cast - https://github.com/spf13/cobra - https://github.com/spf13/pflag - https://github.com/spf13/viper - https://github.com/subosito/gotenv - https://github.com/yuin/goldmark - https://github.com/yuin/goldmark-emoji - https://golang.org/x/exp - https://golang.org/x/net - https://golang.org/x/sync - https://golang.org/x/sys - https://golang.org/x/term - https://golang.org/x/text - https://gopkg.in/ini.v1 - https://gopkg.in/yaml.v3