@aion0/forge 0.5.44 → 0.5.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/RELEASE_NOTES.md +3 -15
- package/package.json +1 -1
- package/tsconfig.json +3 -1
- package/intellij-plugin/README.md +0 -53
- package/intellij-plugin/build.gradle.kts +0 -60
- package/intellij-plugin/gradle/gradle-daemon-jvm.properties +0 -12
- package/intellij-plugin/gradle.properties +0 -9
- package/intellij-plugin/publish.sh +0 -78
- package/intellij-plugin/settings.gradle.kts +0 -7
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/LoginAction.kt +0 -49
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/LogoutAction.kt +0 -18
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/OpenWebUIAction.kt +0 -13
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/SwitchConnectionAction.kt +0 -26
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/api/ForgeClient.kt +0 -115
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/auth/Auth.kt +0 -31
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/connection/ConnectionManager.kt +0 -95
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/settings/ForgeConfigurable.kt +0 -81
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/DocsView.kt +0 -99
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeStatusBarWidgetFactory.kt +0 -94
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeToolWindowFactory.kt +0 -27
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeTreeView.kt +0 -176
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/Helpers.kt +0 -48
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/PipelinesView.kt +0 -226
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/TerminalsView.kt +0 -309
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/TreeNodeData.kt +0 -33
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/WorkspacesView.kt +0 -166
- package/intellij-plugin/src/main/resources/META-INF/plugin.xml +0 -88
- package/intellij-plugin/src/main/resources/icons/forge.svg +0 -3
- package/vscode-extension/.vscodeignore +0 -11
- package/vscode-extension/README.md +0 -48
- package/vscode-extension/media/icon.png +0 -0
- package/vscode-extension/media/icon.svg +0 -3
- package/vscode-extension/package-lock.json +0 -4046
- package/vscode-extension/package.json +0 -514
- package/vscode-extension/publish.sh +0 -49
- package/vscode-extension/src/api/client.ts +0 -217
- package/vscode-extension/src/auth/auth.ts +0 -32
- package/vscode-extension/src/commands/auth.ts +0 -44
- package/vscode-extension/src/commands/connection.ts +0 -113
- package/vscode-extension/src/commands/docs.ts +0 -40
- package/vscode-extension/src/commands/pipeline.ts +0 -103
- package/vscode-extension/src/commands/server.ts +0 -50
- package/vscode-extension/src/commands/smith.ts +0 -112
- package/vscode-extension/src/commands/task.ts +0 -43
- package/vscode-extension/src/commands/terminal.ts +0 -279
- package/vscode-extension/src/commands/workspace.ts +0 -138
- package/vscode-extension/src/connection/manager.ts +0 -80
- package/vscode-extension/src/docs/fs-provider.ts +0 -94
- package/vscode-extension/src/docs/result-provider.ts +0 -33
- package/vscode-extension/src/docs/transport.ts +0 -22
- package/vscode-extension/src/extension.ts +0 -314
- package/vscode-extension/src/statusbar.ts +0 -70
- package/vscode-extension/src/terminal/pseudoterm.ts +0 -123
- package/vscode-extension/src/views/docs.ts +0 -145
- package/vscode-extension/src/views/pipelines.ts +0 -222
- package/vscode-extension/src/views/terminals.ts +0 -91
- package/vscode-extension/src/views/workspaces.ts +0 -243
- package/vscode-extension/tsconfig.json +0 -16
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
package com.aion0.forge.settings
|
|
2
|
-
|
|
3
|
-
import com.aion0.forge.connection.ConnectionManager
|
|
4
|
-
import com.aion0.forge.connection.ForgeConnection
|
|
5
|
-
import com.intellij.openapi.options.Configurable
|
|
6
|
-
import com.intellij.ui.ToolbarDecorator
|
|
7
|
-
import com.intellij.ui.table.JBTable
|
|
8
|
-
import javax.swing.JComponent
|
|
9
|
-
import javax.swing.JPanel
|
|
10
|
-
import javax.swing.JTextField
|
|
11
|
-
import javax.swing.table.DefaultTableModel
|
|
12
|
-
|
|
13
|
-
/** "Tools → Forge" preferences pane: edit the list of connections and pick the
|
|
14
|
-
* active one. Tokens are stored separately in PasswordSafe (cleared via the
|
|
15
|
-
* Forge: Logout action). */
|
|
16
|
-
class ForgeConfigurable : Configurable {
|
|
17
|
-
private lateinit var rootPanel: JPanel
|
|
18
|
-
private lateinit var activeNameField: JTextField
|
|
19
|
-
private lateinit var tableModel: DefaultTableModel
|
|
20
|
-
private lateinit var table: JBTable
|
|
21
|
-
|
|
22
|
-
override fun getDisplayName(): String = "Forge"
|
|
23
|
-
|
|
24
|
-
override fun createComponent(): JComponent {
|
|
25
|
-
tableModel = object : DefaultTableModel(arrayOf("Name", "Server URL", "Terminal URL"), 0) {
|
|
26
|
-
override fun isCellEditable(row: Int, col: Int) = true
|
|
27
|
-
}
|
|
28
|
-
table = JBTable(tableModel)
|
|
29
|
-
|
|
30
|
-
for (c in ConnectionManager.get().list()) {
|
|
31
|
-
tableModel.addRow(arrayOf(c.name, c.serverUrl, c.terminalUrl))
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
val tableWithToolbar = ToolbarDecorator.createDecorator(table)
|
|
35
|
-
.setAddAction { tableModel.addRow(arrayOf("New", "http://localhost:8403", "ws://localhost:8404")) }
|
|
36
|
-
.setRemoveAction {
|
|
37
|
-
val sel = table.selectedRow
|
|
38
|
-
if (sel >= 0 && tableModel.rowCount > 1) tableModel.removeRow(sel)
|
|
39
|
-
}
|
|
40
|
-
.createPanel()
|
|
41
|
-
|
|
42
|
-
activeNameField = JTextField(ConnectionManager.get().active().name)
|
|
43
|
-
|
|
44
|
-
rootPanel = JPanel().apply {
|
|
45
|
-
layout = javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS)
|
|
46
|
-
add(javax.swing.JLabel("Active connection name:"))
|
|
47
|
-
add(activeNameField)
|
|
48
|
-
add(javax.swing.Box.createVerticalStrut(8))
|
|
49
|
-
add(javax.swing.JLabel("Saved connections:"))
|
|
50
|
-
add(tableWithToolbar)
|
|
51
|
-
}
|
|
52
|
-
return rootPanel
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
override fun isModified(): Boolean {
|
|
56
|
-
val current = collectFromTable()
|
|
57
|
-
return current != ConnectionManager.get().list() ||
|
|
58
|
-
activeNameField.text != ConnectionManager.get().active().name
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
override fun apply() {
|
|
62
|
-
val list = collectFromTable()
|
|
63
|
-
ConnectionManager.get().replaceAll(list, activeNameField.text.trim())
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
override fun reset() {
|
|
67
|
-
tableModel.rowCount = 0
|
|
68
|
-
for (c in ConnectionManager.get().list()) {
|
|
69
|
-
tableModel.addRow(arrayOf(c.name, c.serverUrl, c.terminalUrl))
|
|
70
|
-
}
|
|
71
|
-
activeNameField.text = ConnectionManager.get().active().name
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
private fun collectFromTable(): List<ForgeConnection> = (0 until tableModel.rowCount).map { i ->
|
|
75
|
-
ForgeConnection(
|
|
76
|
-
name = (tableModel.getValueAt(i, 0) ?: "").toString().trim(),
|
|
77
|
-
serverUrl = (tableModel.getValueAt(i, 1) ?: "").toString().trim(),
|
|
78
|
-
terminalUrl = (tableModel.getValueAt(i, 2) ?: "").toString().trim(),
|
|
79
|
-
)
|
|
80
|
-
}.filter { it.name.isNotEmpty() }
|
|
81
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
package com.aion0.forge.ui.toolwindow
|
|
2
|
-
|
|
3
|
-
import com.aion0.forge.api.ForgeClient
|
|
4
|
-
import com.google.gson.JsonElement
|
|
5
|
-
import com.intellij.icons.AllIcons
|
|
6
|
-
import com.intellij.openapi.actionSystem.AnAction
|
|
7
|
-
import com.intellij.openapi.actionSystem.AnActionEvent
|
|
8
|
-
import com.intellij.openapi.fileEditor.FileEditorManager
|
|
9
|
-
import com.intellij.openapi.project.Project
|
|
10
|
-
import com.intellij.openapi.vfs.LocalFileSystem
|
|
11
|
-
import org.jetbrains.plugins.terminal.TerminalView
|
|
12
|
-
import java.io.File
|
|
13
|
-
import javax.swing.tree.DefaultMutableTreeNode
|
|
14
|
-
|
|
15
|
-
class DocsView(project: Project) : ForgeTreeView(project) {
|
|
16
|
-
|
|
17
|
-
override fun rootLabel() = "docs"
|
|
18
|
-
|
|
19
|
-
override fun reload(): List<DefaultMutableTreeNode> {
|
|
20
|
-
val r = ForgeClient.get().request("/api/docs")
|
|
21
|
-
if (r.status == 401 || r.status == 403) return listOf(DefaultMutableTreeNode(TreeNodeData.Hint("🔑 Tools → Forge: Login")))
|
|
22
|
-
if (!r.ok || r.data == null || !r.data.isJsonObject) {
|
|
23
|
-
return listOf(DefaultMutableTreeNode(TreeNodeData.Hint("⚠ ${r.error ?: "Not connected"}")))
|
|
24
|
-
}
|
|
25
|
-
val roots = r.data.asJsonObject.getAsJsonArray("roots") ?: return listOf(DefaultMutableTreeNode(TreeNodeData.Hint("No doc roots — add one in forge Settings → Doc Roots")))
|
|
26
|
-
val rootPaths = r.data.asJsonObject.getAsJsonArray("rootPaths")
|
|
27
|
-
if (roots.size() == 0) return listOf(DefaultMutableTreeNode(TreeNodeData.Hint("No doc roots")))
|
|
28
|
-
|
|
29
|
-
return (0 until roots.size()).map { i ->
|
|
30
|
-
val rootName = roots[i].asString
|
|
31
|
-
val rootPath = rootPaths?.get(i)?.asString ?: rootName
|
|
32
|
-
val node = DefaultMutableTreeNode(TreeNodeData.DocRoot("📚 $rootName", i, rootPath, rootName))
|
|
33
|
-
val sub = ForgeClient.get().request("/api/docs?root=$i")
|
|
34
|
-
sub.data?.asJsonObject?.getAsJsonArray("tree")?.forEach { addChildNode(node, it, i, rootPath) }
|
|
35
|
-
node
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private fun addChildNode(parent: DefaultMutableTreeNode, el: JsonElement, rootIdx: Int, rootPath: String) {
|
|
40
|
-
val obj = el.asJsonObject
|
|
41
|
-
val name = obj.get("name")?.asString ?: "?"
|
|
42
|
-
val type = obj.get("type")?.asString ?: "file"
|
|
43
|
-
val relPath = obj.get("path")?.asString ?: name
|
|
44
|
-
val fileType = obj.get("fileType")?.asString
|
|
45
|
-
val emoji = when {
|
|
46
|
-
type == "dir" -> "📁"
|
|
47
|
-
fileType == "md" -> "📄"
|
|
48
|
-
fileType == "image" -> "🖼"
|
|
49
|
-
else -> "📑"
|
|
50
|
-
}
|
|
51
|
-
val data = if (type == "dir") TreeNodeData.DocDir("$emoji $name", rootIdx, rootPath, relPath)
|
|
52
|
-
else TreeNodeData.DocFile("$emoji $name", rootIdx, rootPath, relPath, fileType)
|
|
53
|
-
val node = DefaultMutableTreeNode(data)
|
|
54
|
-
if (type == "dir") obj.getAsJsonArray("children")?.forEach { addChildNode(node, it, rootIdx, rootPath) }
|
|
55
|
-
parent.add(node)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
override fun onDoubleClick(data: TreeNodeData, node: DefaultMutableTreeNode) {
|
|
59
|
-
when (data) {
|
|
60
|
-
is TreeNodeData.DocFile -> openFile(data)
|
|
61
|
-
else -> {}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
override fun contextActions(data: TreeNodeData, node: DefaultMutableTreeNode): List<AnAction> = when (data) {
|
|
66
|
-
is TreeNodeData.DocRoot -> listOf(
|
|
67
|
-
act("Open Terminal Here", AllIcons.Debugger.Console) { openTerminalAt(data.rootPath) },
|
|
68
|
-
)
|
|
69
|
-
is TreeNodeData.DocDir -> listOf(
|
|
70
|
-
act("Open Terminal Here", AllIcons.Debugger.Console) { openTerminalAt("${data.rootPath}/${data.relPath}") },
|
|
71
|
-
)
|
|
72
|
-
is TreeNodeData.DocFile -> listOf(
|
|
73
|
-
act("Open", AllIcons.Actions.Edit) { openFile(data) },
|
|
74
|
-
)
|
|
75
|
-
else -> emptyList()
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Open the file via the local filesystem (forge is local-by-default —
|
|
79
|
-
* remote forges would need an HTTP-backed VFS, deferred). */
|
|
80
|
-
private fun openFile(f: TreeNodeData.DocFile) {
|
|
81
|
-
val abs = File(f.rootPath, f.relPath)
|
|
82
|
-
if (!abs.isFile) {
|
|
83
|
-
notify(project, "Forge: file not found locally — ${abs.absolutePath} (remote forge support TBD)", com.intellij.notification.NotificationType.WARNING)
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
val vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(abs) ?: return
|
|
87
|
-
FileEditorManager.getInstance(project).openFile(vf, true)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
private fun openTerminalAt(absDir: String) {
|
|
91
|
-
val terminalView = TerminalView.getInstance(project)
|
|
92
|
-
val widget = terminalView.createLocalShellWidget(absDir, "forge: ${File(absDir).name}")
|
|
93
|
-
widget.executeCommand("claude --dangerously-skip-permissions")
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
private fun act(name: String, icon: javax.swing.Icon?, run: () -> Unit) = object : AnAction(name, null, icon) {
|
|
97
|
-
override fun actionPerformed(e: AnActionEvent) = run()
|
|
98
|
-
}
|
|
99
|
-
}
|
package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeStatusBarWidgetFactory.kt
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
package com.aion0.forge.ui.toolwindow
|
|
2
|
-
|
|
3
|
-
import com.aion0.forge.api.ForgeClient
|
|
4
|
-
import com.aion0.forge.auth.Auth
|
|
5
|
-
import com.aion0.forge.connection.ConnectionListener
|
|
6
|
-
import com.aion0.forge.connection.ConnectionManager
|
|
7
|
-
import com.aion0.forge.connection.ForgeConnection
|
|
8
|
-
import com.intellij.openapi.actionSystem.ActionManager
|
|
9
|
-
import com.intellij.openapi.application.ApplicationManager
|
|
10
|
-
import com.intellij.openapi.project.Project
|
|
11
|
-
import com.intellij.openapi.wm.StatusBar
|
|
12
|
-
import com.intellij.openapi.wm.StatusBarWidget
|
|
13
|
-
import com.intellij.openapi.wm.StatusBarWidgetFactory
|
|
14
|
-
import com.intellij.util.Alarm
|
|
15
|
-
import java.awt.event.MouseEvent
|
|
16
|
-
import javax.swing.Timer
|
|
17
|
-
|
|
18
|
-
class ForgeStatusBarWidgetFactory : StatusBarWidgetFactory {
|
|
19
|
-
override fun getId(): String = "com.aion0.forge.statusbar"
|
|
20
|
-
override fun getDisplayName(): String = "Forge"
|
|
21
|
-
override fun isAvailable(project: Project): Boolean = true
|
|
22
|
-
override fun createWidget(project: Project): StatusBarWidget = ForgeStatusBarWidget(project)
|
|
23
|
-
override fun disposeWidget(widget: StatusBarWidget) {}
|
|
24
|
-
override fun canBeEnabledOn(statusBar: StatusBar): Boolean = true
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
class ForgeStatusBarWidget(private val project: Project) : StatusBarWidget,
|
|
28
|
-
StatusBarWidget.TextPresentation {
|
|
29
|
-
|
|
30
|
-
private var statusBar: StatusBar? = null
|
|
31
|
-
// `this` is a Disposable (StatusBarWidget extends Disposable). The Alarm
|
|
32
|
-
// requires a parent Disposable for non-Swing threads — registering with
|
|
33
|
-
// ourselves means the Alarm is auto-disposed when the widget goes away.
|
|
34
|
-
private val alarm: Alarm by lazy { Alarm(Alarm.ThreadToUse.POOLED_THREAD, this) }
|
|
35
|
-
private var lastText: String = "Forge: …"
|
|
36
|
-
private var lastTooltip: String = "Forge"
|
|
37
|
-
private var pollTimer: Timer? = null
|
|
38
|
-
|
|
39
|
-
override fun ID(): String = "com.aion0.forge.statusbar"
|
|
40
|
-
override fun getPresentation(): StatusBarWidget.WidgetPresentation = this
|
|
41
|
-
|
|
42
|
-
override fun install(statusBar: StatusBar) {
|
|
43
|
-
this.statusBar = statusBar
|
|
44
|
-
// Refresh on connection change.
|
|
45
|
-
ApplicationManager.getApplication().messageBus.connect()
|
|
46
|
-
.subscribe(ConnectionListener.TOPIC, object : ConnectionListener {
|
|
47
|
-
override fun onConnectionChanged(active: ForgeConnection) = scheduleRefresh()
|
|
48
|
-
})
|
|
49
|
-
scheduleRefresh()
|
|
50
|
-
// Periodic poll so connectivity changes (server up/down) reflect.
|
|
51
|
-
pollTimer = Timer(5_000) { scheduleRefresh() }.apply { start() }
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
override fun dispose() {
|
|
55
|
-
pollTimer?.stop()
|
|
56
|
-
pollTimer = null
|
|
57
|
-
// alarm is disposed automatically (we passed `this` as parent).
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
override fun getText(): String = lastText
|
|
61
|
-
override fun getTooltipText(): String = lastTooltip
|
|
62
|
-
override fun getAlignment(): Float = 0f
|
|
63
|
-
|
|
64
|
-
override fun getClickConsumer(): com.intellij.util.Consumer<MouseEvent>? =
|
|
65
|
-
com.intellij.util.Consumer { _ ->
|
|
66
|
-
// Click → run "Switch Connection" action.
|
|
67
|
-
val action = ActionManager.getInstance().getAction("com.aion0.forge.action.SwitchConnectionAction")
|
|
68
|
-
ActionManager.getInstance().tryToExecute(action, null, null, "Forge", true)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
private fun scheduleRefresh() {
|
|
72
|
-
alarm.cancelAllRequests()
|
|
73
|
-
alarm.addRequest({
|
|
74
|
-
val conn = ConnectionManager.get().active()
|
|
75
|
-
val reachable = ForgeClient.get().ping()
|
|
76
|
-
val token = Auth.get().getToken(conn.name)
|
|
77
|
-
val text = when {
|
|
78
|
-
!reachable -> "Forge ⊘ ${conn.name}"
|
|
79
|
-
token.isNullOrBlank() -> "Forge ⚠ ${conn.name}"
|
|
80
|
-
else -> "Forge ⚡ ${conn.name}"
|
|
81
|
-
}
|
|
82
|
-
val tooltip = when {
|
|
83
|
-
!reachable -> "${conn.name} — server unreachable (${conn.serverUrl})"
|
|
84
|
-
token.isNullOrBlank() -> "${conn.name} — login required"
|
|
85
|
-
else -> "${conn.name} — connected (${conn.serverUrl})"
|
|
86
|
-
}
|
|
87
|
-
ApplicationManager.getApplication().invokeLater {
|
|
88
|
-
lastText = text
|
|
89
|
-
lastTooltip = tooltip
|
|
90
|
-
statusBar?.updateWidget(ID())
|
|
91
|
-
}
|
|
92
|
-
}, 100)
|
|
93
|
-
}
|
|
94
|
-
}
|
package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeToolWindowFactory.kt
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
package com.aion0.forge.ui.toolwindow
|
|
2
|
-
|
|
3
|
-
import com.intellij.openapi.project.DumbAware
|
|
4
|
-
import com.intellij.openapi.project.Project
|
|
5
|
-
import com.intellij.openapi.util.Disposer
|
|
6
|
-
import com.intellij.openapi.wm.ToolWindow
|
|
7
|
-
import com.intellij.openapi.wm.ToolWindowFactory
|
|
8
|
-
import com.intellij.ui.content.Content
|
|
9
|
-
import com.intellij.ui.content.ContentFactory
|
|
10
|
-
|
|
11
|
-
class ForgeToolWindowFactory : ToolWindowFactory, DumbAware {
|
|
12
|
-
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
|
|
13
|
-
val cf = ContentFactory.getInstance()
|
|
14
|
-
addView(toolWindow, cf, "Workspaces", WorkspacesView(project))
|
|
15
|
-
addView(toolWindow, cf, "Terminals", TerminalsView(project))
|
|
16
|
-
addView(toolWindow, cf, "Pipelines", PipelinesView(project))
|
|
17
|
-
addView(toolWindow, cf, "Docs", DocsView(project))
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
private fun addView(tw: ToolWindow, cf: ContentFactory, name: String, view: ForgeTreeView) {
|
|
21
|
-
val content: Content = cf.createContent(view.component(), name, false)
|
|
22
|
-
// Tie the view's lifecycle to the tab content so background polling
|
|
23
|
-
// stops when the user closes the tool window.
|
|
24
|
-
Disposer.register(content, view)
|
|
25
|
-
tw.contentManager.addContent(content)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
package com.aion0.forge.ui.toolwindow
|
|
2
|
-
|
|
3
|
-
import com.aion0.forge.connection.ConnectionListener
|
|
4
|
-
import com.aion0.forge.connection.ForgeConnection
|
|
5
|
-
import com.intellij.openapi.Disposable
|
|
6
|
-
import com.intellij.openapi.actionSystem.ActionManager
|
|
7
|
-
import com.intellij.openapi.actionSystem.AnAction
|
|
8
|
-
import com.intellij.openapi.actionSystem.AnActionEvent
|
|
9
|
-
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
|
10
|
-
import com.intellij.openapi.application.ApplicationManager
|
|
11
|
-
import com.intellij.openapi.project.Project
|
|
12
|
-
import com.intellij.openapi.util.Disposer
|
|
13
|
-
import com.intellij.ui.PopupHandler
|
|
14
|
-
import com.intellij.ui.ScrollPaneFactory
|
|
15
|
-
import com.intellij.ui.treeStructure.Tree
|
|
16
|
-
import com.intellij.util.Alarm
|
|
17
|
-
import java.awt.BorderLayout
|
|
18
|
-
import java.awt.event.MouseAdapter
|
|
19
|
-
import java.awt.event.MouseEvent
|
|
20
|
-
import javax.swing.JPanel
|
|
21
|
-
import javax.swing.tree.DefaultMutableTreeNode
|
|
22
|
-
import javax.swing.tree.DefaultTreeModel
|
|
23
|
-
import javax.swing.tree.TreePath
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Common boilerplate for the four Forge tool-window tabs.
|
|
27
|
-
*
|
|
28
|
-
* - Tree component wrapped in a scroll pane
|
|
29
|
-
* - Toolbar with a refresh button
|
|
30
|
-
* - Auto-refresh every 5 s
|
|
31
|
-
* - Refresh on active-connection change
|
|
32
|
-
*
|
|
33
|
-
* Subclasses override [reload] to populate the [root] node and [refreshUi].
|
|
34
|
-
*/
|
|
35
|
-
abstract class ForgeTreeView(protected val project: Project) : Disposable {
|
|
36
|
-
protected val root: DefaultMutableTreeNode = DefaultMutableTreeNode(rootLabel())
|
|
37
|
-
protected val treeModel = DefaultTreeModel(root)
|
|
38
|
-
protected val tree = Tree(treeModel).apply {
|
|
39
|
-
isRootVisible = false
|
|
40
|
-
showsRootHandles = true
|
|
41
|
-
}
|
|
42
|
-
private val alarm = Alarm(Alarm.ThreadToUse.POOLED_THREAD, this)
|
|
43
|
-
|
|
44
|
-
/** Build the panel — wrap toolbar + tree in a JPanel. */
|
|
45
|
-
fun component(): JPanel {
|
|
46
|
-
val panel = JPanel(BorderLayout())
|
|
47
|
-
// Toolbar
|
|
48
|
-
val actions = DefaultActionGroup().apply {
|
|
49
|
-
add(RefreshAction())
|
|
50
|
-
toolbarActions(this)
|
|
51
|
-
}
|
|
52
|
-
val toolbar = ActionManager.getInstance().createActionToolbar("ForgeView", actions, true)
|
|
53
|
-
toolbar.targetComponent = tree
|
|
54
|
-
panel.add(toolbar.component, BorderLayout.NORTH)
|
|
55
|
-
// Tree
|
|
56
|
-
panel.add(ScrollPaneFactory.createScrollPane(tree), BorderLayout.CENTER)
|
|
57
|
-
// Click + right-click handlers — subclasses override.
|
|
58
|
-
tree.addMouseListener(object : MouseAdapter() {
|
|
59
|
-
override fun mouseClicked(e: MouseEvent) {
|
|
60
|
-
if (e.clickCount == 2 && !e.isPopupTrigger) {
|
|
61
|
-
val path = tree.getPathForLocation(e.x, e.y) ?: return
|
|
62
|
-
val node = path.lastPathComponent as? DefaultMutableTreeNode ?: return
|
|
63
|
-
val data = node.userObject as? TreeNodeData ?: return
|
|
64
|
-
onDoubleClick(data, node)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
PopupHandler.installPopupMenu(tree, object : DefaultActionGroup() {
|
|
69
|
-
override fun getChildren(e: AnActionEvent?): Array<AnAction> {
|
|
70
|
-
val node = selectedNode() ?: return emptyArray()
|
|
71
|
-
val data = node.userObject as? TreeNodeData ?: return emptyArray()
|
|
72
|
-
return contextActions(data, node).toTypedArray()
|
|
73
|
-
}
|
|
74
|
-
}, "ForgeViewPopup")
|
|
75
|
-
|
|
76
|
-
// Listen for connection changes → reload.
|
|
77
|
-
ApplicationManager.getApplication().messageBus.connect(this)
|
|
78
|
-
.subscribe(ConnectionListener.TOPIC, object : ConnectionListener {
|
|
79
|
-
override fun onConnectionChanged(active: ForgeConnection) = scheduleReload()
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
scheduleReload()
|
|
83
|
-
// Periodic 5s refresh.
|
|
84
|
-
startPolling()
|
|
85
|
-
return panel
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private fun startPolling() {
|
|
89
|
-
// Tail-recursive scheduling so we always wait 5s after each completion.
|
|
90
|
-
if (!Disposer.isDisposed(this)) {
|
|
91
|
-
alarm.addRequest({
|
|
92
|
-
doReloadAsync()
|
|
93
|
-
startPolling()
|
|
94
|
-
}, 5_000)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private fun scheduleReload() {
|
|
99
|
-
alarm.cancelAllRequests()
|
|
100
|
-
alarm.addRequest({ doReloadAsync() }, 50)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private fun doReloadAsync() {
|
|
104
|
-
val newChildren = runCatching { reload() }.getOrElse {
|
|
105
|
-
listOf(DefaultMutableTreeNode("⚠ ${it.message ?: "error"}"))
|
|
106
|
-
}
|
|
107
|
-
ApplicationManager.getApplication().invokeLater {
|
|
108
|
-
// Snapshot the userObject-string path of every expanded node so we can
|
|
109
|
-
// re-expand them after the tree is rebuilt — otherwise the 5s poll
|
|
110
|
-
// collapses anything the user opened (Docs subfolders, Pipeline runs, …).
|
|
111
|
-
val expanded = collectExpandedPaths()
|
|
112
|
-
root.removeAllChildren()
|
|
113
|
-
for (n in newChildren) root.add(n)
|
|
114
|
-
treeModel.reload(root)
|
|
115
|
-
reExpand(expanded)
|
|
116
|
-
// Continue polling unless we've been disposed.
|
|
117
|
-
if (Disposer.isDisposed(this)) alarm.cancelAllRequests()
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** Path identity of every expanded node, keyed by the chain of userObject
|
|
122
|
-
* toStrings from root → leaf (root itself excluded). */
|
|
123
|
-
private fun collectExpandedPaths(): Set<List<String>> {
|
|
124
|
-
val out = mutableSetOf<List<String>>()
|
|
125
|
-
val descendants = tree.getExpandedDescendants(TreePath(root.path)) ?: return out
|
|
126
|
-
while (descendants.hasMoreElements()) {
|
|
127
|
-
val tp = descendants.nextElement()
|
|
128
|
-
val parts = tp.path
|
|
129
|
-
if (parts.size <= 1) continue
|
|
130
|
-
out.add((1 until parts.size).map { (parts[it] as DefaultMutableTreeNode).userObject?.toString().orEmpty() })
|
|
131
|
-
}
|
|
132
|
-
return out
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
private fun reExpand(expanded: Set<List<String>>) {
|
|
136
|
-
if (expanded.isEmpty()) return
|
|
137
|
-
fun visit(node: DefaultMutableTreeNode, prefix: List<String>) {
|
|
138
|
-
for (i in 0 until node.childCount) {
|
|
139
|
-
val child = node.getChildAt(i) as DefaultMutableTreeNode
|
|
140
|
-
val key = prefix + (child.userObject?.toString().orEmpty())
|
|
141
|
-
if (key in expanded) tree.expandPath(TreePath(child.path))
|
|
142
|
-
visit(child, key)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
visit(root, emptyList())
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** Build the children of the synthetic root (top-level rows the user sees). */
|
|
149
|
-
protected abstract fun reload(): List<DefaultMutableTreeNode>
|
|
150
|
-
|
|
151
|
-
/** Override to add view-specific buttons to the toolbar (after Refresh). */
|
|
152
|
-
protected open fun toolbarActions(group: DefaultActionGroup) {}
|
|
153
|
-
|
|
154
|
-
/** Override to handle a left double-click on a typed tree node. */
|
|
155
|
-
protected open fun onDoubleClick(data: TreeNodeData, node: DefaultMutableTreeNode) {}
|
|
156
|
-
|
|
157
|
-
/** Override to add right-click menu items for a typed tree node. */
|
|
158
|
-
protected open fun contextActions(data: TreeNodeData, node: DefaultMutableTreeNode): List<AnAction> = emptyList()
|
|
159
|
-
|
|
160
|
-
/** Label for the hidden synthetic root — never shown but useful as a key. */
|
|
161
|
-
protected open fun rootLabel(): String = "ROOT"
|
|
162
|
-
|
|
163
|
-
/** Trigger a refresh from subclasses (e.g. after running an action). */
|
|
164
|
-
fun refresh() = scheduleReload()
|
|
165
|
-
|
|
166
|
-
protected fun selectedNode(): DefaultMutableTreeNode? =
|
|
167
|
-
tree.lastSelectedPathComponent as? DefaultMutableTreeNode
|
|
168
|
-
|
|
169
|
-
override fun dispose() {
|
|
170
|
-
alarm.cancelAllRequests()
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private inner class RefreshAction : AnAction("Refresh", "Refresh from Forge", com.intellij.icons.AllIcons.Actions.Refresh) {
|
|
174
|
-
override fun actionPerformed(e: AnActionEvent) = scheduleReload()
|
|
175
|
-
}
|
|
176
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
package com.aion0.forge.ui.toolwindow
|
|
2
|
-
|
|
3
|
-
import com.aion0.forge.api.ApiResult
|
|
4
|
-
import com.aion0.forge.api.ForgeClient
|
|
5
|
-
import com.intellij.notification.NotificationGroupManager
|
|
6
|
-
import com.intellij.notification.NotificationType
|
|
7
|
-
import com.intellij.openapi.application.ApplicationManager
|
|
8
|
-
import com.intellij.openapi.progress.ProgressIndicator
|
|
9
|
-
import com.intellij.openapi.progress.ProgressManager
|
|
10
|
-
import com.intellij.openapi.progress.Task
|
|
11
|
-
import com.intellij.openapi.project.Project
|
|
12
|
-
|
|
13
|
-
internal fun notify(project: Project?, message: String, type: NotificationType = NotificationType.INFORMATION) {
|
|
14
|
-
NotificationGroupManager.getInstance().getNotificationGroup("Forge")
|
|
15
|
-
.createNotification(message, type)
|
|
16
|
-
.notify(project)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
internal fun runBg(project: Project?, title: String, work: () -> Unit) {
|
|
20
|
-
ProgressManager.getInstance().run(object : Task.Backgroundable(project, title, false) {
|
|
21
|
-
override fun run(indicator: ProgressIndicator) { work() }
|
|
22
|
-
})
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Convenience: run an API call on a bg thread, then show a success/failure
|
|
26
|
-
* notification on EDT, then call onSuccess (used to trigger view refreshes). */
|
|
27
|
-
internal fun runApi(project: Project?, title: String, call: () -> ApiResult, onSuccess: () -> Unit = {}) {
|
|
28
|
-
runBg(project, title) {
|
|
29
|
-
val res = call()
|
|
30
|
-
ApplicationManager.getApplication().invokeLater {
|
|
31
|
-
if (res.ok) {
|
|
32
|
-
notify(project, "Forge: $title — ok")
|
|
33
|
-
onSuccess()
|
|
34
|
-
} else {
|
|
35
|
-
notify(project, "Forge: $title failed — ${res.error}", NotificationType.ERROR)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** WS action against the workspace daemon: POST /api/workspace/<id>/agents
|
|
42
|
-
* with `{action, agentId, ...}`. */
|
|
43
|
-
internal fun wsAction(workspaceId: String, action: String, body: Map<String, Any> = emptyMap()): ApiResult =
|
|
44
|
-
ForgeClient.get().request(
|
|
45
|
-
"/api/workspace/$workspaceId/agents",
|
|
46
|
-
method = "POST",
|
|
47
|
-
body = mapOf("action" to action) + body,
|
|
48
|
-
)
|