Skip to content

fix: chinese input issue and optimize input handling#13

Merged
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
deepin-mozart:master
Jan 26, 2026
Merged

fix: chinese input issue and optimize input handling#13
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
deepin-mozart:master

Conversation

@deepin-mozart
Copy link
Contributor

support Chinese input in dtkaivalidator

Log:
Bug: https://pms.uniontech.com/bug-view-348743.html

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: deepin-mozart

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@deepin-ci-robot
Copy link

deepin pr auto review

这份代码修改主要实现了两个功能:一是通过自定义 getChineseInput 函数支持终端下的中文输入与回显,二是修改了 CMakeLists.txt 中的链接库名称。

以下是对这些修改的详细审查意见,涵盖语法逻辑、代码质量、性能和安全等方面:

1. 语法逻辑

  • UTF-8 处理逻辑
    • getChineseInput 中,处理退格键的逻辑是:先删除所有以 10xxxxxx 开头的后续字节,再删除首字节。这是符合 UTF-8 编码规则的(首字节以 11... 开头,后续字节以 10... 开头),逻辑正确。
  • 终端属性恢复
    • Ctrl+C (SIGINT) 分支中,代码调用了 tcsetattr 恢复终端设置后调用 exit(0)。这比直接崩溃要好,但存在异常安全问题(详见下文)。
    • 在正常循环结束后也恢复了终端设置,逻辑闭环。
  • CMakeLists.txt
    • Dtk${DTK_VERSION_MAJOR}::Core 改为 Dtk::Core。这通常意味着 CMake 配置中使用了自动别名机制,只要工程配置正确,语法上是没问题的。

2. 代码质量

  • 可移植性
    • 严重问题getChineseInput 函数包含了 <termios.h><unistd.h>,这是 POSIX 标准,仅适用于 Linux/Unix/macOS。在 Windows (MSVC/MinGW) 上无法编译。如果这是一个跨平台项目,必须使用预处理器指令(#ifdef _WIN32)进行平台隔离,或者使用像 ncurses / conio.h 这样的跨平台库来封装终端操作。
  • 异常安全
    • 问题:如果 input += ch 抛出异常(例如内存不足),或者 std::cout 抛出异常,程序将直接跳出 while 循环或函数,导致 tcsetattr 恢复终端设置的代码无法执行。终端将保持“非规范模式、无回显”的状态,用户在 shell 中将看不到输入,体验极差。
    • 改进建议:应使用 RAII (Resource Acquisition Is Initialization) 模式来管理终端属性。创建一个辅助类,在构造函数中保存并修改属性,在析构函数中自动恢复属性。
  • 终端宽度假设
    • 问题:代码中硬编码了中文字符占 2 个显示宽度:std::cout << "\b \b\b \b"。虽然这在大多数终端中是正确的,但并非所有终端都如此(例如某些等宽字体设置或特殊终端模拟器)。
    • 改进建议:可以使用 wcwidth() 函数(来自 <wchar.h>)来动态获取字符的显示宽度,而不是假设为 2。
  • Ctrl+C 处理
    • 问题exit(0) 表示程序正常退出,但用户按下 Ctrl+C 通常意图是中断。虽然能恢复终端状态,但会绕过栈上可能存在的其他析构函数(取决于 exit 的实现和全局对象状态)。
    • 改进建议:建议使用 raise(SIGINT) 或者简单地 return false 让调用者处理退出逻辑,或者注册信号处理函数统一处理。
  • 代码重复
    • getChineseInput 中的逻辑比较复杂,如果项目中有多个地方需要类似的交互,建议封装成一个类,不仅处理输入,还处理历史记录(上下箭头)等功能。

3. 代码性能

  • I/O 操作
    • 每次输入一个字符都调用 readstd::cout,这是交互式终端程序的常规做法,性能瓶颈主要在于用户输入速度,而非 CPU 处理速度,因此性能方面没有显著问题。
  • 字符串操作
    • input.pop_back()input += chstd::string 中通常是均摊 O(1) 的,性能良好。

4. 代码安全

  • 缓冲区溢出
    • 使用 read(STDIN_FILENO, &ch, 1) 每次读取一个字节,不存在缓冲区溢出风险。
  • 输入验证
    • 函数接收 std::string &input 作为引用,并直接修改。这依赖于调用者传递的是有效对象。
    • 对于 UTF-8 的处理逻辑虽然正确,但如果用户输入了不合法的 UTF-8 字节序列(例如输入流被损坏),代码可能会误判或删除错误的字节数。不过对于标准终端输入,这种情况极少见。
  • 资源泄漏
    • 如前所述,异常可能导致终端属性未恢复,这是一种资源(终端状态)泄漏。

改进建议代码示例 (RAII 封装)

为了解决异常安全和代码复用问题,建议将终端属性修改封装在一个类中:

#include <termios.h>
#include <unistd.h>
#include <stdexcept>

class TerminalModeGuard {
public:
    TerminalModeGuard() {
        if (tcgetattr(STDIN_FILENO, &old_termios) == -1) {
            throw std::runtime_error("Failed to get terminal attributes");
        }
        new_termios = old_termios;
        new_termios.c_lflag &= ~(ICANON | ECHO);
        if (tcsetattr(STDIN_FILENO, TCSANOW, &new_termios) == -1) {
            throw std::runtime_error("Failed to set terminal attributes");
        }
    }

    ~TerminalModeGuard() {
        // 无论是否发生异常,析构函数都会执行,确保终端状态恢复
        tcsetattr(STDIN_FILENO, TCSANOW, &old_termios);
    }

    // 禁止拷贝
    TerminalModeGuard(const TerminalModeGuard&) = delete;
    TerminalModeGuard& operator=(const TerminalModeGuard&) = delete;

private:
    struct termios old_termios;
    struct termios new_termios;
};

bool getChineseInput(std::string &input, const std::string &prompt = "") {
    if (!prompt.empty()) {
        std::cout << prompt;
        std::cout.flush();
    }

    // RAII 自动管理终端模式
    TerminalModeGuard guard; 

    input.clear();
    char ch;
    while (true) {
        // 注意:read 可能会被信号中断,返回 EINTR,建议处理
        ssize_t n = read(STDIN_FILENO, &ch, 1);
        if (n <= 0) break; // 简化处理,实际生产代码应检查 errno

        if (ch == '\n' || ch == '\r') {
            std::cout << std::endl;
            break;
        } else if (ch == 127 || ch == 8) { // Backspace
            if (!input.empty()) {
                // ... (保持原有的 UTF-8 处理逻辑不变) ...
                // 建议:引入 wcwidth 处理显示宽度
            }
        } else if (ch == 3) { // Ctrl+C
            // guard 析构函数会自动在这里被调用,恢复终端
            // 这里可以选择抛出异常或者返回特定状态,不建议直接 exit
            return false; 
        } else {
            input += ch;
            std::cout << ch << std::flush;
        }
    }
    return true;
}

总结

这份代码修改在功能上实现了预期的目标(支持中文输入),但在健壮性(异常安全)和可移植性(Windows 支持)方面存在隐患。建议引入 RAII 机制管理终端资源,并考虑使用 wcwidth 优化显示逻辑。如果项目需要支持 Windows,必须增加平台宏判断。

@deepin-mozart
Copy link
Contributor Author

/forcemerge

@deepin-bot
Copy link

deepin-bot bot commented Jan 26, 2026

This pr force merged! (status: unstable)

@deepin-bot deepin-bot bot merged commit 5601cfa into linuxdeepin:master Jan 26, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants