chore: improve editor

This commit is contained in:
hstyi
2025-06-29 12:08:24 +08:00
committed by hstyi
parent 70fc5e3228
commit 287f6973f0
13 changed files with 166 additions and 8 deletions

View File

@@ -4,7 +4,7 @@ plugins {
project.version = "0.0.4"
project.version = "0.0.5"
dependencies {

View File

@@ -17,15 +17,13 @@ import kotlin.io.path.absolutePathString
import kotlin.io.path.name
class EditorDialog(file: Path, owner: Window, myDisposable: Disposable) : DialogWrapper(null) {
class EditorDialog(file: Path, owner: Window, private val myDisposable: Disposable) : DialogWrapper(null) {
private val filename = file.name
private val filepath = File(file.absolutePathString())
private val editorPanel = EditorPanel(this, filepath)
init {
Disposer.register(disposable, myDisposable)
size = Dimension(UIManager.getInt("Dialog.width"), UIManager.getInt("Dialog.height"))
isModal = false
controlsVisible = true
@@ -49,6 +47,18 @@ class EditorDialog(file: Path, owner: Window, myDisposable: Disposable) : Dialog
doCancelAction()
}
})
Disposer.register(myDisposable, object : Disposable {
override fun dispose() {
doCancelAction()
}
})
Disposer.register(disposable, object : Disposable {
override fun dispose() {
Disposer.dispose(myDisposable)
}
})
}
override fun doCancelAction() {

View File

@@ -2,18 +2,24 @@ package app.termora.plugins.editor
import app.termora.DocumentAdaptor
import app.termora.DynamicColor
import app.termora.EnableManager
import app.termora.Icons
import app.termora.database.DatabaseManager
import com.formdev.flatlaf.FlatLaf
import com.formdev.flatlaf.extras.components.FlatTextField
import com.formdev.flatlaf.extras.components.FlatToolBar
import kotlinx.serialization.json.Json
import org.apache.commons.io.FilenameUtils
import org.dom4j.io.OutputFormat
import org.dom4j.io.SAXReader
import org.dom4j.io.XMLWriter
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea
import org.fife.ui.rsyntaxtextarea.SyntaxConstants
import org.fife.ui.rsyntaxtextarea.Theme
import org.fife.ui.rtextarea.RTextScrollPane
import org.fife.ui.rtextarea.SearchContext
import org.fife.ui.rtextarea.SearchEngine
import org.slf4j.LoggerFactory
import java.awt.BorderLayout
import java.awt.Insets
import java.awt.event.ActionEvent
@@ -21,22 +27,40 @@ import java.awt.event.KeyEvent
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import java.io.File
import java.io.StringReader
import java.io.StringWriter
import javax.swing.*
import javax.swing.SwingConstants.VERTICAL
import javax.swing.event.DocumentEvent
import kotlin.math.max
class EditorPanel(private val window: JDialog, private val file: File) : JPanel(BorderLayout()) {
companion object {
private val log = LoggerFactory.getLogger(EditorPanel::class.java)
}
private var text = file.readText(Charsets.UTF_8)
private val layeredPane = LayeredPane()
private val textArea = RSyntaxTextArea()
private val scrollPane = RTextScrollPane(textArea)
private val findPanel = FlatToolBar().apply { isFloatable = false }
private val toolbar = FlatToolBar().apply { isFloatable = false }
private val searchTextField = FlatTextField()
private val closeFindPanelBtn = JButton(Icons.close)
private val nextBtn = JButton(Icons.down)
private val prevBtn = JButton(Icons.up)
private val context = SearchContext()
private val softWrapBtn = JToggleButton(Icons.softWrap)
private val scrollUpBtn = JButton(Icons.scrollUp)
private val scrollEndBtn = JButton(Icons.scrollDown)
private val prettyBtn = JButton(Icons.reformatCode)
private val enableManager get() = EnableManager.getInstance()
private val prettyJson = Json {
prettyPrint = true
}
init {
initView()
@@ -48,6 +72,7 @@ class EditorPanel(private val window: JDialog, private val file: File) : JPanel(
textArea.font = textArea.font.deriveFont(DatabaseManager.getInstance().terminal.fontSize.toFloat())
textArea.text = text
textArea.antiAliasingEnabled = true
softWrapBtn.isSelected = enableManager.getFlag("Plugins.editor.softWrap", false)
val theme = if (FlatLaf.isLafDark())
Theme.load(javaClass.getResourceAsStream("/org/fife/ui/rsyntaxtextarea/themes/dark.xml"))
@@ -89,9 +114,13 @@ class EditorPanel(private val window: JDialog, private val file: File) : JPanel(
else -> SyntaxConstants.SYNTAX_STYLE_NONE
}
// 只有 JSON 才可以格式化
prettyBtn.isVisible = textArea.syntaxEditingStyle == SyntaxConstants.SYNTAX_STYLE_JSON ||
textArea.syntaxEditingStyle == SyntaxConstants.SYNTAX_STYLE_XML
textArea.discardAllEdits()
scrollPane.border = BorderFactory.createMatteBorder(1, 0, 0, 0, DynamicColor.BorderColor)
scrollPane.border = BorderFactory.createMatteBorder(0, 0, 0, 1, DynamicColor.BorderColor)
findPanel.isVisible = false
findPanel.isOpaque = true
@@ -110,8 +139,19 @@ class EditorPanel(private val window: JDialog, private val file: File) : JPanel(
BorderFactory.createEmptyBorder(2, 2, 2, 2)
)
toolbar.orientation = VERTICAL
toolbar.add(scrollUpBtn)
toolbar.add(prettyBtn)
toolbar.add(softWrapBtn)
toolbar.add(scrollEndBtn)
val viewPanel = JPanel(BorderLayout())
viewPanel.add(scrollPane, BorderLayout.CENTER)
viewPanel.add(toolbar, BorderLayout.EAST)
viewPanel.border = BorderFactory.createMatteBorder(1, 0, 0, 0, DynamicColor.BorderColor)
layeredPane.add(findPanel, JLayeredPane.MODAL_LAYER as Any)
layeredPane.add(scrollPane, JLayeredPane.DEFAULT_LAYER as Any)
layeredPane.add(viewPanel, JLayeredPane.DEFAULT_LAYER as Any)
add(layeredPane, BorderLayout.CENTER)
}
@@ -126,6 +166,13 @@ class EditorPanel(private val window: JDialog, private val file: File) : JPanel(
}
})
softWrapBtn.addActionListener {
enableManager.getFlag("Plugins.editor.softWrap", softWrapBtn.isSelected)
textArea.lineWrap = softWrapBtn.isSelected
}
scrollUpBtn.addActionListener { scrollPane.verticalScrollBar.value = 0 }
scrollEndBtn.addActionListener { scrollPane.verticalScrollBar.value = scrollPane.verticalScrollBar.maximum }
textArea.inputMap.put(
KeyStroke.getKeyStroke(KeyEvent.VK_S, toolkit.menuShortcutKeyMaskEx),
@@ -135,6 +182,10 @@ class EditorPanel(private val window: JDialog, private val file: File) : JPanel(
KeyStroke.getKeyStroke(KeyEvent.VK_F, toolkit.menuShortcutKeyMaskEx),
"Find"
)
textArea.inputMap.put(
KeyStroke.getKeyStroke(KeyEvent.VK_F, toolkit.menuShortcutKeyMaskEx or KeyEvent.SHIFT_DOWN_MASK),
"Format"
)
searchTextField.inputMap.put(
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
@@ -159,6 +210,33 @@ class EditorPanel(private val window: JDialog, private val file: File) : JPanel(
}
})
textArea.actionMap.put("Format", object : AbstractAction() {
override fun actionPerformed(e: ActionEvent) {
if (textArea.syntaxEditingStyle == SyntaxConstants.SYNTAX_STYLE_JSON) {
runCatching {
val json = prettyJson.parseToJsonElement(textArea.text)
textArea.text = prettyJson.encodeToString(json)
}.onFailure {
if (log.isErrorEnabled) {
log.error(it.message, it)
}
}
} else if (textArea.syntaxEditingStyle == SyntaxConstants.SYNTAX_STYLE_XML) {
runCatching {
val document = SAXReader().read(StringReader(textArea.text))
val sw = StringWriter()
val writer = XMLWriter(sw, OutputFormat.createPrettyPrint())
writer.write(document)
textArea.text = sw.toString()
}.onFailure {
if (log.isErrorEnabled) {
log.error(it.message, it)
}
}
}
}
})
textArea.actionMap.put("Find", object : AbstractAction("Find") {
override fun actionPerformed(e: ActionEvent) {
findPanel.isVisible = true
@@ -185,6 +263,10 @@ class EditorPanel(private val window: JDialog, private val file: File) : JPanel(
searchTextField.addActionListener { nextBtn.doClick(0) }
prettyBtn.addActionListener(searchTextField.actionMap.get("Format"))
prevBtn.addActionListener { search(false) }
nextBtn.addActionListener { search(true) }
}

View File

@@ -33,6 +33,10 @@ object Icons {
val empty by lazy { DynamicIcon("icons/empty.svg") }
val changelog by lazy { DynamicIcon("icons/changelog.svg", "icons/changelog_dark.svg") }
val add by lazy { DynamicIcon("icons/add.svg", "icons/add_dark.svg") }
val softWrap by lazy { DynamicIcon("icons/softWrap.svg", "icons/softWrap_dark.svg") }
val scrollUp by lazy { DynamicIcon("icons/scrollUp.svg", "icons/scrollUp_dark.svg") }
val reformatCode by lazy { DynamicIcon("icons/reformatCode.svg", "icons/reformatCode_dark.svg") }
val scrollDown by lazy { DynamicIcon("icons/scrollDown.svg", "icons/scrollDown_dark.svg") }
val locate by lazy { DynamicIcon("icons/locate.svg", "icons/locate_dark.svg") }
val percentage by lazy { DynamicIcon("icons/percentage.svg", "icons/percentage_dark.svg") }
val text by lazy { DynamicIcon("icons/text.svg", "icons/text_dark.svg") }

View File

@@ -882,6 +882,11 @@ class TransportPanel(
private inner class EditTransferListener : TransferListener, Disposable {
private val transferIds = mutableSetOf<String>()
private val sftp get() = DatabaseManager.getInstance().sftp
private val parentDisposable = Disposer.newDisposable()
init {
Disposer.register(this, parentDisposable)
}
override fun onTransferChanged(
transfer: Transfer,
@@ -915,13 +920,22 @@ class TransportPanel(
}
}
val disposed = AtomicBoolean(false)
Disposer.register(disposable, object : Disposable {
override fun dispose() {
job.cancel()
disposed.compareAndSet(false, true)
}
})
Disposer.register(parentDisposable, object : Disposable {
override fun dispose() {
job.cancel()
if (disposed.compareAndSet(false, true)) {
Disposer.dispose(disposable)
}
}
})
Disposer.register(this, disposable)
}
private fun startEditor(localPath: Path): Disposable {

View File

@@ -0,0 +1,8 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="12" width="12" height="1" rx="0.5" fill="#6C707E"/>
<rect x="6" y="6" width="8" height="1" rx="0.5" fill="#6C707E"/>
<rect x="6" y="9" width="8" height="1" rx="0.5" fill="#6C707E"/>
<rect x="2" y="3" width="12" height="1" rx="0.5" fill="#6C707E"/>
<path d="M2.8 6.1C2.64849 5.98637 2.44579 5.96809 2.27639 6.05279C2.107 6.13749 2 6.31062 2 6.5V9.5C2 9.68939 2.107 9.86252 2.27639 9.94722C2.44579 10.0319 2.64849 10.0136 2.8 9.9L4.8 8.4C4.9259 8.30558 5 8.15738 5 8C5 7.84262 4.9259 7.69443 4.8 7.6L2.8 6.1Z" fill="#4682FA"/>
</svg>

After

Width:  |  Height:  |  Size: 770 B

View File

@@ -0,0 +1,8 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="12" width="12" height="1" rx="0.5" fill="#CED0D6"/>
<rect x="6" y="6" width="8" height="1" rx="0.5" fill="#CED0D6"/>
<rect x="6" y="9" width="8" height="1" rx="0.5" fill="#CED0D6"/>
<rect x="2" y="3" width="12" height="1" rx="0.5" fill="#CED0D6"/>
<path d="M2.8 6.1C2.64849 5.98637 2.44579 5.96809 2.27639 6.05279C2.107 6.13749 2 6.31062 2 6.5V9.5C2 9.68939 2.107 9.86252 2.27639 9.94722C2.44579 10.0319 2.64849 10.0136 2.8 9.9L4.8 8.4C4.9259 8.30558 5 8.15738 5 8C5 7.84262 4.9259 7.69443 4.8 7.6L2.8 6.1Z" fill="#548AF7"/>
</svg>

After

Width:  |  Height:  |  Size: 770 B

View File

@@ -0,0 +1,5 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 3.5H5.5M1.5 8H5.5M1.5 12.5H14.5" stroke="#6C707E" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5 3C11.7761 3 12 3.22386 12 3.5L12 9.29291L14.1465 7.14646C14.3417 6.95119 14.6583 6.9512 14.8536 7.14646C15.0488 7.34172 15.0488 7.65831 14.8536 7.85357L11.8536 10.8536C11.6583 11.0488 11.3417 11.0488 11.1464 10.8536L8.14648 7.85356C7.95121 7.6583 7.95122 7.34172 8.14648 7.14646C8.34174 6.9512 8.65832 6.9512 8.85358 7.14646L11 9.29289L11 3.5C11 3.22386 11.2239 3 11.5 3Z" fill="#6C707E"/>
</svg>

After

Width:  |  Height:  |  Size: 788 B

View File

@@ -0,0 +1,5 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 3.5H5.5M1.5 8H5.5M1.5 12.5H14.5" stroke="#CED0D6" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5 3C11.7761 3 12 3.22386 12 3.5L12 9.29291L14.1464 7.14646C14.3417 6.95119 14.6583 6.9512 14.8536 7.14646C15.0488 7.34172 15.0488 7.65831 14.8536 7.85357L11.8535 10.8536C11.6583 11.0488 11.3417 11.0488 11.1464 10.8536L8.14645 7.85356C7.95118 7.6583 7.95118 7.34172 8.14645 7.14646C8.34171 6.9512 8.65829 6.9512 8.85355 7.14646L11 9.29289L11 3.5C11 3.22386 11.2238 3 11.5 3Z" fill="#CED0D6"/>
</svg>

After

Width:  |  Height:  |  Size: 788 B

View File

@@ -0,0 +1,7 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 3C1.22386 3 1 3.22386 1 3.5C1 3.77614 1.22386 4 1.5 4H14.5C14.7761 4 15 3.77614 15 3.5C15 3.22386 14.7761 3 14.5 3H1.5Z" fill="#6C707E"/>
<path d="M1.5 7.5C1.22386 7.5 1 7.72386 1 8C1 8.27614 1.22386 8.5 1.5 8.5H5.5C5.77614 8.5 6 8.27614 6 8C6 7.72386 5.77614 7.5 5.5 7.5H1.5Z" fill="#6C707E"/>
<path d="M1.5 12C1.22386 12 1 12.2239 1 12.5C1 12.7761 1.22386 13 1.5 13H5.5C5.77614 13 6 12.7761 6 12.5C6 12.2239 5.77614 12 5.5 12H1.5Z" fill="#6C707E"/>
<path d="M11 12.5C11 12.7761 11.2239 13 11.5 13C11.7762 13 12 12.7761 12 12.5V6.70711L14.1464 8.85354C14.3417 9.0488 14.6583 9.0488 14.8536 8.85354C15.0488 8.65828 15.0488 8.3417 14.8536 8.14644L11.8536 5.14645C11.6583 4.95119 11.3417 4.95118 11.1465 5.14644L8.14645 8.14643C7.95119 8.34169 7.95118 8.65828 8.14644 8.85354C8.3417 9.0488 8.65829 9.04881 8.85355 8.85355L11 6.70709V12.5Z" fill="#6C707E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,7 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 3C1.22386 3 1 3.22386 1 3.5C1 3.77614 1.22386 4 1.5 4H14.5C14.7761 4 15 3.77614 15 3.5C15 3.22386 14.7761 3 14.5 3H1.5Z" fill="#CED0D6"/>
<path d="M1.5 7.5C1.22386 7.5 1 7.72386 1 8C1 8.27614 1.22386 8.5 1.5 8.5H5.5C5.77614 8.5 6 8.27614 6 8C6 7.72386 5.77614 7.5 5.5 7.5H1.5Z" fill="#CED0D6"/>
<path d="M1.5 12C1.22386 12 1 12.2239 1 12.5C1 12.7761 1.22386 13 1.5 13H5.5C5.77614 13 6 12.7761 6 12.5C6 12.2239 5.77614 12 5.5 12H1.5Z" fill="#CED0D6"/>
<path d="M11 12.5C11 12.7761 11.2239 13 11.5 13C11.7762 13 12 12.7761 12 12.5V6.70711L14.1464 8.85354C14.3417 9.0488 14.6583 9.0488 14.8536 8.85354C15.0488 8.65828 15.0488 8.3417 14.8536 8.14644L11.8536 5.14645C11.6583 4.95119 11.3417 4.95118 11.1465 5.14644L8.14645 8.14643C7.95119 8.34169 7.95118 8.65828 8.14644 8.85354C8.3417 9.0488 8.65829 9.04881 8.85355 8.85355L11 6.70709V12.5Z" fill="#CED0D6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,4 @@
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 3C1.22386 3 1 3.22386 1 3.5C1 3.77614 1.22386 4 1.5 4V3ZM14.5 4C14.7761 4 15 3.77614 15 3.5C15 3.22386 14.7761 3 14.5 3V4ZM1.5 7.5C1.22386 7.5 1 7.72386 1 8C1 8.27614 1.22386 8.5 1.5 8.5V7.5ZM8.5 12.5L8.14645 12.1464L7.79289 12.5L8.14645 12.8536L8.5 12.5ZM10.8536 10.8536C11.0488 10.6583 11.0488 10.3417 10.8536 10.1464C10.6583 9.95118 10.3417 9.95118 10.1464 10.1464L10.8536 10.8536ZM10.1464 14.8536C10.3417 15.0488 10.6583 15.0488 10.8536 14.8536C11.0488 14.6583 11.0488 14.3417 10.8536 14.1464L10.1464 14.8536ZM1.5 12C1.22386 12 1 12.2239 1 12.5C1 12.7761 1.22386 13 1.5 13V12ZM5.5 13C5.77614 13 6 12.7761 6 12.5C6 12.2239 5.77614 12 5.5 12V13ZM1.5 4H14.5V3H1.5V4ZM1.5 8.5H12.25V7.5H1.5V8.5ZM12.25 12H8.5V13H12.25V12ZM8.85355 12.8536L10.8536 10.8536L10.1464 10.1464L8.14645 12.1464L8.85355 12.8536ZM8.14645 12.8536L10.1464 14.8536L10.8536 14.1464L8.85355 12.1464L8.14645 12.8536ZM1.5 13H5.5V12H1.5V13ZM12.25 13C13.7688 13 15 11.7688 15 10.25H14C14 11.2165 13.2165 12 12.25 12V13ZM12.25 8.5C13.2165 8.5 14 9.2835 14 10.25H15C15 8.73122 13.7688 7.5 12.25 7.5V8.5Z" fill="#6C707E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,4 @@
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 3C1.22386 3 1 3.22386 1 3.5C1 3.77614 1.22386 4 1.5 4V3ZM14.5 4C14.7761 4 15 3.77614 15 3.5C15 3.22386 14.7761 3 14.5 3V4ZM1.5 7.5C1.22386 7.5 1 7.72386 1 8C1 8.27614 1.22386 8.5 1.5 8.5V7.5ZM8.5 12.5L8.14645 12.1464L7.79289 12.5L8.14645 12.8536L8.5 12.5ZM10.8536 10.8536C11.0488 10.6583 11.0488 10.3417 10.8536 10.1464C10.6583 9.95118 10.3417 9.95118 10.1464 10.1464L10.8536 10.8536ZM10.1464 14.8536C10.3417 15.0488 10.6583 15.0488 10.8536 14.8536C11.0488 14.6583 11.0488 14.3417 10.8536 14.1464L10.1464 14.8536ZM1.5 12C1.22386 12 1 12.2239 1 12.5C1 12.7761 1.22386 13 1.5 13V12ZM5.5 13C5.77614 13 6 12.7761 6 12.5C6 12.2239 5.77614 12 5.5 12V13ZM1.5 4H14.5V3H1.5V4ZM1.5 8.5H12.25V7.5H1.5V8.5ZM12.25 12H8.5V13H12.25V12ZM8.85355 12.8536L10.8536 10.8536L10.1464 10.1464L8.14645 12.1464L8.85355 12.8536ZM8.14645 12.8536L10.1464 14.8536L10.8536 14.1464L8.85355 12.1464L8.14645 12.8536ZM1.5 13H5.5V12H1.5V13ZM12.25 13C13.7688 13 15 11.7688 15 10.25H14C14 11.2165 13.2165 12 12.25 12V13ZM12.25 8.5C13.2165 8.5 14 9.2835 14 10.25H15C15 8.73122 13.7688 7.5 12.25 7.5V8.5Z" fill="#CED0D6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB