mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
chore!: migrate to version 2.x
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
package app.termora.plugins.editor
|
||||
|
||||
import app.termora.DialogWrapper
|
||||
import app.termora.Disposable
|
||||
import app.termora.Disposer
|
||||
import app.termora.OptionPane
|
||||
import app.termora.sftp.absolutePathString
|
||||
import org.apache.commons.vfs2.FileObject
|
||||
import java.awt.Dimension
|
||||
import java.awt.Window
|
||||
import java.awt.event.WindowAdapter
|
||||
import java.awt.event.WindowEvent
|
||||
import java.io.File
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JOptionPane
|
||||
import javax.swing.UIManager
|
||||
|
||||
|
||||
class EditorDialog(file: FileObject, owner: Window, myDisposable: Disposable) : DialogWrapper(null) {
|
||||
|
||||
private val filename = file.name.baseName
|
||||
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
|
||||
isResizable = true
|
||||
title = filename
|
||||
iconImages = owner.iconImages
|
||||
escapeDispose = false
|
||||
defaultCloseOperation = DO_NOTHING_ON_CLOSE
|
||||
|
||||
initEvents()
|
||||
|
||||
setLocationRelativeTo(owner)
|
||||
|
||||
init()
|
||||
}
|
||||
|
||||
|
||||
private fun initEvents() {
|
||||
addWindowListener(object : WindowAdapter() {
|
||||
override fun windowClosing(e: WindowEvent?) {
|
||||
doCancelAction()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun doCancelAction() {
|
||||
if (editorPanel.changes()) {
|
||||
if (OptionPane.showConfirmDialog(
|
||||
this,
|
||||
"文件尚未保存,你确定要退出吗?",
|
||||
optionType = JOptionPane.OK_CANCEL_OPTION,
|
||||
) != JOptionPane.OK_OPTION
|
||||
) {
|
||||
return
|
||||
}
|
||||
}
|
||||
super.doCancelAction()
|
||||
}
|
||||
|
||||
override fun createCenterPanel(): JComponent {
|
||||
return editorPanel
|
||||
}
|
||||
|
||||
override fun createSouthPanel(): JComponent? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package app.termora.plugins.editor
|
||||
|
||||
import app.termora.DocumentAdaptor
|
||||
import app.termora.DynamicColor
|
||||
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 org.apache.commons.io.FilenameUtils
|
||||
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 java.awt.BorderLayout
|
||||
import java.awt.Insets
|
||||
import java.awt.event.ActionEvent
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.WindowAdapter
|
||||
import java.awt.event.WindowEvent
|
||||
import java.io.File
|
||||
import javax.swing.*
|
||||
import javax.swing.event.DocumentEvent
|
||||
import kotlin.math.max
|
||||
|
||||
class EditorPanel(private val window: JDialog, private val file: File) : JPanel(BorderLayout()) {
|
||||
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 searchTextField = FlatTextField()
|
||||
private val closeFindPanelBtn = JButton(Icons.close)
|
||||
private val nextBtn = JButton(Icons.down)
|
||||
private val prevBtn = JButton(Icons.up)
|
||||
private val context = SearchContext()
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
|
||||
private fun initView() {
|
||||
textArea.font = textArea.font.deriveFont(DatabaseManager.getInstance().terminal.fontSize.toFloat())
|
||||
textArea.text = text
|
||||
textArea.antiAliasingEnabled = true
|
||||
|
||||
val theme = if (FlatLaf.isLafDark())
|
||||
Theme.load(javaClass.getResourceAsStream("/org/fife/ui/rsyntaxtextarea/themes/dark.xml"))
|
||||
else
|
||||
Theme.load(javaClass.getResourceAsStream("/org/fife/ui/rsyntaxtextarea/themes/idea.xml"))
|
||||
|
||||
theme.apply(textArea)
|
||||
|
||||
val extension = FilenameUtils.getExtension(file.name)?.lowercase()
|
||||
textArea.syntaxEditingStyle = when (extension) {
|
||||
"java" -> SyntaxConstants.SYNTAX_STYLE_JAVA
|
||||
"kt" -> SyntaxConstants.SYNTAX_STYLE_KOTLIN
|
||||
"properties" -> SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE
|
||||
"cpp", "c++" -> SyntaxConstants.SYNTAX_STYLE_CPLUSPLUS
|
||||
"c" -> SyntaxConstants.SYNTAX_STYLE_C
|
||||
"cs" -> SyntaxConstants.SYNTAX_STYLE_CSHARP
|
||||
"css" -> SyntaxConstants.SYNTAX_STYLE_CSS
|
||||
"html", "htm", "htmlx" -> SyntaxConstants.SYNTAX_STYLE_HTML
|
||||
"js" -> SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT
|
||||
"ts" -> SyntaxConstants.SYNTAX_STYLE_TYPESCRIPT
|
||||
"xml", "svg" -> SyntaxConstants.SYNTAX_STYLE_XML
|
||||
"yaml", "yml" -> SyntaxConstants.SYNTAX_STYLE_YAML
|
||||
"sh", "shell" -> SyntaxConstants.SYNTAX_STYLE_UNIX_SHELL
|
||||
"sql" -> SyntaxConstants.SYNTAX_STYLE_SQL
|
||||
"bat" -> SyntaxConstants.SYNTAX_STYLE_WINDOWS_BATCH
|
||||
"py" -> SyntaxConstants.SYNTAX_STYLE_PYTHON
|
||||
"php" -> SyntaxConstants.SYNTAX_STYLE_PHP
|
||||
"lua" -> SyntaxConstants.SYNTAX_STYLE_LUA
|
||||
"less" -> SyntaxConstants.SYNTAX_STYLE_LESS
|
||||
"jsp" -> SyntaxConstants.SYNTAX_STYLE_JSP
|
||||
"json" -> SyntaxConstants.SYNTAX_STYLE_JSON
|
||||
"ini" -> SyntaxConstants.SYNTAX_STYLE_INI
|
||||
"hosts" -> SyntaxConstants.SYNTAX_STYLE_HOSTS
|
||||
"go" -> SyntaxConstants.SYNTAX_STYLE_GO
|
||||
"dtd" -> SyntaxConstants.SYNTAX_STYLE_DTD
|
||||
"dart" -> SyntaxConstants.SYNTAX_STYLE_DART
|
||||
"csv" -> SyntaxConstants.SYNTAX_STYLE_CSV
|
||||
"md" -> SyntaxConstants.SYNTAX_STYLE_MARKDOWN
|
||||
else -> SyntaxConstants.SYNTAX_STYLE_NONE
|
||||
}
|
||||
|
||||
textArea.discardAllEdits()
|
||||
|
||||
scrollPane.border = BorderFactory.createMatteBorder(1, 0, 0, 0, DynamicColor.BorderColor)
|
||||
|
||||
findPanel.isVisible = false
|
||||
findPanel.isOpaque = true
|
||||
findPanel.background = DynamicColor("window")
|
||||
|
||||
searchTextField.background = findPanel.background
|
||||
searchTextField.padding = Insets(0, 4, 0, 0)
|
||||
searchTextField.border = BorderFactory.createEmptyBorder()
|
||||
|
||||
findPanel.add(searchTextField)
|
||||
findPanel.add(prevBtn)
|
||||
findPanel.add(nextBtn)
|
||||
findPanel.add(closeFindPanelBtn)
|
||||
findPanel.border = BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createMatteBorder(0, 1, 1, 0, DynamicColor.BorderColor),
|
||||
BorderFactory.createEmptyBorder(2, 2, 2, 2)
|
||||
)
|
||||
|
||||
layeredPane.add(findPanel, JLayeredPane.MODAL_LAYER as Any)
|
||||
layeredPane.add(scrollPane, JLayeredPane.DEFAULT_LAYER as Any)
|
||||
|
||||
add(layeredPane, BorderLayout.CENTER)
|
||||
}
|
||||
|
||||
|
||||
private fun initEvents() {
|
||||
|
||||
window.addWindowListener(object : WindowAdapter() {
|
||||
override fun windowOpened(e: WindowEvent?) {
|
||||
scrollPane.verticalScrollBar.value = 0
|
||||
window.removeWindowListener(this)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
textArea.inputMap.put(
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_S, toolkit.menuShortcutKeyMaskEx),
|
||||
"Save"
|
||||
)
|
||||
textArea.inputMap.put(
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_F, toolkit.menuShortcutKeyMaskEx),
|
||||
"Find"
|
||||
)
|
||||
|
||||
searchTextField.inputMap.put(
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
|
||||
"Esc"
|
||||
)
|
||||
|
||||
searchTextField.actionMap.put("Esc", object : AbstractAction("Esc") {
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
textArea.clearMarkAllHighlights()
|
||||
textArea.requestFocusInWindow()
|
||||
findPanel.isVisible = false
|
||||
}
|
||||
})
|
||||
|
||||
closeFindPanelBtn.addActionListener { searchTextField.actionMap.get("Esc").actionPerformed(it) }
|
||||
|
||||
textArea.actionMap.put("Save", object : AbstractAction("Save") {
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
file.writeText(textArea.text, Charsets.UTF_8)
|
||||
text = textArea.text
|
||||
window.title = file.name
|
||||
}
|
||||
})
|
||||
|
||||
textArea.actionMap.put("Find", object : AbstractAction("Find") {
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
findPanel.isVisible = true
|
||||
searchTextField.selectAll()
|
||||
searchTextField.requestFocusInWindow()
|
||||
}
|
||||
})
|
||||
|
||||
textArea.document.addDocumentListener(object : DocumentAdaptor() {
|
||||
override fun changedUpdate(e: DocumentEvent) {
|
||||
window.title = if (textArea.text.hashCode() != text.hashCode()) {
|
||||
"${file.name} *"
|
||||
} else {
|
||||
file.name
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
searchTextField.document.addDocumentListener(object : DocumentAdaptor() {
|
||||
override fun changedUpdate(e: DocumentEvent) {
|
||||
search()
|
||||
}
|
||||
})
|
||||
|
||||
searchTextField.addActionListener { nextBtn.doClick(0) }
|
||||
|
||||
prevBtn.addActionListener { search(false) }
|
||||
nextBtn.addActionListener { search(true) }
|
||||
}
|
||||
|
||||
private fun search(searchForward: Boolean = true) {
|
||||
textArea.clearMarkAllHighlights()
|
||||
|
||||
|
||||
val text: String = searchTextField.getText()
|
||||
if (text.isEmpty()) return
|
||||
context.searchFor = text
|
||||
context.searchForward = searchForward
|
||||
context.wholeWord = false
|
||||
val result = SearchEngine.find(textArea, context)
|
||||
|
||||
prevBtn.isEnabled = result.markedCount > 0
|
||||
nextBtn.isEnabled = result.markedCount > 0
|
||||
|
||||
}
|
||||
|
||||
fun changes() = text != textArea.text
|
||||
|
||||
private inner class LayeredPane : JLayeredPane() {
|
||||
override fun doLayout() {
|
||||
synchronized(treeLock) {
|
||||
for (c in components) {
|
||||
if (c == findPanel) {
|
||||
val height = max(findPanel.preferredSize.height, findPanel.height)
|
||||
val x = width / 2
|
||||
c.setBounds(x, 1, width - x, height)
|
||||
} else {
|
||||
c.setBounds(0, 0, width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package app.termora.plugins.editor
|
||||
|
||||
import app.termora.plugin.Extension
|
||||
import app.termora.plugin.ExtensionSupport
|
||||
import app.termora.plugin.Plugin
|
||||
import app.termora.sftp.SFTPEditFileExtension
|
||||
|
||||
class EditorPlugin : Plugin {
|
||||
private val support = ExtensionSupport()
|
||||
|
||||
init {
|
||||
support.addExtension(SFTPEditFileExtension::class.java) { MySFTPEditFileExtension.instance }
|
||||
}
|
||||
|
||||
override fun getAuthor(): String {
|
||||
return "TermoraDev"
|
||||
}
|
||||
|
||||
|
||||
override fun getName(): String {
|
||||
return "SFTP File Editor"
|
||||
}
|
||||
|
||||
override fun <T : Extension> getExtensions(clazz: Class<T>): List<T> {
|
||||
return support.getExtensions(clazz)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package app.termora.plugins.editor
|
||||
|
||||
import app.termora.Disposable
|
||||
import app.termora.Disposer
|
||||
import app.termora.sftp.SFTPEditFileExtension
|
||||
import app.termora.sftp.absolutePathString
|
||||
import org.apache.commons.vfs2.FileObject
|
||||
import java.awt.Window
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
class MySFTPEditFileExtension private constructor() : SFTPEditFileExtension {
|
||||
companion object {
|
||||
val instance = MySFTPEditFileExtension()
|
||||
}
|
||||
|
||||
override fun edit(owner: Window, file: FileObject): Disposable {
|
||||
val disposable = Disposer.newDisposable()
|
||||
SwingUtilities.invokeLater { EditorDialog(file, owner, disposable).isVisible = true }
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
22
plugins/editor/src/main/resources/META-INF/plugin.xml
Normal file
22
plugins/editor/src/main/resources/META-INF/plugin.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<termora-plugin>
|
||||
|
||||
<id>editor</id>
|
||||
|
||||
<name>SFTP File Editor</name>
|
||||
|
||||
<version>${projectVersion}</version>
|
||||
|
||||
<termora-version since=">=${rootProjectVersion}" until=""/>
|
||||
|
||||
<entry>app.termora.plugins.editor.EditorPlugin</entry>
|
||||
|
||||
<descriptions>
|
||||
<description>Edit SFTP files using the built-in editor</description>
|
||||
<description language="zh_CN">使用内置编辑器编辑 SFTP 文件</description>
|
||||
<description language="zh_TW">使用內建編輯器編輯 SFTP 文件</description>
|
||||
</descriptions>
|
||||
|
||||
<vendor url="https://github.com/TermoraDev">TermoraDev</vendor>
|
||||
|
||||
|
||||
</termora-plugin>
|
||||
@@ -0,0 +1,6 @@
|
||||
<!-- 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 3.86667C1 2.83574 1.7835 2 2.75 2H6.03823C6.29871 2 6.5489 2.10163 6.73559 2.28327L8.5 4L13 4C14.1046 4 15 4.89543 15 6V7.47774C14.2142 6.80872 13.0333 6.84543 12.2909 7.58786L7 12.8787V14H2.75C1.7835 14 1 13.1643 1 12.1333V3.86667Z" />
|
||||
<path d="M8.09379 5H13C13.5523 5 14 5.44772 14 6V7.02381C14.3594 7.07711 14.7072 7.22842 15 7.47774V6C15 4.89543 14.1046 4 13 4L8.5 4L6.73559 2.28327C6.5489 2.10163 6.29871 2 6.03823 2H2.75C1.7835 2 1 2.83574 1 3.86667V12.1333C1 13.1643 1.7835 14 2.75 14H7V13H2.75C2.3956 13 2 12.6738 2 12.1333V3.86667C2 3.32624 2.3956 3 2.75 3H6.03823L8.09379 5Z" fill="#6C707E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4122 8.29497C14.0217 7.90444 13.3885 7.90444 12.998 8.29497L11.6466 9.64633L8 13.2929V16H10.7071L15.7051 11.0021C16.0956 10.6116 16.0956 9.97839 15.7051 9.58786L14.4122 8.29497ZM14 11.2929L14.998 10.295L13.7051 9.00208L12.7071 10L14 11.2929ZM12 10.7072L13.2929 12L10.2929 15H9V13.7072L12 10.7072Z" fill="#6C707E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,6 @@
|
||||
<!-- 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 3.86667C1 2.83574 1.7835 2 2.75 2H6.03823C6.29871 2 6.5489 2.10163 6.73559 2.28327L8.5 4L13 4C14.1046 4 15 4.89543 15 6V7.47774C14.2142 6.80872 13.0333 6.84543 12.2909 7.58786L7 12.8787V14H2.75C1.7835 14 1 13.1643 1 12.1333V3.86667Z" />
|
||||
<path d="M8.09379 5H13C13.5523 5 14 5.44772 14 6V7.02381C14.3594 7.07711 14.7072 7.22842 15 7.47774V6C15 4.89543 14.1046 4 13 4L8.5 4L6.73559 2.28327C6.5489 2.10163 6.29871 2 6.03823 2H2.75C1.7835 2 1 2.83574 1 3.86667V12.1333C1 13.1643 1.7835 14 2.75 14H7V13H2.75C2.3956 13 2 12.6738 2 12.1333V3.86667C2 3.32624 2.3956 3 2.75 3H6.03823L8.09379 5Z" fill="#CED0D6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4122 8.29497C14.0217 7.90444 13.3885 7.90444 12.998 8.29497L11.6466 9.64633L8 13.2929V16H10.7071L15.7051 11.0021C16.0956 10.6116 16.0956 9.97839 15.7051 9.58786L14.4122 8.29497ZM14 11.2929L14.998 10.295L13.7051 9.00208L12.7071 10L14 11.2929ZM12 10.7072L13.2929 12L10.2929 15H9V13.7072L12 10.7072Z" fill="#CED0D6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
Reference in New Issue
Block a user