Skip to content

Commit 1680587

Browse files
authored
[0137] 输入框支持粘贴内容自适应高度 (#3402)
1 parent c7e732e commit 1680587

4 files changed

Lines changed: 90 additions & 8 deletions

File tree

devel/0137.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,21 @@
1111
## 3 如何测试
1212

1313
### 3.1 确定性测试(单元测试)
14-
本项目无对应单元测试。
14+
1. 构建并运行现有单元测试:
15+
```bash
16+
xmake b qt_chat_tab_widget_test
17+
xmake r qt_chat_tab_widget_test
18+
```
19+
2. 测试应覆盖 `count_input_lines` 的保底逻辑以及基于排版高度的行数估算辅助函数。
1520

1621
### 3.2 非确定性测试(文档验证)
1722
1. 启动 Mogan STEM,点击顶部工具栏的 AI 图标打开 Chat 标签页
1823
2. 观察输入框默认高度是否为三行(约三行文本的高度)
1924
3. 在输入框中连续按回车键,观察输入框高度是否随内容增加而向上延伸
2025
4. 输入多行文本后,确认输入框没有出现不必要的滚动条,而是直接扩展高度
2126
5. 删除多行内容后,确认输入框高度能相应收缩
27+
6. **粘贴长文本(不带换行符)**:从外部复制一段超过输入框宽度的长文本并粘贴,观察输入框高度是否随排版后的实际行数自动增高
28+
7. **粘贴多行文本**:复制包含多个换行符的多行文本并粘贴,确认输入框高度同时反映段落数和长行的自动折行
2229

2330
## 4 如何提交
2431

@@ -33,6 +40,7 @@ xmake b stem
3340
1. 将 AI 对话输入框的默认高度调整为三行文本的高度
3441
2. 输入框支持根据内容行数自适应向上延伸高度
3542
3. 当用户删除内容时,输入框高度相应收缩
43+
4. 修复粘贴长文本(无换行符)或多行文本时,输入框高度无法按实际排版行数自动适配的问题
3644

3745
## 6 Why
3846

@@ -41,6 +49,8 @@ xmake b stem
4149
- 减少用户在输入长消息时的滚动操作
4250
- 更符合现代聊天产品的交互习惯
4351

52+
此外,之前的自适应实现仅基于文档树段落数(`DOCUMENT` 子节点数)进行估算,未考虑 TeXmacs 排版引擎的实际折行。当用户粘贴不带换行符的长文本时,段落数不变,输入框不会增高,导致文本被截断或需要滚动,严重影响体验。因此需要引入排版后实际高度的查询机制。
53+
4454
## 7 How
4555

4656
### 7.1 默认高度设为三行
@@ -59,10 +69,19 @@ xmake b stem
5969

6070
当用户删除内容导致段落数减少时,`adjust_input_height()` 同样会降低 `minimumHeight`。最小值始终不低于三行默认值。
6171

62-
## 8 已知限制
72+
### 7.4 基于排版实际高度的视觉行数估算
73+
74+
为了解决粘贴长文本(无换行符)时高度不自适应的问题,在 `adjust_input_height()` 中新增对 TeXmacs 排版引擎实际输出高度的查询:
6375

64-
当前实现通过 `count_input_lines()` 统计文档的**段落数**`DOCUMENT` 节点的子节点数)来调整高度,而非实际的视觉行数。因此:
65-
- 按回车键创建新段落时,输入框能正确变大。
66-
- 粘贴不带换行符的长文本时,由于段落数不变,输入框**不会**随内容自动增高。
76+
1. 通过 `panel->inputEditorWidget->findChild<QTMWidget*>()` 获取嵌入编辑器的 `QTMWidget`
77+
2. 调用 `editor->tm_widget()` 得到 `qt_simple_widget_rep*`,再 `dynamic_cast<edit_interface_rep*>` 获取编辑器接口。
78+
3. 调用 `ed->get_total_height(true)` 获取排版后内容的总高度(`SI` 单位)。
79+
4. 使用 `to_qsize(0, h).height()``SI` 转换为像素高度。
80+
5. 将像素高度除以 `kInputLineHeight` 得到等效视觉行数。
81+
6. 将该视觉行数与基于文档树段落数的保底值取较大者,作为最终目标行数。
82+
83+
TeXmacs 的默认 `page-medium``papyrus`,此时 `eb->h()` 直接反映所有排版内容的总高度,无需处理分页带来的额外结构。
84+
85+
## 8 已知限制
6786

68-
后续如需支持粘贴长文本时的自适应,需要改用能够计算实际排版行数的方式(如查询编辑器内部排版后的行高)
87+
`get_total_height(true)` 包含少量页边距(`top + dtop + bot + dbot`),因此空文档时返回的高度略大于零,计算出的等效行数可能为 1 而非 0。由于最终会与 `kInputDefaultLines`(3)取最大值,该偏差不影响实际表现。若后续需要更精确的内容高度,可调用 `get_total_height(false)` 获取去除装饰后的高度

src/Plugins/Qt/qt_chat_tab_widget.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "QTMGuiHelper.hpp"
1414
#include "QTMStyle.hpp"
1515
#include "QTMWidget.hpp"
16+
#include "edit_interface.hpp"
1617
#include "new_buffer.hpp"
1718
#include "qt_dpi_utils.hpp"
1819
#include "qt_gui.hpp"
@@ -986,6 +987,19 @@ QTChatTabWidget::count_input_lines (tree body) {
986987
return N (body);
987988
}
988989

990+
/**
991+
* @brief 根据排版后的实际高度估算等效行数。
992+
* @param contentHeight 排版后的内容高度(SI 单位)。
993+
* @return 等效行数,若高度无效则返回 0。
994+
*/
995+
int
996+
QTChatTabWidget::estimate_lines_from_height (SI contentHeight) {
997+
if (contentHeight <= 0) return 0;
998+
int px= to_qsize (0, contentHeight).height ();
999+
if (px <= 0) return 0;
1000+
return (px + kInputLineHeight - 1) / kInputLineHeight;
1001+
}
1002+
9891003
/**
9901004
* @brief 根据输入内容自适应调整输入框高度。
9911005
* @param panel 目标会话面板。
@@ -995,8 +1009,18 @@ QTChatTabWidget::adjust_input_height (ChatConversationPanel* panel) {
9951009
if (!panel || !panel->inputEditorWidget) return;
9961010

9971011
tree body = read_input_message (panel);
998-
int lines = count_input_lines (body);
999-
int targetLines= qMax (kInputDefaultLines, lines);
1012+
int docLines = count_input_lines (body);
1013+
int visualLines= 0;
1014+
1015+
if (QTMWidget* editor= panel->inputEditorWidget->findChild<QTMWidget*> ()) {
1016+
if (edit_interface_rep* ed=
1017+
dynamic_cast<edit_interface_rep*> (editor->tm_widget ())) {
1018+
visualLines= estimate_lines_from_height (ed->get_total_height (true));
1019+
}
1020+
}
1021+
1022+
int lines = qMax (docLines, visualLines);
1023+
int targetLines = qMax (kInputDefaultLines, lines);
10001024
targetLines = qMin (targetLines, kInputMaxLines);
10011025
int targetHeight= DpiUtils::scaled (kInputLineHeight * targetLines);
10021026

src/Plugins/Qt/qt_chat_tab_widget.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,13 @@ class QTChatTabWidget : public QWidget {
262262
*/
263263
static int count_input_lines (tree body);
264264

265+
/**
266+
* @brief 根据排版后的实际高度估算等效行数。
267+
* @param contentHeight 排版后的内容高度(SI 单位)。
268+
* @return 等效行数,若高度无效则返回 0。
269+
*/
270+
static int estimate_lines_from_height (SI contentHeight);
271+
265272
/**
266273
* @brief 判断文档主体是否实际为空。
267274
* @param body TeXmacs 文档树。

tests/Plugins/Qt/qt_chat_tab_widget_test.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "Qt/qt_chat_tab_widget.hpp"
99
#include "base.hpp"
10+
#include "qt_utilities.hpp"
1011
#include <QtTest/QtTest>
1112

1213
using namespace moebius;
@@ -69,6 +70,37 @@ private slots:
6970
tree doc= tree (DOCUMENT, "para1", "para2");
7071
QVERIFY (!QTChatTabWidget::is_empty_document_body (doc));
7172
}
73+
74+
void test_estimate_lines_from_height_zero () {
75+
QCOMPARE (QTChatTabWidget::estimate_lines_from_height (0), 0);
76+
}
77+
78+
void test_estimate_lines_from_height_negative () {
79+
QCOMPARE (QTChatTabWidget::estimate_lines_from_height (-100), 0);
80+
}
81+
82+
void test_estimate_lines_from_height_less_than_one_line () {
83+
// kInputLineHeight == 22
84+
// 高度不足一行时应返回 1(向上取整)
85+
SI h= (22 / 2) * PIXEL;
86+
QCOMPARE (QTChatTabWidget::estimate_lines_from_height (h), 1);
87+
}
88+
89+
void test_estimate_lines_from_height_exact_one_line () {
90+
SI h= 22 * PIXEL;
91+
QCOMPARE (QTChatTabWidget::estimate_lines_from_height (h), 1);
92+
}
93+
94+
void test_estimate_lines_from_height_three_lines () {
95+
SI h= (22 * 3) * PIXEL;
96+
QCOMPARE (QTChatTabWidget::estimate_lines_from_height (h), 3);
97+
}
98+
99+
void test_estimate_lines_from_height_with_rounding () {
100+
// 高度为 2.5 行时应向上取整为 3
101+
SI h= ((22 * 5) / 2) * PIXEL;
102+
QCOMPARE (QTChatTabWidget::estimate_lines_from_height (h), 3);
103+
}
72104
};
73105

74106
QTEST_MAIN (TestChatTabWidget)

0 commit comments

Comments
 (0)