TextEditor: highlight selected text

Change-Id: Ib46908decef51d478f6954a116108c48e4a07ed3
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
David Schulz
2023-10-11 12:39:47 +02:00
parent 5ac19328fd
commit 6f4b9639ce
18 changed files with 215 additions and 4 deletions

View File

@@ -605,6 +605,7 @@ public:
void paintRightMarginLine(const PaintEventData &data, QPainter &painter) const;
void paintBlockHighlight(const PaintEventData &data, QPainter &painter) const;
void paintSearchResultOverlay(const PaintEventData &data, QPainter &painter) const;
void paintSelectionOverlay(const PaintEventData &data, QPainter &painter) const;
void paintIfDefedOutBlocks(const PaintEventData &data, QPainter &painter) const;
void paintFindScope(const PaintEventData &data, QPainter &painter) const;
void paintCurrentLineHighlight(const PaintEventData &data, QPainter &painter) const;
@@ -690,6 +691,7 @@ public:
int length;
};
void addSearchResultsToScrollBar(const QVector<SearchResult> &results);
void addSelectionHighlightToScrollBar(const QVector<SearchResult> &selections);
void adjustScrollBarRanges();
void setFindScope(const MultiTextCursor &scope);
@@ -766,6 +768,7 @@ public:
TextEditorOverlay *m_overlay = nullptr;
SnippetOverlay *m_snippetOverlay = nullptr;
TextEditorOverlay *m_searchResultOverlay = nullptr;
TextEditorOverlay *m_selectionHighlightOverlay = nullptr;
bool snippetCheckCursor(const QTextCursor &cursor);
void snippetTabOrBacktab(bool forward);
@@ -811,6 +814,7 @@ public:
QString m_findText;
FindFlags m_findFlags;
void highlightSearchResults(const QTextBlock &block, const PaintEventData &data) const;
void highlightSelection(const QTextBlock &block) const;
QTimer m_delayedUpdateTimer;
void setExtraSelections(Utils::Id kind, const QList<QTextEdit::ExtraSelection> &selections);
@@ -871,7 +875,9 @@ public:
CommentDefinition m_commentDefinition;
QFuture<SearchResultItems> m_searchFuture;
QFuture<SearchResultItems> m_selectionHighlightFuture;
QVector<SearchResult> m_searchResults;
QVector<SearchResult> m_selectionResults;
QTimer m_scrollBarUpdateTimer;
HighlightScrollBarController *m_highlightScrollBarController = nullptr;
bool m_scrollBarUpdateScheduled = false;
@@ -995,6 +1001,7 @@ TextEditorWidgetPrivate::TextEditorWidgetPrivate(TextEditorWidget *parent)
, m_overlay(new TextEditorOverlay(q))
, m_snippetOverlay(new SnippetOverlay(q))
, m_searchResultOverlay(new TextEditorOverlay(q))
, m_selectionHighlightOverlay(new TextEditorOverlay(q))
, m_refactorOverlay(new RefactorOverlay(q))
, m_marksVisible(false)
, m_codeFoldingVisible(false)
@@ -1010,6 +1017,7 @@ TextEditorWidgetPrivate::TextEditorWidgetPrivate(TextEditorWidget *parent)
, m_clipboardAssistProvider(new ClipboardAssistProvider)
, m_autoCompleter(new AutoCompleter)
{
m_selectionHighlightOverlay->show();
auto aggregate = new Aggregation::Aggregate;
m_find = new TextEditorWidgetFind(q);
connect(m_find, &BaseTextFind::highlightAllRequested,
@@ -1139,6 +1147,8 @@ TextEditorWidgetPrivate::~TextEditorWidgetPrivate()
delete m_highlightScrollBarController;
if (m_searchFuture.isRunning())
m_searchFuture.cancel();
if (m_selectionHighlightFuture.isRunning())
m_selectionHighlightFuture.cancel();
}
static QFrame *createSeparator(const QString &styleSheet)
@@ -3346,10 +3356,12 @@ void TextEditorWidgetPrivate::documentAboutToBeReloaded()
m_overlay->clear();
m_snippetOverlay->clear();
m_searchResultOverlay->clear();
m_selectionHighlightOverlay->clear();
m_refactorOverlay->clear();
// clear search results
m_searchResults.clear();
m_selectionResults.clear();
}
void TextEditorWidgetPrivate::documentReloadFinished(bool success)
@@ -4070,6 +4082,35 @@ void TextEditorWidgetPrivate::highlightSearchResults(const QTextBlock &block, co
}
}
void TextEditorWidgetPrivate::highlightSelection(const QTextBlock &block) const
{
if (!m_displaySettings.m_highlightSelection || m_cursors.hasMultipleCursors())
return;
const QString selection = m_cursors.selectedText();
if (selection.trimmed().isEmpty())
return;
const int blockPosition = block.position();
QString text = block.text();
text.replace(QChar::Nbsp, QLatin1Char(' '));
const int l = selection.length();
for (int idx = text.indexOf(selection, 0, Qt::CaseInsensitive);
idx >= 0;
idx = text.indexOf(selection, idx + 1, Qt::CaseInsensitive)) {
const int start = blockPosition + idx;
const int end = start + l;
if (!Utils::contains(m_selectionHighlightOverlay->selections(),
[&](const OverlaySelection &selection) {
return selection.m_cursor_begin.position() == start
&& selection.m_cursor_end.position() == end;
})) {
m_selectionHighlightOverlay->addOverlaySelection(start, end, {}, {});
}
}
}
void TextEditorWidgetPrivate::startCursorFlashTimer()
{
const int flashTime = QApplication::cursorFlashTime();
@@ -4108,6 +4149,44 @@ void TextEditorWidgetPrivate::updateCursorSelections()
selections << QTextEdit::ExtraSelection{cursor, selectionFormat};
}
q->setExtraSelections(TextEditorWidget::CursorSelection, selections);
m_selectionHighlightOverlay->clear();
if (m_selectionHighlightFuture.isRunning())
m_selectionHighlightFuture.cancel();
m_selectionResults.clear();
if (!m_highlightScrollBarController)
return;
m_highlightScrollBarController->removeHighlights(Constants::SCROLL_BAR_SELECTION);
if (!m_displaySettings.m_highlightSelection || m_cursors.hasMultipleCursors())
return;
const QString txt = m_cursors.selectedText();
if (txt.trimmed().isEmpty())
return;
m_selectionHighlightFuture = Utils::asyncRun(Utils::searchInContents,
txt,
FindFlags{},
m_document->filePath(),
m_document->plainText());
Utils::onResultReady(m_selectionHighlightFuture,
this,
[this](const SearchResultItems &resultList) {
QList<SearchResult> results;
for (const SearchResultItem &item : resultList) {
int start = item.mainRange().begin.positionInDocument(
m_document->document());
int end = item.mainRange().end.positionInDocument(
m_document->document());
results << SearchResult{start, end - start};
}
m_selectionResults = results;
addSelectionHighlightToScrollBar(results);
});
}
void TextEditorWidgetPrivate::moveCursor(QTextCursor::MoveOperation operation,
@@ -4489,6 +4568,40 @@ void TextEditorWidgetPrivate::paintSearchResultOverlay(const PaintEventData &dat
data.eventRect);
}
void TextEditorWidgetPrivate::paintSelectionOverlay(const PaintEventData &data,
QPainter &painter) const
{
if (m_cursors.hasMultipleCursors())
return;
const QString expr = m_cursors.selectedText();
if (expr.isEmpty())
return;
const int margin = 5;
QTextBlock block = data.block;
QPointF offset = data.offset;
while (block.isValid()) {
QRectF blockBoundingRect = q->blockBoundingRect(block).translated(offset);
if (blockBoundingRect.bottom() >= data.eventRect.top() - margin
&& blockBoundingRect.top() <= data.eventRect.bottom() + margin) {
highlightSelection(block);
}
offset.ry() += blockBoundingRect.height();
if (offset.y() > data.viewportRect.height() + margin)
break;
block = TextEditor::nextVisibleBlock(block, data.doc);
}
QColor selection = m_document->fontSettings().toTextCharFormat(C_SELECTION).background().color();
const QColor text = m_document->fontSettings().toTextCharFormat(C_TEXT).background().color();
selection.setAlphaF(StyleHelper::luminance(text) > 0.5 ? 0.25 : 0.5);
m_selectionHighlightOverlay->fill(&painter, selection, data.eventRect);
}
void TextEditorWidgetPrivate::paintIfDefedOutBlocks(const PaintEventData &data,
QPainter &painter) const
{
@@ -5076,6 +5189,8 @@ void TextEditorWidget::paintEvent(QPaintEvent *e)
d->paintFindScope(data, painter);
// paint search results on top of the find scope
d->paintSearchResultOverlay(data, painter);
// paint selection highlights
d->paintSelectionOverlay(data, painter);
}
while (data.block.isValid()) {
@@ -6947,6 +7062,33 @@ void TextEditorWidgetPrivate::addSearchResultsToScrollBar(const QVector<SearchRe
}
}
void TextEditorWidgetPrivate::addSelectionHighlightToScrollBar(
const QVector<SearchResult> &selections)
{
if (!m_highlightScrollBarController)
return;
for (const SearchResult &result : selections) {
const QTextBlock &block = q->document()->findBlock(result.start);
if (block.isValid() && block.isVisible()) {
if (q->lineWrapMode() == QPlainTextEdit::WidgetWidth) {
const int firstLine = block.layout()->lineForTextPosition(result.start - block.position()).lineNumber();
const int lastLine = block.layout()->lineForTextPosition(result.start - block.position() + result.length).lineNumber();
for (int line = firstLine; line <= lastLine; ++line) {
m_highlightScrollBarController->addHighlight(
{Constants::SCROLL_BAR_SELECTION, block.firstLineNumber() + line,
Theme::TextEditor_Selection_ScrollBarColor, Highlight::NormalPriority});
}
} else {
m_highlightScrollBarController->addHighlight(
{Constants::SCROLL_BAR_SELECTION,
block.blockNumber(),
Theme::TextEditor_Selection_ScrollBarColor,
Highlight::NormalPriority});
}
}
}
}
Highlight markToHighlight(TextMark *mark, int lineNumber)
{
return Highlight(mark->category().id,
@@ -6968,6 +7110,9 @@ void TextEditorWidgetPrivate::updateHighlightScrollBarNow()
// update search results
addSearchResultsToScrollBar(m_searchResults);
// update search selection
addSelectionHighlightToScrollBar(m_selectionResults);
// update text marks
const TextMarks marks = m_document->marks();
for (TextMark *mark : marks) {
@@ -7900,6 +8045,7 @@ void TextEditorWidget::setDisplaySettings(const DisplaySettings &ds)
d->updateFileLineEndingVisible();
d->updateHighlights();
d->setupScrollBar();
d->updateCursorSelections();
viewport()->update();
extraArea()->update();
}