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

如何在终端中捕获用户滚动操作?以Kilo编辑器开发场景为例

解决自定义编辑器无法捕获终端滚轮滚动的问题

你遇到的问题核心是:终端滚轮滚动发送的是多字节转义序列,而当前代码每次只读取单个字符,导致转义序列没被完整解析,终端直接触发了默认的滚动行为。

问题根源

终端滚轮滚动时,会发送类似\x1b[A、\x1b[B(普通上下滚动)或\x1b[5~、\x1b[6~(PageUp/PageDown级滚动)的多字节序列。你的代码每次只读1个字符,当读到第一个字符\x1b(ESC)后,后续字符没被读取,终端就直接把剩下的序列解析成滚动指令了,程序根本没机会捕获。

解决方法

修改代码,让它能识别并解析多字节转义序列,同时调整终端读取超时设置,确保能完整读取序列:

#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

struct termios orig_termios;

void disable_raw_mode(void) {
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}

void enable_raw_mode(void) {
    tcgetattr(STDIN_FILENO, &orig_termios);
    atexit(disable_raw_mode);

    struct termios raw = orig_termios;
    raw.c_iflag &= ~(BRKINT | INPCK | PARMRK | INLCR | IGNCR | ISTRIP | ICRNL | IXON);
    raw.c_oflag &= ~(OPOST);
    raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
    raw.c_cflag &= ~(CSIZE | PARENB);
    raw.c_cflag |= (CS8);
    // 设置超时和最小读取数,方便捕获多字节转义序列
    raw.c_cc[VMIN] = 0;
    raw.c_cc[VTIME] = 1; // 100ms超时,确保能读到后续字符

    tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}

// 专门处理按键读取,解析转义序列
int read_key(void) {
    char c;
    int nread;

    while ((nread = read(STDIN_FILENO, &c, 1)) == 1) {
        // 不是ESC,直接返回
        if (c != '\x1b') {
            return c;
        }

        // 读到ESC,继续读后续字符解析转义序列
        char seq[3];
        if ((nread = read(STDIN_FILENO, &seq[0], 1)) != 1) return '\x1b';
        if ((nread = read(STDIN_FILENO, &seq[1], 1)) != 1) return '\x1b';

        // 匹配常见的滚轮/方向键转义序列
        if (seq[0] == '[') {
            switch (seq[1]) {
                case 'A': return '^'; // 滚轮向上/上箭头
                case 'B': return 'v'; // 滚轮向下/下箭头
                case '5': return 'P'; // PageUp/滚轮向上(部分终端)
                case '6': return 'N'; // PageDown/滚轮向下(部分终端)
            }
        }

        // 未知转义序列,返回ESC
        return '\x1b';
    }

    return -1;
}

int main(void) {
    enable_raw_mode();
  
    int c;
    while ((c = read_key()) != -1) {
        if (iscntrl(c)) {
            printf("%d\n", c);
            // 识别滚轮事件
            if (c == '^') printf("滚轮向上触发\n");
            if (c == 'v') printf("滚轮向下触发\n");
            if (c == 'P') printf("PageUp/滚轮向上触发\n");
            if (c == 'N') printf("PageDown/滚轮向下触发\n");
        } else {
            printf("%d ('%c')\n", c, c);
        }
    }

    return 0;
}

关键修改说明

  1. 终端超时设置:在enable_raw_mode里设置VMIN=0和VTIME=1,让read函数在100ms内读取所有可用字符,不会因为等待单个字符而错过转义序列的后续字节。
  2. 转义序列解析:新增read_key函数,当读到ESC字符时,继续读取后续字符,匹配常见的滚轮转义序列,转换成自定义的标识(比如^和v),这样主程序就能捕获到滚轮事件,进而自己控制光标或内容滚动,而不是让终端代劳。
  3. 适配不同终端:不同终端的滚轮转义序列可能有差异,你可以运行修改后的程序,滚动滚轮查看输出的字符编码,再对应调整read_key里的匹配规则。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 08:37:21