如何在GNU Readline提示符中清除换行后的文本?
rl_redisplay Handling Line Wraps in C REPL I'm building a simple REPL in C with syntax highlighting, so I wrote a custom rl_redisplay function using ANSI escape codes for text highlighting. My initial implementation worked fine until I started scrolling through history commands that wrap across multiple lines due to terminal size limits—only the last line would get cleared, leaving leftover text at the top of the terminal.
Here's the initial code:
void redisplay(void) { fputs("\033[2K\r", stdout); char *hl = highlight_line(rl_line_buffer); const char *line = hl ? hl : rl_line_buffer; fputs(rl_display_prompt, stdout); fputs(line, stdout); fflush(stdout); if (hl) free(hl); }
I tried fixing this by calculating the number of lines to scroll up and clear, but this version frequently deletes lines above the input area (almost every time), causing terminal text loss:
void redisplay(void) { int rows, cols; rl_get_screen_size(&rows, &cols); // 计算当前显示内容占用的行数 int prompt_len = strlen(rl_display_prompt); int total_len = prompt_len + rl_end; int lines_used = (total_len / cols) + 1; // 移至第一行开头并清除所有内容 for (int i = 1; i < lines_used; i++) { printf("\033[A"); // 向上移动 } printf("\r\033[J"); // 移至行首并清除下方内容 // 绘制提示符和高亮内容 char *hl = highlight_line(rl_line_buffer); const char *line = hl ? hl : rl_line_buffer; fputs(rl_display_prompt, stdout); fputs(line, stdout); // 计算光标应处位置 int cursor_pos = prompt_len + rl_point; int cursor_row = cursor_pos / cols; int cursor_col = cursor_pos % cols; // 计算当前位置(刚打印内容的末尾) int current_pos = prompt_len + rl_end; int current_row = current_pos / cols; int current_col = current_pos % cols; // 将光标移至正确位置 int row_diff = current_row - cursor_row; if (row_diff > 0) { printf("\033[%dA", row_diff); // 向上移动 } // 移至正确列 printf("\r"); // 行首 if (cursor_col > 0) { printf("\033[%dC", cursor_col); // 向右移动 } fflush(stdout); if (hl) free(hl); }
I'm wondering if there's a way to reliably position the cursor at the start of the prompt (no matter how many lines the input wraps across), so I can just use \033[2K\r to clear and redraw the text properly.
I've run into this exact issue before—your core problem is that you're calculating line usage based on raw string lengths, but ANSI escape sequences (from your highlighting) don't take up any display width. This leads to either undercalculating lines (leaving leftover text) or overcalculating (accidentally deleting lines above your input).
Here's how to fix it properly:
1. Add a Function to Calculate Actual Display Width
First, write a helper function that ignores ANSI escape codes and returns the true number of columns the text will occupy on screen:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <readline/readline.h> #include <readline/history.h> // Calculate the visible width of a string (ignores ANSI escape sequences) int get_display_width(const char *str) { int width = 0; int in_escape = 0; while (*str != '\0') { if (*str == '\033') { // Entered an ANSI escape sequence in_escape = 1; } else if (in_escape) { // Escape sequences end with a letter (e.g., 'm' for color, 'C' for cursor move) if ((*str >= 'a' && *str <= 'z') || (*str >= 'A' && *str <= 'Z')) { in_escape = 0; } } else { // Count visible characters (adjust here if you need to support wide chars) width++; } str++; } return width; } // Fallback strndup if your system doesn't have it (common on non-GNU systems) char *strndup(const char *s, size_t n) { size_t len = strnlen(s, n); char *dup = malloc(len + 1); if (dup) { memcpy(dup, s, len); dup[len] = '\0'; } return dup; }
2. Rewrite the redisplay Function
Use the display width function to get accurate line counts and cursor positions:
void redisplay(void) { int rows, cols; rl_get_screen_size(&rows, &cols); // Get highlighted line (use original if highlighting fails) char *hl = highlight_line(rl_line_buffer); const char *line = hl ? hl : rl_line_buffer; // Calculate actual visible width of prompt + input int prompt_width = get_display_width(rl_display_prompt); int line_width = get_display_width(line); int total_width = prompt_width + line_width; // Calculate number of lines used (round up with (x + y -1)/y) int lines_used = (total_width + cols - 1) / cols; // Scroll up to the start of the prompt if (lines_used > 1) { printf("\033[%dA", lines_used - 1); } // Clear from cursor to end of screen (removes all input lines) printf("\r\033[0J"); // Redraw prompt and highlighted input fputs(rl_display_prompt, stdout); fputs(line, stdout); // Calculate correct cursor position (based on visible width) int cursor_input_width = get_display_width(strndup(rl_line_buffer, rl_point)); int cursor_total_width = prompt_width + cursor_input_width; int cursor_row = (cursor_total_width - 1) / cols; // 0-indexed row int cursor_col = (cursor_total_width - 1) % cols; // 0-indexed column // Calculate position at end of redrawn text int end_total_width = prompt_width + line_width; int end_row = (end_total_width - 1) / cols; // Move cursor from end of text to correct position if (end_row > cursor_row) { printf("\033[%dA", end_row - cursor_row); } // Move to correct column (ANSI uses 1-indexed columns) printf("\r\033[%dC", cursor_col + 1); fflush(stdout); // Clean up highlighted buffer if (hl) free(hl); }
Key Fixes Explained
- Accurate Line Counting: By ignoring ANSI escapes, we get the true number of lines the input occupies, so we don't scroll too far up (deleting unrelated lines) or not far enough (leaving leftovers).
- Proper Cursor Positioning: The cursor's position is calculated using visible width, so it stays aligned with the user's input even when highlighting adds escape codes.
- Clean Clear: Using
\033[0Jafter scrolling to the prompt start ensures all input lines are cleared without affecting text above the REPL.
内容的提问来源于stack exchange,提问作者Natrome tex

