mirror of
https://github.com/tmux/tmux.git
synced 2026-10-12 05:34:24 +00:00
Match Kitty image cursor movement to terminal behaviour and add regression coverage.
This commit is contained in:
14
image.c
14
image.c
@@ -1817,7 +1817,7 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg,
|
|||||||
u_int cx = s->cx, cy = s->cy;
|
u_int cx = s->cx, cy = s->cy;
|
||||||
u_int x, y, i, run, sx, sy, lines, origin_y = 0;
|
u_int x, y, i, run, sx, sy, lines, origin_y = 0;
|
||||||
u_int hist_origin_y, region_height, remaining, chunk;
|
u_int hist_origin_y, region_height, remaining, chunk;
|
||||||
u_int cursor_x = 0;
|
u_int cursor_x = 0, cursor_rows;
|
||||||
|
|
||||||
sx = im->sx;
|
sx = im->sx;
|
||||||
if (sx > screen_size_x(s) - cx)
|
if (sx > screen_size_x(s) - cx)
|
||||||
@@ -1826,12 +1826,17 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg,
|
|||||||
if (sx == 0)
|
if (sx == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
/* Kitty advances to the last image row; SIXEL advances past it. */
|
||||||
|
cursor_rows = sy;
|
||||||
|
if (input == IMAGE_INPUT_KITTY)
|
||||||
|
cursor_rows--;
|
||||||
|
|
||||||
/* Scroll images that move the cursor, or clip stationary placements. */
|
/* Scroll images that move the cursor, or clip stationary placements. */
|
||||||
if (im->flags & IMAGE_FLAG_NO_CURSOR) {
|
if (im->flags & IMAGE_FLAG_NO_CURSOR) {
|
||||||
if (sy > screen_size_y(s) - cy)
|
if (sy > screen_size_y(s) - cy)
|
||||||
sy = screen_size_y(s) - cy;
|
sy = screen_size_y(s) - cy;
|
||||||
} else if (screen_size_y(s) - cy <= sy) {
|
} else if (screen_size_y(s) - cy <= cursor_rows) {
|
||||||
lines = sy - (screen_size_y(s) - cy) + 1;
|
lines = cursor_rows - (screen_size_y(s) - cy) + 1;
|
||||||
|
|
||||||
/* screen_write_scrollup() clamps lines per call, so loop it. */
|
/* screen_write_scrollup() clamps lines per call, so loop it. */
|
||||||
region_height = s->rlower - s->rupper + 1;
|
region_height = s->rlower - s->rupper + 1;
|
||||||
@@ -1852,6 +1857,7 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg,
|
|||||||
screen_write_cursormove(ctx, -1, cy - lines, 0);
|
screen_write_cursormove(ctx, -1, cy - lines, 0);
|
||||||
cy = s->cy;
|
cy = s->cy;
|
||||||
sy -= origin_y;
|
sy -= origin_y;
|
||||||
|
cursor_rows -= origin_y;
|
||||||
}
|
}
|
||||||
|
|
||||||
placement = image_placement_create(gd, im, input, app_image_id,
|
placement = image_placement_create(gd, im, input, app_image_id,
|
||||||
@@ -1899,7 +1905,7 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg,
|
|||||||
if (!(im->flags & IMAGE_FLAG_NO_CURSOR)) {
|
if (!(im->flags & IMAGE_FLAG_NO_CURSOR)) {
|
||||||
if (input == IMAGE_INPUT_KITTY)
|
if (input == IMAGE_INPUT_KITTY)
|
||||||
cursor_x = cx + sx;
|
cursor_x = cx + sx;
|
||||||
screen_write_cursormove(ctx, cursor_x, cy + sy, 0);
|
screen_write_cursormove(ctx, cursor_x, cy + cursor_rows, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ with open(output, 'w') as f:
|
|||||||
time.sleep(30)
|
time.sleep(30)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def check(name, command, cursor, expected='', text=None, render=None, resize=None):
|
def check(name, command, cursor, expected='', text=None, render=None, resize=None,
|
||||||
|
history=None):
|
||||||
output = directory / name
|
output = directory / name
|
||||||
command_file = directory / (name + '.input')
|
command_file = directory / (name + '.input')
|
||||||
command_file.write_text(command, encoding='utf-8')
|
command_file.write_text(command, encoding='utf-8')
|
||||||
@@ -75,6 +76,9 @@ def check(name, command, cursor, expected='', text=None, render=None, resize=Non
|
|||||||
run('resize-window', '-t', pane, '-x', str(resize[0]), '-y', str(resize[1]))
|
run('resize-window', '-t', pane, '-x', str(resize[0]), '-y', str(resize[1]))
|
||||||
actual = run('display-message', '-pt', pane, '#{cursor_x},#{cursor_y}')
|
actual = run('display-message', '-pt', pane, '#{cursor_x},#{cursor_y}')
|
||||||
assert actual == cursor, (name, 'cursor', actual, cursor)
|
assert actual == cursor, (name, 'cursor', actual, cursor)
|
||||||
|
if history is not None:
|
||||||
|
actual = int(run('display-message', '-pt', pane, '#{history_size}'))
|
||||||
|
assert actual == history, (name, 'history', actual, history)
|
||||||
if text is not None:
|
if text is not None:
|
||||||
actual = run('capture-pane', '-pt', pane, '-S0', '-E0')
|
actual = run('capture-pane', '-pt', pane, '-S0', '-E0')
|
||||||
assert actual == text, (name, 'text', actual, text)
|
assert actual == text, (name, 'text', actual, text)
|
||||||
@@ -115,20 +119,31 @@ with tempfile.TemporaryDirectory(prefix='tmux-kitty-protocol-') as tmp:
|
|||||||
origin = '\033[3;6H' # Column 6, row 3 (zero-based 5,2).
|
origin = '\033[3;6H' # Column 6, row 3 (zero-based 5,2).
|
||||||
pixel = '/wAA/w=='
|
pixel = '/wAA/w=='
|
||||||
placement = 'a=T,q=2,f=32,s=1,v=1,c=3,r=2'
|
placement = 'a=T,q=2,f=32,s=1,v=1,c=3,r=2'
|
||||||
check('placement', origin + graphics(placement, pixel), '8,4')
|
# Kitty leaves the cursor beside the image's last row.
|
||||||
|
check('placement', origin + graphics(placement, pixel), '8,3')
|
||||||
|
one_row = placement.replace('r=2', 'r=1')
|
||||||
|
check('one-row', origin + graphics(one_row, pixel), '8,2')
|
||||||
check('no-cursor', origin + graphics(placement + ',C=1', pixel), '5,2')
|
check('no-cursor', origin + graphics(placement + ',C=1', pixel), '5,2')
|
||||||
check('clipped', '\033[3;39H' + graphics(placement, pixel), '39,4')
|
check('explicit-cursor', origin + graphics(placement + ',C=0', pixel), '8,3')
|
||||||
check('scrolled', '\033[12;6H' + graphics(placement, pixel), '8,11')
|
check('clipped', '\033[3;39H' + graphics(placement, pixel), '39,3')
|
||||||
|
check('fits-bottom', '\033[11;6H' + graphics(placement, pixel), '8,11',
|
||||||
|
history=0)
|
||||||
|
check('last-row', '\033[12;6H' + graphics(one_row, pixel), '8,11', history=0)
|
||||||
|
check('scrolled', '\033[12;6H' + graphics(placement, pixel), '8,11', history=1)
|
||||||
|
check('no-cursor-bottom', '\033[12;6H' +
|
||||||
|
graphics(placement + ',C=1', pixel), '5,11', history=0)
|
||||||
|
check('taller-than-screen', '\033[1;6H' +
|
||||||
|
graphics(placement.replace('r=2', 'r=15'), pixel), '8,11', history=3)
|
||||||
check('transmit', origin + graphics('a=t,q=2,f=32,s=1,v=1,i=7', pixel), '5,2')
|
check('transmit', origin + graphics('a=t,q=2,f=32,s=1,v=1,i=7', pixel), '5,2')
|
||||||
check('virtual', origin + graphics(placement + ',U=1,i=7', pixel), '5,2')
|
check('virtual', origin + graphics(placement + ',U=1,i=7', pixel), '5,2')
|
||||||
check('put', origin + graphics('a=t,q=2,f=32,s=1,v=1,i=7', pixel) +
|
check('put', origin + graphics('a=t,q=2,f=32,s=1,v=1,i=7', pixel) +
|
||||||
graphics('a=p,q=2,i=7,c=3,r=2'), '8,4')
|
graphics('a=p,q=2,i=7,c=3,r=2'), '8,3')
|
||||||
check('columns-only', origin + graphics('a=T,q=2,f=32,s=1,v=1,c=2', pixel),
|
check('columns-only', origin + graphics('a=T,q=2,f=32,s=1,v=1,c=2', pixel),
|
||||||
'7,3')
|
'7,2')
|
||||||
check('rows-only', origin + graphics('a=T,q=2,f=32,s=1,v=1,r=2', pixel),
|
check('rows-only', origin + graphics('a=T,q=2,f=32,s=1,v=1,r=2', pixel),
|
||||||
'9,4')
|
'9,3')
|
||||||
check('chunks', graphics(placement + ',m=1', '/wAA') + origin +
|
check('chunks', graphics(placement + ',m=1', '/wAA') + origin +
|
||||||
graphics('m=0', '/w=='), '8,4')
|
graphics('m=0', '/w=='), '8,3')
|
||||||
check('chunks-no-cursor', graphics(placement + ',C=1,m=1', '/wAA') + origin +
|
check('chunks-no-cursor', graphics(placement + ',C=1,m=1', '/wAA') + origin +
|
||||||
graphics('m=0', '/w=='), '5,2')
|
graphics('m=0', '/w=='), '5,2')
|
||||||
check('sixel', origin + '\033Pq"1;1;1;1#0;2;100;0;0#0@\033\\', '0,3')
|
check('sixel', origin + '\033Pq"1;1;1;1#0;2;100;0;0#0@\033\\', '0,3')
|
||||||
@@ -137,7 +152,7 @@ with tempfile.TemporaryDirectory(prefix='tmux-kitty-protocol-') as tmp:
|
|||||||
check('kitty-delete-preserves-sixel', sixel + graphics('a=d,d=A,q=2'),
|
check('kitty-delete-preserves-sixel', sixel + graphics('a=d,d=A,q=2'),
|
||||||
'0,1', render='^' + re.escape(sixel_render) + '$')
|
'0,1', render='^' + re.escape(sixel_render) + '$')
|
||||||
check('put-reply', origin + graphics('a=t,q=2,f=32,s=1,v=1,i=7', pixel) +
|
check('put-reply', origin + graphics('a=t,q=2,f=32,s=1,v=1,i=7', pixel) +
|
||||||
graphics('a=p,i=7,p=9,c=3,r=2'), '8,4', graphics('i=7,p=9', 'OK'))
|
graphics('a=p,i=7,p=9,c=3,r=2'), '8,3', graphics('i=7,p=9', 'OK'))
|
||||||
check('missing-reply', origin + graphics('a=p,i=7,p=9'), '5,2',
|
check('missing-reply', origin + graphics('a=p,i=7,p=9'), '5,2',
|
||||||
graphics('i=7,p=9', 'ENOENT'))
|
graphics('i=7,p=9', 'ENOENT'))
|
||||||
check('invalid-reply', origin + graphics('a=T,i=7,p=9,f=32,s=1,v=1', '!!!!'),
|
check('invalid-reply', origin + graphics('a=T,i=7,p=9,f=32,s=1,v=1', '!!!!'),
|
||||||
@@ -148,7 +163,7 @@ with tempfile.TemporaryDirectory(prefix='tmux-kitty-protocol-') as tmp:
|
|||||||
graphics('i=7', 'EINVAL'))
|
graphics('i=7', 'EINVAL'))
|
||||||
check('empty-decoded-chunk',
|
check('empty-decoded-chunk',
|
||||||
graphics(placement + ',m=1', ' ') + origin +
|
graphics(placement + ',m=1', ' ') + origin +
|
||||||
graphics('m=0', pixel), '8,4')
|
graphics('m=0', pixel), '8,3')
|
||||||
check('quiet-ok', origin + graphics('a=t,q=1,i=7,f=32,s=1,v=1', pixel), '5,2')
|
check('quiet-ok', origin + graphics('a=t,q=1,i=7,f=32,s=1,v=1', pixel), '5,2')
|
||||||
check('quiet-one-error', origin + graphics('a=t,q=1,i=7,f=32,s=1,v=1', '!!!!'),
|
check('quiet-one-error', origin + graphics('a=t,q=1,i=7,f=32,s=1,v=1', '!!!!'),
|
||||||
'5,2', graphics('i=7', 'EINVAL'))
|
'5,2', graphics('i=7', 'EINVAL'))
|
||||||
@@ -156,13 +171,13 @@ with tempfile.TemporaryDirectory(prefix='tmux-kitty-protocol-') as tmp:
|
|||||||
check('anonymous', origin + graphics('a=t,f=32,s=1,v=1', pixel), '5,2')
|
check('anonymous', origin + graphics('a=t,f=32,s=1,v=1', pixel), '5,2')
|
||||||
check('anonymous-error', origin + graphics('a=t,f=32,s=1,v=1', '!!!!'), '5,2')
|
check('anonymous-error', origin + graphics('a=t,f=32,s=1,v=1', '!!!!'), '5,2')
|
||||||
check('anonymous-placement', origin + graphics('a=T,p=9,f=32,s=1,v=1,c=3,r=2',
|
check('anonymous-placement', origin + graphics('a=T,p=9,f=32,s=1,v=1,c=3,r=2',
|
||||||
pixel), '8,4')
|
pixel), '8,3')
|
||||||
check('delete-no-reply', origin + graphics('a=t,q=2,i=7,f=32,s=1,v=1', pixel) +
|
check('delete-no-reply', origin + graphics('a=t,q=2,i=7,f=32,s=1,v=1', pixel) +
|
||||||
graphics('a=d,d=I,i=7'), '5,2')
|
graphics('a=d,d=I,i=7'), '5,2')
|
||||||
for format, raw in [(24, b'\xff\0\0'), (32, b'\xff\0\0\xff')]:
|
for format, raw in [(24, b'\xff\0\0'), (32, b'\xff\0\0\xff')]:
|
||||||
payload = base64.b64encode(zlib.compress(raw)).decode()
|
payload = base64.b64encode(zlib.compress(raw)).decode()
|
||||||
check('compressed-' + str(format), origin +
|
check('compressed-' + str(format), origin +
|
||||||
graphics('a=T,q=2,f=%d,s=1,v=1,c=3,r=2,o=z' % format, payload), '8,4')
|
graphics('a=T,q=2,f=%d,s=1,v=1,c=3,r=2,o=z' % format, payload), '8,3')
|
||||||
# Compressed PNG uploads need S to describe the complete PNG byte count.
|
# Compressed PNG uploads need S to describe the complete PNG byte count.
|
||||||
png = b'\x89PNG\r\n\x1a\n'
|
png = b'\x89PNG\r\n\x1a\n'
|
||||||
for kind, data in [
|
for kind, data in [
|
||||||
@@ -175,11 +190,11 @@ with tempfile.TemporaryDirectory(prefix='tmux-kitty-protocol-') as tmp:
|
|||||||
control = 'a=T,i=7,f=100,c=3,r=2,o=z'
|
control = 'a=T,i=7,f=100,c=3,r=2,o=z'
|
||||||
sized = control + ',S=' + str(len(png))
|
sized = control + ',S=' + str(len(png))
|
||||||
png_origin = '\033[1;6H'
|
png_origin = '\033[1;6H'
|
||||||
check('compressed-png', png_origin + graphics(sized, payload), '8,2',
|
check('compressed-png', png_origin + graphics(sized, payload), '8,1',
|
||||||
graphics('i=7', 'OK'), render=r'48;2;255;0;0m')
|
graphics('i=7', 'OK'), render=r'48;2;255;0;0m')
|
||||||
split = len(payload) // 8 * 4
|
split = len(payload) // 8 * 4
|
||||||
check('compressed-png-chunks', graphics(sized + ',m=1', payload[:split]) +
|
check('compressed-png-chunks', graphics(sized + ',m=1', payload[:split]) +
|
||||||
png_origin + graphics('m=0', payload[split:]), '8,2',
|
png_origin + graphics('m=0', payload[split:]), '8,1',
|
||||||
graphics('i=7', 'OK'), render=r'48;2;255;0;0m')
|
graphics('i=7', 'OK'), render=r'48;2;255;0;0m')
|
||||||
check('compressed-png-missing-size', origin + graphics(control, payload),
|
check('compressed-png-missing-size', origin + graphics(control, payload),
|
||||||
'5,2', graphics('i=7', 'EINVAL'))
|
'5,2', graphics('i=7', 'EINVAL'))
|
||||||
@@ -348,7 +363,7 @@ with tempfile.TemporaryDirectory(prefix='tmux-kitty-protocol-') as tmp:
|
|||||||
check('id-and-number', graphics('a=t,i=7,I=8,f=32,s=1,v=1', pixel),
|
check('id-and-number', graphics('a=t,i=7,I=8,f=32,s=1,v=1', pixel),
|
||||||
'0,0', graphics('i=7,I=8', 'EINVAL'))
|
'0,0', graphics('i=7,I=8', 'EINVAL'))
|
||||||
check('unknown-extension', graphics('a=T,q=2,f=32,s=1,v=1,c=3,r=2,k=9', pixel),
|
check('unknown-extension', graphics('a=T,q=2,f=32,s=1,v=1,c=3,r=2,k=9', pixel),
|
||||||
'3,2')
|
'3,1')
|
||||||
finally:
|
finally:
|
||||||
subprocess.run(tmux + ['kill-server'], stdout=subprocess.DEVNULL,
|
subprocess.run(tmux + ['kill-server'], stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL)
|
stderr=subprocess.DEVNULL)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ $TMUX new-window -d -n kitty-chunks "
|
|||||||
printf '\033_Gm=0\033\\'
|
printf '\033_Gm=0\033\\'
|
||||||
sleep 10"
|
sleep 10"
|
||||||
sleep 1
|
sleep 1
|
||||||
[ "$($TMUX display-message -pt:kitty-chunks '#{cursor_y}')" = 1 ] || exit 1
|
[ "$($TMUX display-message -pt:kitty-chunks '#{cursor_y}')" = 0 ] || exit 1
|
||||||
$TMUX kill-window -t:kitty-chunks || exit 1
|
$TMUX kill-window -t:kitty-chunks || exit 1
|
||||||
|
|
||||||
# Accept two padding characters too, with pixel data in the final chunk.
|
# Accept two padding characters too, with pixel data in the final chunk.
|
||||||
@@ -44,7 +44,7 @@ $TMUX new-window -d -n kitty-chunks "
|
|||||||
printf '\033_Gm=0;AAD/\033\\'
|
printf '\033_Gm=0;AAD/\033\\'
|
||||||
sleep 10"
|
sleep 10"
|
||||||
sleep 1
|
sleep 1
|
||||||
[ "$($TMUX display-message -pt:kitty-chunks '#{cursor_y}')" = 1 ] || exit 1
|
[ "$($TMUX display-message -pt:kitty-chunks '#{cursor_y}')" = 0 ] || exit 1
|
||||||
$TMUX kill-window -t:kitty-chunks || exit 1
|
$TMUX kill-window -t:kitty-chunks || exit 1
|
||||||
|
|
||||||
# Images scroll as grid cells, while capture output contains ordinary spaces.
|
# Images scroll as grid cells, while capture output contains ordinary spaces.
|
||||||
@@ -76,14 +76,14 @@ $TMUX new-window -d "
|
|||||||
printf '\033_Ga=d,d=I,q=2,i=9\033\\'
|
printf '\033_Ga=d,d=I,q=2,i=9\033\\'
|
||||||
sleep 10"
|
sleep 10"
|
||||||
sleep 1
|
sleep 1
|
||||||
[ "$($TMUX display-message -pt:2 '#{cursor_y}')" = 1 ] || exit 1
|
[ "$($TMUX display-message -pt:2 '#{cursor_y}')" = 0 ] || exit 1
|
||||||
|
|
||||||
# PNG Kitty input uses the shared image decoder and canonical cell sizing.
|
# PNG Kitty input uses the shared image decoder and canonical cell sizing.
|
||||||
$TMUX new-window -d "
|
$TMUX new-window -d "
|
||||||
printf '\033_Ga=T,q=2,f=100;iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGUExURf8AAP///0EdNBEAAAABYktHRAH/Ai3eAAAAB3RJTUUH6ggCDAECH324BwAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=\033\\'
|
printf '\033_Ga=T,q=2,f=100;iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGUExURf8AAP///0EdNBEAAAABYktHRAH/Ai3eAAAAB3RJTUUH6ggCDAECH324BwAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=\033\\'
|
||||||
sleep 10"
|
sleep 10"
|
||||||
sleep 1
|
sleep 1
|
||||||
[ "$($TMUX display-message -pt:3 '#{cursor_y}')" = 1 ] || exit 1
|
[ "$($TMUX display-message -pt:3 '#{cursor_y}')" = 0 ] || exit 1
|
||||||
|
|
||||||
# SIXEL input reaches the same grid layer and copy-mode paths.
|
# SIXEL input reaches the same grid layer and copy-mode paths.
|
||||||
$TMUX new-window -d "cat '$FIXTURE'; sleep 10"
|
$TMUX new-window -d "cat '$FIXTURE'; sleep 10"
|
||||||
@@ -180,8 +180,8 @@ $TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
|||||||
[ "$(sed -n 4p $TMP)" = " *" ] || exit 1
|
[ "$(sed -n 4p $TMP)" = " *" ] || exit 1
|
||||||
|
|
||||||
# With normal cursor movement, scrolling is calculated from the full image
|
# With normal cursor movement, scrolling is calculated from the full image
|
||||||
# height. Rows which scrolled above the pane are then cropped from the top, so
|
# height. Only two rows scroll, leaving the full image visible and the cursor
|
||||||
# the bottom three source rows remain visible and the cursor is on the last row.
|
# beside its last row.
|
||||||
$TMUX2 new-window -d "
|
$TMUX2 new-window -d "
|
||||||
printf '\033[3;1H'
|
printf '\033[3;1H'
|
||||||
printf '\033_Ga=T,q=2,f=32,s=1,v=8,c=1,r=4;AAAA/wAAAP9VVVX/VVVV/6qqqv+qqqr///////////8=\033\\'
|
printf '\033_Ga=T,q=2,f=32,s=1,v=8,c=1,r=4;AAAA/wAAAP9VVVX/VVVV/6qqqv+qqqr///////////8=\033\\'
|
||||||
@@ -189,10 +189,12 @@ $TMUX2 new-window -d "
|
|||||||
$TMUX2 select-window -t:2 || exit 1
|
$TMUX2 select-window -t:2 || exit 1
|
||||||
sleep 1
|
sleep 1
|
||||||
[ "$($TMUX2 display-message -p '#{cursor_y}')" = 3 ] || exit 1
|
[ "$($TMUX2 display-message -p '#{cursor_y}')" = 3 ] || exit 1
|
||||||
|
[ "$($TMUX2 display-message -p '#{history_size}')" = 2 ] || exit 1
|
||||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||||
[ "$(sed -n 1p $TMP)" = "-" ] || exit 1
|
[ "$(sed -n 1p $TMP)" = "" ] || exit 1
|
||||||
[ "$(sed -n 2p $TMP)" = "*" ] || exit 1
|
[ "$(sed -n 2p $TMP)" = "-" ] || exit 1
|
||||||
[ "$(sed -n 3p $TMP)" = "@" ] || exit 1
|
[ "$(sed -n 3p $TMP)" = "*" ] || exit 1
|
||||||
|
[ "$(sed -n 4p $TMP)" = "@" ] || exit 1
|
||||||
|
|
||||||
# Image rows remain cell-aligned when a narrower terminal causes text
|
# Image rows remain cell-aligned when a narrower terminal causes text
|
||||||
# reflow. The ten-column rows are clipped to five columns, not split into four
|
# reflow. The ten-column rows are clipped to five columns, not split into four
|
||||||
|
|||||||
Reference in New Issue
Block a user