mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: Highlighter keywords support regex (#507)
This commit is contained in:
@@ -24,6 +24,11 @@ data class KeywordHighlight(
|
||||
*/
|
||||
val matchCase: Boolean = false,
|
||||
|
||||
/**
|
||||
* 是否是正则表达式
|
||||
*/
|
||||
val regex: Boolean = false,
|
||||
|
||||
/**
|
||||
* 0 是取前景色
|
||||
*/
|
||||
|
||||
@@ -20,10 +20,8 @@ class KeywordHighlightDialog(owner: Window) : DialogWrapper(owner) {
|
||||
private val model = KeywordHighlightTableModel()
|
||||
private val table = FlatTable()
|
||||
private val keywordHighlightManager by lazy { KeywordHighlightManager.getInstance() }
|
||||
private val colorPalette by lazy {
|
||||
TerminalFactory.getInstance().createTerminal().getTerminalModel()
|
||||
.getColorPalette()
|
||||
}
|
||||
private val terminal by lazy { TerminalFactory.getInstance().createTerminal() }
|
||||
private val colorPalette by lazy { terminal.getTerminalModel().getColorPalette() }
|
||||
|
||||
private val addBtn = JButton(I18n.getString("termora.new-host.tunneling.add"))
|
||||
private val editBtn = JButton(I18n.getString("termora.keymgr.edit"))
|
||||
@@ -130,6 +128,7 @@ class KeywordHighlightDialog(owner: Window) : DialogWrapper(owner) {
|
||||
|
||||
addBtn.addActionListener {
|
||||
val dialog = NewKeywordHighlightDialog(this, colorPalette)
|
||||
dialog.setLocationRelativeTo(this)
|
||||
dialog.isVisible = true
|
||||
val keywordHighlight = dialog.keywordHighlight
|
||||
if (keywordHighlight != null) {
|
||||
@@ -143,6 +142,7 @@ class KeywordHighlightDialog(owner: Window) : DialogWrapper(owner) {
|
||||
if (row > -1) {
|
||||
var keywordHighlight = model.getKeywordHighlight(row)
|
||||
val dialog = NewKeywordHighlightDialog(this, colorPalette)
|
||||
dialog.setLocationRelativeTo(this)
|
||||
dialog.keywordTextField.text = keywordHighlight.keyword
|
||||
dialog.descriptionTextField.text = keywordHighlight.description
|
||||
|
||||
@@ -176,6 +176,7 @@ class KeywordHighlightDialog(owner: Window) : DialogWrapper(owner) {
|
||||
dialog.underlineCheckBox.isSelected = keywordHighlight.underline
|
||||
dialog.lineThroughCheckBox.isSelected = keywordHighlight.lineThrough
|
||||
dialog.matchCaseBtn.isSelected = keywordHighlight.matchCase
|
||||
dialog.regexBtn.isSelected = keywordHighlight.regex
|
||||
|
||||
dialog.isVisible = true
|
||||
|
||||
@@ -211,6 +212,12 @@ class KeywordHighlightDialog(owner: Window) : DialogWrapper(owner) {
|
||||
editBtn.isEnabled = table.selectedRowCount > 0
|
||||
deleteBtn.isEnabled = editBtn.isEnabled
|
||||
}
|
||||
|
||||
Disposer.register(disposable, object : Disposable {
|
||||
override fun dispose() {
|
||||
terminal.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun createCenterPanel(): JComponent {
|
||||
|
||||
@@ -5,6 +5,7 @@ import app.termora.terminal.*
|
||||
import app.termora.terminal.panel.TerminalDisplay
|
||||
import app.termora.terminal.panel.TerminalPaintListener
|
||||
import app.termora.terminal.panel.TerminalPanel
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.awt.Graphics
|
||||
import kotlin.math.min
|
||||
import kotlin.random.Random
|
||||
@@ -18,9 +19,10 @@ class KeywordHighlightPaintListener private constructor() : TerminalPaintListene
|
||||
}
|
||||
|
||||
private val tag = Random.nextInt()
|
||||
private val log = LoggerFactory.getLogger(KeywordHighlightPaintListener::class.java)
|
||||
}
|
||||
|
||||
private val keywordHighlightManager by lazy { KeywordHighlightManager.getInstance() }
|
||||
private val keywordHighlightManager get() = KeywordHighlightManager.getInstance()
|
||||
|
||||
override fun before(
|
||||
offset: Int,
|
||||
@@ -36,7 +38,8 @@ class KeywordHighlightPaintListener private constructor() : TerminalPaintListene
|
||||
}
|
||||
|
||||
val document = terminal.getDocument()
|
||||
val kinds = SubstrFinder(object : Iterator<TerminalLine> {
|
||||
val kinds = mutableListOf<FindKind>()
|
||||
val iterator = object : Iterator<TerminalLine> {
|
||||
private var index = offset + 1
|
||||
private val maxCount = min(index + count, document.getLineCount())
|
||||
override fun hasNext(): Boolean {
|
||||
@@ -46,8 +49,24 @@ class KeywordHighlightPaintListener private constructor() : TerminalPaintListene
|
||||
override fun next(): TerminalLine {
|
||||
return document.getLine(index++)
|
||||
}
|
||||
}
|
||||
|
||||
}, CharArraySubstr(highlight.keyword.toCharArray())).find(!highlight.matchCase)
|
||||
if (highlight.regex) {
|
||||
try {
|
||||
val regex = if (highlight.matchCase)
|
||||
highlight.keyword.toRegex()
|
||||
else highlight.keyword.toRegex(RegexOption.IGNORE_CASE)
|
||||
RegexFinder(regex, iterator).find()
|
||||
.apply { kinds.addAll(this) }
|
||||
} catch (e: Exception) {
|
||||
if (log.isDebugEnabled) {
|
||||
log.error(e.message, e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SubstrFinder(iterator, CharArraySubstr(highlight.keyword.toCharArray())).find(!highlight.matchCase)
|
||||
.apply { kinds.addAll(this) }
|
||||
}
|
||||
|
||||
for (kind in kinds) {
|
||||
terminal.getMarkupModel().addHighlighter(
|
||||
@@ -77,6 +96,75 @@ class KeywordHighlightPaintListener private constructor() : TerminalPaintListene
|
||||
terminal.getMarkupModel().removeAllHighlighters(tag)
|
||||
}
|
||||
|
||||
private class RegexFinder(
|
||||
private val regex: Regex,
|
||||
private val iterator: Iterator<TerminalLine>
|
||||
) {
|
||||
private data class Coords(val row: Int, val col: Int)
|
||||
private data class MatchResultWithCoords(
|
||||
val match: String,
|
||||
val coords: List<Coords>
|
||||
)
|
||||
|
||||
fun find(): List<FindKind> {
|
||||
|
||||
val lines = mutableListOf<TerminalLine>()
|
||||
val kinds = mutableListOf<FindKind>()
|
||||
|
||||
for ((index, line) in iterator.withIndex()) {
|
||||
|
||||
lines.add(line)
|
||||
if (line.wrapped) continue
|
||||
|
||||
val data = mutableListOf<MutableList<Char>>()
|
||||
for (e in lines) {
|
||||
data.add(mutableListOf())
|
||||
for (c in e.chars()) {
|
||||
if (c.first.isNull) break
|
||||
if (c.first.isSoftHyphen) continue
|
||||
data.last().add(c.first)
|
||||
}
|
||||
}
|
||||
|
||||
lines.clear()
|
||||
|
||||
val resultWithCoords = findMatchesWithCoords(data)
|
||||
if (resultWithCoords.isEmpty()) continue
|
||||
val offset = index - data.size + 1
|
||||
|
||||
for (e in resultWithCoords) {
|
||||
val coords = e.coords
|
||||
if (coords.isEmpty()) continue
|
||||
kinds.add(
|
||||
FindKind(
|
||||
startPosition = Position(coords.first().row + offset + 1, coords.first().col + 1),
|
||||
endPosition = Position(coords.last().row + offset + 1, coords.last().col + 1)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return kinds
|
||||
}
|
||||
|
||||
private fun findMatchesWithCoords(data: List<List<Char>>): List<MatchResultWithCoords> {
|
||||
val flatChars = StringBuilder()
|
||||
val indexMap = mutableListOf<Coords>()
|
||||
|
||||
// 拉平成字符串,并记录每个字符的位置
|
||||
for ((rowIndex, row) in data.withIndex()) {
|
||||
for ((colIndex, char) in row.withIndex()) {
|
||||
flatChars.append(char)
|
||||
indexMap.add(Coords(rowIndex, colIndex))
|
||||
}
|
||||
}
|
||||
|
||||
return regex.findAll(flatChars.toString())
|
||||
.map { MatchResultWithCoords(it.value, indexMap.subList(it.range.first, it.range.last + 1)) }
|
||||
.toList()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class KeywordHighlightHighlighter(
|
||||
range: HighlighterRange, terminal: Terminal,
|
||||
@@ -93,4 +181,6 @@ class KeywordHighlightPaintListener private constructor() : TerminalPaintListene
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
package app.termora.highlight
|
||||
|
||||
import app.termora.DialogWrapper
|
||||
import app.termora.DynamicColor
|
||||
import app.termora.I18n
|
||||
import app.termora.Icons
|
||||
import app.termora.Database
|
||||
import app.termora.*
|
||||
import app.termora.terminal.ColorPalette
|
||||
import app.termora.terminal.TerminalColor
|
||||
import com.formdev.flatlaf.FlatClientProperties
|
||||
@@ -46,6 +42,7 @@ class NewKeywordHighlightDialog(
|
||||
I18n.getString("termora.highlight.background-color")
|
||||
)
|
||||
val matchCaseBtn = JToggleButton(Icons.matchCase)
|
||||
val regexBtn = JToggleButton(Icons.regex)
|
||||
|
||||
|
||||
private val textColorRevert = JButton(Icons.revert)
|
||||
@@ -85,6 +82,7 @@ class NewKeywordHighlightDialog(
|
||||
|
||||
val box = FlatToolBar()
|
||||
box.add(matchCaseBtn)
|
||||
box.add(regexBtn)
|
||||
keywordTextField.trailingComponent = box
|
||||
|
||||
repaintKeywordHighlightView()
|
||||
@@ -218,6 +216,7 @@ class NewKeywordHighlightDialog(
|
||||
keyword = keywordTextField.text,
|
||||
description = descriptionTextField.text,
|
||||
matchCase = matchCaseBtn.isSelected,
|
||||
regex = regexBtn.isSelected,
|
||||
textColor = if (textColor.colorIndex != -1) textColor.colorIndex else textColor.color.toRGB(),
|
||||
backgroundColor = if (backgroundColor.colorIndex != -1) backgroundColor.colorIndex else backgroundColor.color.toRGB(),
|
||||
bold = boldCheckBox.isSelected,
|
||||
|
||||
Reference in New Issue
Block a user