You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在GNU Readline提示符中清除换行后的文本?

Problem with Custom 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.


Solution

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[0J after scrolling to the prompt start ensures all input lines are cleared without affecting text above the REPL.

内容的提问来源于stack exchange,提问作者Natrome tex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 12:44:06