mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 10:22:58 +08:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc4333da21 | ||
|
|
184f6d46dc | ||
|
|
68788905fe | ||
|
|
fc46216a3f | ||
|
|
563143645e | ||
|
|
891ccb901b | ||
|
|
928a866fe7 | ||
|
|
ea25b5b46f | ||
|
|
1de10e6129 | ||
|
|
aaf9c2e8d2 | ||
|
|
b8196b5730 | ||
|
|
0a83e8beb4 | ||
|
|
bdf29b27e7 | ||
|
|
96da7eac41 | ||
|
|
71c0751692 | ||
|
|
442f334af2 | ||
|
|
48302a519f | ||
|
|
c00f759f15 | ||
|
|
1736dd909e | ||
|
|
1f01e368dd | ||
|
|
bfba958b7e | ||
|
|
758121b523 | ||
|
|
06e9a89e82 | ||
|
|
0ba6ac3305 | ||
|
|
993f220b8b | ||
|
|
8755c4ad23 | ||
|
|
77cb102dd6 | ||
|
|
89cfb0b451 | ||
|
|
6bdd83f208 | ||
|
|
8f86057dcc | ||
|
|
a7d7ffa2cc | ||
|
|
d51cbeee13 | ||
|
|
deb2a0151e | ||
|
|
e1c4e9312d | ||
|
|
c7233357bd | ||
|
|
eff8d565d0 |
124
build.gradle.kts
124
build.gradle.kts
@@ -14,13 +14,14 @@ plugins {
|
|||||||
java
|
java
|
||||||
idea
|
idea
|
||||||
application
|
application
|
||||||
|
`maven-publish`
|
||||||
alias(libs.plugins.kotlin.jvm)
|
alias(libs.plugins.kotlin.jvm)
|
||||||
alias(libs.plugins.kotlinx.serialization)
|
alias(libs.plugins.kotlinx.serialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
group = "app.termora"
|
group = "app.termora"
|
||||||
version = "1.0.13"
|
version = "1.0.15"
|
||||||
|
|
||||||
val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
|
val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
|
||||||
val arch: ArchitectureInternal = DefaultNativePlatform.getCurrentArchitecture()
|
val arch: ArchitectureInternal = DefaultNativePlatform.getCurrentArchitecture()
|
||||||
@@ -56,67 +57,67 @@ dependencies {
|
|||||||
|
|
||||||
// implementation(platform(libs.koin.bom))
|
// implementation(platform(libs.koin.bom))
|
||||||
// implementation(libs.koin.core)
|
// implementation(libs.koin.core)
|
||||||
implementation(libs.slf4j.api)
|
api(libs.slf4j.api)
|
||||||
implementation(libs.pty4j)
|
api(libs.pty4j)
|
||||||
implementation(libs.slf4j.tinylog)
|
api(libs.slf4j.tinylog)
|
||||||
implementation(libs.tinylog.impl)
|
api(libs.tinylog.impl)
|
||||||
implementation(libs.commons.codec)
|
api(libs.commons.codec)
|
||||||
implementation(libs.commons.io)
|
api(libs.commons.io)
|
||||||
implementation(libs.commons.lang3)
|
api(libs.commons.lang3)
|
||||||
implementation(libs.commons.csv)
|
api(libs.commons.csv)
|
||||||
implementation(libs.commons.net)
|
api(libs.commons.net)
|
||||||
implementation(libs.commons.text)
|
api(libs.commons.text)
|
||||||
implementation(libs.commons.compress)
|
api(libs.commons.compress)
|
||||||
implementation(libs.commons.vfs2) { exclude(group = "*", module = "*") }
|
api(libs.commons.vfs2) { exclude(group = "*", module = "*") }
|
||||||
implementation(libs.kotlinx.coroutines.swing)
|
api(libs.kotlinx.coroutines.swing)
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
api(libs.kotlinx.coroutines.core)
|
||||||
|
|
||||||
implementation(libs.flatlaf) {
|
api(libs.flatlaf) {
|
||||||
artifact {
|
artifact {
|
||||||
if (useNoNativesFlatLaf) {
|
if (useNoNativesFlatLaf) {
|
||||||
classifier = "no-natives"
|
classifier = "no-natives"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
implementation(libs.flatlaf.extras) {
|
api(libs.flatlaf.extras) {
|
||||||
if (useNoNativesFlatLaf) {
|
if (useNoNativesFlatLaf) {
|
||||||
exclude(group = "com.formdev", module = "flatlaf")
|
exclude(group = "com.formdev", module = "flatlaf")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
implementation(libs.flatlaf.swingx) {
|
api(libs.flatlaf.swingx) {
|
||||||
if (useNoNativesFlatLaf) {
|
if (useNoNativesFlatLaf) {
|
||||||
exclude(group = "com.formdev", module = "flatlaf")
|
exclude(group = "com.formdev", module = "flatlaf")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation(libs.kotlinx.serialization.json)
|
api(libs.kotlinx.serialization.json)
|
||||||
implementation(libs.swingx)
|
api(libs.swingx)
|
||||||
implementation(libs.jgoodies.forms)
|
api(libs.jgoodies.forms)
|
||||||
implementation(libs.jna)
|
api(libs.jna)
|
||||||
implementation(libs.jna.platform)
|
api(libs.jna.platform)
|
||||||
implementation(libs.versioncompare)
|
api(libs.versioncompare)
|
||||||
implementation(libs.oshi.core)
|
api(libs.oshi.core)
|
||||||
implementation(libs.jSystemThemeDetector) { exclude(group = "*", module = "*") }
|
api(libs.jSystemThemeDetector) { exclude(group = "*", module = "*") }
|
||||||
implementation(libs.jfa) { exclude(group = "*", module = "*") }
|
api(libs.jfa) { exclude(group = "*", module = "*") }
|
||||||
implementation(libs.jbr.api)
|
api(libs.jbr.api)
|
||||||
implementation(libs.okhttp)
|
api(libs.okhttp)
|
||||||
implementation(libs.okhttp.logging)
|
api(libs.okhttp.logging)
|
||||||
implementation(libs.sshd.core)
|
api(libs.sshd.core)
|
||||||
implementation(libs.commonmark)
|
api(libs.commonmark)
|
||||||
implementation(libs.jgit)
|
api(libs.jgit)
|
||||||
implementation(libs.jgit.sshd) { exclude(group = "*", module = "sshd-osgi") }
|
api(libs.jgit.sshd) { exclude(group = "*", module = "sshd-osgi") }
|
||||||
implementation(libs.jgit.agent) { exclude(group = "*", module = "sshd-osgi") }
|
api(libs.jgit.agent) { exclude(group = "*", module = "sshd-osgi") }
|
||||||
implementation(libs.eddsa)
|
api(libs.eddsa)
|
||||||
implementation(libs.jnafilechooser)
|
api(libs.jnafilechooser)
|
||||||
implementation(libs.xodus.vfs)
|
api(libs.xodus.vfs)
|
||||||
implementation(libs.xodus.openAPI)
|
api(libs.xodus.openAPI)
|
||||||
implementation(libs.xodus.environment)
|
api(libs.xodus.environment)
|
||||||
implementation(libs.bip39)
|
api(libs.bip39)
|
||||||
implementation(libs.colorpicker)
|
api(libs.colorpicker)
|
||||||
implementation(libs.mixpanel)
|
api(libs.mixpanel)
|
||||||
implementation(libs.jSerialComm)
|
api(libs.jSerialComm)
|
||||||
implementation(libs.ini4j)
|
api(libs.ini4j)
|
||||||
implementation(libs.restart4j)
|
api(libs.restart4j)
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
@@ -147,6 +148,37 @@ application {
|
|||||||
mainClass = "app.termora.MainKt"
|
mainClass = "app.termora.MainKt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
create<MavenPublication>("mavenJava") {
|
||||||
|
from(components["java"])
|
||||||
|
pom {
|
||||||
|
name = project.name
|
||||||
|
description = "Termora is a terminal emulator and SSH client for Windows, macOS and Linux"
|
||||||
|
url = "https://github.com/TermoraDev/termora"
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name = "AGPL-3.0"
|
||||||
|
url = "https://opensource.org/license/agpl-v3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
name = "hstyi"
|
||||||
|
url = "https://github.com/hstyi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scm {
|
||||||
|
url = "https://github.com/TermoraDev/termora"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tasks.test {
|
tasks.test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
[versions]
|
[versions]
|
||||||
kotlin = "2.1.20"
|
kotlin = "2.1.21"
|
||||||
slf4j = "2.0.17"
|
slf4j = "2.0.17"
|
||||||
pty4j = "0.13.3"
|
pty4j = "0.13.4"
|
||||||
tinylog = "2.7.0"
|
tinylog = "2.7.0"
|
||||||
kotlinx-coroutines = "1.10.2"
|
kotlinx-coroutines = "1.10.2"
|
||||||
flatlaf = "3.5.4"
|
flatlaf = "3.6"
|
||||||
kotlinx-serialization-json = "1.8.1"
|
kotlinx-serialization-json = "1.8.1"
|
||||||
commons-codec = "1.18.0"
|
commons-codec = "1.18.0"
|
||||||
commons-lang3 = "3.17.0"
|
commons-lang3 = "3.17.0"
|
||||||
@@ -16,14 +16,14 @@ commons-vfs2="2.10.0"
|
|||||||
swingx = "1.6.5-1"
|
swingx = "1.6.5-1"
|
||||||
jgoodies-forms = "1.9.0"
|
jgoodies-forms = "1.9.0"
|
||||||
jfa = "1.2.0"
|
jfa = "1.2.0"
|
||||||
oshi = "6.6.5"
|
oshi = "6.8.1"
|
||||||
versioncompare = "1.4.1"
|
versioncompare = "1.4.1"
|
||||||
jna = "5.17.0"
|
jna = "5.17.0"
|
||||||
jSystemThemeDetector = "3.9.1"
|
jSystemThemeDetector = "3.9.1"
|
||||||
commons-io = "2.18.0"
|
commons-io = "2.19.0"
|
||||||
jbr-api = "17.1.10.1"
|
jbr-api = "17.1.10.1"
|
||||||
hutool = "5.8.37"
|
hutool = "5.8.37"
|
||||||
jsch = "0.2.25"
|
jsch = "0.2.26"
|
||||||
okhttp = "4.12.0"
|
okhttp = "4.12.0"
|
||||||
sshj = "0.39.0"
|
sshj = "0.39.0"
|
||||||
sshd-core = "2.15.0"
|
sshd-core = "2.15.0"
|
||||||
@@ -35,7 +35,7 @@ bip39 = "1.0.9"
|
|||||||
colorpicker = "2.0.1"
|
colorpicker = "2.0.1"
|
||||||
rhino = "1.8.0"
|
rhino = "1.8.0"
|
||||||
delight-rhino-sandbox = "0.0.17"
|
delight-rhino-sandbox = "0.0.17"
|
||||||
testcontainers = "1.20.6"
|
testcontainers = "1.21.0"
|
||||||
mixpanel = "1.5.3"
|
mixpanel = "1.5.3"
|
||||||
jSerialComm = "2.11.0"
|
jSerialComm = "2.11.0"
|
||||||
ini4j = "0.5.5-2"
|
ini4j = "0.5.5-2"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.security.GeneralSecurityException;
|
|||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class CombinedKeyIdentityProvider implements KeyIdentityProvider {
|
public class CombinedKeyIdentityProvider implements KeyIdentityProvider {
|
||||||
|
|
||||||
private final List<KeyIdentityProvider> providers = new ArrayList<>();
|
private final List<KeyIdentityProvider> providers = new ArrayList<>();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import static com.formdev.flatlaf.util.UIScale.scale;
|
|||||||
/**
|
/**
|
||||||
* 如果要升级 FlatLaf 需要检查是否兼容
|
* 如果要升级 FlatLaf 需要检查是否兼容
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class MyFlatTabbedPaneUI extends FlatTabbedPaneUI {
|
public class MyFlatTabbedPaneUI extends FlatTabbedPaneUI {
|
||||||
@Override
|
@Override
|
||||||
protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
|
protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
|
||||||
|
|||||||
@@ -523,6 +523,11 @@ class Database private constructor(private val env: Environment) : Disposable {
|
|||||||
*/
|
*/
|
||||||
var beep by BooleanPropertyDelegate(true)
|
var beep by BooleanPropertyDelegate(true)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 超链接
|
||||||
|
*/
|
||||||
|
var hyperlink by BooleanPropertyDelegate(true)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 光标闪烁
|
* 光标闪烁
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ enum class Protocol {
|
|||||||
SSH,
|
SSH,
|
||||||
Local,
|
Local,
|
||||||
Serial,
|
Serial,
|
||||||
|
RDP,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 交互式的 SFTP,此协议只在系统内部交互不应该暴露给用户也不应该持久化
|
* 交互式的 SFTP,此协议只在系统内部交互不应该暴露给用户也不应该持久化
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.apache.sshd.client.session.ClientSession
|
|||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.Dimension
|
import java.awt.Dimension
|
||||||
import java.awt.Window
|
import java.awt.Window
|
||||||
|
import java.util.*
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
|
|
||||||
class HostDialog(owner: Window, host: Host? = null) : DialogWrapper(owner) {
|
class HostDialog(owner: Window, host: Host? = null) : DialogWrapper(owner) {
|
||||||
@@ -54,7 +55,8 @@ class HostDialog(owner: Window, host: Host? = null) : DialogWrapper(owner) {
|
|||||||
isEnabled = false
|
isEnabled = false
|
||||||
|
|
||||||
swingCoroutineScope.launch(Dispatchers.IO) {
|
swingCoroutineScope.launch(Dispatchers.IO) {
|
||||||
testConnection(pane.getHost())
|
// 因为测试连接的时候从数据库读取会导致失效,所以这里生成随机ID
|
||||||
|
testConnection(pane.getHost().copy(id = UUID.randomUUID().toSimpleString()))
|
||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
putValue(NAME, I18n.getString("termora.new-host.test-connection"))
|
putValue(NAME, I18n.getString("termora.new-host.test-connection"))
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
|
|||||||
@@ -320,6 +320,7 @@ open class HostOptionsPane : OptionsPane() {
|
|||||||
protocolTypeComboBox.addItem(Protocol.SSH)
|
protocolTypeComboBox.addItem(Protocol.SSH)
|
||||||
protocolTypeComboBox.addItem(Protocol.Local)
|
protocolTypeComboBox.addItem(Protocol.Local)
|
||||||
protocolTypeComboBox.addItem(Protocol.Serial)
|
protocolTypeComboBox.addItem(Protocol.Serial)
|
||||||
|
protocolTypeComboBox.addItem(Protocol.RDP)
|
||||||
|
|
||||||
authenticationTypeComboBox.addItem(AuthenticationType.No)
|
authenticationTypeComboBox.addItem(AuthenticationType.No)
|
||||||
authenticationTypeComboBox.addItem(AuthenticationType.Password)
|
authenticationTypeComboBox.addItem(AuthenticationType.Password)
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class HostTreeNode(host: Host) : SimpleTreeNode<Host>(host) {
|
|||||||
return when (host.protocol) {
|
return when (host.protocol) {
|
||||||
Protocol.Folder -> if (expanded) FlatTreeOpenIcon() else FlatTreeClosedIcon()
|
Protocol.Folder -> if (expanded) FlatTreeOpenIcon() else FlatTreeClosedIcon()
|
||||||
Protocol.Serial -> if (selected && hasFocus) Icons.plugin.dark else Icons.plugin
|
Protocol.Serial -> if (selected && hasFocus) Icons.plugin.dark else Icons.plugin
|
||||||
|
Protocol.RDP -> if (selected && hasFocus) Icons.microsoftWindows.dark else Icons.microsoftWindows
|
||||||
else -> if (selected && hasFocus) Icons.terminal.dark else Icons.terminal
|
else -> if (selected && hasFocus) Icons.terminal.dark else Icons.terminal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ object Icons {
|
|||||||
val revert by lazy { DynamicIcon("icons/revert.svg", "icons/revert_dark.svg") }
|
val revert by lazy { DynamicIcon("icons/revert.svg", "icons/revert_dark.svg") }
|
||||||
val edit by lazy { DynamicIcon("icons/edit.svg", "icons/edit_dark.svg") }
|
val edit by lazy { DynamicIcon("icons/edit.svg", "icons/edit_dark.svg") }
|
||||||
val microsoft by lazy { DynamicIcon("icons/microsoft.svg", "icons/microsoft_dark.svg") }
|
val microsoft by lazy { DynamicIcon("icons/microsoft.svg", "icons/microsoft_dark.svg") }
|
||||||
|
val microsoftWindows by lazy { DynamicIcon("icons/microsoftWindows.svg", "icons/microsoftWindows_dark.svg") }
|
||||||
val tencent by lazy { DynamicIcon("icons/tencent.svg") }
|
val tencent by lazy { DynamicIcon("icons/tencent.svg") }
|
||||||
val google by lazy { DynamicIcon("icons/google-small.svg") }
|
val google by lazy { DynamicIcon("icons/google-small.svg") }
|
||||||
val aliyun by lazy { DynamicIcon("icons/aliyun.svg") }
|
val aliyun by lazy { DynamicIcon("icons/aliyun.svg") }
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import java.awt.event.*
|
|||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
import javax.swing.plaf.TabbedPaneUI
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
class MyTabbedPane : FlatTabbedPane() {
|
class MyTabbedPane : FlatTabbedPane() {
|
||||||
@@ -21,18 +20,12 @@ class MyTabbedPane : FlatTabbedPane() {
|
|||||||
private val owner
|
private val owner
|
||||||
get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
|
get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
|
||||||
.getData(DataProviders.TermoraFrame) as TermoraFrame
|
.getData(DataProviders.TermoraFrame) as TermoraFrame
|
||||||
private val myUI = MyFlatTabbedPaneUI()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
isFocusable = false
|
isFocusable = false
|
||||||
super.setUI(myUI)
|
|
||||||
initEvents()
|
initEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setUI(ui: TabbedPaneUI?) {
|
|
||||||
super.setUI(myUI)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateUI() {
|
override fun updateUI() {
|
||||||
styleMap = mapOf(
|
styleMap = mapOf(
|
||||||
"focusColor" to UIManager.getColor("TabbedPane.selectedBackground"),
|
"focusColor" to UIManager.getColor("TabbedPane.selectedBackground"),
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import javax.xml.parsers.DocumentBuilderFactory
|
|||||||
import javax.xml.xpath.XPathConstants
|
import javax.xml.xpath.XPathConstants
|
||||||
import javax.xml.xpath.XPathFactory
|
import javax.xml.xpath.XPathFactory
|
||||||
|
|
||||||
|
@Suppress("CascadeIf")
|
||||||
class NewHostTree : SimpleTree() {
|
class NewHostTree : SimpleTree() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -97,7 +98,7 @@ class NewHostTree : SimpleTree() {
|
|||||||
// 是否显示更多信息
|
// 是否显示更多信息
|
||||||
if (isShowMoreInfo) {
|
if (isShowMoreInfo) {
|
||||||
val color = if (sel) {
|
val color = if (sel) {
|
||||||
if (tree.hasFocus()) {
|
if (tree.hasFocus() || isPopupMenu) {
|
||||||
UIManager.getColor("textHighlightText")
|
UIManager.getColor("textHighlightText")
|
||||||
} else {
|
} else {
|
||||||
this.foreground
|
this.foreground
|
||||||
@@ -110,15 +111,15 @@ class NewHostTree : SimpleTree() {
|
|||||||
"""<font color=rgb(${color.red},${color.green},${color.blue})>${it}</font>"""
|
"""<font color=rgb(${color.red},${color.green},${color.blue})>${it}</font>"""
|
||||||
}
|
}
|
||||||
|
|
||||||
if (host.protocol == Protocol.SSH) {
|
// @formatter:off
|
||||||
text =
|
if (host.protocol == Protocol.SSH || host.protocol == Protocol.RDP) {
|
||||||
"<html>${host.name} ${fontTag.apply("${host.username}@${host.host}")}</html>"
|
text = "<html>${host.name} ${fontTag.apply("${host.username}@${host.host}")}</html>"
|
||||||
} else if (host.protocol == Protocol.Serial) {
|
} else if (host.protocol == Protocol.Serial) {
|
||||||
text =
|
text = "<html>${host.name} ${fontTag.apply(host.options.serialComm.port)}</html>"
|
||||||
"<html>${host.name} ${fontTag.apply(host.options.serialComm.port)}</html>"
|
|
||||||
} else if (host.protocol == Protocol.Folder) {
|
} else if (host.protocol == Protocol.Folder) {
|
||||||
text = "<html>${host.name}${fontTag.apply(" (${node.childCount})")}</html>"
|
text = "<html>${host.name}${fontTag.apply(" (${node.getAllChildren().size})")}</html>"
|
||||||
}
|
}
|
||||||
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
val c = super.getTreeCellRendererComponent(tree, text, sel, expanded, leaf, row, hasFocus)
|
val c = super.getTreeCellRendererComponent(tree, text, sel, expanded, leaf, row, hasFocus)
|
||||||
@@ -137,6 +138,9 @@ class NewHostTree : SimpleTree() {
|
|||||||
if (doubleClickConnection && SwingUtilities.isLeftMouseButton(e) && e.clickCount % 2 == 0) {
|
if (doubleClickConnection && SwingUtilities.isLeftMouseButton(e) && e.clickCount % 2 == 0) {
|
||||||
val lastNode = lastSelectedPathComponent as? HostTreeNode ?: return
|
val lastNode = lastSelectedPathComponent as? HostTreeNode ?: return
|
||||||
if (lastNode.host.protocol != Protocol.Folder) {
|
if (lastNode.host.protocol != Protocol.Folder) {
|
||||||
|
val path = tree.getClosestPathForLocation(e.x, e.y) ?: return
|
||||||
|
val bounds = tree.getRowBounds(tree.getRowForPath(path)) ?: return
|
||||||
|
if ((e.y >= bounds.y && e.y < (bounds.y + bounds.height)).not()) return
|
||||||
openHostAction?.actionPerformed(OpenHostActionEvent(e.source, lastNode.host, e))
|
openHostAction?.actionPerformed(OpenHostActionEvent(e.source, lastNode.host, e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,6 +422,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
private val selectCopyComboBox = YesOrNoComboBox()
|
private val selectCopyComboBox = YesOrNoComboBox()
|
||||||
private val autoCloseTabComboBox = YesOrNoComboBox()
|
private val autoCloseTabComboBox = YesOrNoComboBox()
|
||||||
private val floatingToolbarComboBox = YesOrNoComboBox()
|
private val floatingToolbarComboBox = YesOrNoComboBox()
|
||||||
|
private val hyperlinkComboBox = YesOrNoComboBox()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initView()
|
initView()
|
||||||
@@ -499,6 +500,13 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hyperlinkComboBox.addItemListener { e ->
|
||||||
|
if (e.stateChange == ItemEvent.SELECTED) {
|
||||||
|
terminalSetting.hyperlink = hyperlinkComboBox.selectedItem as Boolean
|
||||||
|
TerminalPanelFactory.getInstance().repaintAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cursorBlinkComboBox.addItemListener { e ->
|
cursorBlinkComboBox.addItemListener { e ->
|
||||||
if (e.stateChange == ItemEvent.SELECTED) {
|
if (e.stateChange == ItemEvent.SELECTED) {
|
||||||
terminalSetting.cursorBlink = cursorBlinkComboBox.selectedItem as Boolean
|
terminalSetting.cursorBlink = cursorBlinkComboBox.selectedItem as Boolean
|
||||||
@@ -591,6 +599,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
fontComboBox.selectedItem = terminalSetting.font
|
fontComboBox.selectedItem = terminalSetting.font
|
||||||
debugComboBox.selectedItem = terminalSetting.debug
|
debugComboBox.selectedItem = terminalSetting.debug
|
||||||
beepComboBox.selectedItem = terminalSetting.beep
|
beepComboBox.selectedItem = terminalSetting.beep
|
||||||
|
hyperlinkComboBox.selectedItem = terminalSetting.hyperlink
|
||||||
cursorBlinkComboBox.selectedItem = terminalSetting.cursorBlink
|
cursorBlinkComboBox.selectedItem = terminalSetting.cursorBlink
|
||||||
cursorStyleComboBox.selectedItem = terminalSetting.cursor
|
cursorStyleComboBox.selectedItem = terminalSetting.cursor
|
||||||
selectCopyComboBox.selectedItem = terminalSetting.selectCopy
|
selectCopyComboBox.selectedItem = terminalSetting.selectCopy
|
||||||
@@ -613,7 +622,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
private fun getCenterComponent(): JComponent {
|
private fun getCenterComponent(): JComponent {
|
||||||
val layout = FormLayout(
|
val layout = FormLayout(
|
||||||
"left:pref, $formMargin, default:grow, $formMargin, left:pref, $formMargin, pref, default:grow",
|
"left:pref, $formMargin, default:grow, $formMargin, left:pref, $formMargin, pref, default:grow",
|
||||||
"pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
|
"pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
|
||||||
)
|
)
|
||||||
|
|
||||||
val beepBtn = JButton(Icons.run)
|
val beepBtn = JButton(Icons.run)
|
||||||
@@ -636,6 +645,8 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
.add("${I18n.getString("termora.settings.terminal.beep")}:").xy(1, rows)
|
.add("${I18n.getString("termora.settings.terminal.beep")}:").xy(1, rows)
|
||||||
.add(beepComboBox).xy(3, rows)
|
.add(beepComboBox).xy(3, rows)
|
||||||
.add(beepBtn).xy(5, rows).apply { rows += step }
|
.add(beepBtn).xy(5, rows).apply { rows += step }
|
||||||
|
.add("${I18n.getString("termora.settings.terminal.hyperlink")}:").xy(1, rows)
|
||||||
|
.add(hyperlinkComboBox).xy(3, rows).apply { rows += step }
|
||||||
.add("${I18n.getString("termora.settings.terminal.select-copy")}:").xy(1, rows)
|
.add("${I18n.getString("termora.settings.terminal.select-copy")}:").xy(1, rows)
|
||||||
.add(selectCopyComboBox).xy(3, rows).apply { rows += step }
|
.add(selectCopyComboBox).xy(3, rows).apply { rows += step }
|
||||||
.add("${I18n.getString("termora.settings.terminal.cursor-style")}:").xy(1, rows)
|
.add("${I18n.getString("termora.settings.terminal.cursor-style")}:").xy(1, rows)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import org.apache.sshd.common.channel.ChannelFactory
|
|||||||
import org.apache.sshd.common.channel.PtyChannelConfiguration
|
import org.apache.sshd.common.channel.PtyChannelConfiguration
|
||||||
import org.apache.sshd.common.channel.PtyChannelConfigurationHolder
|
import org.apache.sshd.common.channel.PtyChannelConfigurationHolder
|
||||||
import org.apache.sshd.common.cipher.CipherNone
|
import org.apache.sshd.common.cipher.CipherNone
|
||||||
|
import org.apache.sshd.common.compression.BuiltinCompressions
|
||||||
import org.apache.sshd.common.config.keys.KeyRandomArt
|
import org.apache.sshd.common.config.keys.KeyRandomArt
|
||||||
import org.apache.sshd.common.config.keys.KeyUtils
|
import org.apache.sshd.common.config.keys.KeyUtils
|
||||||
import org.apache.sshd.common.future.CloseFuture
|
import org.apache.sshd.common.future.CloseFuture
|
||||||
@@ -47,6 +48,7 @@ import org.apache.sshd.common.kex.BuiltinDHFactories
|
|||||||
import org.apache.sshd.common.keyprovider.KeyIdentityProvider
|
import org.apache.sshd.common.keyprovider.KeyIdentityProvider
|
||||||
import org.apache.sshd.common.session.Session
|
import org.apache.sshd.common.session.Session
|
||||||
import org.apache.sshd.common.session.SessionListener
|
import org.apache.sshd.common.session.SessionListener
|
||||||
|
import org.apache.sshd.common.signature.BuiltinSignatures
|
||||||
import org.apache.sshd.common.util.net.SshdSocketAddress
|
import org.apache.sshd.common.util.net.SshdSocketAddress
|
||||||
import org.apache.sshd.core.CoreModuleProperties
|
import org.apache.sshd.core.CoreModuleProperties
|
||||||
import org.apache.sshd.server.forward.AcceptAllForwardingFilter
|
import org.apache.sshd.server.forward.AcceptAllForwardingFilter
|
||||||
@@ -339,6 +341,24 @@ object SshClients {
|
|||||||
)
|
)
|
||||||
builder.keyExchangeFactories(keyExchangeFactories)
|
builder.keyExchangeFactories(keyExchangeFactories)
|
||||||
|
|
||||||
|
val compressionFactories = ClientBuilder.setUpDefaultCompressionFactories(true).toMutableList()
|
||||||
|
for (compression in listOf(
|
||||||
|
BuiltinCompressions.none,
|
||||||
|
BuiltinCompressions.zlib,
|
||||||
|
BuiltinCompressions.delayedZlib
|
||||||
|
)) {
|
||||||
|
if (compressionFactories.contains(compression)) continue
|
||||||
|
compressionFactories.add(compression)
|
||||||
|
}
|
||||||
|
builder.compressionFactories(compressionFactories)
|
||||||
|
|
||||||
|
val signatureFactories = ClientBuilder.setUpDefaultSignatureFactories(true).toMutableList()
|
||||||
|
for (signature in BuiltinSignatures.entries) {
|
||||||
|
if (signatureFactories.contains(signature)) continue
|
||||||
|
signatureFactories.add(signature)
|
||||||
|
}
|
||||||
|
builder.signatureFactories(signatureFactories)
|
||||||
|
|
||||||
if (host.tunnelings.isEmpty() && host.options.jumpHosts.isEmpty()) {
|
if (host.tunnelings.isEmpty() && host.options.jumpHosts.isEmpty()) {
|
||||||
builder.forwardingFilter(RejectAllForwardingFilter.INSTANCE)
|
builder.forwardingFilter(RejectAllForwardingFilter.INSTANCE)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import app.termora.actions.AnActionEvent
|
|||||||
import app.termora.actions.DataProviders
|
import app.termora.actions.DataProviders
|
||||||
import app.termora.actions.MultipleAction
|
import app.termora.actions.MultipleAction
|
||||||
import app.termora.highlight.KeywordHighlightPaintListener
|
import app.termora.highlight.KeywordHighlightPaintListener
|
||||||
|
import app.termora.terminal.DataKey
|
||||||
import app.termora.terminal.PtyConnector
|
import app.termora.terminal.PtyConnector
|
||||||
import app.termora.terminal.Terminal
|
import app.termora.terminal.Terminal
|
||||||
import app.termora.terminal.panel.TerminalHyperlinkPaintListener
|
import app.termora.terminal.panel.TerminalHyperlinkPaintListener
|
||||||
@@ -40,6 +41,10 @@ class TerminalPanelFactory : Disposable {
|
|||||||
fun createTerminalPanel(terminal: Terminal, ptyConnector: PtyConnector): TerminalPanel {
|
fun createTerminalPanel(terminal: Terminal, ptyConnector: PtyConnector): TerminalPanel {
|
||||||
val writer = MyTerminalWriter(ptyConnector)
|
val writer = MyTerminalWriter(ptyConnector)
|
||||||
val terminalPanel = TerminalPanel(terminal, writer)
|
val terminalPanel = TerminalPanel(terminal, writer)
|
||||||
|
|
||||||
|
// processDeviceStatusReport
|
||||||
|
terminal.getTerminalModel().setData(DataKey.TerminalWriter, writer)
|
||||||
|
|
||||||
terminalPanel.addTerminalPaintListener(MultipleTerminalListener())
|
terminalPanel.addTerminalPaintListener(MultipleTerminalListener())
|
||||||
terminalPanel.addTerminalPaintListener(KeywordHighlightPaintListener.getInstance())
|
terminalPanel.addTerminalPaintListener(KeywordHighlightPaintListener.getInstance())
|
||||||
terminalPanel.addTerminalPaintListener(TerminalHyperlinkPaintListener.getInstance())
|
terminalPanel.addTerminalPaintListener(TerminalHyperlinkPaintListener.getInstance())
|
||||||
|
|||||||
@@ -63,10 +63,9 @@ class TermoraFrame : JFrame(), DataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseDragged(e: MouseEvent) {
|
override fun mouseDragged(e: MouseEvent) {
|
||||||
val mouseLayer = getMouseLayer() ?: return
|
|
||||||
getMouseMotionListener()?.mouseDragged(
|
getMouseMotionListener()?.mouseDragged(
|
||||||
MouseEvent(
|
MouseEvent(
|
||||||
mouseLayer,
|
e.component,
|
||||||
e.id,
|
e.id,
|
||||||
e.`when`,
|
e.`when`,
|
||||||
e.modifiersEx,
|
e.modifiersEx,
|
||||||
@@ -87,13 +86,6 @@ class TermoraFrame : JFrame(), DataProvider {
|
|||||||
return getHandler() as? MouseMotionListener
|
return getHandler() as? MouseMotionListener
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMouseLayer(): JComponent? {
|
|
||||||
val titlePane = getTitlePane() ?: return null
|
|
||||||
val handlerField = titlePane.javaClass.getDeclaredField("mouseLayer") ?: return null
|
|
||||||
handlerField.isAccessible = true
|
|
||||||
return handlerField.get(titlePane) as? JComponent
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getHandler(): Any? {
|
private fun getHandler(): Any? {
|
||||||
val titlePane = getTitlePane() ?: return null
|
val titlePane = getTitlePane() ?: return null
|
||||||
val handlerField = titlePane.javaClass.getDeclaredField("handler") ?: return null
|
val handlerField = titlePane.javaClass.getDeclaredField("handler") ?: return null
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
package app.termora.actions
|
package app.termora.actions
|
||||||
|
|
||||||
import app.termora.*
|
import app.termora.*
|
||||||
|
import com.formdev.flatlaf.util.SystemInfo
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
import org.apache.commons.io.IOUtils
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
import java.awt.datatransfer.DataFlavor
|
||||||
|
import java.awt.datatransfer.StringSelection
|
||||||
|
import java.net.URI
|
||||||
|
import java.util.*
|
||||||
|
import javax.swing.JOptionPane
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
class OpenHostAction : AnAction() {
|
class OpenHostAction : AnAction() {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -26,10 +39,70 @@ class OpenHostAction : AnAction() {
|
|||||||
Protocol.SSH -> SSHTerminalTab(windowScope, evt.host)
|
Protocol.SSH -> SSHTerminalTab(windowScope, evt.host)
|
||||||
Protocol.Serial -> SerialTerminalTab(windowScope, evt.host)
|
Protocol.Serial -> SerialTerminalTab(windowScope, evt.host)
|
||||||
Protocol.SFTPPty -> SFTPPtyTerminalTab(windowScope, evt.host)
|
Protocol.SFTPPty -> SFTPPtyTerminalTab(windowScope, evt.host)
|
||||||
|
Protocol.RDP -> openRDP(windowScope, evt.host)
|
||||||
else -> LocalTerminalTab(windowScope, evt.host)
|
else -> LocalTerminalTab(windowScope, evt.host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tab is TerminalTab) {
|
||||||
terminalTabbedManager.addTerminalTab(tab)
|
terminalTabbedManager.addTerminalTab(tab)
|
||||||
|
if (tab is PtyHostTerminalTab) {
|
||||||
tab.start()
|
tab.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openRDP(windowScope: WindowScope, host: Host) {
|
||||||
|
if (SystemInfo.isLinux) {
|
||||||
|
OptionPane.showMessageDialog(
|
||||||
|
windowScope.window,
|
||||||
|
"Linux cannot connect to Windows Remote Server, Supported only for macOS and Windows",
|
||||||
|
messageType = JOptionPane.WARNING_MESSAGE
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SystemInfo.isMacOS) {
|
||||||
|
if (!FileUtils.getFile("/Applications/Windows App.app").exists()) {
|
||||||
|
val option = OptionPane.showConfirmDialog(
|
||||||
|
windowScope.window,
|
||||||
|
"If you want to connect to a Windows Remote Server, You have to install the Windows App",
|
||||||
|
optionType = JOptionPane.OK_CANCEL_OPTION
|
||||||
|
)
|
||||||
|
if (option == JOptionPane.OK_OPTION) {
|
||||||
|
Application.browse(URI.create("https://apps.apple.com/app/windows-app/id1295203466"))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val sb = StringBuilder()
|
||||||
|
sb.append("full address:s:").append(host.host).append(':').append(host.port).appendLine()
|
||||||
|
sb.append("username:s:").append(host.username).appendLine()
|
||||||
|
|
||||||
|
val file = FileUtils.getFile(Application.getTemporaryDir(), UUID.randomUUID().toSimpleString() + ".rdp")
|
||||||
|
file.outputStream().use { IOUtils.write(sb.toString(), it, Charsets.UTF_8) }
|
||||||
|
|
||||||
|
if (host.authentication.type == AuthenticationType.Password) {
|
||||||
|
val systemClipboard = windowScope.window.toolkit.systemClipboard
|
||||||
|
val password = host.authentication.password
|
||||||
|
systemClipboard.setContents(StringSelection(password), null)
|
||||||
|
// clear password
|
||||||
|
swingCoroutineScope.launch(Dispatchers.IO) {
|
||||||
|
delay(30.seconds)
|
||||||
|
if (systemClipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
|
||||||
|
if (systemClipboard.getData(DataFlavor.stringFlavor) == password) {
|
||||||
|
systemClipboard.setContents(StringSelection(StringUtils.EMPTY), null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SystemInfo.isMacOS) {
|
||||||
|
ProcessBuilder("open", file.absolutePath).start()
|
||||||
|
} else if (SystemInfo.isWindows) {
|
||||||
|
ProcessBuilder("mstsc", file.absolutePath).start()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,6 +51,7 @@ class ChooseColorTemplateDialog(owner: Window, title: String) : DialogWrapper(ow
|
|||||||
val customBtn = JButton("Custom")
|
val customBtn = JButton("Custom")
|
||||||
customBtn.addActionListener {
|
customBtn.addActionListener {
|
||||||
val dialog = MyColorPickerDialog(this)
|
val dialog = MyColorPickerDialog(this)
|
||||||
|
dialog.setLocationRelativeTo(this)
|
||||||
dialog.colorPicker.color = defaultColor
|
dialog.colorPicker.color = defaultColor
|
||||||
dialog.isVisible = true
|
dialog.isVisible = true
|
||||||
val color = dialog.color
|
val color = dialog.color
|
||||||
|
|||||||
@@ -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,74 @@ 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
|
||||||
|
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 +181,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()
|
||||||
@@ -187,6 +185,7 @@ class NewKeywordHighlightDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createColorPanel(color: Color, title: String): ColorPanel {
|
private fun createColorPanel(color: Color, title: String): ColorPanel {
|
||||||
|
val owner = this
|
||||||
val arc = UIManager.getInt("Component.arc")
|
val arc = UIManager.getInt("Component.arc")
|
||||||
val lineBorder = FlatLineBorder(Insets(1, 1, 1, 1), DynamicColor.BorderColor, 1f, arc)
|
val lineBorder = FlatLineBorder(Insets(1, 1, 1, 1), DynamicColor.BorderColor, 1f, arc)
|
||||||
val colorPanel = ColorPanel(color)
|
val colorPanel = ColorPanel(color)
|
||||||
@@ -195,7 +194,8 @@ class NewKeywordHighlightDialog(
|
|||||||
colorPanel.addMouseListener(object : MouseAdapter() {
|
colorPanel.addMouseListener(object : MouseAdapter() {
|
||||||
override fun mouseClicked(e: MouseEvent) {
|
override fun mouseClicked(e: MouseEvent) {
|
||||||
if (SwingUtilities.isLeftMouseButton(e)) {
|
if (SwingUtilities.isLeftMouseButton(e)) {
|
||||||
val dialog = ChooseColorTemplateDialog(this@NewKeywordHighlightDialog, title)
|
val dialog = ChooseColorTemplateDialog(owner, title)
|
||||||
|
dialog.setLocationRelativeTo(owner)
|
||||||
dialog.defaultColor = colorPanel.color
|
dialog.defaultColor = colorPanel.color
|
||||||
dialog.isVisible = true
|
dialog.isVisible = true
|
||||||
colorPanel.color = dialog.color ?: return
|
colorPanel.color = dialog.color ?: return
|
||||||
@@ -218,6 +218,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,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package app.termora.keymap
|
package app.termora.keymap
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.util.SystemInfo
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
@@ -23,7 +24,14 @@ class KeyShortcut(val keyStroke: KeyStroke) : Shortcut() {
|
|||||||
text = text.replace("MINUS", "-")
|
text = text.replace("MINUS", "-")
|
||||||
}
|
}
|
||||||
|
|
||||||
return text.toCharArray().joinToString(" + ")
|
text = text.toCharArray().joinToString(" + ")
|
||||||
|
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
||||||
|
text = text.replace("⇧", "Shift")
|
||||||
|
text = text.replace("⌃", "Ctrl")
|
||||||
|
text = text.replace("⌥", "Alt")
|
||||||
|
}
|
||||||
|
|
||||||
|
return text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import app.termora.*
|
|||||||
import app.termora.actions.AnActionEvent
|
import app.termora.actions.AnActionEvent
|
||||||
import app.termora.actions.SettingsAction
|
import app.termora.actions.SettingsAction
|
||||||
import app.termora.sftp.FileSystemViewTable.AskTransfer.Action
|
import app.termora.sftp.FileSystemViewTable.AskTransfer.Action
|
||||||
|
import app.termora.vfs2.VFSWalker
|
||||||
import app.termora.vfs2.sftp.MySftpFileObject
|
import app.termora.vfs2.sftp.MySftpFileObject
|
||||||
import app.termora.vfs2.sftp.MySftpFileSystem
|
import app.termora.vfs2.sftp.MySftpFileSystem
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
import com.formdev.flatlaf.FlatClientProperties
|
||||||
@@ -37,7 +38,6 @@ import java.nio.file.FileVisitor
|
|||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.nio.file.StandardOpenOption
|
import java.nio.file.StandardOpenOption
|
||||||
import java.nio.file.attribute.BasicFileAttributes
|
import java.nio.file.attribute.BasicFileAttributes
|
||||||
import java.nio.file.attribute.FileTime
|
|
||||||
import java.text.MessageFormat
|
import java.text.MessageFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
@@ -45,6 +45,21 @@ import java.util.regex.Pattern
|
|||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
import javax.swing.table.DefaultTableCellRenderer
|
import javax.swing.table.DefaultTableCellRenderer
|
||||||
import kotlin.collections.ArrayDeque
|
import kotlin.collections.ArrayDeque
|
||||||
|
import kotlin.collections.List
|
||||||
|
import kotlin.collections.all
|
||||||
|
import kotlin.collections.contains
|
||||||
|
import kotlin.collections.filter
|
||||||
|
import kotlin.collections.filterIsInstance
|
||||||
|
import kotlin.collections.find
|
||||||
|
import kotlin.collections.forEach
|
||||||
|
import kotlin.collections.isEmpty
|
||||||
|
import kotlin.collections.isNotEmpty
|
||||||
|
import kotlin.collections.last
|
||||||
|
import kotlin.collections.listOf
|
||||||
|
import kotlin.collections.map
|
||||||
|
import kotlin.collections.mapOf
|
||||||
|
import kotlin.collections.mutableListOf
|
||||||
|
import kotlin.collections.sortedArray
|
||||||
import kotlin.io.path.absolutePathString
|
import kotlin.io.path.absolutePathString
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
@@ -218,7 +233,7 @@ class FileSystemViewTable(
|
|||||||
val localTarget = sftpPanel.getLocalTarget()
|
val localTarget = sftpPanel.getLocalTarget()
|
||||||
val table = localTarget.getData(SFTPDataProviders.FileSystemViewTable) ?: return false
|
val table = localTarget.getData(SFTPDataProviders.FileSystemViewTable) ?: return false
|
||||||
// 委托最左侧的本地文件系统传输
|
// 委托最左侧的本地文件系统传输
|
||||||
table.transfer(paths, true, targetWorkdir)
|
table.transfer(paths, true, targetWorkdir, fileSystemViewPanel)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -360,34 +375,7 @@ class FileSystemViewTable(
|
|||||||
override fun actionPerformed(e: ActionEvent) {
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
val last = files.last()
|
val last = files.last()
|
||||||
if (last !is MySftpFileObject) return
|
if (last !is MySftpFileObject) return
|
||||||
|
changePermission(last)
|
||||||
val dialog = PosixFilePermissionDialog(
|
|
||||||
SwingUtilities.getWindowAncestor(table),
|
|
||||||
model.getFilePermissions(last)
|
|
||||||
)
|
|
||||||
val permissions = dialog.open() ?: return
|
|
||||||
|
|
||||||
if (fileSystemViewPanel.requestLoading()) {
|
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
|
||||||
val c = runCatching { last.setPosixFilePermissions(permissions) }.onFailure {
|
|
||||||
withContext(Dispatchers.Swing) {
|
|
||||||
OptionPane.showMessageDialog(
|
|
||||||
owner,
|
|
||||||
ExceptionUtils.getMessage(it),
|
|
||||||
messageType = JOptionPane.ERROR_MESSAGE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop loading
|
|
||||||
fileSystemViewPanel.stopLoading()
|
|
||||||
|
|
||||||
// reload
|
|
||||||
if (c.isSuccess) {
|
|
||||||
fileSystemViewPanel.reload(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
refresh.addActionListener { fileSystemViewPanel.reload() }
|
refresh.addActionListener { fileSystemViewPanel.reload() }
|
||||||
@@ -409,6 +397,80 @@ class FileSystemViewTable(
|
|||||||
popupMenu.show(table, e.x, e.y)
|
popupMenu.show(table, e.x, e.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun changePermission(file: MySftpFileObject) {
|
||||||
|
|
||||||
|
val dialog = PosixFilePermissionDialog(
|
||||||
|
SwingUtilities.getWindowAncestor(table),
|
||||||
|
model.getFilePermissions(file)
|
||||||
|
)
|
||||||
|
val permissions = dialog.open() ?: return
|
||||||
|
val isIncludeSubdirectories = dialog.isIncludeSubdirectories()
|
||||||
|
|
||||||
|
if (fileSystemViewPanel.requestLoading()) {
|
||||||
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
|
val c = runCatching {
|
||||||
|
file.setPosixFilePermissions(permissions)
|
||||||
|
if (isIncludeSubdirectories && file.isFolder) {
|
||||||
|
file.refresh()
|
||||||
|
VFSWalker.walk(file, object : FileVisitor<FileObject> {
|
||||||
|
override fun preVisitDirectory(
|
||||||
|
dir: FileObject,
|
||||||
|
attrs: BasicFileAttributes
|
||||||
|
): FileVisitResult {
|
||||||
|
dir.refresh()
|
||||||
|
if (dir is MySftpFileObject) {
|
||||||
|
dir.setPosixFilePermissions(permissions)
|
||||||
|
}
|
||||||
|
return FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFile(
|
||||||
|
file: FileObject,
|
||||||
|
attrs: BasicFileAttributes
|
||||||
|
): FileVisitResult {
|
||||||
|
if (file is MySftpFileObject) {
|
||||||
|
file.setPosixFilePermissions(permissions)
|
||||||
|
}
|
||||||
|
return FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFileFailed(
|
||||||
|
file: FileObject,
|
||||||
|
exc: IOException
|
||||||
|
): FileVisitResult {
|
||||||
|
return FileVisitResult.TERMINATE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun postVisitDirectory(
|
||||||
|
dir: FileObject,
|
||||||
|
exc: IOException?
|
||||||
|
): FileVisitResult {
|
||||||
|
return FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
withContext(Dispatchers.Swing) {
|
||||||
|
OptionPane.showMessageDialog(
|
||||||
|
owner,
|
||||||
|
ExceptionUtils.getMessage(it),
|
||||||
|
messageType = JOptionPane.ERROR_MESSAGE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop loading
|
||||||
|
fileSystemViewPanel.stopLoading()
|
||||||
|
|
||||||
|
// reload
|
||||||
|
if (c.isSuccess) {
|
||||||
|
fileSystemViewPanel.reload(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun renameSelection() {
|
private fun renameSelection() {
|
||||||
val index = selectedRow
|
val index = selectedRow
|
||||||
if (index < 0) return
|
if (index < 0) return
|
||||||
@@ -619,12 +681,13 @@ class FileSystemViewTable(
|
|||||||
private fun transfer(
|
private fun transfer(
|
||||||
files: List<FileObject>,
|
files: List<FileObject>,
|
||||||
fromLocalSystem: Boolean = false,
|
fromLocalSystem: Boolean = false,
|
||||||
targetWorkdir: FileObject? = null
|
targetWorkdir: FileObject? = null,
|
||||||
|
target: FileSystemViewPanel? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
assertEventDispatchThread()
|
assertEventDispatchThread()
|
||||||
|
|
||||||
val target = sftpPanel.getTarget(table) ?: return
|
val target = (target ?: sftpPanel.getTarget(table)) ?: return
|
||||||
val table = target.getData(SFTPDataProviders.FileSystemViewTable) ?: return
|
val table = target.getData(SFTPDataProviders.FileSystemViewTable) ?: return
|
||||||
var isApplyAll = false
|
var isApplyAll = false
|
||||||
var lastAction = Action.Overwrite
|
var lastAction = Action.Overwrite
|
||||||
@@ -650,7 +713,7 @@ class FileSystemViewTable(
|
|||||||
|
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
try {
|
try {
|
||||||
doTransfer(file, lastAction, fromLocalSystem, targetWorkdir)
|
doTransfer(file, lastAction, fromLocalSystem, targetWorkdir, target)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (log.isErrorEnabled) {
|
if (log.isErrorEnabled) {
|
||||||
log.error(e.message, e)
|
log.error(e.message, e)
|
||||||
@@ -801,10 +864,11 @@ class FileSystemViewTable(
|
|||||||
file: FileObject,
|
file: FileObject,
|
||||||
action: Action,
|
action: Action,
|
||||||
fromLocalSystem: Boolean,
|
fromLocalSystem: Boolean,
|
||||||
targetWorkdir: FileObject?
|
targetWorkdir: FileObject?,
|
||||||
|
target: FileSystemViewPanel? = null
|
||||||
) {
|
) {
|
||||||
val sftpPanel = this.sftpPanel
|
val sftpPanel = this.sftpPanel
|
||||||
val target = sftpPanel.getTarget(table) ?: return
|
val target = (target ?: sftpPanel.getTarget(table)) ?: return
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义一个添加器,它可以自动的判断导入/拖拽行为
|
* 定义一个添加器,它可以自动的判断导入/拖拽行为
|
||||||
@@ -886,36 +950,7 @@ class FileSystemViewTable(
|
|||||||
dir: FileObject,
|
dir: FileObject,
|
||||||
visitor: FileVisitor<FileObject>,
|
visitor: FileVisitor<FileObject>,
|
||||||
): FileVisitResult {
|
): FileVisitResult {
|
||||||
|
return VFSWalker.walk(dir, visitor)
|
||||||
// clear cache
|
|
||||||
if (visitor.preVisitDirectory(dir, EmptyBasicFileAttributes.INSTANCE) == FileVisitResult.TERMINATE) {
|
|
||||||
return FileVisitResult.TERMINATE
|
|
||||||
}
|
|
||||||
|
|
||||||
for (e in dir.children) {
|
|
||||||
if (e.name.baseName == ".." || e.name.baseName == ".") continue
|
|
||||||
if (e.isFolder) {
|
|
||||||
if (walk(dir.resolveFile(e.name.baseName), visitor) == FileVisitResult.TERMINATE) {
|
|
||||||
return FileVisitResult.TERMINATE
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val result = visitor.visitFile(
|
|
||||||
dir.resolveFile(e.name.baseName),
|
|
||||||
EmptyBasicFileAttributes.INSTANCE
|
|
||||||
)
|
|
||||||
if (result == FileVisitResult.TERMINATE) {
|
|
||||||
return FileVisitResult.TERMINATE
|
|
||||||
} else if (result == FileVisitResult.SKIP_SUBTREE) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visitor.postVisitDirectory(dir, null) == FileVisitResult.TERMINATE) {
|
|
||||||
return FileVisitResult.TERMINATE
|
|
||||||
}
|
|
||||||
|
|
||||||
return FileVisitResult.CONTINUE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addTransport(
|
private fun addTransport(
|
||||||
@@ -974,47 +1009,5 @@ class FileSystemViewTable(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class EmptyBasicFileAttributes : BasicFileAttributes {
|
|
||||||
companion object {
|
|
||||||
val INSTANCE = EmptyBasicFileAttributes()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun lastModifiedTime(): FileTime {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun lastAccessTime(): FileTime {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun creationTime(): FileTime {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isRegularFile(): Boolean {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isDirectory(): Boolean {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isSymbolicLink(): Boolean {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isOther(): Boolean {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun size(): Long {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fileKey(): Any {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -157,8 +157,12 @@ class FileSystemViewTableModel : DefaultTableModel() {
|
|||||||
fun getPathNames(): Set<String> {
|
fun getPathNames(): Set<String> {
|
||||||
val names = linkedSetOf<String>()
|
val names = linkedSetOf<String>()
|
||||||
for (i in 0 until rowCount) {
|
for (i in 0 until rowCount) {
|
||||||
|
if (hasParent && i == 0) {
|
||||||
|
names.add("..")
|
||||||
|
} else {
|
||||||
names.add(getFileObject(i).name.baseName)
|
names.add(getFileObject(i).name.baseName)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class PosixFilePermissionDialog(
|
|||||||
private val otherRead = JCheckBox(I18n.getString("termora.transport.permissions.read"))
|
private val otherRead = JCheckBox(I18n.getString("termora.transport.permissions.read"))
|
||||||
private val otherWrite = JCheckBox(I18n.getString("termora.transport.permissions.write"))
|
private val otherWrite = JCheckBox(I18n.getString("termora.transport.permissions.write"))
|
||||||
private val otherExecute = JCheckBox(I18n.getString("termora.transport.permissions.execute"))
|
private val otherExecute = JCheckBox(I18n.getString("termora.transport.permissions.execute"))
|
||||||
|
private val includeSubFolder = JCheckBox(I18n.getString("termora.transport.permissions.include-subfolder"))
|
||||||
|
|
||||||
private var isCancelled = false
|
private var isCancelled = false
|
||||||
|
|
||||||
@@ -60,13 +61,14 @@ class PosixFilePermissionDialog(
|
|||||||
otherRead.isFocusable = false
|
otherRead.isFocusable = false
|
||||||
otherWrite.isFocusable = false
|
otherWrite.isFocusable = false
|
||||||
otherExecute.isFocusable = false
|
otherExecute.isFocusable = false
|
||||||
|
includeSubFolder.isFocusable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createCenterPanel(): JComponent {
|
override fun createCenterPanel(): JComponent {
|
||||||
val formMargin = "7dlu"
|
val formMargin = "7dlu"
|
||||||
val layout = FormLayout(
|
val layout = FormLayout(
|
||||||
"default:grow, $formMargin, default:grow, $formMargin, default:grow",
|
"default:grow, $formMargin, default:grow, $formMargin, default:grow",
|
||||||
"pref, $formMargin, pref, $formMargin, pref"
|
"pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
|
||||||
)
|
)
|
||||||
|
|
||||||
val builder = FormBuilder.create().padding("0, $formMargin, $formMargin, $formMargin")
|
val builder = FormBuilder.create().padding("0, $formMargin, $formMargin, $formMargin")
|
||||||
@@ -95,6 +97,8 @@ class PosixFilePermissionDialog(
|
|||||||
otherBox.border = BorderFactory.createTitledBorder(I18n.getString("termora.transport.permissions.others"))
|
otherBox.border = BorderFactory.createTitledBorder(I18n.getString("termora.transport.permissions.others"))
|
||||||
builder.add(otherBox).xy(5, 3)
|
builder.add(otherBox).xy(5, 3)
|
||||||
|
|
||||||
|
builder.add(includeSubFolder).xyw(1, 5, 5)
|
||||||
|
|
||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +107,10 @@ class PosixFilePermissionDialog(
|
|||||||
super.doCancelAction()
|
super.doCancelAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isIncludeSubdirectories(): Boolean {
|
||||||
|
return includeSubFolder.isSelected
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 返回空表示取消了
|
* @return 返回空表示取消了
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -56,6 +56,16 @@ class SFTPAction : AnAction("SFTP", Icons.folder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val host = hostManager.getHost(hostId) ?: return
|
val host = hostManager.getHost(hostId) ?: return
|
||||||
|
for (i in 0 until tabbed.tabCount) {
|
||||||
|
val c = tabbed.getComponentAt(i)
|
||||||
|
if (c is SFTPFileSystemViewPanel) {
|
||||||
|
if (c.state == SFTPFileSystemViewPanel.State.Initialized) {
|
||||||
|
c.selectHost(host)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tabbed.addSFTPFileSystemViewPanelTab(host)
|
tabbed.addSFTPFileSystemViewPanelTab(host)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import java.awt.event.MouseAdapter
|
|||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
|
import javax.swing.event.TreeExpansionEvent
|
||||||
|
import javax.swing.event.TreeExpansionListener
|
||||||
|
|
||||||
class SFTPFileSystemViewPanel(
|
class SFTPFileSystemViewPanel(
|
||||||
var host: Host? = null,
|
var host: Host? = null,
|
||||||
@@ -35,17 +37,18 @@ class SFTPFileSystemViewPanel(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(SFTPFileSystemViewPanel::class.java)
|
private val log = LoggerFactory.getLogger(SFTPFileSystemViewPanel::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
private enum class State {
|
enum class State {
|
||||||
Initialized,
|
Initialized,
|
||||||
Connecting,
|
Connecting,
|
||||||
Connected,
|
Connected,
|
||||||
ConnectFailed,
|
ConnectFailed,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var state = State.Initialized
|
var state = State.Initialized
|
||||||
|
private set
|
||||||
private val cardLayout = CardLayout()
|
private val cardLayout = CardLayout()
|
||||||
private val cardPanel = JPanel(cardLayout)
|
private val cardPanel = JPanel(cardLayout)
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
@@ -283,12 +286,20 @@ class SFTPFileSystemViewPanel(
|
|||||||
val node = tree.getLastSelectedPathNode() ?: return
|
val node = tree.getLastSelectedPathNode() ?: return
|
||||||
if (node.isFolder) return
|
if (node.isFolder) return
|
||||||
val host = node.data as Host
|
val host = node.data as Host
|
||||||
that.setTabTitle(host.name)
|
selectHost(host)
|
||||||
that.host = host
|
|
||||||
that.connect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
tree.addTreeExpansionListener(object : TreeExpansionListener {
|
||||||
|
override fun treeExpanded(event: TreeExpansionEvent) {
|
||||||
|
properties.putString("SFTPTabbed.Tree.state", TreeUtils.saveExpansionState(tree))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun treeCollapsed(event: TreeExpansionEvent) {
|
||||||
|
properties.putString("SFTPTabbed.Tree.state", TreeUtils.saveExpansionState(tree))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
@@ -305,6 +316,12 @@ class SFTPFileSystemViewPanel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun selectHost(host: Host) {
|
||||||
|
that.setTabTitle(host.name)
|
||||||
|
that.host = host
|
||||||
|
that.connect()
|
||||||
|
}
|
||||||
|
|
||||||
private fun setTabTitle(title: String) {
|
private fun setTabTitle(title: String) {
|
||||||
val tabbed = SwingUtilities.getAncestorOfClass(JTabbedPane::class.java, that)
|
val tabbed = SwingUtilities.getAncestorOfClass(JTabbedPane::class.java, that)
|
||||||
if (tabbed is JTabbedPane) {
|
if (tabbed is JTabbedPane) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import app.termora.actions.AnAction
|
|||||||
import app.termora.actions.AnActionEvent
|
import app.termora.actions.AnActionEvent
|
||||||
import com.formdev.flatlaf.extras.components.FlatPopupMenu
|
import com.formdev.flatlaf.extras.components.FlatPopupMenu
|
||||||
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
||||||
import java.awt.Point
|
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.MouseAdapter
|
||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
@@ -13,7 +12,6 @@ import javax.swing.JButton
|
|||||||
import javax.swing.JToolBar
|
import javax.swing.JToolBar
|
||||||
import javax.swing.SwingUtilities
|
import javax.swing.SwingUtilities
|
||||||
import javax.swing.UIManager
|
import javax.swing.UIManager
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
@Suppress("DuplicatedCode")
|
@Suppress("DuplicatedCode")
|
||||||
class SFTPTabbed(private val transportManager: TransportManager) : FlatTabbedPane(), Disposable {
|
class SFTPTabbed(private val transportManager: TransportManager) : FlatTabbedPane(), Disposable {
|
||||||
@@ -43,23 +41,20 @@ class SFTPTabbed(private val transportManager: TransportManager) : FlatTabbedPan
|
|||||||
private fun initEvents() {
|
private fun initEvents() {
|
||||||
addBtn.addActionListener(object : AnAction() {
|
addBtn.addActionListener(object : AnAction() {
|
||||||
override fun actionPerformed(evt: AnActionEvent) {
|
override fun actionPerformed(evt: AnActionEvent) {
|
||||||
val dialog = NewHostTreeDialog(SwingUtilities.getWindowAncestor(tabbed))
|
for (i in 0 until tabCount) {
|
||||||
dialog.location = Point(
|
val c = getComponentAt(i)
|
||||||
max(0, addBtn.locationOnScreen.x - dialog.width / 2 + addBtn.width / 2),
|
if (c !is SFTPFileSystemViewPanel) continue
|
||||||
addBtn.locationOnScreen.y + max(tabHeight, addBtn.height)
|
if (c.state != SFTPFileSystemViewPanel.State.Initialized) continue
|
||||||
)
|
selectedIndex = i
|
||||||
dialog.setFilter { it.host.protocol == Protocol.SSH }
|
return
|
||||||
dialog.setTreeName("SFTPTabbed.Tree")
|
|
||||||
dialog.allowMulti = true
|
|
||||||
dialog.isVisible = true
|
|
||||||
|
|
||||||
val hosts = dialog.hosts
|
|
||||||
if (hosts.isEmpty()) return
|
|
||||||
|
|
||||||
for (host in hosts) {
|
|
||||||
addSFTPFileSystemViewPanelTab(host)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加一个新的
|
||||||
|
addTab(
|
||||||
|
I18n.getString("termora.transport.sftp.select-host"),
|
||||||
|
SFTPFileSystemViewPanel(transportManager = transportManager)
|
||||||
|
)
|
||||||
|
selectedIndex = tabCount - 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import app.termora.Icons
|
|||||||
import app.termora.actions.AnAction
|
import app.termora.actions.AnAction
|
||||||
import app.termora.actions.AnActionEvent
|
import app.termora.actions.AnActionEvent
|
||||||
import app.termora.terminal.ControlCharacters
|
import app.termora.terminal.ControlCharacters
|
||||||
|
import app.termora.terminal.Null
|
||||||
import app.termora.terminal.panel.TerminalWriter
|
import app.termora.terminal.panel.TerminalWriter
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.apache.commons.text.StringEscapeUtils
|
import org.apache.commons.text.StringEscapeUtils
|
||||||
|
|
||||||
class SnippetAction private constructor() : AnAction(I18n.getString("termora.snippet.title"), Icons.codeSpan) {
|
class SnippetAction private constructor() : AnAction(I18n.getString("termora.snippet.title"), Icons.codeSpan) {
|
||||||
@@ -33,11 +35,23 @@ class SnippetAction private constructor() : AnAction(I18n.getString("termora.sni
|
|||||||
"\\a" to ControlCharacters.BEL,
|
"\\a" to ControlCharacters.BEL,
|
||||||
"\\e" to ControlCharacters.ESC,
|
"\\e" to ControlCharacters.ESC,
|
||||||
)
|
)
|
||||||
|
val chars = snippet.snippet.toCharArray()
|
||||||
|
for (i in chars.indices) {
|
||||||
|
val c = chars[i]
|
||||||
|
if (i == 0) continue
|
||||||
|
if (c != '\n') continue
|
||||||
|
if (chars[i - 1] != '\\') continue
|
||||||
|
// 每一行的最后一个 \ 比较特殊,先转成 null 然后再去 unescapeJava
|
||||||
|
chars[i - 1] = Char.Null
|
||||||
|
}
|
||||||
|
|
||||||
var text = StringEscapeUtils.unescapeJava(snippet.snippet)
|
var text = chars.joinToString(StringUtils.EMPTY)
|
||||||
|
text = StringEscapeUtils.unescapeJava(text)
|
||||||
for (e in map.entries) {
|
for (e in map.entries) {
|
||||||
text = text.replace(e.key, e.value.toString())
|
text = text.replace(e.key, e.value.toString())
|
||||||
}
|
}
|
||||||
|
text = text.replace(Char.Null, '\\')
|
||||||
|
|
||||||
writer.write(TerminalWriter.WriteRequest.fromBytes(text.toByteArray(writer.getCharset())))
|
writer.write(TerminalWriter.WriteRequest.fromBytes(text.toByteArray(writer.getCharset())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,8 +41,10 @@ class SnippetPanel : JPanel(BorderLayout()), Disposable {
|
|||||||
private fun initViews() {
|
private fun initViews() {
|
||||||
val splitPane = JSplitPane()
|
val splitPane = JSplitPane()
|
||||||
splitPane.border = BorderFactory.createMatteBorder(1, 0, 0, 0, DynamicColor.BorderColor)
|
splitPane.border = BorderFactory.createMatteBorder(1, 0, 0, 0, DynamicColor.BorderColor)
|
||||||
|
val scrollPane = JScrollPane(snippetTree)
|
||||||
|
scrollPane.border = BorderFactory.createEmptyBorder()
|
||||||
|
|
||||||
leftPanel.add(snippetTree, BorderLayout.CENTER)
|
leftPanel.add(scrollPane, BorderLayout.CENTER)
|
||||||
leftPanel.border = BorderFactory.createCompoundBorder(
|
leftPanel.border = BorderFactory.createCompoundBorder(
|
||||||
BorderFactory.createMatteBorder(0, 0, 0, 1, DynamicColor.BorderColor),
|
BorderFactory.createMatteBorder(0, 0, 0, 1, DynamicColor.BorderColor),
|
||||||
BorderFactory.createEmptyBorder(4, 4, 4, 4)
|
BorderFactory.createEmptyBorder(4, 4, 4, 4)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package app.termora.snippet
|
package app.termora.snippet
|
||||||
|
|
||||||
|
import app.termora.I18n
|
||||||
import app.termora.SimpleTreeModel
|
import app.termora.SimpleTreeModel
|
||||||
import javax.swing.tree.MutableTreeNode
|
import javax.swing.tree.MutableTreeNode
|
||||||
import javax.swing.tree.TreeNode
|
import javax.swing.tree.TreeNode
|
||||||
@@ -8,7 +9,7 @@ class SnippetTreeModel : SimpleTreeModel<Snippet>(
|
|||||||
SnippetTreeNode(
|
SnippetTreeNode(
|
||||||
Snippet(
|
Snippet(
|
||||||
id = "0",
|
id = "0",
|
||||||
name = "全部片段",
|
name = I18n.getString("termora.snippet.title"),
|
||||||
type = SnippetType.Folder
|
type = SnippetType.Folder
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -399,6 +399,16 @@ class ControlSequenceIntroducerProcessor(terminal: Terminal, reader: TerminalRea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).
|
||||||
|
'Z' -> {
|
||||||
|
val count = args.toInt(1)
|
||||||
|
val cursorModel = terminal.getCursorModel()
|
||||||
|
for (i in 0 until count) {
|
||||||
|
val x = terminal.getTabulator().previousTab(cursorModel.getPosition().x - 1) + 1
|
||||||
|
terminal.getCursorModel().move(cursorModel.getPosition().y, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// split
|
// split
|
||||||
';' -> {
|
';' -> {
|
||||||
args.append(ch)
|
args.append(ch)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package app.termora.terminal.panel
|
|||||||
|
|
||||||
import app.termora.Application
|
import app.termora.Application
|
||||||
import app.termora.ApplicationScope
|
import app.termora.ApplicationScope
|
||||||
|
import app.termora.Database
|
||||||
import app.termora.terminal.*
|
import app.termora.terminal.*
|
||||||
import java.awt.Graphics
|
import java.awt.Graphics
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
@@ -16,6 +17,7 @@ class TerminalHyperlinkPaintListener private constructor() : TerminalPaintListen
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val regex = Regex("https?://\\S*[^.\\s'\",()<>\\[\\]]")
|
private val regex = Regex("https?://\\S*[^.\\s'\",()<>\\[\\]]")
|
||||||
|
private val isEnableHyperlink get() = Database.getDatabase().terminal.hyperlink
|
||||||
|
|
||||||
override fun before(
|
override fun before(
|
||||||
offset: Int,
|
offset: Int,
|
||||||
@@ -25,6 +27,9 @@ class TerminalHyperlinkPaintListener private constructor() : TerminalPaintListen
|
|||||||
terminalDisplay: TerminalDisplay,
|
terminalDisplay: TerminalDisplay,
|
||||||
terminal: Terminal
|
terminal: Terminal
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
if (isEnableHyperlink.not()) return
|
||||||
|
|
||||||
val document = terminal.getDocument()
|
val document = terminal.getDocument()
|
||||||
var startOffset = offset
|
var startOffset = offset
|
||||||
var endOffset = startOffset + count
|
var endOffset = startOffset + count
|
||||||
@@ -91,4 +96,18 @@ class TerminalHyperlinkPaintListener private constructor() : TerminalPaintListen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun after(
|
||||||
|
offset: Int,
|
||||||
|
count: Int,
|
||||||
|
g: Graphics,
|
||||||
|
terminalPanel: TerminalPanel,
|
||||||
|
terminalDisplay: TerminalDisplay,
|
||||||
|
terminal: Terminal
|
||||||
|
) {
|
||||||
|
if (isEnableHyperlink.not()) {
|
||||||
|
// 删除之前的
|
||||||
|
terminal.getMarkupModel().removeAllHighlighters(Highlighter.HYPERLINK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -79,6 +79,8 @@ class TerminalPanelKeyAdapter(
|
|||||||
val encode = terminal.getKeyEncoder().encode(AWTTerminalKeyEvent(e))
|
val encode = terminal.getKeyEncoder().encode(AWTTerminalKeyEvent(e))
|
||||||
if (encode.isNotEmpty()) {
|
if (encode.isNotEmpty()) {
|
||||||
writer.write(TerminalWriter.WriteRequest.fromBytes(encode.toByteArray(writer.getCharset())))
|
writer.write(TerminalWriter.WriteRequest.fromBytes(encode.toByteArray(writer.getCharset())))
|
||||||
|
// scroll to bottom
|
||||||
|
terminal.getScrollingModel().scrollTo(Int.MAX_VALUE)
|
||||||
e.consume()
|
e.consume()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +93,8 @@ class TerminalPanelKeyAdapter(
|
|||||||
if (isAltPressedOnly(e) && Character.isDefined(e.keyChar)) {
|
if (isAltPressedOnly(e) && Character.isDefined(e.keyChar)) {
|
||||||
val c = String(charArrayOf(ASCII_ESC, simpleMapKeyCodeToChar(e)))
|
val c = String(charArrayOf(ASCII_ESC, simpleMapKeyCodeToChar(e)))
|
||||||
writer.write(TerminalWriter.WriteRequest.fromBytes(c.toByteArray(writer.getCharset())))
|
writer.write(TerminalWriter.WriteRequest.fromBytes(c.toByteArray(writer.getCharset())))
|
||||||
|
// scroll to bottom
|
||||||
|
terminal.getScrollingModel().scrollTo(Int.MAX_VALUE)
|
||||||
e.consume()
|
e.consume()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package app.termora.terminal.panel.vw
|
|||||||
|
|
||||||
import app.termora.*
|
import app.termora.*
|
||||||
import com.formdev.flatlaf.extras.components.FlatToolBar
|
import com.formdev.flatlaf.extras.components.FlatToolBar
|
||||||
|
import com.formdev.flatlaf.util.SystemInfo
|
||||||
import java.awt.*
|
import java.awt.*
|
||||||
import java.awt.event.*
|
import java.awt.event.*
|
||||||
import java.beans.PropertyChangeEvent
|
import java.beans.PropertyChangeEvent
|
||||||
import java.beans.PropertyChangeListener
|
import java.beans.PropertyChangeListener
|
||||||
|
import javax.imageio.ImageIO
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@@ -333,6 +335,15 @@ open class VisualWindowPanel(protected val id: String, protected val visualWindo
|
|||||||
title = getWindowTitle()
|
title = getWindowTitle()
|
||||||
isAlwaysOnTop = isAlwaysTop
|
isAlwaysOnTop = isAlwaysTop
|
||||||
|
|
||||||
|
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
||||||
|
val sizes = listOf(16, 20, 24, 28, 32, 48, 64)
|
||||||
|
val loader = TermoraFrame::class.java.classLoader
|
||||||
|
val images = sizes.mapNotNull { e ->
|
||||||
|
loader.getResourceAsStream("icons/termora_${e}x${e}.png")?.use { ImageIO.read(it) }
|
||||||
|
}
|
||||||
|
iconImages = images
|
||||||
|
}
|
||||||
|
|
||||||
initEvents()
|
initEvents()
|
||||||
|
|
||||||
init()
|
init()
|
||||||
|
|||||||
88
src/main/kotlin/app/termora/vfs2/VFSWalker.kt
Normal file
88
src/main/kotlin/app/termora/vfs2/VFSWalker.kt
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package app.termora.vfs2
|
||||||
|
|
||||||
|
import org.apache.commons.vfs2.FileObject
|
||||||
|
import java.nio.file.FileVisitResult
|
||||||
|
import java.nio.file.FileVisitor
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes
|
||||||
|
import java.nio.file.attribute.FileTime
|
||||||
|
|
||||||
|
object VFSWalker {
|
||||||
|
fun walk(
|
||||||
|
dir: FileObject,
|
||||||
|
visitor: FileVisitor<FileObject>,
|
||||||
|
): FileVisitResult {
|
||||||
|
|
||||||
|
// clear cache
|
||||||
|
if (visitor.preVisitDirectory(dir, EmptyBasicFileAttributes.INSTANCE) == FileVisitResult.TERMINATE) {
|
||||||
|
return FileVisitResult.TERMINATE
|
||||||
|
}
|
||||||
|
|
||||||
|
for (e in dir.children) {
|
||||||
|
if (e.name.baseName == ".." || e.name.baseName == ".") continue
|
||||||
|
if (e.isFolder) {
|
||||||
|
if (walk(dir.resolveFile(e.name.baseName), visitor) == FileVisitResult.TERMINATE) {
|
||||||
|
return FileVisitResult.TERMINATE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val result = visitor.visitFile(
|
||||||
|
dir.resolveFile(e.name.baseName),
|
||||||
|
EmptyBasicFileAttributes.INSTANCE
|
||||||
|
)
|
||||||
|
if (result == FileVisitResult.TERMINATE) {
|
||||||
|
return FileVisitResult.TERMINATE
|
||||||
|
} else if (result == FileVisitResult.SKIP_SUBTREE) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visitor.postVisitDirectory(dir, null) == FileVisitResult.TERMINATE) {
|
||||||
|
return FileVisitResult.TERMINATE
|
||||||
|
}
|
||||||
|
|
||||||
|
return FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EmptyBasicFileAttributes : BasicFileAttributes {
|
||||||
|
companion object {
|
||||||
|
val INSTANCE = EmptyBasicFileAttributes()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun lastModifiedTime(): FileTime {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun lastAccessTime(): FileTime {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun creationTime(): FileTime {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isRegularFile(): Boolean {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isDirectory(): Boolean {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isSymbolicLink(): Boolean {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isOther(): Boolean {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun size(): Long {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fileKey(): Any {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,6 +73,7 @@ termora.settings.terminal.size=Size
|
|||||||
termora.settings.terminal.max-rows=Max rows
|
termora.settings.terminal.max-rows=Max rows
|
||||||
termora.settings.terminal.debug=Debug mode
|
termora.settings.terminal.debug=Debug mode
|
||||||
termora.settings.terminal.beep=Beep
|
termora.settings.terminal.beep=Beep
|
||||||
|
termora.settings.terminal.hyperlink=Hyperlink
|
||||||
termora.settings.terminal.select-copy=Select copy
|
termora.settings.terminal.select-copy=Select copy
|
||||||
termora.settings.terminal.cursor-style=Cursor type
|
termora.settings.terminal.cursor-style=Cursor type
|
||||||
termora.settings.terminal.cursor-blink=Cursor blink
|
termora.settings.terminal.cursor-blink=Cursor blink
|
||||||
@@ -309,6 +310,7 @@ termora.transport.permissions.execute=Execute
|
|||||||
termora.transport.permissions.owner=Owner
|
termora.transport.permissions.owner=Owner
|
||||||
termora.transport.permissions.group=Group
|
termora.transport.permissions.group=Group
|
||||||
termora.transport.permissions.others=Others
|
termora.transport.permissions.others=Others
|
||||||
|
termora.transport.permissions.include-subfolder=Include subdirectories
|
||||||
|
|
||||||
termora.transport.sftp.retry=Retry
|
termora.transport.sftp.retry=Retry
|
||||||
termora.transport.sftp.select-another-host=Select another host
|
termora.transport.sftp.select-another-host=Select another host
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ termora.settings.terminal.size=大小
|
|||||||
termora.settings.terminal.max-rows=最大行数
|
termora.settings.terminal.max-rows=最大行数
|
||||||
termora.settings.terminal.debug=调试模式
|
termora.settings.terminal.debug=调试模式
|
||||||
termora.settings.terminal.beep=蜂鸣声
|
termora.settings.terminal.beep=蜂鸣声
|
||||||
|
termora.settings.terminal.hyperlink=超链接
|
||||||
termora.settings.terminal.select-copy=选中复制
|
termora.settings.terminal.select-copy=选中复制
|
||||||
termora.settings.terminal.cursor-style=光标样式
|
termora.settings.terminal.cursor-style=光标样式
|
||||||
termora.settings.terminal.cursor-blink=光标闪烁
|
termora.settings.terminal.cursor-blink=光标闪烁
|
||||||
@@ -299,7 +300,7 @@ termora.transport.sftp.status.done=已完成
|
|||||||
termora.transport.sftp.status.failed=已失败
|
termora.transport.sftp.status.failed=已失败
|
||||||
|
|
||||||
|
|
||||||
termora.transport.sftp.already-exists.message1=此文件夹已包含一下名称的对象
|
termora.transport.sftp.already-exists.message1=此文件夹已包含以下名称的对象
|
||||||
termora.transport.sftp.already-exists.message2=请选择要执行的操作
|
termora.transport.sftp.already-exists.message2=请选择要执行的操作
|
||||||
termora.transport.sftp.already-exists.overwrite=覆盖
|
termora.transport.sftp.already-exists.overwrite=覆盖
|
||||||
termora.transport.sftp.already-exists.append=追加
|
termora.transport.sftp.already-exists.append=追加
|
||||||
@@ -322,6 +323,7 @@ termora.transport.permissions.execute=执行
|
|||||||
termora.transport.permissions.owner=所有者
|
termora.transport.permissions.owner=所有者
|
||||||
termora.transport.permissions.group=组
|
termora.transport.permissions.group=组
|
||||||
termora.transport.permissions.others=其他
|
termora.transport.permissions.others=其他
|
||||||
|
termora.transport.permissions.include-subfolder=包含子目录
|
||||||
|
|
||||||
# transport job
|
# transport job
|
||||||
termora.transport.jobs.table.name=名称
|
termora.transport.jobs.table.name=名称
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ termora.settings.terminal.font=字體
|
|||||||
termora.settings.terminal.size=大小
|
termora.settings.terminal.size=大小
|
||||||
termora.settings.terminal.max-rows=最大行數
|
termora.settings.terminal.max-rows=最大行數
|
||||||
termora.settings.terminal.debug=偵錯模式
|
termora.settings.terminal.debug=偵錯模式
|
||||||
termora.settings.terminal.beep=蜂鳴聲
|
termora.settings.terminal.beep=超連結
|
||||||
|
termora.settings.terminal.hyperlink=Hyperlink
|
||||||
termora.settings.terminal.select-copy=選取複製
|
termora.settings.terminal.select-copy=選取複製
|
||||||
termora.settings.terminal.cursor-style=遊標風格
|
termora.settings.terminal.cursor-style=遊標風格
|
||||||
termora.settings.terminal.cursor-blink=遊標閃爍
|
termora.settings.terminal.cursor-blink=遊標閃爍
|
||||||
@@ -294,7 +295,7 @@ termora.transport.sftp.status.waiting=等待中
|
|||||||
termora.transport.sftp.status.done=已完成
|
termora.transport.sftp.status.done=已完成
|
||||||
termora.transport.sftp.status.failed=已失敗
|
termora.transport.sftp.status.failed=已失敗
|
||||||
|
|
||||||
termora.transport.sftp.already-exists.message1=此資料夾已包含一下名稱的對象
|
termora.transport.sftp.already-exists.message1=此資料夾已包含以下名稱的對象
|
||||||
termora.transport.sftp.already-exists.message2=請選擇要執行的操作
|
termora.transport.sftp.already-exists.message2=請選擇要執行的操作
|
||||||
termora.transport.sftp.already-exists.overwrite=覆蓋
|
termora.transport.sftp.already-exists.overwrite=覆蓋
|
||||||
termora.transport.sftp.already-exists.append=追加
|
termora.transport.sftp.already-exists.append=追加
|
||||||
@@ -305,6 +306,17 @@ termora.transport.sftp.already-exists.destination=目標文件
|
|||||||
termora.transport.sftp.already-exists.source=原始檔
|
termora.transport.sftp.already-exists.source=原始檔
|
||||||
termora.transport.sftp.already-exists.actions=操作
|
termora.transport.sftp.already-exists.actions=操作
|
||||||
|
|
||||||
|
# permissions
|
||||||
|
termora.transport.permissions=更改權限
|
||||||
|
termora.transport.permissions.file-folder-permissions=檔案/資料夾權限
|
||||||
|
termora.transport.permissions.read=讀取
|
||||||
|
termora.transport.permissions.write=寫入
|
||||||
|
termora.transport.permissions.execute=執行
|
||||||
|
termora.transport.permissions.owner=所有者
|
||||||
|
termora.transport.permissions.group=群組
|
||||||
|
termora.transport.permissions.others=其他
|
||||||
|
termora.transport.permissions.include-subfolder=包含子目錄
|
||||||
|
|
||||||
# transport job
|
# transport job
|
||||||
termora.transport.jobs.table.name=名稱
|
termora.transport.jobs.table.name=名稱
|
||||||
termora.transport.jobs.table.status=狀態
|
termora.transport.jobs.table.status=狀態
|
||||||
|
|||||||
4
src/main/resources/icons/microsoftWindows.svg
Normal file
4
src/main/resources/icons/microsoftWindows.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<!-- 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="M2.00098 2H7.00195L7.00098 7H2L2.00098 2ZM8.00293 2H13V7H8.00293V2ZM2 7.99902L7 8V13.001L2 13V7.99902ZM8.00195 8H12.999L12.998 13.001H8.00195" fill="#6C707E"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 399 B |
4
src/main/resources/icons/microsoftWindows_dark.svg
Normal file
4
src/main/resources/icons/microsoftWindows_dark.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<!-- 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="M2.00098 2H7.00195L7.00098 7H2L2.00098 2ZM8.00293 2H13V7H8.00293V2ZM2 7.99902L7 8V13.001L2 13V7.99902ZM8.00195 8H12.999L12.998 13.001H8.00195" fill="#CED0D6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 399 B |
@@ -1,6 +1,6 @@
|
|||||||
FROM linuxserver/openssh-server
|
FROM linuxserver/openssh-server
|
||||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||||
&& apk update && apk add wget gcc g++ git make zsh htop stress-ng inetutils-telnet xclock xcalc xorg-server xinit && wget https://ohse.de/uwe/releases/lrzsz-0.12.20.tar.gz \
|
&& apk update && apk add wget tmux gcc g++ git make zsh htop stress-ng inetutils-telnet xclock xcalc xorg-server xinit && wget https://ohse.de/uwe/releases/lrzsz-0.12.20.tar.gz \
|
||||||
&& tar -xf lrzsz-0.12.20.tar.gz && cd lrzsz-0.12.20 && ./configure && make && make install \
|
&& tar -xf lrzsz-0.12.20.tar.gz && cd lrzsz-0.12.20 && ./configure && make && make install \
|
||||||
&& ln -s /usr/local/bin/lrz /usr/local/bin/rz && ln -s /usr/local/bin/lsz /usr/local/bin/sz
|
&& ln -s /usr/local/bin/lrz /usr/local/bin/rz && ln -s /usr/local/bin/lsz /usr/local/bin/sz
|
||||||
RUN sed -i 's/#AllowAgentForwarding yes/AllowAgentForwarding yes/g' /etc/ssh/sshd_config
|
RUN sed -i 's/#AllowAgentForwarding yes/AllowAgentForwarding yes/g' /etc/ssh/sshd_config
|
||||||
|
|||||||
Reference in New Issue
Block a user