mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 10:22: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 matchCase: Boolean = false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是正则表达式
|
||||||
|
*/
|
||||||
|
val regex: Boolean = false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 0 是取前景色
|
* 0 是取前景色
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,10 +20,8 @@ class KeywordHighlightDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
private val model = KeywordHighlightTableModel()
|
private val model = KeywordHighlightTableModel()
|
||||||
private val table = FlatTable()
|
private val table = FlatTable()
|
||||||
private val keywordHighlightManager by lazy { KeywordHighlightManager.getInstance() }
|
private val keywordHighlightManager by lazy { KeywordHighlightManager.getInstance() }
|
||||||
private val colorPalette by lazy {
|
private val terminal by lazy { TerminalFactory.getInstance().createTerminal() }
|
||||||
TerminalFactory.getInstance().createTerminal().getTerminalModel()
|
private val colorPalette by lazy { terminal.getTerminalModel().getColorPalette() }
|
||||||
.getColorPalette()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val addBtn = JButton(I18n.getString("termora.new-host.tunneling.add"))
|
private val addBtn = JButton(I18n.getString("termora.new-host.tunneling.add"))
|
||||||
private val editBtn = JButton(I18n.getString("termora.keymgr.edit"))
|
private val editBtn = JButton(I18n.getString("termora.keymgr.edit"))
|
||||||
@@ -130,6 +128,7 @@ class KeywordHighlightDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
|
|
||||||
addBtn.addActionListener {
|
addBtn.addActionListener {
|
||||||
val dialog = NewKeywordHighlightDialog(this, colorPalette)
|
val dialog = NewKeywordHighlightDialog(this, colorPalette)
|
||||||
|
dialog.setLocationRelativeTo(this)
|
||||||
dialog.isVisible = true
|
dialog.isVisible = true
|
||||||
val keywordHighlight = dialog.keywordHighlight
|
val keywordHighlight = dialog.keywordHighlight
|
||||||
if (keywordHighlight != null) {
|
if (keywordHighlight != null) {
|
||||||
@@ -143,6 +142,7 @@ class KeywordHighlightDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
if (row > -1) {
|
if (row > -1) {
|
||||||
var keywordHighlight = model.getKeywordHighlight(row)
|
var keywordHighlight = model.getKeywordHighlight(row)
|
||||||
val dialog = NewKeywordHighlightDialog(this, colorPalette)
|
val dialog = NewKeywordHighlightDialog(this, colorPalette)
|
||||||
|
dialog.setLocationRelativeTo(this)
|
||||||
dialog.keywordTextField.text = keywordHighlight.keyword
|
dialog.keywordTextField.text = keywordHighlight.keyword
|
||||||
dialog.descriptionTextField.text = keywordHighlight.description
|
dialog.descriptionTextField.text = keywordHighlight.description
|
||||||
|
|
||||||
@@ -176,6 +176,7 @@ class KeywordHighlightDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
dialog.underlineCheckBox.isSelected = keywordHighlight.underline
|
dialog.underlineCheckBox.isSelected = keywordHighlight.underline
|
||||||
dialog.lineThroughCheckBox.isSelected = keywordHighlight.lineThrough
|
dialog.lineThroughCheckBox.isSelected = keywordHighlight.lineThrough
|
||||||
dialog.matchCaseBtn.isSelected = keywordHighlight.matchCase
|
dialog.matchCaseBtn.isSelected = keywordHighlight.matchCase
|
||||||
|
dialog.regexBtn.isSelected = keywordHighlight.regex
|
||||||
|
|
||||||
dialog.isVisible = true
|
dialog.isVisible = true
|
||||||
|
|
||||||
@@ -211,6 +212,12 @@ class KeywordHighlightDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
editBtn.isEnabled = table.selectedRowCount > 0
|
editBtn.isEnabled = table.selectedRowCount > 0
|
||||||
deleteBtn.isEnabled = editBtn.isEnabled
|
deleteBtn.isEnabled = editBtn.isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Disposer.register(disposable, object : Disposable {
|
||||||
|
override fun dispose() {
|
||||||
|
terminal.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createCenterPanel(): JComponent {
|
override fun createCenterPanel(): JComponent {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import app.termora.terminal.*
|
|||||||
import app.termora.terminal.panel.TerminalDisplay
|
import app.termora.terminal.panel.TerminalDisplay
|
||||||
import app.termora.terminal.panel.TerminalPaintListener
|
import app.termora.terminal.panel.TerminalPaintListener
|
||||||
import app.termora.terminal.panel.TerminalPanel
|
import app.termora.terminal.panel.TerminalPanel
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import java.awt.Graphics
|
import java.awt.Graphics
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@@ -18,9 +19,10 @@ class KeywordHighlightPaintListener private constructor() : TerminalPaintListene
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val tag = Random.nextInt()
|
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(
|
override fun before(
|
||||||
offset: Int,
|
offset: Int,
|
||||||
@@ -36,7 +38,8 @@ class KeywordHighlightPaintListener private constructor() : TerminalPaintListene
|
|||||||
}
|
}
|
||||||
|
|
||||||
val document = terminal.getDocument()
|
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 var index = offset + 1
|
||||||
private val maxCount = min(index + count, document.getLineCount())
|
private val maxCount = min(index + count, document.getLineCount())
|
||||||
override fun hasNext(): Boolean {
|
override fun hasNext(): Boolean {
|
||||||
@@ -46,8 +49,24 @@ class KeywordHighlightPaintListener private constructor() : TerminalPaintListene
|
|||||||
override fun next(): TerminalLine {
|
override fun next(): TerminalLine {
|
||||||
return document.getLine(index++)
|
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) {
|
for (kind in kinds) {
|
||||||
terminal.getMarkupModel().addHighlighter(
|
terminal.getMarkupModel().addHighlighter(
|
||||||
@@ -77,6 +96,75 @@ class KeywordHighlightPaintListener private constructor() : TerminalPaintListene
|
|||||||
terminal.getMarkupModel().removeAllHighlighters(tag)
|
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(
|
private class KeywordHighlightHighlighter(
|
||||||
range: HighlighterRange, terminal: Terminal,
|
range: HighlighterRange, terminal: Terminal,
|
||||||
@@ -94,3 +182,5 @@ class KeywordHighlightPaintListener private constructor() : TerminalPaintListene
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
package app.termora.highlight
|
package app.termora.highlight
|
||||||
|
|
||||||
import app.termora.DialogWrapper
|
import app.termora.*
|
||||||
import app.termora.DynamicColor
|
|
||||||
import app.termora.I18n
|
|
||||||
import app.termora.Icons
|
|
||||||
import app.termora.Database
|
|
||||||
import app.termora.terminal.ColorPalette
|
import app.termora.terminal.ColorPalette
|
||||||
import app.termora.terminal.TerminalColor
|
import app.termora.terminal.TerminalColor
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
import com.formdev.flatlaf.FlatClientProperties
|
||||||
@@ -46,6 +42,7 @@ class NewKeywordHighlightDialog(
|
|||||||
I18n.getString("termora.highlight.background-color")
|
I18n.getString("termora.highlight.background-color")
|
||||||
)
|
)
|
||||||
val matchCaseBtn = JToggleButton(Icons.matchCase)
|
val matchCaseBtn = JToggleButton(Icons.matchCase)
|
||||||
|
val regexBtn = JToggleButton(Icons.regex)
|
||||||
|
|
||||||
|
|
||||||
private val textColorRevert = JButton(Icons.revert)
|
private val textColorRevert = JButton(Icons.revert)
|
||||||
@@ -85,6 +82,7 @@ class NewKeywordHighlightDialog(
|
|||||||
|
|
||||||
val box = FlatToolBar()
|
val box = FlatToolBar()
|
||||||
box.add(matchCaseBtn)
|
box.add(matchCaseBtn)
|
||||||
|
box.add(regexBtn)
|
||||||
keywordTextField.trailingComponent = box
|
keywordTextField.trailingComponent = box
|
||||||
|
|
||||||
repaintKeywordHighlightView()
|
repaintKeywordHighlightView()
|
||||||
@@ -218,6 +216,7 @@ class NewKeywordHighlightDialog(
|
|||||||
keyword = keywordTextField.text,
|
keyword = keywordTextField.text,
|
||||||
description = descriptionTextField.text,
|
description = descriptionTextField.text,
|
||||||
matchCase = matchCaseBtn.isSelected,
|
matchCase = matchCaseBtn.isSelected,
|
||||||
|
regex = regexBtn.isSelected,
|
||||||
textColor = if (textColor.colorIndex != -1) textColor.colorIndex else textColor.color.toRGB(),
|
textColor = if (textColor.colorIndex != -1) textColor.colorIndex else textColor.color.toRGB(),
|
||||||
backgroundColor = if (backgroundColor.colorIndex != -1) backgroundColor.colorIndex else backgroundColor.color.toRGB(),
|
backgroundColor = if (backgroundColor.colorIndex != -1) backgroundColor.colorIndex else backgroundColor.color.toRGB(),
|
||||||
bold = boldCheckBox.isSelected,
|
bold = boldCheckBox.isSelected,
|
||||||
|
|||||||
Reference in New Issue
Block a user