[vcs-log] highlight text matches in the table

GitOrigin-RevId: 87239c349bfbad159803eef9296691adadc4022a
This commit is contained in:
Julia Beliaeva
2023-04-21 21:34:55 +02:00
committed by intellij-monorepo-bot
parent 3935eedbdd
commit 9a6a8736af
4 changed files with 72 additions and 5 deletions

View File

@@ -758,6 +758,8 @@ vcs.log.copy.filters.to.new.tab=true
vcs.log.copy.filters.to.new.tab.description=If enabled, "Open New Vcs Log Tab" action will copy active filters to the new tab.
vcs.log.filter.text.on.the.fly=false
vcs.log.filter.text.on.the.fly.description=If enabled, applies text filter to the Log while typing
vcs.log.filter.text.highlight.matches=false
vcs.log.filter.text.highlight.matches.description=Highlight text filter matches in the Log table
vcs.log.max.changes.shown=50000
vcs.log.max.changes.shown.description=Limit for showing commits in the changes view.
vcs.log.max.branches.shown=100

View File

@@ -1,6 +1,7 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.vcs.log.ui.render;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkRenderer;
import com.intellij.openapi.vcs.changes.ui.CurrentBranchComponent;
@@ -10,6 +11,8 @@ import com.intellij.ui.scale.JBUIScale;
import com.intellij.ui.speedSearch.SpeedSearchUtil;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.StartupUiUtil;
import com.intellij.vcs.log.VcsLogFilterCollection;
import com.intellij.vcs.log.VcsLogTextFilter;
import com.intellij.vcs.log.VcsRef;
import com.intellij.vcs.log.data.VcsLogData;
import com.intellij.vcs.log.graph.EdgePrintElement;
@@ -21,6 +24,7 @@ import com.intellij.vcs.log.ui.table.VcsLogCellRenderer;
import com.intellij.vcs.log.ui.table.VcsLogGraphTable;
import com.intellij.vcs.log.ui.table.column.Commit;
import com.intellij.vcs.log.ui.table.column.VcsLogColumnManager;
import com.intellij.vcs.log.visible.filters.VcsLogTextFilterWithMatches;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -239,6 +243,13 @@ public class GraphCommitCellRenderer extends TypeSafeTableCellRenderer<GraphComm
private void appendText(@NotNull GraphCommitCell cell, @NotNull SimpleTextAttributes style, boolean isSelected) {
myIssueLinkRenderer.appendTextWithLinks(StringUtil.replace(cell.getText(), "\t", " ").trim(), style);
SpeedSearchUtil.applySpeedSearchHighlighting(myGraphTable, this, false, isSelected);
if (Registry.is("vcs.log.filter.text.highlight.matches")) {
VcsLogTextFilter textFilter = myGraphTable.getModel().getVisiblePack().getFilters().get(VcsLogFilterCollection.TEXT_FILTER);
if (textFilter instanceof VcsLogTextFilterWithMatches textFilterWithMatches) {
String text = getCharSequence(false).toString();
SpeedSearchUtil.applySpeedSearchHighlighting(this, textFilterWithMatches.matchingRanges(text), isSelected);
}
}
}
private int getAvailableWidth(int column, int graphWidth) {

View File

@@ -1,15 +1,15 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.vcs.log.visible.filters
import com.intellij.openapi.util.TextRange
import com.intellij.vcs.log.VcsLogDetailsFilter
import com.intellij.vcs.log.VcsLogTextFilter
import org.jetbrains.annotations.NonNls
/**
* @see VcsLogFilterObject.fromPattern
*/
internal data class VcsLogTextFilterImpl(private val text: String,
private val isMatchCase: Boolean) : VcsLogDetailsFilter, VcsLogTextFilter {
private val isMatchCase: Boolean) : VcsLogDetailsFilter, VcsLogTextFilterWithMatches {
override fun matches(message: String): Boolean = message.contains(text, !isMatchCase)
@@ -19,6 +19,18 @@ internal data class VcsLogTextFilterImpl(private val text: String,
override fun matchesCase(): Boolean = isMatchCase
override fun matchingRanges(message: String): Iterable<TextRange> {
return generateSequence({ findMatchingRange(message, null) }) {
lastRange -> findMatchingRange(message, lastRange)
}.asIterable()
}
private fun findMatchingRange(message: String, previousRange: TextRange?): TextRange? {
val startIndex = previousRange?.endOffset ?: 0
val startOffset = message.indexOf(text, startIndex, !isMatchCase).takeIf { it >= 0 } ?: return null
return TextRange(startOffset, startOffset + text.length)
}
@NonNls
override fun toString(): String {
return "containing '$text' ${caseSensitiveText()}"

View File

@@ -2,16 +2,33 @@
package com.intellij.vcs.log.visible.filters
import com.intellij.openapi.util.Comparing
import com.intellij.openapi.util.TextRange
import com.intellij.vcs.log.VcsLogDetailsFilter
import com.intellij.vcs.log.VcsLogTextFilter
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.NonNls
import java.util.*
import java.util.regex.Pattern
@ApiStatus.Experimental
interface VcsLogTextFilterWithMatches : VcsLogTextFilter {
override fun matches(message: String): Boolean {
return matchingRanges(message).iterator().hasNext()
}
/**
* Returns text ranges for matches in the specified commit message.
*
* @param message a commit message to match
* @return an Iterable containing text ranges for matches
*/
fun matchingRanges(message: String): Iterable<TextRange>
}
/**
* @see VcsLogFilterObject.fromPattern
*/
internal data class VcsLogRegexTextFilter(private val pattern: Pattern) : VcsLogDetailsFilter, VcsLogTextFilter {
internal data class VcsLogRegexTextFilter(private val pattern: Pattern) : VcsLogDetailsFilter, VcsLogTextFilterWithMatches {
override fun matches(message: String): Boolean = pattern.matcher(message).find()
override fun getText(): String = pattern.pattern()
@@ -20,6 +37,10 @@ internal data class VcsLogRegexTextFilter(private val pattern: Pattern) : VcsLog
override fun matchesCase(): Boolean = (pattern.flags() and Pattern.CASE_INSENSITIVE) == 0
override fun matchingRanges(message: String): Iterable<TextRange> {
return Iterable { pattern.matcher(message).results().map { TextRange(it.start(), it.end()) }.iterator() }
}
@NonNls
override fun toString(): String {
return "matching '$text' ${caseSensitiveText()}"
@@ -30,7 +51,7 @@ internal data class VcsLogRegexTextFilter(private val pattern: Pattern) : VcsLog
* @see VcsLogFilterObject.fromPatternsList
*/
internal class VcsLogMultiplePatternsTextFilter(val patterns: List<String>,
private val isMatchCase: Boolean) : VcsLogDetailsFilter, VcsLogTextFilter {
private val isMatchCase: Boolean) : VcsLogDetailsFilter, VcsLogTextFilterWithMatches {
override fun getText(): String = if (patterns.size == 1) patterns.single() else patterns.joinToString("|") { Pattern.quote(it) }
override fun isRegex(): Boolean = patterns.size > 1
@@ -39,6 +60,27 @@ internal class VcsLogMultiplePatternsTextFilter(val patterns: List<String>,
override fun matches(message: String): Boolean = patterns.any { message.contains(it, !isMatchCase) }
override fun matchingRanges(message: String): Iterable<TextRange> {
return generateSequence({ findNextMatch(message, null) }) { previousRange ->
findNextMatch(message, previousRange)
}.asIterable()
}
private fun findNextMatch(message: String, previousRange: TextRange?): TextRange? {
val startIndex = previousRange?.endOffset ?: 0
var match: TextRange? = null
for (pattern in patterns) {
val patternIndex = message.indexOf(pattern, startIndex, !isMatchCase)
if (patternIndex < 0) continue
if (match == null || patternIndex <= match.startOffset) {
match = TextRange(patternIndex, patternIndex + pattern.length)
}
}
return match
}
@NonNls
override fun toString(): String {
return "containing at least one of the ${patterns.joinToString(", ") { s -> "'$s'" }} ${caseSensitiveText()}"