feat: keyword highlight set

This commit is contained in:
hstyi
2025-07-18 21:47:57 +08:00
committed by GitHub
parent f99e3e9147
commit ae7730fb35
20 changed files with 545 additions and 259 deletions

View File

@@ -8,6 +8,16 @@ import org.apache.commons.lang3.StringUtils
data class KeywordHighlight(
val id: String = randomUUID(),
/**
* Set id默认 0
*/
val parentId: String = "0",
/**
* [KeywordHighlightType]
*/
val type: KeywordHighlightType = KeywordHighlightType.Highlight,
/**
* 关键词
*/

View File

@@ -1,12 +1,9 @@
package app.termora.highlight
import app.termora.ApplicationScope
import app.termora.Disposable
import app.termora.Disposer
import app.termora.*
import app.termora.account.Account
import app.termora.account.AccountExtension
import app.termora.account.AccountManager
import app.termora.assertEventDispatchThread
import app.termora.database.DataType
import app.termora.database.DatabaseChangedExtension
import app.termora.plugin.internal.extension.DynamicExtensionHandler
@@ -20,7 +17,7 @@ import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.min
import kotlin.random.Random
class KeywordHighlightPaintListener private constructor() : TerminalPaintListener, Disposable {
internal class KeywordHighlightPaintListener private constructor() : TerminalPaintListener, Disposable {
companion object {
fun getInstance(): KeywordHighlightPaintListener {
@@ -93,10 +90,21 @@ class KeywordHighlightPaintListener private constructor() : TerminalPaintListene
}
}
// 默认情况是:默认集
var keywordHighlightSetId = "0"
val terminalModel = terminal.getTerminalModel()
if (terminalModel.hasData(HostTerminalTab.Host)) {
val host = terminalModel.getData(HostTerminalTab.Host)
keywordHighlightSetId = host.options.extras["keywordHighlightSetId"] ?: keywordHighlightSetId
}
// -1 表示不使用高亮集
if (keywordHighlightSetId == "-1") return
for (highlight in keywordHighlights) {
if (highlight.enabled.not()) {
continue
}
if (highlight.enabled.not()) continue
if (highlight.type != KeywordHighlightType.Highlight) continue
if (keywordHighlightSetId != highlight.parentId) continue
val document = terminal.getDocument()
val kinds = mutableListOf<FindKind>()

View File

@@ -4,6 +4,8 @@ import app.termora.*
import app.termora.Application.ohMyJson
import app.termora.account.AccountOwner
import app.termora.terminal.TerminalColor
import com.formdev.flatlaf.extras.components.FlatPopupMenu
import com.formdev.flatlaf.extras.components.FlatTabbedPane
import com.formdev.flatlaf.extras.components.FlatTable
import com.jgoodies.forms.builder.FormBuilder
import com.jgoodies.forms.layout.FormLayout
@@ -12,12 +14,13 @@ import org.apache.commons.lang3.exception.ExceptionUtils
import java.awt.BorderLayout
import java.awt.Color
import java.awt.Component
import java.awt.event.ActionEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.io.File
import java.nio.charset.StandardCharsets
import javax.swing.*
import javax.swing.border.EmptyBorder
import javax.swing.JTabbedPane.SCROLL_TAB_LAYOUT
import javax.swing.table.DefaultTableCellRenderer
import javax.swing.table.TableCellRenderer
@@ -25,17 +28,11 @@ import javax.swing.table.TableCellRenderer
class KeywordHighlightPanel(private val accountOwner: AccountOwner) : JPanel(BorderLayout()), Disposable {
private val owner get() = SwingUtilities.getWindowAncestor(this)
private val model = KeywordHighlightTableModel(accountOwner)
private val table = FlatTable()
private val keywordHighlightManager get() = KeywordHighlightManager.getInstance()
private val terminal = TerminalFactory.getInstance().createTerminal()
private val colorPalette get() = terminal.getTerminalModel().getColorPalette()
private val addBtn = JButton(I18n.getString("termora.new-host.tunneling.add"))
private val editBtn = JButton(I18n.getString("termora.keymgr.edit"))
private val deleteBtn = JButton(I18n.getString("termora.remove"))
private val importBtn = JButton(I18n.getString("termora.keymgr.import"))
private val exportBtn = JButton(I18n.getString("termora.keymgr.export"))
private val tabbed = FlatTabbedPane()
init {
initView()
@@ -43,205 +40,50 @@ class KeywordHighlightPanel(private val accountOwner: AccountOwner) : JPanel(Bor
}
private fun initView() {
model.addColumn(I18n.getString("termora.highlight.keyword"))
model.addColumn(I18n.getString("termora.highlight.preview"))
model.addColumn(I18n.getString("termora.highlight.description"))
table.fillsViewportHeight = true
table.tableHeader.reorderingAllowed = false
table.model = model
editBtn.isEnabled = false
deleteBtn.isEnabled = false
tabbed.styleMap = mapOf(
"focusColor" to DynamicColor("TabbedPane.background"),
"hoverColor" to DynamicColor("TabbedPane.background"),
"inactiveUnderlineColor" to DynamicColor("TabbedPane.underlineColor"),
)
tabbed.isHasFullBorder = false
tabbed.tabPlacement = JTabbedPane.LEFT
tabbed.tabType = FlatTabbedPane.TabType.underlined
tabbed.isTabsClosable = false
tabbed.tabLayoutPolicy = SCROLL_TAB_LAYOUT
tabbed.tabWidthMode = FlatTabbedPane.TabWidthMode.preferred
tabbed.isFocusable = false
tabbed.tabHeight = UIManager.getInt("TabbedPane.tabHeight") - 8
// keyword
table.columnModel.getColumn(0).setCellRenderer(object : JCheckBox(), TableCellRenderer {
init {
horizontalAlignment = LEFT
verticalAlignment = CENTER
}
val sets = keywordHighlightManager.getKeywordHighlights(accountOwner.id)
.filter { it.type == KeywordHighlightType.Set }
.sortedBy { it.sort }
override fun getTableCellRendererComponent(
table: JTable,
value: Any?,
isSelected: Boolean,
hasFocus: Boolean,
row: Int,
column: Int
): Component {
if (value is KeywordHighlight) {
text = value.keyword
super.setSelected(value.enabled)
}
if (isSelected) {
foreground = table.selectionForeground
super.setBackground(table.selectionBackground)
} else {
foreground = table.foreground
background = table.background
}
return this
}
tabbed.addTab(I18n.getString("termora.highlight.default-set"), KeywordHighlightMiniPanel("0"))
})
// preview
table.columnModel.getColumn(1).setCellRenderer(object : DefaultTableCellRenderer() {
private val keywordHighlightView = KeywordHighlightView(0)
init {
keywordHighlightView.border = null
}
override fun getTableCellRendererComponent(
table: JTable,
value: Any?,
isSelected: Boolean,
hasFocus: Boolean,
row: Int,
column: Int
): Component {
if (value is KeywordHighlight) {
keywordHighlightView.setKeywordHighlight(value, colorPalette)
if (isSelected) keywordHighlightView.backgroundColor = table.selectionBackground
return keywordHighlightView
}
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column)
}
})
add(createCenterPanel(), BorderLayout.CENTER)
for (highlight in sets) {
tabbed.addTab(highlight.keyword, KeywordHighlightMiniPanel(highlight.id))
}
add(tabbed, BorderLayout.CENTER)
}
private fun initEvents() {
table.addMouseListener(object : MouseAdapter() {
tabbed.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
if (SwingUtilities.isLeftMouseButton(e)) {
val row = table.rowAtPoint(e.point)
val column = table.columnAtPoint(e.point)
if (row >= 0 && column == 0) {
val keywordHighlight = model.getKeywordHighlight(row)
keywordHighlightManager.addKeywordHighlight(
keywordHighlight.copy(enabled = !keywordHighlight.enabled),
accountOwner
)
model.fireTableCellUpdated(row, column)
}
if (SwingUtilities.isRightMouseButton(e).not()) {
return
}
val index = tabbed.indexAtLocation(e.x, e.y)
if (index < 0) return
tabbed.selectedIndex = index
showContextmenu(index, e)
}
})
addBtn.addActionListener {
val dialog = NewKeywordHighlightDialog(owner, colorPalette)
dialog.setLocationRelativeTo(owner)
dialog.isVisible = true
val keywordHighlight = dialog.keywordHighlight
if (keywordHighlight != null) {
keywordHighlightManager.addKeywordHighlight(keywordHighlight, accountOwner)
model.fireTableRowsInserted(model.rowCount - 1, model.rowCount)
}
}
editBtn.addActionListener {
val row = table.selectedRow
if (row > -1) {
var keywordHighlight = model.getKeywordHighlight(row)
val dialog = NewKeywordHighlightDialog(owner, colorPalette)
dialog.setLocationRelativeTo(owner)
dialog.keywordTextField.text = keywordHighlight.keyword
dialog.descriptionTextField.text = keywordHighlight.description
if (keywordHighlight.textColor <= 16) {
if (keywordHighlight.textColor == 0) {
dialog.textColor.color = Color(colorPalette.getColor(TerminalColor.Basic.FOREGROUND))
} else {
dialog.textColor.color = Color(colorPalette.getXTerm256Color(keywordHighlight.textColor))
}
dialog.textColor.colorIndex = keywordHighlight.textColor
} else {
dialog.textColor.color = Color(keywordHighlight.textColor)
dialog.textColor.colorIndex = -1
}
if (keywordHighlight.backgroundColor <= 16) {
if (keywordHighlight.backgroundColor == 0) {
dialog.backgroundColor.color = Color(colorPalette.getColor(TerminalColor.Basic.BACKGROUND))
} else {
dialog.backgroundColor.color =
Color(colorPalette.getXTerm256Color(keywordHighlight.backgroundColor))
}
dialog.backgroundColor.colorIndex = keywordHighlight.backgroundColor
} else {
dialog.backgroundColor.color = Color(keywordHighlight.backgroundColor)
dialog.backgroundColor.colorIndex = -1
}
dialog.boldCheckBox.isSelected = keywordHighlight.bold
dialog.italicCheckBox.isSelected = keywordHighlight.italic
dialog.underlineCheckBox.isSelected = keywordHighlight.underline
dialog.lineThroughCheckBox.isSelected = keywordHighlight.lineThrough
dialog.matchCaseBtn.isSelected = keywordHighlight.matchCase
dialog.regexBtn.isSelected = keywordHighlight.regex
dialog.isVisible = true
val value = dialog.keywordHighlight
if (value != null) {
keywordHighlight = value.copy(id = keywordHighlight.id, sort = keywordHighlight.sort)
keywordHighlightManager.addKeywordHighlight(keywordHighlight, accountOwner)
model.fireTableRowsUpdated(row, row)
}
}
}
deleteBtn.addActionListener {
if (table.selectedRowCount > 0) {
if (OptionPane.showConfirmDialog(
SwingUtilities.getWindowAncestor(this),
I18n.getString("termora.keymgr.delete-warning"),
messageType = JOptionPane.WARNING_MESSAGE
) == JOptionPane.YES_OPTION
) {
val rows = table.selectedRows.sorted().reversed()
for (row in rows) {
val id = model.getKeywordHighlight(row).id
keywordHighlightManager.removeKeywordHighlight(id)
model.fireTableRowsDeleted(row, row)
}
}
}
}
table.selectionModel.addListSelectionListener {
editBtn.isEnabled = table.selectedRowCount > 0
deleteBtn.isEnabled = editBtn.isEnabled
}
exportBtn.addActionListener {
val fileChooser = FileChooser()
fileChooser.fileSelectionMode = JFileChooser.FILES_ONLY
fileChooser.win32Filters.add(Pair("All files", listOf("*")))
fileChooser.showSaveDialog(owner, "highlights.json").thenAccept { file ->
file?.outputStream()?.use {
val highlights = keywordHighlightManager.getKeywordHighlights(accountOwner.id)
.map { e -> e.copy(id = randomUUID()) }
IOUtils.write(ohMyJson.encodeToString(highlights), it, StandardCharsets.UTF_8)
}
}
}
importBtn.addActionListener {
val chooser = FileChooser()
chooser.osxAllowedFileTypes = listOf("json")
chooser.allowsMultiSelection = false
chooser.win32Filters.add(Pair("JSON files", listOf("json")))
chooser.fileSelectionMode = JFileChooser.FILES_ONLY
chooser.showOpenDialog(owner)
.thenAccept { if (it.isNotEmpty()) SwingUtilities.invokeLater { importKeywordHighlights(it.first()) } }
}
Disposer.register(this, object : Disposable {
override fun dispose() {
terminal.close()
@@ -249,54 +91,343 @@ class KeywordHighlightPanel(private val accountOwner: AccountOwner) : JPanel(Bor
})
}
private fun importKeywordHighlights(file: File) {
try {
val highlights = ohMyJson.decodeFromString<List<KeywordHighlight>>(file.readText())
.map { it.copy(id = randomUUID()) }
for (highlight in highlights) {
private fun showContextmenu(index: Int, e: MouseEvent) {
var offset = 0
for (i in 0..index) offset += tabbed.ui.getTabBounds(tabbed, i).height
val popupMenu = FlatPopupMenu()
popupMenu.add(I18n.getString("termora.new-host.tunneling.add")).addActionListener(object : AbstractAction() {
override fun actionPerformed(e: ActionEvent) {
val text = OptionPane.showInputDialog(owner) ?: return
if (text.isBlank()) return
val highlight = KeywordHighlight(
keyword = text,
type = KeywordHighlightType.Set
)
keywordHighlightManager.addKeywordHighlight(highlight, accountOwner)
model.fireTableRowsInserted(model.rowCount - 1, model.rowCount)
tabbed.addTab(text, KeywordHighlightMiniPanel(highlight.id))
tabbed.selectedIndex = tabbed.tabCount - 1
}
} catch (e: Exception) {
OptionPane.showMessageDialog(
owner,
message = e.message ?: ExceptionUtils.getRootCauseMessage(e),
messageType = JOptionPane.ERROR_MESSAGE,
})
if (index > 0) {
popupMenu.add(I18n.getString("termora.tabbed.contextmenu.rename"))
.addActionListener(object : AbstractAction() {
override fun actionPerformed(e: ActionEvent) {
val tab = tabbed.getComponentAt(index) as? KeywordHighlightMiniPanel ?: return
val title = tabbed.getTitleAt(index) ?: return
val text = OptionPane.showInputDialog(owner, value = title) ?: return
if (text.isBlank() || title == text) return
val highlight = keywordHighlightManager.getKeywordHighlights(accountOwner.id)
.filter { it.type == KeywordHighlightType.Set }
.firstOrNull { it.id == tab.setId } ?: return
keywordHighlightManager.addKeywordHighlight(
highlight.copy(
updateDate = System.currentTimeMillis(),
keyword = text
), accountOwner
)
tabbed.setTitleAt(index, text)
}
})
popupMenu.add(I18n.getString("termora.remove")).addActionListener(object : AbstractAction() {
override fun actionPerformed(e: ActionEvent) {
val tab = tabbed.getComponentAt(index) as? KeywordHighlightMiniPanel ?: return
if (OptionPane.showConfirmDialog(
owner,
I18n.getString("termora.keymgr.delete-warning"),
I18n.getString("termora.remove"),
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE
) != JOptionPane.YES_OPTION
) return
tabbed.removeTabAt(index)
for (highlight in keywordHighlightManager.getKeywordHighlights(accountOwner.id)) {
if (highlight.parentId == tab.setId || highlight.id == tab.setId) {
keywordHighlightManager.removeKeywordHighlight(highlight.id)
}
}
}
})
}
popupMenu.show(e.component, e.x, e.y)
}
private inner class KeywordHighlightMiniPanel(val setId: String) : JPanel(BorderLayout()) {
private val addBtn = JButton(I18n.getString("termora.new-host.tunneling.add"))
private val editBtn = JButton(I18n.getString("termora.keymgr.edit"))
private val deleteBtn = JButton(I18n.getString("termora.remove"))
private val importBtn = JButton(I18n.getString("termora.keymgr.import"))
private val exportBtn = JButton(I18n.getString("termora.keymgr.export"))
private val model = KeywordHighlightTableModel(accountOwner, setId)
private val table = FlatTable()
init {
initView()
initEvents()
}
private fun initView() {
model.addColumn(I18n.getString("termora.highlight.keyword"))
model.addColumn(I18n.getString("termora.highlight.preview"))
model.addColumn(I18n.getString("termora.highlight.description"))
table.fillsViewportHeight = true
table.tableHeader.reorderingAllowed = false
table.model = model
editBtn.isEnabled = false
deleteBtn.isEnabled = false
// keyword
table.columnModel.getColumn(0).setCellRenderer(object : JCheckBox(), TableCellRenderer {
init {
horizontalAlignment = LEFT
verticalAlignment = CENTER
}
override fun getTableCellRendererComponent(
table: JTable,
value: Any?,
isSelected: Boolean,
hasFocus: Boolean,
row: Int,
column: Int
): Component {
if (value is KeywordHighlight) {
text = value.keyword
super.setSelected(value.enabled)
}
if (isSelected) {
foreground = table.selectionForeground
super.setBackground(table.selectionBackground)
} else {
foreground = table.foreground
background = table.background
}
return this
}
})
// preview
table.columnModel.getColumn(1).setCellRenderer(object : DefaultTableCellRenderer() {
private val keywordHighlightView = KeywordHighlightView(0)
init {
keywordHighlightView.border = null
}
override fun getTableCellRendererComponent(
table: JTable,
value: Any?,
isSelected: Boolean,
hasFocus: Boolean,
row: Int,
column: Int
): Component {
if (value is KeywordHighlight) {
keywordHighlightView.setKeywordHighlight(value, colorPalette)
if (isSelected) keywordHighlightView.backgroundColor = table.selectionBackground
return keywordHighlightView
}
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column)
}
})
add(createCenterPanel(), BorderLayout.CENTER)
}
private fun initEvents() {
table.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
if (SwingUtilities.isLeftMouseButton(e)) {
val row = table.rowAtPoint(e.point)
val column = table.columnAtPoint(e.point)
if (row >= 0 && column == 0) {
val keywordHighlight = model.getKeywordHighlight(row)
keywordHighlightManager.addKeywordHighlight(
keywordHighlight.copy(enabled = keywordHighlight.enabled.not()),
accountOwner
)
model.fireTableCellUpdated(row, column)
} else if (row < 0) {
table.clearSelection()
}
}
}
})
addBtn.addActionListener {
val dialog = NewKeywordHighlightDialog(owner, colorPalette)
dialog.setLocationRelativeTo(owner)
dialog.isVisible = true
val keywordHighlight = dialog.keywordHighlight
if (keywordHighlight != null) {
keywordHighlightManager.addKeywordHighlight(keywordHighlight.copy(parentId = setId), accountOwner)
model.fireTableRowsInserted(model.rowCount - 1, model.rowCount)
}
}
editBtn.addActionListener {
val row = table.selectedRow
if (row > -1) {
var keywordHighlight = model.getKeywordHighlight(row)
val dialog = NewKeywordHighlightDialog(owner, colorPalette)
dialog.setLocationRelativeTo(owner)
dialog.keywordTextField.text = keywordHighlight.keyword
dialog.descriptionTextField.text = keywordHighlight.description
if (keywordHighlight.textColor <= 16) {
if (keywordHighlight.textColor == 0) {
dialog.textColor.color = Color(colorPalette.getColor(TerminalColor.Basic.FOREGROUND))
} else {
dialog.textColor.color = Color(colorPalette.getXTerm256Color(keywordHighlight.textColor))
}
dialog.textColor.colorIndex = keywordHighlight.textColor
} else {
dialog.textColor.color = Color(keywordHighlight.textColor)
dialog.textColor.colorIndex = -1
}
if (keywordHighlight.backgroundColor <= 16) {
if (keywordHighlight.backgroundColor == 0) {
dialog.backgroundColor.color = Color(colorPalette.getColor(TerminalColor.Basic.BACKGROUND))
} else {
dialog.backgroundColor.color =
Color(colorPalette.getXTerm256Color(keywordHighlight.backgroundColor))
}
dialog.backgroundColor.colorIndex = keywordHighlight.backgroundColor
} else {
dialog.backgroundColor.color = Color(keywordHighlight.backgroundColor)
dialog.backgroundColor.colorIndex = -1
}
dialog.boldCheckBox.isSelected = keywordHighlight.bold
dialog.italicCheckBox.isSelected = keywordHighlight.italic
dialog.underlineCheckBox.isSelected = keywordHighlight.underline
dialog.lineThroughCheckBox.isSelected = keywordHighlight.lineThrough
dialog.matchCaseBtn.isSelected = keywordHighlight.matchCase
dialog.regexBtn.isSelected = keywordHighlight.regex
dialog.isVisible = true
val value = dialog.keywordHighlight
if (value != null) {
keywordHighlight = value.copy(
id = keywordHighlight.id, parentId = setId,
sort = keywordHighlight.sort
)
keywordHighlightManager.addKeywordHighlight(keywordHighlight, accountOwner)
model.fireTableRowsUpdated(row, row)
}
}
}
deleteBtn.addActionListener {
if (table.selectedRowCount > 0) {
if (OptionPane.showConfirmDialog(
SwingUtilities.getWindowAncestor(this),
I18n.getString("termora.keymgr.delete-warning"),
messageType = JOptionPane.WARNING_MESSAGE
) == JOptionPane.YES_OPTION
) {
val rows = table.selectedRows.sorted().reversed()
for (row in rows) {
val id = model.getKeywordHighlight(row).id
keywordHighlightManager.removeKeywordHighlight(id)
model.fireTableRowsDeleted(row, row)
}
}
}
}
table.selectionModel.addListSelectionListener {
editBtn.isEnabled = table.selectedRowCount > 0
deleteBtn.isEnabled = editBtn.isEnabled
}
exportBtn.addActionListener {
val fileChooser = FileChooser()
fileChooser.fileSelectionMode = JFileChooser.FILES_ONLY
fileChooser.win32Filters.add(Pair("All files", listOf("*")))
fileChooser.showSaveDialog(owner, "highlights.json").thenAccept { file ->
file?.outputStream()?.use {
val highlights = keywordHighlightManager.getKeywordHighlights(accountOwner.id)
.filter { e -> e.parentId == setId }
.map { e -> e.copy(id = randomUUID()) }
IOUtils.write(ohMyJson.encodeToString(highlights), it, StandardCharsets.UTF_8)
}
}
}
importBtn.addActionListener {
val chooser = FileChooser()
chooser.osxAllowedFileTypes = listOf("json")
chooser.allowsMultiSelection = false
chooser.win32Filters.add(Pair("JSON files", listOf("json")))
chooser.fileSelectionMode = JFileChooser.FILES_ONLY
chooser.showOpenDialog(owner)
.thenAccept { if (it.isNotEmpty()) SwingUtilities.invokeLater { importKeywordHighlights(it.first()) } }
}
}
private fun importKeywordHighlights(file: File) {
try {
val highlights = ohMyJson.decodeFromString<List<KeywordHighlight>>(file.readText())
.map { it.copy(id = randomUUID(), parentId = setId) }
for (highlight in highlights) {
keywordHighlightManager.addKeywordHighlight(highlight, accountOwner)
model.fireTableRowsInserted(model.rowCount - 1, model.rowCount)
}
} catch (e: Exception) {
OptionPane.showMessageDialog(
owner,
message = e.message ?: ExceptionUtils.getRootCauseMessage(e),
messageType = JOptionPane.ERROR_MESSAGE,
)
}
}
private fun createCenterPanel(): JComponent {
val panel = JPanel(BorderLayout())
panel.add(JScrollPane(table).apply {
border = BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(8, 8, 8, 0),
BorderFactory.createMatteBorder(1, 1, 1, 1, DynamicColor.BorderColor)
)
}, BorderLayout.CENTER)
var rows = 1
val step = 2
val formMargin = "4dlu"
val layout = FormLayout(
"default:grow",
"pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
)
panel.add(
FormBuilder.create().layout(layout)
.border(BorderFactory.createEmptyBorder(8, 8, 8, 8))
.add(addBtn).xy(1, rows).apply { rows += step }
.add(editBtn).xy(1, rows).apply { rows += step }
.add(deleteBtn).xy(1, rows).apply { rows += step }
.add(importBtn).xy(1, rows).apply { rows += step }
.add(exportBtn).xy(1, rows).apply { rows += step }
.build(),
BorderLayout.EAST)
return panel
}
}
private fun createCenterPanel(): JComponent {
val panel = JPanel(BorderLayout())
panel.add(JScrollPane(table).apply {
border = BorderFactory.createMatteBorder(1, 1, 1, 1, DynamicColor.BorderColor)
}, BorderLayout.CENTER)
var rows = 1
val step = 2
val formMargin = "4dlu"
val layout = FormLayout(
"default:grow",
"pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
)
panel.add(
FormBuilder.create().layout(layout).padding(EmptyBorder(0, 12, 0, 0))
.add(addBtn).xy(1, rows).apply { rows += step }
.add(editBtn).xy(1, rows).apply { rows += step }
.add(deleteBtn).xy(1, rows).apply { rows += step }
.add(importBtn).xy(1, rows).apply { rows += step }
.add(exportBtn).xy(1, rows).apply { rows += step }
.build(),
BorderLayout.EAST)
panel.border = BorderFactory.createEmptyBorder(
12,
12, 12, 12
)
return panel
}
}

View File

@@ -3,8 +3,14 @@ package app.termora.highlight
import app.termora.account.AccountOwner
import javax.swing.table.DefaultTableModel
class KeywordHighlightTableModel(private val accountOwner: AccountOwner) : DefaultTableModel() {
private val rows get() = KeywordHighlightManager.getInstance().getKeywordHighlights(accountOwner.id)
class KeywordHighlightTableModel(
private val accountOwner: AccountOwner,
private val setId: String
) : DefaultTableModel() {
private val rows
get() = KeywordHighlightManager.getInstance()
.getKeywordHighlights(accountOwner.id).filter { it.parentId == setId }
.filter { it.type == KeywordHighlightType.Highlight }
override fun isCellEditable(row: Int, column: Int): Boolean {
return false

View File

@@ -0,0 +1,6 @@
package app.termora.highlight
enum class KeywordHighlightType {
Set,
Highlight,
}

View File

@@ -3,6 +3,10 @@ package app.termora.plugin.internal
import app.termora.*
import app.termora.OptionsPane.Companion.FORM_MARGIN
import app.termora.OptionsPane.Option
import app.termora.account.AccountOwner
import app.termora.highlight.KeywordHighlight
import app.termora.highlight.KeywordHighlightManager
import app.termora.highlight.KeywordHighlightType
import app.termora.plugin.internal.telnet.TelnetHostOptionsPane.Backspace
import com.formdev.flatlaf.extras.components.FlatTabbedPane
import com.formdev.flatlaf.ui.FlatTextBorder
@@ -24,6 +28,8 @@ class BasicTerminalOption() : JPanel(BorderLayout()), Option {
var showBackspaceComboBox: Boolean = false
var showCharacterAtATimeTextField: Boolean = false
var showAltModifierComboBox: Boolean = true
var showHighlightSet: Boolean = true
var accountOwner: AccountOwner? = null
val charsetComboBox = JComboBox<String>()
val startupCommandTextField = OutlineTextField()
@@ -32,6 +38,7 @@ class BasicTerminalOption() : JPanel(BorderLayout()), Option {
val loginScripts = mutableListOf<LoginScript>()
val backspaceComboBox = JComboBox<Backspace>()
val altModifierComboBox = JComboBox<AltKeyModifier>()
val highlightSetComboBox = JComboBox<KeywordHighlight>()
val characterAtATimeTextField = YesOrNoComboBox()
@@ -82,6 +89,36 @@ class BasicTerminalOption() : JPanel(BorderLayout()), Option {
}
}
val accountOwner = this.accountOwner
if (showHighlightSet && accountOwner != null) {
val highlights = KeywordHighlightManager.getInstance()
.getKeywordHighlights(accountOwner.id)
.filter { it.type == KeywordHighlightType.Set }
highlightSetComboBox.addItem(KeywordHighlight(id = "-1", keyword = "None"))
val defaultHighlight = KeywordHighlight(id = "0", keyword = I18n.getString("termora.highlight.default-set"))
highlightSetComboBox.addItem(defaultHighlight)
for (highlight in highlights) {
highlightSetComboBox.addItem(highlight)
}
highlightSetComboBox.selectedItem = defaultHighlight
highlightSetComboBox.renderer = object : DefaultListCellRenderer() {
override fun getListCellRendererComponent(
list: JList<*>?,
value: Any?,
index: Int,
isSelected: Boolean,
cellHasFocus: Boolean
): Component? {
var text = value?.toString() ?: value
if (value is KeywordHighlight) {
text = value.keyword
}
return super.getListCellRendererComponent(list, text, index, isSelected, cellHasFocus)
}
}
}
if (showBackspaceComboBox) {
backspaceComboBox.addItem(Backspace.Delete)
@@ -136,9 +173,10 @@ class BasicTerminalOption() : JPanel(BorderLayout()), Option {
private fun getCenterComponent(): JComponent {
val layout = FormLayout(
"left:pref, $FORM_MARGIN, default:grow",
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
)
val accountOwner = this.accountOwner
var rows = 1
val step = 2
val builder = FormBuilder.create().layout(layout)
@@ -151,6 +189,11 @@ class BasicTerminalOption() : JPanel(BorderLayout()), Option {
.add(charsetComboBox).xy(3, rows).apply { rows += step }
}
if (showHighlightSet && accountOwner != null) {
builder.add("${I18n.getString("termora.highlight")}:").xy(1, rows)
.add(highlightSetComboBox).xy(3, rows).apply { rows += step }
}
if (showAltModifierComboBox) {
builder.add("${I18n.getString("termora.new-host.terminal.alt-modifier")}:").xy(1, rows)
.add(altModifierComboBox).xy(3, rows).apply { rows += step }

View File

@@ -4,6 +4,8 @@ import app.termora.Host
import app.termora.Options
import app.termora.OptionsPane
import app.termora.SerialComm
import app.termora.account.AccountOwner
import app.termora.highlight.KeywordHighlight
import app.termora.plugin.internal.AltKeyModifier
import app.termora.plugin.internal.BasicGeneralOption
import app.termora.plugin.internal.BasicTerminalOption
@@ -12,12 +14,14 @@ import java.awt.Window
import javax.swing.JTextField
import javax.swing.SwingUtilities
internal open class LocalHostOptionsPane : OptionsPane() {
internal open class LocalHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPane() {
protected val generalOption = BasicGeneralOption()
private val terminalOption = BasicTerminalOption().apply {
showCharsetComboBox = true
showEnvironmentTextArea = true
showStartupCommandTextField = true
showHighlightSet = true
accountOwner = this@LocalHostOptionsPane.accountOwner
init()
}
protected val owner: Window get() = SwingUtilities.getWindowAncestor(this)
@@ -43,6 +47,8 @@ internal open class LocalHostOptionsPane : OptionsPane() {
extras = mutableMapOf(
"altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString()
?: AltKeyModifier.EightBit.name),
"keywordHighlightSetId" to ((terminalOption.highlightSetComboBox.selectedItem as? KeywordHighlight)?.id
?: "-1"),
)
)
@@ -66,6 +72,15 @@ internal open class LocalHostOptionsPane : OptionsPane() {
val altModifier = host.options.extras["altModifier"] ?: AltKeyModifier.EightBit.name
terminalOption.altModifierComboBox.selectedItem = runCatching { AltKeyModifier.valueOf(altModifier) }
.getOrNull() ?: AltKeyModifier.EightBit
val keywordHighlightSetId = host.options.extras["keywordHighlightSetId"]
for (i in 0 until terminalOption.highlightSetComboBox.itemCount) {
val item = terminalOption.highlightSetComboBox.getItemAt(i)
if (item.id == keywordHighlightSetId) {
terminalOption.highlightSetComboBox.selectedItem = item
break
}
}
}
fun validateFields(): Boolean {

View File

@@ -2,12 +2,13 @@ package app.termora.plugin.internal.local
import app.termora.Disposer
import app.termora.Host
import app.termora.account.AccountOwner
import app.termora.protocol.ProtocolHostPanel
import java.awt.BorderLayout
internal class LocalProtocolHostPanel : ProtocolHostPanel() {
internal class LocalProtocolHostPanel(accountOwner: AccountOwner) : ProtocolHostPanel() {
private val pane = LocalHostOptionsPane()
private val pane = LocalHostOptionsPane(accountOwner)
init {
initView()

View File

@@ -16,7 +16,7 @@ internal class LocalProtocolHostPanelExtension private constructor() : ProtocolH
}
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
return LocalProtocolHostPanel()
return LocalProtocolHostPanel(accountOwner)
}
override fun ordered(): Long {

View File

@@ -2,6 +2,7 @@ package app.termora.plugin.internal.ssh
import app.termora.*
import app.termora.account.AccountOwner
import app.termora.highlight.KeywordHighlight
import app.termora.keymgr.KeyManager
import app.termora.keymgr.KeyManagerDialog
import app.termora.plugin.internal.AltKeyModifier
@@ -37,6 +38,8 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
showEnvironmentTextArea = true
showStartupCommandTextField = true
showHeartbeatIntervalTextField = true
showHighlightSet = true
accountOwner = this@SSHHostOptionsPane.accountOwner
init()
}
private val jumpHostsOption = JumpHostsOption()
@@ -108,6 +111,8 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
extras = mutableMapOf(
"altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString()
?: AltKeyModifier.EightBit.name),
"keywordHighlightSetId" to ((terminalOption.highlightSetComboBox.selectedItem as? KeywordHighlight)?.id
?: "-1"),
)
)
@@ -157,6 +162,17 @@ internal class SSHHostOptionsPane(private val accountOwner: AccountOwner) : Opti
terminalOption.altModifierComboBox.selectedItem = runCatching { AltKeyModifier.valueOf(altModifier) }
.getOrNull() ?: AltKeyModifier.EightBit
val keywordHighlightSetId = host.options.extras["keywordHighlightSetId"]
for (i in 0 until terminalOption.highlightSetComboBox.itemCount) {
val item = terminalOption.highlightSetComboBox.getItemAt(i)
if (item.id == keywordHighlightSetId) {
terminalOption.highlightSetComboBox.selectedItem = item
break
}
}
tunnelingOption.tunnelings.addAll(host.tunnelings)
tunnelingOption.x11ForwardingCheckBox.isSelected = host.options.enableX11Forwarding
tunnelingOption.x11ServerTextField.text = StringUtils.defaultIfBlank(host.options.x11Forwarding, "localhost:0")

View File

@@ -2,6 +2,7 @@ package app.termora.plugin.internal.telnet
import app.termora.*
import app.termora.account.AccountOwner
import app.termora.highlight.KeywordHighlight
import app.termora.plugin.internal.AltKeyModifier
import app.termora.plugin.internal.BasicProxyOption
import app.termora.plugin.internal.BasicTerminalOption
@@ -16,7 +17,7 @@ import java.awt.event.ComponentEvent
import javax.swing.*
class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPane() {
protected val generalOption = GeneralOption()
private val generalOption = GeneralOption()
// telnet 不支持代理密码
private val proxyOption = BasicProxyOption(authenticationTypes = listOf())
@@ -27,6 +28,7 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
showCharacterAtATimeTextField = true
showEnvironmentTextArea = true
showLoginScripts = true
accountOwner = this@TelnetHostOptionsPane.accountOwner
init()
}
@@ -70,6 +72,8 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
"character-at-a-time" to (terminalOption.characterAtATimeTextField.selectedItem?.toString() ?: "false"),
"altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString()
?: AltKeyModifier.EightBit.name),
"keywordHighlightSetId" to ((terminalOption.highlightSetComboBox.selectedItem as? KeywordHighlight)?.id
?: "-1"),
)
)
@@ -109,6 +113,16 @@ class TelnetHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPan
terminalOption.altModifierComboBox.selectedItem = runCatching { AltKeyModifier.valueOf(altModifier) }
.getOrNull() ?: AltKeyModifier.EightBit
val keywordHighlightSetId = host.options.extras["keywordHighlightSetId"]
for (i in 0 until terminalOption.highlightSetComboBox.itemCount) {
val item = terminalOption.highlightSetComboBox.getItemAt(i)
if (item.id == keywordHighlightSetId) {
terminalOption.highlightSetComboBox.selectedItem = item
break
}
}
terminalOption.loginScripts.clear()
terminalOption.loginScripts.addAll(host.options.loginScripts)
}

View File

@@ -1,6 +1,7 @@
package app.termora.plugin.internal.wsl
import app.termora.*
import app.termora.highlight.KeywordHighlight
import app.termora.plugin.internal.AltKeyModifier
import app.termora.plugin.internal.BasicTerminalOption
import com.formdev.flatlaf.FlatClientProperties
@@ -47,6 +48,8 @@ internal open class WSLHostOptionsPane : OptionsPane() {
"wsl-guid" to wsl.guid, "wsl-flavor" to wsl.flavor,
"altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString()
?: AltKeyModifier.EightBit.name),
"keywordHighlightSetId" to ((terminalOption.highlightSetComboBox.selectedItem as? KeywordHighlight)?.id
?: "-1"),
)
)
@@ -76,6 +79,18 @@ internal open class WSLHostOptionsPane : OptionsPane() {
}
}
val altModifier = host.options.extras["altModifier"] ?: AltKeyModifier.EightBit.name
terminalOption.altModifierComboBox.selectedItem = runCatching { AltKeyModifier.valueOf(altModifier) }
.getOrNull() ?: AltKeyModifier.EightBit
val keywordHighlightSetId = host.options.extras["keywordHighlightSetId"]
for (i in 0 until terminalOption.highlightSetComboBox.itemCount) {
val item = terminalOption.highlightSetComboBox.getItemAt(i)
if (item.id == keywordHighlightSetId) {
terminalOption.highlightSetComboBox.selectedItem = item
break
}
}
}
fun validateFields(): Boolean {

View File

@@ -266,6 +266,7 @@ termora.terminal-logger.open-in-folder=Open in {0}
# Highlight
termora.highlight=Highlight Sets
termora.highlight.default-set=Default Set
termora.highlight.text-color=Text Color
termora.highlight.background-color=Background Color
termora.highlight.keyword=Keyword

View File

@@ -230,6 +230,7 @@ termora.terminal-logger.open-in-folder=Открыть в {0}
# Highlight
termora.highlight=Персонализация
termora.highlight.default-set=Набор по умолчанию
termora.highlight.text-color=Цвет Текста
termora.highlight.background-color=Фоновый Цвет
termora.highlight.keyword=Слово

View File

@@ -263,6 +263,7 @@ termora.terminal-logger.open-in-folder=在 {0} 中打开
# Highlight
termora.highlight=关键词高亮
termora.highlight.default-set=默认集
termora.highlight.text-color=文本颜色
termora.highlight.background-color=背景颜色
termora.highlight.keyword=关键词

View File

@@ -259,6 +259,7 @@ termora.terminal-logger.open-in-folder=在 {0} 中打開
# Highlight
termora.highlight=關鍵字高亮
termora.highlight.default-set=預設集
termora.highlight.text-color=文字顏色
termora.highlight.background-color=背景顏色
termora.highlight.keyword=關鍵字