patch 9.1.1355: The pum_redraw() function is too complex

Problem:  The pum_redraw function is too complex and difficult to
          maintain with nested loops and mixed responsibilities handling
          both RTL and LTR text rendering.
Solution: Extracted core rendering logic into dedicated helper functions
          (pum_display_rtl_text, pum_display_ltr_text, pum_draw_scrollbar,
          pum_process_item) while preserving the original behavior. This
          improves code readability and maintainability (glepnir).

closes: #17204

Signed-off-by: glepnir <glephunter@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/popupmenu.c b/src/popupmenu.c
index 38ab24b..15d899c 100644
--- a/src/popupmenu.c
+++ b/src/popupmenu.c
@@ -574,6 +574,301 @@
     return user_attr[type] > 0 ? hl_combine_attr(attr, user_attr[type]) : attr;
 }
 
+#ifdef FEAT_RIGHTLEFT
+/*
+ * Display RTL text with proper attributes in the popup menu.
+ * Returns the adjusted column position after drawing.
+ */
+    static int
+pum_display_rtl_text(
+	int	row,
+	int     col,
+	char_u  *st,
+	int     attr,
+	int     *attrs,
+	int     width,
+	int     width_limit,
+	int     totwidth,
+	int     next_isempty)
+{
+    char_u  *rt;
+    int     cells;
+    int     over_cell = 0;
+    int     truncated = FALSE;
+    int     pad = next_isempty ? 0 : 2;
+    int     remaining;
+    int	    truncrl = curwin->w_fill_chars.truncrl != NUL
+					? curwin->w_fill_chars.truncrl : '<';
+
+    if (st == NULL)
+	return col;
+
+    rt = reverse_text(st);
+    if (rt == NULL)
+    {
+	VIM_CLEAR(st);
+	return col;
+    }
+
+    char_u *rt_start = rt;
+    cells = mb_string2cells(rt, -1);
+    truncated = width_limit == p_pmw && width_limit - totwidth < cells + pad;
+
+    // only draw the text that fits
+    if (cells > width_limit)
+    {
+	do
+	{
+	    cells -= has_mbyte ? (*mb_ptr2cells)(rt) : 1;
+	    MB_PTR_ADV(rt);
+	} while (cells > width_limit);
+
+	if (cells < width_limit)
+	{
+	    // Most left character requires 2-cells
+	    // but only 1 cell is available on screen.
+	    // Put a '<' on the left of the pum item.
+	    *(--rt) = '<';
+	    cells++;
+	}
+    }
+
+    if (truncated)
+    {
+	char_u  *orig_rt = rt;
+	int     size = 0;
+
+	remaining = width_limit - totwidth - 1;
+	cells = mb_string2cells(rt, -1);
+	if (cells > remaining)
+	{
+	    while (cells > remaining)
+	    {
+		MB_PTR_ADV(orig_rt);
+		cells -= has_mbyte ? (*mb_ptr2cells)(orig_rt) : 1;
+	    }
+	}
+	size = (int)STRLEN(orig_rt);
+	if (cells < remaining)
+	    over_cell = remaining - cells;
+
+	cells = mb_string2cells(orig_rt, size);
+	width = cells + over_cell + 1;
+	rt = orig_rt;
+
+	screen_putchar(truncrl, row, col - width + 1, attr);
+
+	if (over_cell > 0)
+	    screen_fill(row, row + 1, col - width + 2,
+		    col - width + 2 + over_cell, ' ', ' ', attr);
+    }
+
+    if (attrs == NULL)
+	screen_puts_len(rt, (int)STRLEN(rt), row, col - cells + 1, attr);
+    else
+	pum_screen_puts_with_attrs(row, col - cells + 1, cells, rt,
+		(int)STRLEN(rt), attrs);
+
+    vim_free(rt_start);
+    VIM_CLEAR(st);
+    return col - width;
+}
+#endif
+
+/*
+ * Display LTR text with proper attributes in the popup menu.
+ * Returns the adjusted column position after drawing.
+ */
+    static int
+pum_display_ltr_text(
+	int     row,
+	int     col,
+	char_u  *st,
+	int     attr,
+	int     *attrs,
+	int     width,        // width already calculated in outer loop
+	int     width_limit,
+	int     totwidth,
+	int     next_isempty)
+{
+    int     size;
+    int     cells;
+    char_u  *st_end = NULL;
+    int     over_cell = 0;
+    int     pad = next_isempty ? 0 : 2;
+    int     truncated;
+    int     remaining;
+    int	    trunc = curwin->w_fill_chars.trunc != NUL
+					    ? curwin->w_fill_chars.trunc : '>';
+
+    if (st == NULL)
+	return col;
+
+    size = (int)STRLEN(st);
+    cells = (*mb_string2cells)(st, size);
+    truncated = width_limit == p_pmw && width_limit - totwidth < cells + pad;
+
+    // only draw the text that fits
+    while (size > 0 && col + cells > width_limit + pum_col)
+    {
+	--size;
+	if (has_mbyte)
+	{
+	    size -= (*mb_head_off)(st, st + size);
+	    cells -= (*mb_ptr2cells)(st + size);
+	}
+	else
+	    --cells;
+    }
+
+    // truncated
+    if (truncated)
+    {
+	remaining = width_limit - totwidth - 1;
+	if (cells > remaining)
+	{
+	    st_end = st + size;
+	    while (st_end > st && cells > remaining)
+	    {
+		MB_PTR_BACK(st, st_end);
+		cells -= has_mbyte ? (*mb_ptr2cells)(st_end) : 1;
+	    }
+	    size = st_end - st;
+	}
+
+	if (cells < remaining)
+	    over_cell = remaining - cells;
+	cells = mb_string2cells(st, size);
+	width = cells + over_cell + 1;
+    }
+
+    if (attrs == NULL)
+	screen_puts_len(st, size, row, col, attr);
+    else
+	pum_screen_puts_with_attrs(row, col, cells, st, size, attrs);
+
+    if (truncated)
+    {
+	if (over_cell > 0)
+	    screen_fill(row, row + 1, col + cells,
+		    col + cells + over_cell, ' ', ' ', attr);
+
+	screen_putchar(trunc, row, col + cells + over_cell, attr);
+    }
+
+    VIM_CLEAR(st);
+    return col + width;
+}
+
+
+/*
+ * Process and display a single popup menu item (text/kind/extra).
+ * Returns the new column position after drawing.
+ */
+    static int
+pum_process_item(
+	int     row,
+	int     col,
+	int     idx,
+	int     j,         // Current position in order array
+	int     *order,    // Order array
+	hlf_T   hlf,
+	int     attr,
+	int     *totwidth_ptr,
+	int     next_isempty)
+{
+    int     item_type = order[j];
+    char_u  *s = NULL;
+    char_u  *p = pum_get_item(idx, item_type);
+    int     width = 0;  // item width
+    int     w;		// char width
+
+    for ( ; ; MB_PTR_ADV(p))
+    {
+        if (s == NULL)
+            s = p;
+        w = ptr2cells(p);
+        if (*p != NUL && *p != TAB && *totwidth_ptr + w <= pum_width)
+        {
+            width += w;
+            continue;
+        }
+
+        // Display the text that fits or comes before a Tab.
+        // First convert it to printable characters.
+        char_u  *st;
+        int     *attrs = NULL;
+        int     saved = *p;
+
+        if (saved != NUL)
+            *p = NUL;
+        st = transstr(s);
+        if (saved != NUL)
+            *p = saved;
+
+        if (item_type == CPT_ABBR)
+            attrs = pum_compute_text_attrs(st, hlf,
+                      pum_array[idx].pum_user_abbr_hlattr);
+#ifdef FEAT_RIGHTLEFT
+        if (pum_rl)
+            col = pum_display_rtl_text(row, col, st, attr, attrs,
+                    width, pum_width, *totwidth_ptr, next_isempty);
+        else
+#endif
+            col = pum_display_ltr_text(row, col, st, attr, attrs,
+                    width, pum_width, *totwidth_ptr, next_isempty);
+
+        if (attrs != NULL)
+            VIM_CLEAR(attrs);
+
+        if (*p != TAB)
+            break;
+
+        // Display two spaces for a Tab.
+#ifdef FEAT_RIGHTLEFT
+        if (pum_rl)
+        {
+            screen_puts_len((char_u *)"  ", 2, row, col - 1, attr);
+            col -= 2;
+        }
+        else
+#endif
+        {
+            screen_puts_len((char_u *)"  ", 2, row, col, attr);
+            col += 2;
+        }
+        *totwidth_ptr += 2;
+        s = NULL;  // start text at next char
+        width = 0;
+    }
+
+    return col;
+}
+
+/*
+ * Draw the scrollbar for the popup menu.
+ */
+    static void
+pum_draw_scrollbar(
+	int	row,
+	int	i,
+	int	thumb_pos,
+	int	thumb_height)
+{
+    if (pum_scrollbar <= 0)
+        return;
+
+    int attr = (i >= thumb_pos && i < thumb_pos + thumb_height) ?
+			highlight_attr[HLF_PST] : highlight_attr[HLF_PSB];
+
+#ifdef FEAT_RIGHTLEFT
+    if (pum_rl)
+	screen_putchar(' ', row, pum_col - pum_width, attr);
+    else
+#endif
+	screen_putchar(' ', row, pum_col + pum_width, attr);
+}
+
 /*
  * Redraw the popup menu, using "pum_first" and "pum_selected".
  */
@@ -582,16 +877,13 @@
 {
     int		row = pum_row;
     int		col;
-    int		attr_scroll = highlight_attr[HLF_PSB];
-    int		attr_thumb = highlight_attr[HLF_PST];
     hlf_T	*hlfs; // array used for highlights
     hlf_T	hlf;
     int		attr;
     int		i, j;
     int		idx;
-    char_u	*s;
     char_u	*p = NULL;
-    int		totwidth, width, w;  // total-width item-width char-width
+    int		totwidth;
     int		thumb_pos = 0;
     int		thumb_height = 1;
     int		item_type;
@@ -604,15 +896,6 @@
     int		last_isabbr = FALSE;
     int		orig_attr = -1;
     int		scroll_range = pum_size - pum_height;
-    int		remaining = 0;
-    int		fcs_trunc;
-
-#ifdef  FEAT_RIGHTLEFT
-    if (pum_rl)
-	fcs_trunc = curwin->w_fill_chars.truncrl;
-    else
-#endif
-	fcs_trunc = curwin->w_fill_chars.trunc;
 
     hlf_T	hlfsNorm[3];
     hlf_T	hlfsSel[3];
@@ -688,221 +971,15 @@
 	    orig_attr = attr;
 	    if (item_type < 2)  // try combine attr with user custom
 		attr = pum_user_attr_combine(idx, item_type, attr);
-	    width = 0;
-	    s = NULL;
 	    p = pum_get_item(idx, item_type);
 
 	    if (j + 1 < 3)
 		next_isempty = pum_get_item(idx, order[j + 1]) == NULL;
 
 	    if (p != NULL)
-		for ( ; ; MB_PTR_ADV(p))
-		{
-		    if (s == NULL)
-			s = p;
-		    w = ptr2cells(p);
-		    if (*p != NUL && *p != TAB && totwidth + w <= pum_width)
-		    {
-			width += w;
-			continue;
-		    }
-
-		    // Display the text that fits or comes before a Tab.
-		    // First convert it to printable characters.
-		    char_u	*st;
-		    int		*attrs = NULL;
-		    int		saved = *p;
-
-		    if (saved != NUL)
-			*p = NUL;
-		    st = transstr(s);
-		    if (saved != NUL)
-			*p = saved;
-
-		    if (item_type == CPT_ABBR)
-			attrs = pum_compute_text_attrs(st, hlf,
-					  pum_array[idx].pum_user_abbr_hlattr);
-#ifdef FEAT_RIGHTLEFT
-		    if (pum_rl)
-		    {
-			if (st != NULL)
-			{
-			    char_u	*rt = reverse_text(st);
-
-			    if (rt != NULL)
-			    {
-				char_u	    *rt_start = rt;
-				int	    cells;
-				int	    over_cell = 0;
-				int	    truncated = FALSE;
-				int	    pad = next_isempty ? 0 : 2;
-
-				cells = mb_string2cells(rt , -1);
-				truncated = pum_width == p_pmw
-					    && pum_width - totwidth < cells + pad;
-
-				// only draw the text that fits
-				if (cells > pum_width)
-				{
-				    do
-				    {
-					cells -= has_mbyte
-						     ? (*mb_ptr2cells)(rt) : 1;
-					MB_PTR_ADV(rt);
-				    } while (cells > pum_width);
-
-				    if (cells < pum_width)
-				    {
-					// Most left character requires 2-cells
-					// but only 1 cell is available on
-					// screen.  Put a '<' on the left of
-					// the pum item.
-					*(--rt) = '<';
-					cells++;
-				    }
-				}
-
-				if (truncated)
-				{
-				    char_u  *orig_rt = rt;
-				    int	    size = 0;
-
-				    remaining = pum_width - totwidth - 1;
-				    cells = mb_string2cells(rt, -1);
-				    if (cells > remaining)
-				    {
-					while (cells > remaining)
-					{
-					    MB_PTR_ADV(orig_rt);
-					    cells -= has_mbyte ? (*mb_ptr2cells)(orig_rt) : 1;
-					}
-				    }
-				    size = (int)STRLEN(orig_rt);
-				    if (cells < remaining)
-					over_cell =  remaining - cells;
-
-				    cells = mb_string2cells(orig_rt, size);
-				    width = cells + over_cell + 1;
-				    rt = orig_rt;
-
-				    if (fcs_trunc != NUL)
-					screen_putchar(fcs_trunc, row, col - width + 1, attr);
-				    else
-					screen_putchar('<', row, col - width + 1, attr);
-
-				    if (over_cell > 0)
-					screen_fill(row, row + 1, col - width + 2,
-						    col - width + 2 + over_cell, ' ', ' ', attr);
-				}
-
-				if (attrs == NULL)
-				    screen_puts_len(rt, (int)STRLEN(rt), row,
-							col - cells + 1, attr);
-				else
-				    pum_screen_puts_with_attrs(row,
-						    col - cells + 1, cells, rt,
-						    (int)STRLEN(rt), attrs);
-
-				vim_free(rt_start);
-			    }
-			    vim_free(st);
-			}
-			col -= width;
-		    }
-		    else
-#endif
-		    {
-			if (st != NULL)
-			{
-			    int		size = (int)STRLEN(st);
-			    int		cells = (*mb_string2cells)(st, size);
-			    char_u	*st_end = NULL;
-			    int		over_cell = 0;
-			    int		pad = next_isempty ? 0 : 2;
-			    int		truncated = pum_width == p_pmw
-					    && pum_width - totwidth < cells + pad;
-
-			    // only draw the text that fits
-			    while (size > 0
-					  && col + cells > pum_width + pum_col)
-			    {
-				--size;
-				if (has_mbyte)
-				{
-				    size -= (*mb_head_off)(st, st + size);
-				    cells -= (*mb_ptr2cells)(st + size);
-				}
-				else
-				    --cells;
-			    }
-
-			    // truncated
-			    if (truncated)
-			    {
-				remaining = pum_width - totwidth - 1;
-				if (cells > remaining)
-				{
-				    st_end = st + size;
-				    while (st_end > st && cells > remaining)
-				    {
-					MB_PTR_BACK(st, st_end);
-					cells -= has_mbyte ? (*mb_ptr2cells)(st_end) : 1;
-				    }
-				    size = st_end - st;
-				}
-
-				if (cells < remaining)
-				    over_cell =  remaining - cells;
-				cells = mb_string2cells(st, size);
-				width = cells + over_cell + 1;
-			    }
-
-			    if (attrs == NULL)
-				screen_puts_len(st, size, row, col, attr);
-			    else
-				pum_screen_puts_with_attrs(row, col, cells,
-							      st, size, attrs);
-			    if (truncated)
-			    {
-				if (over_cell > 0)
-				    screen_fill(row, row + 1, col + cells,
-					    col + cells + over_cell, ' ', ' ', attr);
-				if (fcs_trunc != NUL)
-				    screen_putchar(fcs_trunc, row,
-					    col + cells + over_cell, attr);
-				else
-				    screen_putchar('>', row,
-					    col + cells + over_cell, attr);
-			    }
-
-			    vim_free(st);
-			}
-			col += width;
-		    }
-
-		    if (attrs != NULL)
-			VIM_CLEAR(attrs);
-
-		    if (*p != TAB)
-			break;
-
-		    // Display two spaces for a Tab.
-#ifdef FEAT_RIGHTLEFT
-		    if (pum_rl)
-		    {
-			screen_puts_len((char_u *)"  ", 2, row, col - 1, attr);
-			col -= 2;
-		    }
-		    else
-#endif
-		    {
-			screen_puts_len((char_u *)"  ", 2, row, col, attr);
-			col += 2;
-		    }
-		    totwidth += 2;
-		    s = NULL;  // start text at next char
-		    width = 0;
-		}
+		// Process and display the item
+		col = pum_process_item(row, col, idx, j, order, hlf, attr,
+						    &totwidth, next_isempty);
 
 	    if (j > 0)
 		n = items_width_array[order[1]] + (last_isabbr ? 0 : 1);
@@ -940,19 +1017,7 @@
 #endif
 	    screen_fill(row, row + 1, col, pum_col + pum_width, ' ', ' ',
 								orig_attr);
-	if (pum_scrollbar > 0)
-	{
-#ifdef FEAT_RIGHTLEFT
-	    if (pum_rl)
-		screen_putchar(' ', row, pum_col - pum_width,
-			i >= thumb_pos && i < thumb_pos + thumb_height
-						  ? attr_thumb : attr_scroll);
-	    else
-#endif
-		screen_putchar(' ', row, pum_col + pum_width,
-			i >= thumb_pos && i < thumb_pos + thumb_height
-						  ? attr_thumb : attr_scroll);
-	}
+	pum_draw_scrollbar(row, i, thumb_pos, thumb_height);
 
 	++row;
     }