Skip to content

Commit 74a9b97

Browse files
felladrinroboquat
authored andcommitted
Disconnect the terminal widget when Gitpod Task exits, and update terminal widget tab when Gitpod Task title changes
1 parent 7bc2273 commit 74a9b97

File tree

2 files changed

+126
-16
lines changed

2 files changed

+126
-16
lines changed

components/ide/jetbrains/backend-plugin/build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ plugins {
1313
// Kotlin support - check the latest version at https://plugins.gradle.org/plugin/org.jetbrains.kotlin.jvm
1414
id("org.jetbrains.kotlin.jvm") version "1.7.0"
1515
// gradle-intellij-plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin
16-
id("org.jetbrains.intellij") version "1.8.0"
16+
id("org.jetbrains.intellij") version "1.8.1"
1717
// detekt linter - read more: https://detekt.github.io/detekt/gradle.html
1818
id("io.gitlab.arturbosch.detekt") version "1.17.1"
1919
// ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle

components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodTerminalService.kt

+125-15
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@ package io.gitpod.jetbrains.remote
77
import com.intellij.openapi.client.ClientProjectSession
88
import com.intellij.openapi.diagnostic.thisLogger
99
import com.intellij.util.application
10+
import com.jediterm.terminal.ui.TerminalWidget
11+
import com.jediterm.terminal.ui.TerminalWidgetListener
1012
import com.jetbrains.rdserver.terminal.BackendTerminalManager
11-
import io.gitpod.jetbrains.remote.GitpodManager
1213
import io.gitpod.supervisor.api.Status
1314
import io.gitpod.supervisor.api.StatusServiceGrpc
1415
import io.gitpod.supervisor.api.TerminalOuterClass
1516
import io.gitpod.supervisor.api.TerminalServiceGrpc
17+
import io.grpc.StatusRuntimeException
1618
import io.grpc.stub.ClientCallStreamObserver
1719
import io.grpc.stub.ClientResponseObserver
1820
import org.jetbrains.plugins.terminal.ShellTerminalWidget
1921
import org.jetbrains.plugins.terminal.TerminalView
2022
import java.util.*
2123
import java.util.concurrent.CompletableFuture
24+
import java.util.concurrent.ExecutionException
2225
import java.util.concurrent.TimeUnit
2326

2427
@Suppress("UnstableApiUsage")
@@ -30,9 +33,12 @@ class GitpodTerminalService(session: ClientProjectSession) {
3033
private val terminalView = TerminalView.getInstance(session.project)
3134
private val backendTerminalManager = BackendTerminalManager.getInstance(session.project)
3235
private val terminalServiceFutureStub = TerminalServiceGrpc.newFutureStub(GitpodManager.supervisorChannel)
36+
private val terminalServiceStub = TerminalServiceGrpc.newStub(GitpodManager.supervisorChannel)
3337
private val statusServiceStub = StatusServiceGrpc.newStub(GitpodManager.supervisorChannel)
3438

35-
init { start() }
39+
init {
40+
start()
41+
}
3642

3743
private fun start() {
3844
if (application.isHeadlessEnvironment || hasStarted) return
@@ -49,7 +55,7 @@ class GitpodTerminalService(session: ClientProjectSession) {
4955
}
5056
}
5157

52-
private fun createSharedTerminalAndExecuteCommand(title: String, command: String) {
58+
private fun createSharedTerminalAndExecuteCommand(title: String, command: String): ShellTerminalWidget? {
5359
val registeredTerminals = terminalView.widgets.toMutableList()
5460

5561
backendTerminalManager.createNewSharedTerminal(UUID.randomUUID().toString(), title)
@@ -59,13 +65,19 @@ class GitpodTerminalService(session: ClientProjectSession) {
5965

6066
widget.terminalTitle.change { applicationTitle = title }
6167

62-
(widget as ShellTerminalWidget).executeCommand(command)
68+
val shellTerminalWidget = widget as ShellTerminalWidget
69+
70+
shellTerminalWidget.executeCommand(command)
71+
72+
return shellTerminalWidget
6373
}
74+
75+
return null
6476
}
6577

6678
private fun createTerminalsAttachedToTasks(
67-
terminals: List<TerminalOuterClass.Terminal>,
68-
tasks: List<Status.TaskStatus>
79+
terminals: List<TerminalOuterClass.Terminal>,
80+
tasks: List<Status.TaskStatus>
6981
) {
7082
if (tasks.isEmpty()) return
7183

@@ -93,7 +105,7 @@ class GitpodTerminalService(session: ClientProjectSession) {
93105
val taskStatusRequest = Status.TasksStatusRequest.newBuilder().setObserve(true).build()
94106

95107
val taskStatusResponseObserver = object :
96-
ClientResponseObserver<Status.TasksStatusRequest, Status.TasksStatusResponse> {
108+
ClientResponseObserver<Status.TasksStatusRequest, Status.TasksStatusResponse> {
97109
override fun beforeStart(request: ClientCallStreamObserver<Status.TasksStatusRequest>) = Unit
98110

99111
override fun onNext(response: Status.TasksStatusResponse) {
@@ -120,8 +132,9 @@ class GitpodTerminalService(session: ClientProjectSession) {
120132
}
121133

122134
thisLogger().error(
123-
"gitpod: Got an error while trying to get tasks list from Supervisor. Trying again in on second.",
124-
throwable
135+
"gitpod: Got an error while trying to get tasks list from Supervisor. " +
136+
"Trying again in one second.",
137+
throwable
125138
)
126139
}
127140

@@ -150,8 +163,9 @@ class GitpodTerminalService(session: ClientProjectSession) {
150163
}
151164

152165
thisLogger().error(
153-
"gitpod: Got an error while trying to get terminals list from Supervisor. Trying again in on second.",
154-
throwable
166+
"gitpod: Got an error while trying to get terminals list from Supervisor. " +
167+
"Trying again in one second.",
168+
throwable
155169
)
156170
}
157171

@@ -164,9 +178,105 @@ class GitpodTerminalService(session: ClientProjectSession) {
164178
}
165179

166180
private fun createAttachedSharedTerminal(supervisorTerminal: TerminalOuterClass.Terminal) {
167-
createSharedTerminalAndExecuteCommand(
168-
supervisorTerminal.title,
169-
"gp tasks attach ${supervisorTerminal.alias}"
170-
)
181+
val shellTerminalWidget = createSharedTerminalAndExecuteCommand(
182+
supervisorTerminal.title,
183+
"gp tasks attach ${supervisorTerminal.alias}"
184+
) ?: return
185+
186+
exitTaskWhenTerminalWidgetGetsClosed(supervisorTerminal, shellTerminalWidget)
187+
188+
listenForTaskTerminationAndTitleChanges(supervisorTerminal, shellTerminalWidget)
189+
}
190+
191+
private fun listenForTaskTerminationAndTitleChanges(
192+
supervisorTerminal: TerminalOuterClass.Terminal,
193+
shellTerminalWidget: ShellTerminalWidget
194+
) = application.executeOnPooledThread {
195+
var hasOpenSessions = true
196+
197+
while (hasOpenSessions) {
198+
val completableFuture = CompletableFuture<Void>()
199+
200+
val listenTerminalRequest = TerminalOuterClass.ListenTerminalRequest.newBuilder()
201+
.setAlias(supervisorTerminal.alias)
202+
.build()
203+
204+
val listenTerminalResponseObserver =
205+
object : ClientResponseObserver<
206+
TerminalOuterClass.ListenTerminalRequest,
207+
TerminalOuterClass.ListenTerminalResponse
208+
> {
209+
override fun beforeStart(
210+
request: ClientCallStreamObserver<TerminalOuterClass.ListenTerminalRequest>
211+
) {
212+
@Suppress("ObjectLiteralToLambda")
213+
shellTerminalWidget.addListener(object : TerminalWidgetListener {
214+
override fun allSessionsClosed(widget: TerminalWidget) {
215+
hasOpenSessions = false
216+
request.cancel("gitpod: Terminal closed on the client.", null)
217+
}
218+
})
219+
}
220+
221+
override fun onNext(response: TerminalOuterClass.ListenTerminalResponse) {
222+
when {
223+
response.hasTitle() -> application.invokeLater {
224+
shellTerminalWidget.terminalTitle.change {
225+
applicationTitle = response.title
226+
}
227+
}
228+
229+
response.hasExitCode() -> application.invokeLater {
230+
shellTerminalWidget.close()
231+
}
232+
}
233+
}
234+
235+
override fun onCompleted() = Unit
236+
237+
override fun onError(throwable: Throwable) {
238+
completableFuture.completeExceptionally(throwable)
239+
}
240+
}
241+
242+
terminalServiceStub.listen(listenTerminalRequest, listenTerminalResponseObserver)
243+
244+
try {
245+
completableFuture.get()
246+
} catch (throwable: Throwable) {
247+
if (
248+
throwable is StatusRuntimeException ||
249+
throwable is ExecutionException ||
250+
throwable is InterruptedException
251+
) {
252+
shellTerminalWidget.close()
253+
thisLogger().info("gitpod: Stopped listening to " +
254+
"'${supervisorTerminal.title}' terminal due to an expected exception.")
255+
break
256+
}
257+
258+
thisLogger()
259+
.error("gitpod: Got an error while listening to " +
260+
"'${supervisorTerminal.title}' terminal. Trying again in one second.", throwable)
261+
}
262+
263+
TimeUnit.SECONDS.sleep(1)
264+
}
265+
}
266+
267+
private fun exitTaskWhenTerminalWidgetGetsClosed(
268+
supervisorTerminal: TerminalOuterClass.Terminal,
269+
shellTerminalWidget: ShellTerminalWidget
270+
) {
271+
@Suppress("ObjectLiteralToLambda")
272+
shellTerminalWidget.addListener(object : TerminalWidgetListener {
273+
override fun allSessionsClosed(widget: TerminalWidget) {
274+
terminalServiceFutureStub.shutdown(
275+
TerminalOuterClass.ShutdownTerminalRequest.newBuilder()
276+
.setAlias(supervisorTerminal.alias)
277+
.build()
278+
)
279+
}
280+
})
171281
}
172282
}

0 commit comments

Comments
 (0)