@@ -7,18 +7,21 @@ package io.gitpod.jetbrains.remote
7
7
import com.intellij.openapi.client.ClientProjectSession
8
8
import com.intellij.openapi.diagnostic.thisLogger
9
9
import com.intellij.util.application
10
+ import com.jediterm.terminal.ui.TerminalWidget
11
+ import com.jediterm.terminal.ui.TerminalWidgetListener
10
12
import com.jetbrains.rdserver.terminal.BackendTerminalManager
11
- import io.gitpod.jetbrains.remote.GitpodManager
12
13
import io.gitpod.supervisor.api.Status
13
14
import io.gitpod.supervisor.api.StatusServiceGrpc
14
15
import io.gitpod.supervisor.api.TerminalOuterClass
15
16
import io.gitpod.supervisor.api.TerminalServiceGrpc
17
+ import io.grpc.StatusRuntimeException
16
18
import io.grpc.stub.ClientCallStreamObserver
17
19
import io.grpc.stub.ClientResponseObserver
18
20
import org.jetbrains.plugins.terminal.ShellTerminalWidget
19
21
import org.jetbrains.plugins.terminal.TerminalView
20
22
import java.util.*
21
23
import java.util.concurrent.CompletableFuture
24
+ import java.util.concurrent.ExecutionException
22
25
import java.util.concurrent.TimeUnit
23
26
24
27
@Suppress(" UnstableApiUsage" )
@@ -30,9 +33,12 @@ class GitpodTerminalService(session: ClientProjectSession) {
30
33
private val terminalView = TerminalView .getInstance(session.project)
31
34
private val backendTerminalManager = BackendTerminalManager .getInstance(session.project)
32
35
private val terminalServiceFutureStub = TerminalServiceGrpc .newFutureStub(GitpodManager .supervisorChannel)
36
+ private val terminalServiceStub = TerminalServiceGrpc .newStub(GitpodManager .supervisorChannel)
33
37
private val statusServiceStub = StatusServiceGrpc .newStub(GitpodManager .supervisorChannel)
34
38
35
- init { start() }
39
+ init {
40
+ start()
41
+ }
36
42
37
43
private fun start () {
38
44
if (application.isHeadlessEnvironment || hasStarted) return
@@ -49,7 +55,7 @@ class GitpodTerminalService(session: ClientProjectSession) {
49
55
}
50
56
}
51
57
52
- private fun createSharedTerminalAndExecuteCommand (title : String , command : String ) {
58
+ private fun createSharedTerminalAndExecuteCommand (title : String , command : String ): ShellTerminalWidget ? {
53
59
val registeredTerminals = terminalView.widgets.toMutableList()
54
60
55
61
backendTerminalManager.createNewSharedTerminal(UUID .randomUUID().toString(), title)
@@ -59,13 +65,19 @@ class GitpodTerminalService(session: ClientProjectSession) {
59
65
60
66
widget.terminalTitle.change { applicationTitle = title }
61
67
62
- (widget as ShellTerminalWidget ).executeCommand(command)
68
+ val shellTerminalWidget = widget as ShellTerminalWidget
69
+
70
+ shellTerminalWidget.executeCommand(command)
71
+
72
+ return shellTerminalWidget
63
73
}
74
+
75
+ return null
64
76
}
65
77
66
78
private fun createTerminalsAttachedToTasks (
67
- terminals : List <TerminalOuterClass .Terminal >,
68
- tasks : List <Status .TaskStatus >
79
+ terminals : List <TerminalOuterClass .Terminal >,
80
+ tasks : List <Status .TaskStatus >
69
81
) {
70
82
if (tasks.isEmpty()) return
71
83
@@ -93,7 +105,7 @@ class GitpodTerminalService(session: ClientProjectSession) {
93
105
val taskStatusRequest = Status .TasksStatusRequest .newBuilder().setObserve(true ).build()
94
106
95
107
val taskStatusResponseObserver = object :
96
- ClientResponseObserver <Status .TasksStatusRequest , Status .TasksStatusResponse > {
108
+ ClientResponseObserver <Status .TasksStatusRequest , Status .TasksStatusResponse > {
97
109
override fun beforeStart (request : ClientCallStreamObserver <Status .TasksStatusRequest >) = Unit
98
110
99
111
override fun onNext (response : Status .TasksStatusResponse ) {
@@ -120,8 +132,9 @@ class GitpodTerminalService(session: ClientProjectSession) {
120
132
}
121
133
122
134
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
125
138
)
126
139
}
127
140
@@ -150,8 +163,9 @@ class GitpodTerminalService(session: ClientProjectSession) {
150
163
}
151
164
152
165
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
155
169
)
156
170
}
157
171
@@ -164,9 +178,105 @@ class GitpodTerminalService(session: ClientProjectSession) {
164
178
}
165
179
166
180
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
+ })
171
281
}
172
282
}
0 commit comments