diff --git a/src/common/utils.cpp b/src/common/utils.cpp index e0caceb8..e6398a81 100644 --- a/src/common/utils.cpp +++ b/src/common/utils.cpp @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include #include @@ -562,8 +564,36 @@ QString Utils::getKeyshortcut(QKeyEvent *keyEvent) QString Utils::getKeyshortcutFromKeymap(Settings *settings, const QString &keyCategory, const QString &keyName) { - qDebug() << "Enter getKeyshortcutFromKeymap, keyCategory:" << keyCategory << "keyName:" << keyName; - return settings->settings->option(QString("shortcuts.%1.%2").arg(keyCategory).arg(keyName))->value().toString(); + if (settings == nullptr || settings->settings == nullptr) { + return QString(); + } + + static QHash cache; + static QSet watchedSettings; + const QString cacheKey = QStringLiteral("shortcuts.%1.%2").arg(keyCategory).arg(keyName); + + if (!watchedSettings.contains(settings)) { + watchedSettings.insert(settings); + QObject::connect(settings->settings, &Dtk::Core::DSettings::valueChanged, settings, [&cache](const QString &key) { + if (key.startsWith(QStringLiteral("shortcuts."))) { + cache.clear(); + } + }); + } + + const auto it = cache.constFind(cacheKey); + if (it != cache.constEnd()) { + return it.value(); + } + + Dtk::Core::DSettingsOption *option = settings->settings->option(cacheKey); + if (option == nullptr) { + return QString(); + } + + const QString result = option->value().toString(); + cache.insert(cacheKey, result); + return result; } QPixmap Utils::dropShadow(const QPixmap &source, qreal radius, const QColor &color, const QPoint &offset) diff --git a/src/editor/dtextedit.cpp b/src/editor/dtextedit.cpp index f66ec625..89f1b66c 100644 --- a/src/editor/dtextedit.cpp +++ b/src/editor/dtextedit.cpp @@ -109,16 +109,15 @@ TextEdit::TextEdit(QWidget *parent) // Init widgets. //左边栏控件 滑动条滚动跟新行号 折叠标记 connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this, &TextEdit::slotValueChanged); + // 左侧栏更新节流:textChanged 每次按键都触发,50ms 合并连续按键 + m_leftAreaUpdateTimer.setSingleShot(true); + m_leftAreaUpdateTimer.setInterval(50); + connect(&m_leftAreaUpdateTimer, &QTimer::timeout, this, &TextEdit::updateLeftAreaWidget); connect(this, &QPlainTextEdit::textChanged, this, [this]() { if (m_wrapper) { m_wrapper->UpdateBottomBarWordCnt(characterCount()); } - // 仅行数变化时全量刷新左侧栏;同行编辑由 cursorPositionChanged 局部 update 负责 - const int blockCountNow = blockCount(); - if (blockCountNow != m_lastLeftAreaBlockCount) { - m_lastLeftAreaBlockCount = blockCountNow; - updateLeftAreaWidget(); - } + m_leftAreaUpdateTimer.start(); }); connect(this, &QPlainTextEdit::cursorPositionChanged, this, &TextEdit::cursorPositionChanged); @@ -170,11 +169,6 @@ TextEdit::TextEdit(QWidget *parent) m_scrollAnimation->setEasingCurve(QEasingCurve::InOutExpo); m_scrollAnimation->setDuration(300); - m_typingBurstFlushTimer = new QTimer(this); - m_typingBurstFlushTimer->setSingleShot(true); - m_typingBurstFlushTimer->setInterval(120); - connect(m_typingBurstFlushTimer, &QTimer::timeout, this, &TextEdit::flushDeferredCursorUpdate); - m_cursorMode = Insert; connect(m_scrollAnimation, &QPropertyAnimation::finished, this, &TextEdit::handleScrollFinish, Qt::QueuedConnection); @@ -309,7 +303,6 @@ void TextEdit::deleteSelectTextEx(QTextCursor cursor, QString text, bool currLin void TextEdit::deleteTextEx(QTextCursor cursor) { qDebug() << "Deleting text at position:" << cursor.position(); - flushDeferredCursorUpdate(); QUndoCommand *pDeleteStack = new DeleteTextUndoCommand(cursor, this); m_pUndoStack->push(pDeleteStack); qDebug() << "Text deleted successfully"; @@ -340,32 +333,15 @@ void TextEdit::deleteMultiTextEx(const QList &multiText) void TextEdit::insertSelectTextEx(QTextCursor cursor, QString text) { qDebug() << "Inserting selected text at position:" << cursor.position() << "Length:" << text.length(); - - const bool deferHeavyUpdate = shouldDeferCursorHeavyUpdate(cursor, text); - if (!deferHeavyUpdate) { - flushDeferredCursorUpdate(); - } else { - const int line = cursor.blockNumber(); - if (m_deferCursorHeavyUpdate && line != m_deferCursorLastLine) { - flushDeferredCursorUpdate(); - } - m_deferCursorHeavyUpdate = true; - m_deferCursorLastLine = line; - } - QUndoCommand *pInsertStack = new InsertTextUndoCommand(cursor, text, this); m_pUndoStack->push(pInsertStack); ensureCursorVisible(); - if (deferHeavyUpdate) { - scheduleDeferredCursorUpdateBurst(); - } qDebug() << "Selected text inserted successfully"; } void TextEdit::insertColumnEditTextEx(QString text) { qDebug() << "Inserting column text"; - flushDeferredCursorUpdate(); if (m_isSelectAll) { QPlainTextEdit::selectAll(); } @@ -3016,63 +2992,7 @@ bool TextEdit::setCursorKeywordSeletoin(int position, bool findNext) return false; } -bool TextEdit::shouldDeferCursorHeavyUpdate(const QTextCursor &cursor, const QString &text) const -{ - if (m_cursorMode != Insert - || m_bIsAltMod - || m_readOnlyMode - || m_bReadOnlyPermission - || m_bIsFileOpen - || m_isPreeditBefore - || m_bIsInputMethod) { - return false; - } - - if (cursor.anchor() != cursor.position()) { - return false; - } - - for (const QChar ch : text) { - if (ch == QLatin1Char('\n') || ch == QLatin1Char('\t') || ch == QChar::ParagraphSeparator) { - return false; - } - } - - return true; -} - -void TextEdit::scheduleDeferredCursorUpdateBurst() -{ - m_deferCursorHeavyUpdate = true; - m_deferCursorLastLine = textCursor().blockNumber(); - m_typingBurstFlushTimer->start(); -} - -void TextEdit::flushDeferredCursorUpdate() -{ - if (m_typingBurstFlushTimer != nullptr) { - m_typingBurstFlushTimer->stop(); - } - - if (!m_deferCursorHeavyUpdate) { - return; - } - - m_deferCursorHeavyUpdate = false; - m_deferCursorLastLine = -1; - applyCursorHeavyUpdate(); -} - -void TextEdit::updateCursorPositionStatusLightweight() -{ - QTextCursor cursor = textCursor(); - if (m_wrapper) { - m_wrapper->bottomBar()->updatePosition(cursor.blockNumber() + 1, - cursor.positionInBlock() + 1); - } -} - -void TextEdit::applyCursorHeavyUpdate() +void TextEdit::cursorPositionChanged() { qDebug() << "Cursor position changed"; // 以赋值形式,清空 Bracket 括号的selection @@ -3081,12 +3001,14 @@ void TextEdit::applyCursorHeavyUpdate() m_endBracketSelection = QTextEdit::ExtraSelection(); updateHighlightLineSelection(); - updateHighlightBrackets('(', ')'); - updateHighlightBrackets('{', '}'); - updateHighlightBrackets('[', ']'); + updateHighlightBracketsAll(); renderAllSelections(); - updateCursorPositionStatusLightweight(); + QTextCursor cursor = textCursor(); + if (m_wrapper) { + m_wrapper->bottomBar()->updatePosition(cursor.blockNumber() + 1, + cursor.positionInBlock() + 1); + } m_pLeftAreaWidget->m_pLineNumberArea->update(); m_pLeftAreaWidget->m_pBookMarkArea->update(); @@ -3094,22 +3016,6 @@ void TextEdit::applyCursorHeavyUpdate() qDebug() << "Cursor position changed completed"; } -void TextEdit::cursorPositionChanged() -{ - if (m_deferCursorHeavyUpdate) { - const int line = textCursor().blockNumber(); - if (line != m_deferCursorLastLine) { - flushDeferredCursorUpdate(); - return; - } - - updateCursorPositionStatusLightweight(); - return; - } - - applyCursorHeavyUpdate(); -} - /** * @brief 剪切光标选中的文本 * @param ignoreCheck 是否忽略权限判断(外部已进行),默认false @@ -3277,10 +3183,10 @@ void TextEdit::paste() void TextEdit::highlight() { - QTimer::singleShot(0, this, [&]() { - if (nullptr != m_wrapper) { + QTimer::singleShot(0, this, [this]() { + if (m_wrapper != nullptr) { qDebug() << "Highlighting with wrapper"; - m_wrapper->OnUpdateHighlighter(); + m_wrapper->scheduleHighlight(); } }); qDebug() << "Highlighting completed"; @@ -3635,7 +3541,6 @@ void TextEdit::slotRedoAvailable(bool redoIsAvailable) void TextEdit::redo_() { qDebug() << "Starting redo operation"; - flushDeferredCursorUpdate(); if (!m_pUndoStack->canRedo()) { qDebug() << "Redo operation skipped - nothing to redo"; return; @@ -3667,7 +3572,6 @@ void TextEdit::undo_() { qDebug() << "Starting undo operation"; qDebug() << "Starting undo operation"; - flushDeferredCursorUpdate(); if (!m_pUndoStack->canUndo()) { qDebug() << "Undo operation skipped - nothing to undo"; return; @@ -4044,6 +3948,24 @@ void TextEdit::updateHighlightBrackets(const QChar &openChar, const QChar &close qDebug() << "Updating highlight brackets completed"; } +void TextEdit::updateHighlightBracketsAll() +{ + QTextDocument *doc = document(); + QTextCursor cursor = textCursor(); + const int position = cursor.position(); + + const QChar atPos = doc->characterAt(position); + const QChar atPrev = doc->characterAt(position - 1); + static const QString brackets = QStringLiteral("(){}[]"); + if (!brackets.contains(atPos) && !brackets.contains(atPrev)) { + return; + } + + updateHighlightBrackets('(', ')'); + updateHighlightBrackets('{', '}'); + updateHighlightBrackets('[', ']'); +} + int TextEdit::getFirstVisibleBlockId() const { qDebug() << "Getting first visible block id"; @@ -5593,7 +5515,6 @@ void TextEdit::setTextFinished() qDebug() << "Set text finished"; m_bIsFileOpen = false; m_nLines = blockCount(); - m_lastLeftAreaBlockCount = m_nLines; if (!m_listBookmark.isEmpty()) { qDebug() << "Set text finished, m_listBookmark is not empty"; @@ -6798,8 +6719,7 @@ void TextEdit::updateMark(int from, int charsRemoved, int charsAdded) } } - //渲染所有的指定字符格式 - renderAllSelections(); + // renderAllSelections 和 highlight 已在 cursorPositionChanged / highlight 防抖定时器中统一调度 qDebug() << "updateMark, completed"; } @@ -7710,7 +7630,6 @@ void TextEdit::dropEvent(QDropEvent *event) void TextEdit::inputMethodEvent(QInputMethodEvent *e) { - flushDeferredCursorUpdate(); m_bIsInputMethod = true; if (m_isSelectAll) @@ -7781,7 +7700,6 @@ void TextEdit::inputMethodEvent(QInputMethodEvent *e) void TextEdit::mousePressEvent(QMouseEvent *e) { - flushDeferredCursorUpdate(); if (m_bIsFindClose) { m_bIsFindClose = false; @@ -8301,7 +8219,6 @@ void TextEdit::keyPressEvent(QKeyEvent *e) //列编辑 删除撤销重做 if (modifiers == Qt::NoModifier && (e->key() == Qt::Key_Backspace)) { - flushDeferredCursorUpdate(); if (m_isSelectAll) QPlainTextEdit::selectAll(); @@ -8325,7 +8242,6 @@ void TextEdit::keyPressEvent(QKeyEvent *e) //列编辑 向后删除撤销重做 if (modifiers == Qt::NoModifier && (e->key() == Qt::Key_Delete)) { - flushDeferredCursorUpdate(); if (m_isSelectAll) QPlainTextEdit::selectAll(); diff --git a/src/editor/dtextedit.h b/src/editor/dtextedit.h index 08acc630..ed01ea56 100644 --- a/src/editor/dtextedit.h +++ b/src/editor/dtextedit.h @@ -511,9 +511,6 @@ public slots: void onSelectionArea(); void fingerZoom(QString name, QString direction, int fingers); void cursorPositionChanged(); - void flushDeferredCursorUpdate(); - void applyCursorHeavyUpdate(); - void updateCursorPositionStatusLightweight(); //剪切槽函数 void cut(bool ignoreCheck = false); @@ -589,6 +586,7 @@ public slots: void unCommentSelection(); void setComment(); void removeComment(); + void updateHighlightBracketsAll(); //去除"*{*" "*}*" "*{*}*"跳过当做普通文本处理不折叠 梁卫东2020-09-01 17:16:41 bool blockContainStrBrackets(int line); @@ -788,7 +786,6 @@ private slots: QList m_listBookmark;///< 存储书签的list int m_nBookMarkHoverLine;///< 悬浮效果书签所在的行 int m_nLines;///< 文本总行数 - int m_lastLeftAreaBlockCount = 0;///< 上次刷新左侧栏时的文档行数 bool m_bIsFileOpen;///< 是否在读取文件(导致文本变化) bool m_bIsShortCut;///< 是否在使用书签快捷键 @@ -877,12 +874,7 @@ private slots: bool m_isPreeditBefore = false; // 上一个输入法时间是否是 preedit int m_preeditLengthBefore = 0; - bool shouldDeferCursorHeavyUpdate(const QTextCursor &cursor, const QString &text) const; - void scheduleDeferredCursorUpdateBurst(); - - bool m_deferCursorHeavyUpdate = false; - int m_deferCursorLastLine = -1; - QTimer *m_typingBurstFlushTimer = nullptr; + QTimer m_leftAreaUpdateTimer; Qt::CaseSensitivity defaultCaseSensitive = Qt::CaseInsensitive; // 查找匹配时默认不区分大小写 }; diff --git a/src/editor/editwrapper.cpp b/src/editor/editwrapper.cpp index 6e9d84c1..a4b8b5ef 100644 --- a/src/editor/editwrapper.cpp +++ b/src/editor/editwrapper.cpp @@ -119,6 +119,11 @@ EditWrapper::EditWrapper(Window *window, QWidget *parent) connect(m_pTextEdit, &TextEdit::cursorModeChanged, this, &EditWrapper::handleCursorModeChanged); connect(m_pWaringNotices, &WarningNotices::reloadBtnClicked, this, &EditWrapper::reloadModifyFile); connect(m_pWaringNotices, &WarningNotices::saveAsBtnClicked, m_pWindow, &Window::saveAsFile); + + m_highlightDebounceTimer.setSingleShot(true); + m_highlightDebounceTimer.setInterval(150); + connect(&m_highlightDebounceTimer, &QTimer::timeout, this, &EditWrapper::OnUpdateHighlighter); + // NOTE: 文本高亮会触发重新布局,与界面布局(拖拽、放大窗口)变更时的布局操作冲突,因此调整更新顺序,在布局后刷新高亮 connect(m_pTextEdit->verticalScrollBar(), &QScrollBar::valueChanged, this, [this](int) { qDebug() << "EditWrapper connect verticalScrollBar valueChanged"; @@ -1150,6 +1155,11 @@ void EditWrapper::UpdateBottomBarWordCnt(int cnt) m_pBottomBar->updateWordCount(cnt); } +void EditWrapper::scheduleHighlight() +{ + m_highlightDebounceTimer.start(); +} + /** * @brief 界面显示内容变更时触发,将查询当前显示的内容 * @param forceUpdate 是否强制重设高亮处理,部分高亮无需重复设置 diff --git a/src/editor/editwrapper.h b/src/editor/editwrapper.h index 466eb2ee..8f2e3648 100644 --- a/src/editor/editwrapper.h +++ b/src/editor/editwrapper.h @@ -13,6 +13,7 @@ #include "../common/utils.h" #include #include +#include #include #include #include @@ -143,6 +144,7 @@ public slots: void OnThemeChangeSlot(QString theme); void UpdateBottomBarWordCnt(int cnt); void OnUpdateHighlighter(); + void scheduleHighlight(); //set the value of m_bIsTemFile void setTemFile(bool value); // 设置恢复光标位置(用于懒加载恢复,避免 O(N²) 扫描) @@ -183,6 +185,8 @@ public slots: bool m_bAsyncReadFileFinished = false; bool m_bHasPreProcess = false; // 预处理标识 int m_nRestoreCursorPosition = -1; // 恢复光标位置提示(-1 表示不指定) + + QTimer m_highlightDebounceTimer; }; #endif