@@ -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,6 +33,7 @@ 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
39
init { start() }
@@ -49,7 +53,7 @@ class GitpodTerminalService(session: ClientProjectSession) {
49
53
}
50
54
}
51
55
52
- private fun createSharedTerminalAndExecuteCommand (title : String , command : String ) {
56
+ private fun createSharedTerminalAndExecuteCommand (title : String , command : String ): ShellTerminalWidget ? {
53
57
val registeredTerminals = terminalView.widgets.toMutableList()
54
58
55
59
backendTerminalManager.createNewSharedTerminal(UUID .randomUUID().toString(), title)
@@ -59,8 +63,14 @@ class GitpodTerminalService(session: ClientProjectSession) {
59
63
60
64
widget.terminalTitle.change { applicationTitle = title }
61
65
62
- (widget as ShellTerminalWidget ).executeCommand(command)
66
+ val shellTerminalWidget = widget as ShellTerminalWidget
67
+
68
+ shellTerminalWidget.executeCommand(command)
69
+
70
+ return shellTerminalWidget
63
71
}
72
+
73
+ return null
64
74
}
65
75
66
76
private fun createTerminalsAttachedToTasks (
@@ -120,8 +130,9 @@ class GitpodTerminalService(session: ClientProjectSession) {
120
130
}
121
131
122
132
thisLogger().error(
123
- " gitpod: Got an error while trying to get tasks list from Supervisor. Trying again in on second." ,
124
- throwable
133
+ " gitpod: Got an error while trying to get tasks list from Supervisor. " +
134
+ " Trying again in one second." ,
135
+ throwable
125
136
)
126
137
}
127
138
@@ -150,8 +161,9 @@ class GitpodTerminalService(session: ClientProjectSession) {
150
161
}
151
162
152
163
thisLogger().error(
153
- " gitpod: Got an error while trying to get terminals list from Supervisor. Trying again in on second." ,
154
- throwable
164
+ " gitpod: Got an error while trying to get terminals list from Supervisor. " +
165
+ " Trying again in one second." ,
166
+ throwable
155
167
)
156
168
}
157
169
@@ -164,9 +176,97 @@ class GitpodTerminalService(session: ClientProjectSession) {
164
176
}
165
177
166
178
private fun createAttachedSharedTerminal (supervisorTerminal : TerminalOuterClass .Terminal ) {
167
- createSharedTerminalAndExecuteCommand(
168
- supervisorTerminal.title,
169
- " gp tasks attach ${supervisorTerminal.alias} "
170
- )
179
+ val shellTerminalWidget = createSharedTerminalAndExecuteCommand(
180
+ supervisorTerminal.title,
181
+ " gp tasks attach ${supervisorTerminal.alias} "
182
+ ) ? : return
183
+
184
+ exitTaskWhenTerminalWidgetGetsClosed(supervisorTerminal, shellTerminalWidget)
185
+
186
+ listenForTaskTerminationAndTitleChanges(supervisorTerminal, shellTerminalWidget)
187
+ }
188
+
189
+ private fun listenForTaskTerminationAndTitleChanges (
190
+ supervisorTerminal : TerminalOuterClass .Terminal ,
191
+ shellTerminalWidget : ShellTerminalWidget
192
+ ) = application.executeOnPooledThread {
193
+ var hasOpenSessions = true
194
+
195
+ while (hasOpenSessions) {
196
+ val completableFuture = CompletableFuture <Void >()
197
+
198
+ val listenTerminalRequest = TerminalOuterClass .ListenTerminalRequest .newBuilder()
199
+ .setAlias(supervisorTerminal.alias)
200
+ .build()
201
+
202
+ val listenTerminalResponseObserver =
203
+ object : ClientResponseObserver <TerminalOuterClass .ListenTerminalRequest , TerminalOuterClass .ListenTerminalResponse > {
204
+ override fun beforeStart (request : ClientCallStreamObserver <TerminalOuterClass .ListenTerminalRequest >) {
205
+ @Suppress(" ObjectLiteralToLambda" )
206
+ shellTerminalWidget.addListener(object : TerminalWidgetListener {
207
+ override fun allSessionsClosed (widget : TerminalWidget ) {
208
+ hasOpenSessions = false
209
+ request.cancel(" gitpod: Terminal closed on the client." , null )
210
+ }
211
+ })
212
+ }
213
+
214
+ override fun onNext (response : TerminalOuterClass .ListenTerminalResponse ) {
215
+ when {
216
+ response.hasTitle() -> application.invokeLater {
217
+ shellTerminalWidget.terminalTitle.change {
218
+ applicationTitle = response.title
219
+ }
220
+ }
221
+
222
+ response.hasExitCode() -> application.invokeLater {
223
+ shellTerminalWidget.close()
224
+ }
225
+ }
226
+ }
227
+
228
+ override fun onCompleted () = Unit
229
+
230
+ override fun onError (throwable : Throwable ) {
231
+ completableFuture.completeExceptionally(throwable)
232
+ }
233
+ }
234
+
235
+ terminalServiceStub.listen(listenTerminalRequest, listenTerminalResponseObserver)
236
+
237
+ try {
238
+ completableFuture.get()
239
+ } catch (throwable: Throwable ) {
240
+ if (throwable is StatusRuntimeException || throwable is ExecutionException || throwable is InterruptedException ) {
241
+ shellTerminalWidget.close()
242
+ thisLogger()
243
+ .info(" gitpod: Stopped listening to " +
244
+ " '${supervisorTerminal.title} ' terminal due via an expected exception." , throwable)
245
+ break
246
+ }
247
+
248
+ thisLogger()
249
+ .error(" gitpod: Got an error while listening to " +
250
+ " '${supervisorTerminal.title} ' terminal. Trying again in one second." , throwable)
251
+ }
252
+
253
+ TimeUnit .SECONDS .sleep(1 )
254
+ }
255
+ }
256
+
257
+ private fun exitTaskWhenTerminalWidgetGetsClosed (
258
+ supervisorTerminal : TerminalOuterClass .Terminal ,
259
+ shellTerminalWidget : ShellTerminalWidget
260
+ ) {
261
+ @Suppress(" ObjectLiteralToLambda" )
262
+ shellTerminalWidget.addListener(object : TerminalWidgetListener {
263
+ override fun allSessionsClosed (widget : TerminalWidget ) {
264
+ terminalServiceFutureStub.shutdown(
265
+ TerminalOuterClass .ShutdownTerminalRequest .newBuilder()
266
+ .setAlias(supervisorTerminal.alias)
267
+ .build()
268
+ )
269
+ }
270
+ })
171
271
}
172
272
}
0 commit comments