diff --git a/src/main/kotlin/app/termora/highlight/KeywordHighlight.kt b/src/main/kotlin/app/termora/highlight/KeywordHighlight.kt index f7c7b78..735f0b7 100644 --- a/src/main/kotlin/app/termora/highlight/KeywordHighlight.kt +++ b/src/main/kotlin/app/termora/highlight/KeywordHighlight.kt @@ -24,6 +24,11 @@ data class KeywordHighlight( */ val matchCase: Boolean = false, + /** + * 是否是正则表达式 + */ + val regex: Boolean = false, + /** * 0 是取前景色 */ diff --git a/src/main/kotlin/app/termora/highlight/KeywordHighlightDialog.kt b/src/main/kotlin/app/termora/highlight/KeywordHighlightDialog.kt index 81dc8ee..776d563 100644 --- a/src/main/kotlin/app/termora/highlight/KeywordHighlightDialog.kt +++ b/src/main/kotlin/app/termora/highlight/KeywordHighlightDialog.kt @@ -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 { diff --git a/src/main/kotlin/app/termora/highlight/KeywordHighlightPaintListener.kt b/src/main/kotlin/app/termora/highlight/KeywordHighlightPaintListener.kt index fae45a0..216294b 100644 --- a/src/main/kotlin/app/termora/highlight/KeywordHighlightPaintListener.kt +++ b/src/main/kotlin/app/termora/highlight/KeywordHighlightPaintListener.kt @@ -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 { + val kinds = mutableListOf() + val iterator = object : Iterator { 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 + ) { + private data class Coords(val row: Int, val col: Int) + private data class MatchResultWithCoords( + val match: String, + val coords: List + ) + + fun find(): List { + + val lines = mutableListOf() + val kinds = mutableListOf() + + for ((index, line) in iterator.withIndex()) { + + lines.add(line) + if (line.wrapped) continue + + val data = mutableListOf>() + 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 { + val flatChars = StringBuilder() + val indexMap = mutableListOf() + + // 拉平成字符串,并记录每个字符的位置 + 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 ) } } -} \ No newline at end of file +} + + diff --git a/src/main/kotlin/app/termora/highlight/NewKeywordHighlightDialog.kt b/src/main/kotlin/app/termora/highlight/NewKeywordHighlightDialog.kt index 232d932..e99b8fd 100644 --- a/src/main/kotlin/app/termora/highlight/NewKeywordHighlightDialog.kt +++ b/src/main/kotlin/app/termora/highlight/NewKeywordHighlightDialog.kt @@ -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,