mirror of
https://github.com/tmux/tmux.git
synced 2026-10-07 18:24:22 +00:00
Cleanup
This commit is contained in:
@@ -237,17 +237,12 @@ kitty_redraw_keep(struct tty *tty, struct kitty_image_cache *entry,
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark Kitty placements intersecting a redraw area as stale, without
|
||||
* deleting them yet - see kitty_redraw_finish(). Deleting immediately here
|
||||
* would, for an image whose only placements are in this area, leave it
|
||||
* with none at all until the replacement is placed - some Kitty
|
||||
* implementations free an image's pixel data once it has no placements
|
||||
* left, which would leave the replacement referencing already-discarded
|
||||
* data and render as nothing.
|
||||
*
|
||||
* A placement only partly inside the area is deleted as a whole, but the
|
||||
* redraw only replaces the part inside, so the parts outside are placed
|
||||
* again first - otherwise they would vanish from cells nothing redraws.
|
||||
* Mark placements intersecting a redraw area as stale rather than deleting
|
||||
* them yet (see kitty_redraw_finish()) - some implementations free an
|
||||
* image's pixel data once its last placement is gone, so an image only
|
||||
* placed here would go blank before its replacement lands. A placement only
|
||||
* partly inside the area is deleted as a whole, so place its outside parts
|
||||
* again first or they would vanish from cells nothing redraws.
|
||||
*/
|
||||
void
|
||||
kitty_redraw_start(struct tty *tty, u_int x, u_int y, u_int width,
|
||||
|
||||
@@ -1605,19 +1605,12 @@ sixel_flush_output(struct tty *tty)
|
||||
}
|
||||
|
||||
/*
|
||||
* Queue an image rectangle for SIXEL output.
|
||||
*
|
||||
* The redraw loop hands images to the backend one grid line at a time, so a
|
||||
* placement 24 rows tall arrives as 24 separate one-row rectangles. Writing
|
||||
* each of them straight out means 24 scale and encode passes and 24 DCS
|
||||
* sequences carrying 24 copies of the palette, for what the application sent
|
||||
* as one image - and terminals have to allocate and composite each one. The
|
||||
* rows of a placement arrive in order, so hold a run of vertically adjacent
|
||||
* rows back and write them as one SIXEL when the run ends.
|
||||
*
|
||||
* Anything that is not a continuation of the run flushes it first, and
|
||||
* image_draw_flush at the end of the redraw flushes whatever is left, so no
|
||||
* other terminal output can be reordered across a pending run.
|
||||
* Queue an image rectangle for SIXEL output. The redraw loop hands images to
|
||||
* the backend one grid line at a time, so hold back a run of vertically
|
||||
* adjacent rows and write them as one SIXEL instead of one per row.
|
||||
* Anything that is not a continuation flushes the run first, and
|
||||
* image_draw_flush() flushes what's left at the end of the redraw, so no
|
||||
* other terminal output is reordered across a pending run.
|
||||
*/
|
||||
void
|
||||
sixel_draw_rect(struct tty *tty, const struct image_rect *rectangle,
|
||||
|
||||
56
image.c
56
image.c
@@ -1563,26 +1563,10 @@ image_extend_row(struct image_line *line, struct image_placement *placement,
|
||||
}
|
||||
|
||||
/*
|
||||
* Extend existing image placements to reveal more of their original width
|
||||
* after a pane has grown wider.
|
||||
*
|
||||
* image_write() (below) only creates spans for as much of an image as fit
|
||||
* in the pane at the time it was placed - the rest of the image's pixels
|
||||
* are still retained (struct image is immutable and kept for as long as
|
||||
* any placement references it), but nothing ever revisits that clipping
|
||||
* decision, so a pane that was too narrow when an image was displayed
|
||||
* stays clipped forever, even after growing wide enough to fit the rest.
|
||||
* Unlike height, which recovers via ordinary scrollback (image_write()
|
||||
* scrolls rather than clips when a placement is taller than the pane),
|
||||
* there is no equivalent "scroll right" - this is the only way the extra
|
||||
* width is ever recovered.
|
||||
*
|
||||
* For every grid row with image spans, this finds each distinct placement
|
||||
* referenced there, works out how far its spans already reach (source_x +
|
||||
* width) and its origin column (a span's x - source_x, which is the same
|
||||
* for every span of the same placement), and adds spans for any newly
|
||||
* revealed columns up to whichever is smaller: the image's own full width
|
||||
* or the new pane width.
|
||||
* Extend existing image placements to reveal more of an image after a pane
|
||||
* grows wider. image_write() only creates spans for as much as fit at the
|
||||
* time - unlike height, there is no "scroll right" to recover a clipped
|
||||
* width later, so this is the only way the rest is ever shown.
|
||||
*/
|
||||
void
|
||||
image_grid_resize_width(struct grid *gd, u_int new_sx)
|
||||
@@ -1673,16 +1657,7 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg,
|
||||
} else if (screen_size_y(s) - cy <= sy) {
|
||||
lines = sy - (screen_size_y(s) - cy) + 1;
|
||||
|
||||
/*
|
||||
* screen_write_scrollup() clamps its own lines argument to
|
||||
* at most one scroll region height per call, so a single
|
||||
* call cannot push more than that much history however
|
||||
* large lines is - call it repeatedly to actually push the
|
||||
* full amount, so the history-row loop below (which is
|
||||
* itself bounded against gd->hsize, so it is safe even if
|
||||
* this loop's assumptions are ever wrong) has real rows to
|
||||
* use instead of just whatever fit in one call.
|
||||
*/
|
||||
/* screen_write_scrollup() clamps lines per call, so loop it. */
|
||||
region_height = s->rlower - s->rupper + 1;
|
||||
if (region_height == 0)
|
||||
region_height = 1;
|
||||
@@ -1706,23 +1681,10 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg,
|
||||
placement = image_placement_create(gd, im, input, app_image_id,
|
||||
app_placement_id, z);
|
||||
/*
|
||||
* The rows above origin_y were only ever needed to make the image
|
||||
* fit on screen at all - screen_write_scrollup() already pushed
|
||||
* blank rows into history to make room, so those absolute grid rows
|
||||
* exist and are otherwise unused. Give them spans too, instead of
|
||||
* silently discarding that part of the image: unlike width, which
|
||||
* has no "scroll right" to recover a permanent clip, height already
|
||||
* has ordinary scrollback - it would be wasted if these rows were
|
||||
* left with nothing to show when scrolled back to.
|
||||
*
|
||||
* screen_write_scrollup() clamps its own lines argument to at most
|
||||
* one screen height per call, however many were requested, so it
|
||||
* may not have created a history row for every one of the origin_y
|
||||
* rows this is trying to place - cap to however many actually
|
||||
* exist (gd->hsize) to avoid reading before the start of the grid,
|
||||
* and place the rows closest to the visible area (the highest
|
||||
* source rows below origin_y) in whatever history space there is,
|
||||
* since those are the ones the scrolled-off area would show first.
|
||||
* Give the origin_y rows scrolled into history spans too, so they
|
||||
* show when scrolled back to instead of appearing blank - cap to
|
||||
* gd->hsize since screen_write_scrollup() may not have created a
|
||||
* history row for every one of them.
|
||||
*/
|
||||
hist_origin_y = origin_y;
|
||||
if (hist_origin_y > gd->hsize)
|
||||
|
||||
136
screen-redraw.c
136
screen-redraw.c
@@ -1633,6 +1633,7 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
|
||||
struct redraw_line *line;
|
||||
struct redraw_spans *spans;
|
||||
struct redraw_span *span;
|
||||
enum redraw_image_phase phase;
|
||||
u_int cy;
|
||||
int y, top, bottom;
|
||||
|
||||
@@ -1670,8 +1671,8 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
|
||||
}
|
||||
#endif
|
||||
|
||||
for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE;
|
||||
phase <= REDRAW_IMAGES_AFTER; phase++) {
|
||||
for (phase = REDRAW_IMAGES_BEFORE; phase <= REDRAW_IMAGES_AFTER;
|
||||
phase++) {
|
||||
for (y = top; y < bottom; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
@@ -1714,10 +1715,11 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
|
||||
struct redraw_line *line;
|
||||
struct redraw_spans *spans;
|
||||
struct redraw_span *span;
|
||||
enum redraw_image_phase phase;
|
||||
u_int y, cy, type;
|
||||
|
||||
for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE;
|
||||
phase <= REDRAW_IMAGES_AFTER; phase++) {
|
||||
for (phase = REDRAW_IMAGES_BEFORE; phase <= REDRAW_IMAGES_AFTER;
|
||||
phase++) {
|
||||
for (y = 0; y < scene->sy; y++) {
|
||||
line = &scene->lines[y];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
@@ -1725,45 +1727,46 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
|
||||
else
|
||||
cy = y;
|
||||
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
|
||||
if (phase != REDRAW_TEXT && type != REDRAW_SPAN_PANE)
|
||||
if (phase != REDRAW_TEXT &&
|
||||
type != REDRAW_SPAN_PANE)
|
||||
continue;
|
||||
if (!REDRAW_IS_ALL(flags)) {
|
||||
switch (type) {
|
||||
case REDRAW_SPAN_PANE:
|
||||
if (~flags & REDRAW_PANE)
|
||||
if (!REDRAW_IS_ALL(flags)) {
|
||||
switch (type) {
|
||||
case REDRAW_SPAN_PANE:
|
||||
if (~flags & REDRAW_PANE)
|
||||
continue;
|
||||
break;
|
||||
case REDRAW_SPAN_OUTSIDE:
|
||||
if (~flags & REDRAW_OUTSIDE)
|
||||
continue;
|
||||
break;
|
||||
case REDRAW_SPAN_EMPTY:
|
||||
if (~flags & REDRAW_EMPTY)
|
||||
continue;
|
||||
break;
|
||||
case REDRAW_SPAN_BORDER:
|
||||
if (~flags & REDRAW_PANE_BORDER)
|
||||
continue;
|
||||
break;
|
||||
case REDRAW_SPAN_STATUS:
|
||||
if (~flags & REDRAW_PANE_STATUS)
|
||||
continue;
|
||||
break;
|
||||
case REDRAW_SPAN_SCROLLBAR:
|
||||
if (~flags & REDRAW_PANE_SCROLLBAR)
|
||||
continue;
|
||||
break;
|
||||
case REDRAW_SPAN_MENU:
|
||||
if (~flags & REDRAW_MENU)
|
||||
continue;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
break;
|
||||
case REDRAW_SPAN_OUTSIDE:
|
||||
if (~flags & REDRAW_OUTSIDE)
|
||||
continue;
|
||||
break;
|
||||
case REDRAW_SPAN_EMPTY:
|
||||
if (~flags & REDRAW_EMPTY)
|
||||
continue;
|
||||
break;
|
||||
case REDRAW_SPAN_BORDER:
|
||||
if (~flags & REDRAW_PANE_BORDER)
|
||||
continue;
|
||||
break;
|
||||
case REDRAW_SPAN_STATUS:
|
||||
if (~flags & REDRAW_PANE_STATUS)
|
||||
continue;
|
||||
break;
|
||||
case REDRAW_SPAN_SCROLLBAR:
|
||||
if (~flags & REDRAW_PANE_SCROLLBAR)
|
||||
continue;
|
||||
break;
|
||||
case REDRAW_SPAN_MENU:
|
||||
if (~flags & REDRAW_MENU)
|
||||
continue;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
spans = &line->spans[type];
|
||||
TAILQ_FOREACH(span, spans, entry)
|
||||
redraw_draw_span(dctx, span, cy, phase);
|
||||
spans = &line->spans[type];
|
||||
TAILQ_FOREACH(span, spans, entry)
|
||||
redraw_draw_span(dctx, span, cy, phase);
|
||||
}
|
||||
}
|
||||
#ifdef ENABLE_IMAGES
|
||||
@@ -2210,6 +2213,7 @@ redraw_draw_damage_rectangle(struct redraw_draw_ctx *dctx, u_int x, u_int y,
|
||||
struct redraw_line *line;
|
||||
struct redraw_spans *spans;
|
||||
struct redraw_span *span;
|
||||
enum redraw_image_phase phase;
|
||||
u_int cy, yy, type;
|
||||
|
||||
if (x >= scene->sx || y >= scene->sy)
|
||||
@@ -2223,18 +2227,12 @@ redraw_draw_damage_rectangle(struct redraw_draw_ctx *dctx, u_int x, u_int y,
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/*
|
||||
* Remove any stale Kitty placements this redraw is about to replace,
|
||||
* the same as redraw_draw_pane_lines() does for a full pane redraw -
|
||||
* unlike a plain overwrite of SIXEL pixels, a Kitty placement is a
|
||||
* discrete object that persists until explicitly deleted, so without
|
||||
* this a scroll-triggered redraw (this function, not the full-pane
|
||||
* path) leaves every previous placement behind, all still visible
|
||||
* and now overlapping the newly placed ones.
|
||||
*
|
||||
* Every span type is included, not just panes: when a floating pane
|
||||
* moves, cells where its image was placed can now belong to a border
|
||||
* (or anything else), and a Kitty placement left there would stay
|
||||
* drawn over it.
|
||||
* Remove stale Kitty placements this redraw is about to replace, the
|
||||
* same as redraw_draw_pane_lines() does for a full pane redraw - a
|
||||
* placement persists until explicitly deleted, unlike a plain SIXEL
|
||||
* overwrite. Every span type is included, not just panes, since a
|
||||
* moved floating pane can leave a placement over cells that now
|
||||
* belong to something else.
|
||||
*/
|
||||
for (yy = y; yy < y + sy; yy++) {
|
||||
line = &scene->lines[yy];
|
||||
@@ -2258,8 +2256,8 @@ redraw_draw_damage_rectangle(struct redraw_draw_ctx *dctx, u_int x, u_int y,
|
||||
}
|
||||
#endif
|
||||
|
||||
for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE;
|
||||
phase <= REDRAW_IMAGES_AFTER; phase++) {
|
||||
for (phase = REDRAW_IMAGES_BEFORE; phase <= REDRAW_IMAGES_AFTER;
|
||||
phase++) {
|
||||
for (yy = y; yy < y + sy; yy++) {
|
||||
line = &scene->lines[yy];
|
||||
if (dctx->flags & REDRAW_STATUS_TOP)
|
||||
@@ -2267,28 +2265,26 @@ redraw_draw_damage_rectangle(struct redraw_draw_ctx *dctx, u_int x, u_int y,
|
||||
else
|
||||
cy = yy;
|
||||
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
|
||||
if (phase != REDRAW_TEXT && type != REDRAW_SPAN_PANE)
|
||||
if (phase != REDRAW_TEXT &&
|
||||
type != REDRAW_SPAN_PANE)
|
||||
continue;
|
||||
if (type == REDRAW_SPAN_STATUS)
|
||||
continue;
|
||||
spans = &line->spans[type];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->x >= x + sx)
|
||||
continue;
|
||||
if (span->x + span->width <= x)
|
||||
continue;
|
||||
if (type == REDRAW_SPAN_STATUS) {
|
||||
redraw_damage_refresh_status(dctx,
|
||||
span->data.st.wp);
|
||||
}
|
||||
redraw_draw_span(dctx, span, cy, phase);
|
||||
if (phase == REDRAW_TEXT && type == REDRAW_SPAN_PANE) {
|
||||
redraw_damage_draw_pane_prompt(dctx,
|
||||
span, cy);
|
||||
spans = &line->spans[type];
|
||||
TAILQ_FOREACH(span, spans, entry) {
|
||||
if (span->x >= x + sx)
|
||||
continue;
|
||||
if (span->x + span->width <= x)
|
||||
continue;
|
||||
redraw_draw_span(dctx, span, cy, phase);
|
||||
if (phase == REDRAW_TEXT &&
|
||||
type == REDRAW_SPAN_PANE) {
|
||||
redraw_damage_draw_pane_prompt(
|
||||
dctx, span, cy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_draw_flush(&scene->c->tty);
|
||||
#endif
|
||||
|
||||
6
tmux.h
6
tmux.h
@@ -1815,12 +1815,12 @@ struct tty_term {
|
||||
#define TERM_SIXEL 0x40
|
||||
#define TERM_INVALIDMS 0x80
|
||||
#define TERM_KITTY 0x100
|
||||
#define TERM_NOREPLACE 0x200
|
||||
#ifdef ENABLE_IMAGES
|
||||
#define TERM_IMAGE_QUADRANTS 0x200
|
||||
#define TERM_IMAGE_SEXTANTS 0x400
|
||||
#define TERM_IMAGE_QUADRANTS 0x400
|
||||
#define TERM_IMAGE_SEXTANTS 0x800
|
||||
#define TERM_IMAGESCROLL 0x1000
|
||||
#endif
|
||||
#define TERM_NOREPLACE 0x800
|
||||
int flags;
|
||||
|
||||
LIST_ENTRY(tty_term) entry;
|
||||
|
||||
20
tty.c
20
tty.c
@@ -585,20 +585,12 @@ tty_update_features(struct tty *tty)
|
||||
tty_puts(tty, tty_term_string(tty->term, TTYC_ENESC));
|
||||
|
||||
/*
|
||||
* Features might have changed since the first draw during attach. For
|
||||
* example, this happens when DA responses are received.
|
||||
*
|
||||
* Only redraw when something actually did change. This function is
|
||||
* called for every DA, secondary DA and extended DA answer, and from
|
||||
* the start timer when none arrive - answers which usually just
|
||||
* confirm what is already known, either from a previous answer or
|
||||
* from terminal-features in the configuration. The redraw is not
|
||||
* free: it repaints the pane from tmux's own grid, which discards
|
||||
* anything the pane put on the terminal that tmux does not model,
|
||||
* notably an image written through DCS passthrough. That makes an
|
||||
* unnecessary redraw here visible to the user as an image that
|
||||
* appears and then vanishes a moment later, once per client, with no
|
||||
* way for the application to detect it and redraw.
|
||||
* Features might have changed since the first draw during attach, for
|
||||
* example on a DA response - but only redraw if something actually
|
||||
* did change, since this is called for every DA answer (which are
|
||||
* usually confirming what is already known) and a redraw repaints
|
||||
* from tmux's own grid, discarding anything the pane put on the
|
||||
* terminal that tmux does not model (e.g. a DCS-passthrough image).
|
||||
*/
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
@@ -5340,21 +5340,10 @@ window_copy_write_one(struct window_mode_entry *wme,
|
||||
#ifdef ENABLE_IMAGES
|
||||
/*
|
||||
* Write image-covered cells directly into the grid,
|
||||
* skipping both window_copy_update_style() (so a
|
||||
* selection, current-line or search-mark highlight
|
||||
* never sweeps visibly over the image before it is
|
||||
* recomposited separately - see the
|
||||
* image_redraw_area() call below) and
|
||||
* screen_write_cell(), whose built-in
|
||||
* screen_write_image_damage() call (screen-write.c)
|
||||
* fires on every write regardless of whether
|
||||
* anything actually changed, which would needlessly
|
||||
* re-damage - and so retransmit - the image on every
|
||||
* redraw. The cell must still land in the grid as
|
||||
* normal: a non-graphical client's ASCII fallback for
|
||||
* the image is an ordinary character here, not
|
||||
* something recomposited separately, and depends on
|
||||
* this write the same as any other cell.
|
||||
* skipping window_copy_update_style() (a highlight
|
||||
* must not sweep over the image) and
|
||||
* screen_write_cell() (its image-damage call would
|
||||
* re-damage the image on every redraw for nothing).
|
||||
*/
|
||||
if (image_grid_check_area(gd, fx, fy, gc.data.width,
|
||||
1)) {
|
||||
@@ -5615,18 +5604,11 @@ window_copy_write_line(struct window_mode_entry *wme,
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/*
|
||||
* Copy the backing line's image layers separately from its text
|
||||
* cells. This is not implied by the text cells just written above:
|
||||
* those go via the pane's normal (frequently fast, direct-write)
|
||||
* path, which knows nothing about image content.
|
||||
*
|
||||
* Only report it as needing a redraw when the view has actually
|
||||
* moved (data->image_refresh, set once per window_copy_redraw_lines
|
||||
* call) - text writes never touch image-covered cells (see
|
||||
* window_copy_write_one), so if the view is unmoved this row's
|
||||
* images are already exactly as they should be and redrawing them
|
||||
* anyway just flashes the image on every unrelated redraw (e.g.
|
||||
* every step of a selection drag) for no visible benefit.
|
||||
* Copy the backing line's image layers separately: the text write
|
||||
* above knows nothing about image content. Only redraw them when the
|
||||
* view has actually moved (data->image_refresh) - otherwise they are
|
||||
* already correct and redrawing would just flash them on every
|
||||
* unrelated redraw.
|
||||
*/
|
||||
image_grid_free_line(s->grid,
|
||||
&s->grid->linedata[s->grid->hsize + py]);
|
||||
@@ -5689,17 +5671,10 @@ window_copy_redraw_selection(struct window_mode_entry *wme, u_int old_y)
|
||||
#ifdef ENABLE_IMAGES
|
||||
/*
|
||||
* Only rows whose underlying history position has moved since the last
|
||||
* call need their images recomposited - most redraws are just a selection
|
||||
* or cursor-line style change with the view otherwise unmoved, and that
|
||||
* never touches image-covered cells (see window_copy_write_one()), so
|
||||
* redrawing images for it is needless: on a fast drag it is visible as the
|
||||
* image briefly flashing on every step even though nothing about it
|
||||
* actually changed.
|
||||
*
|
||||
* Every place that calls window_copy_write_line()/window_copy_write_lines()
|
||||
* must call this first - it is not implied by them, since some (the
|
||||
* initial full-screen draw on entering copy mode, window_copy_scroll_up(),
|
||||
* window_copy_scroll_down()) write directly rather than going through
|
||||
* call need their images recomposited, to avoid flashing them on every
|
||||
* unrelated redraw. Every caller of window_copy_write_line()/
|
||||
* window_copy_write_lines() must call this first - it is not implied by
|
||||
* them, since some write directly rather than via
|
||||
* window_copy_redraw_lines().
|
||||
*/
|
||||
static void
|
||||
@@ -5715,14 +5690,9 @@ window_copy_update_image_refresh(struct window_copy_mode_data *data)
|
||||
|
||||
/*
|
||||
* Whether any part of the currently visible backing range carries image
|
||||
* data. A terminal's line insert/delete only shifts character cells - the
|
||||
* pixels of a sixel or Kitty image already on screen stay exactly where
|
||||
* they were sent, so a scroll that uses that fast path (see
|
||||
* window_copy_scroll_up()/window_copy_scroll_down()) leaves stale or
|
||||
* missing image content for every row except the single new one it
|
||||
* explicitly rewrites. When this returns true, callers should fall back to
|
||||
* a full window_copy_redraw_screen() instead, so every visible row's image
|
||||
* is recomposited at its correct new position.
|
||||
* data. A scrolled insert/delete-line fast path only shifts character
|
||||
* cells, leaving image content stale, so callers should fall back to a
|
||||
* full window_copy_redraw_screen() when this returns true.
|
||||
*/
|
||||
static int
|
||||
window_copy_visible_has_images(struct window_copy_mode_data *data)
|
||||
|
||||
Reference in New Issue
Block a user