5
5
package io.gitpod.jetbrains.remote
6
6
7
7
import com.google.protobuf.ByteString
8
+ import com.intellij.openapi.Disposable
8
9
import com.intellij.openapi.application.runInEdt
9
10
import com.intellij.openapi.diagnostic.thisLogger
10
11
import com.intellij.openapi.project.Project
11
12
import com.intellij.openapi.util.Key
12
13
import com.intellij.openapi.wm.ToolWindowManager
13
14
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
14
15
import com.jediterm.terminal.TtyConnector
16
+ import com.jetbrains.rd.util.lifetime.Lifetime
15
17
import com.jetbrains.rdserver.terminal.BackendTerminalManager
16
18
import com.jetbrains.rdserver.terminal.BackendTtyConnector
17
19
import io.gitpod.supervisor.api.Status.*
@@ -20,73 +22,83 @@ import io.gitpod.supervisor.api.TerminalOuterClass
20
22
import io.gitpod.supervisor.api.TerminalServiceGrpc
21
23
import io.grpc.StatusRuntimeException
22
24
import io.grpc.stub.StreamObserver
25
+ import java.io.ByteArrayOutputStream
26
+ import java.io.PipedInputStream
27
+ import java.io.PipedOutputStream
28
+ import java.util.concurrent.CompletableFuture
23
29
import kotlinx.coroutines.*
30
+ import kotlinx.coroutines.future.await
31
+ import kotlinx.coroutines.guava.asDeferred
24
32
import org.jetbrains.plugins.terminal.ShellTerminalWidget
25
33
import org.jetbrains.plugins.terminal.TerminalTabState
26
34
import org.jetbrains.plugins.terminal.TerminalToolWindowFactory
27
35
import org.jetbrains.plugins.terminal.TerminalView
28
36
import org.jetbrains.plugins.terminal.cloud.CloudTerminalProcess
29
37
import org.jetbrains.plugins.terminal.cloud.CloudTerminalRunner
30
- import java.io.ByteArrayOutputStream
31
- import java.io.PipedInputStream
32
- import java.io.PipedOutputStream
33
38
34
- @Suppress(" UnstableApiUsage" )
35
- class GitpodTerminalService (private val project : Project ) {
39
+ @Suppress(" UnstableApiUsage" , " EXPERIMENTAL_IS_NOT_ENABLED" )
40
+ @OptIn(DelicateCoroutinesApi ::class )
41
+ class GitpodTerminalService (private val project : Project ) : Disposable {
36
42
companion object {
37
43
val TITLE_KEY = Key .create<String >(" TITLE_KEY" )
38
44
}
39
45
46
+ private val lifetime = Lifetime .Eternal .createNested()
40
47
private val terminalView = TerminalView .getInstance(project)
41
48
private val terminalServiceStub = TerminalServiceGrpc .newStub(GitpodManager .supervisorChannel)
49
+ private val terminalServiceFutureStub =
50
+ TerminalServiceGrpc .newFutureStub(GitpodManager .supervisorChannel)
42
51
private val statusServiceStub = StatusServiceGrpc .newStub(GitpodManager .supervisorChannel)
43
52
private val backendTerminalManager = BackendTerminalManager .getInstance(project)
44
53
54
+ override fun dispose () {
55
+ lifetime.terminate()
56
+ }
57
+
45
58
init {
46
- afterTerminalToolWindowGetsRegistered {
47
- withSupervisorTasksList { tasksList ->
48
- withSupervisorTerminalsList { terminalsList ->
49
- runInEdt {
50
- for (terminalWidget in terminalView.widgets) {
51
- val terminalContent = terminalView.toolWindow.contentManager.getContent(terminalWidget)
52
- val terminalTitle = terminalContent.getUserData(TITLE_KEY )
53
- if (terminalTitle != null ) {
54
- debug(" Closing terminal $terminalTitle before opening it again." )
55
- terminalWidget.close()
56
- }
57
- }
59
+ GlobalScope .launch {
60
+ getTerminalToolWindowRegisteredEvent().await()
61
+ val tasksList = getSupervisorTasksList().await()
62
+ val terminalsList = getSupervisorTerminalsList().await().terminalsList
63
+ debug(" Got a list of Supervisor terminals: ${terminalsList} " )
64
+ runInEdt {
65
+ for (terminalWidget in terminalView.widgets) {
66
+ val terminalContent =
67
+ terminalView.toolWindow.contentManager.getContent(terminalWidget)
68
+ val terminalTitle = terminalContent.getUserData(TITLE_KEY )
69
+ if (terminalTitle != null ) {
70
+ debug(" Closing terminal $terminalTitle before opening it again." )
71
+ terminalWidget.close()
72
+ }
73
+ }
58
74
59
- if (tasksList.isEmpty() || terminalsList.isEmpty()) {
60
- backendTerminalManager.createNewSharedTerminal(
61
- " GitpodTerminal" ,
62
- " Terminal"
63
- )
64
- } else {
65
- val aliasToTerminalMap:
66
- MutableMap <String , TerminalOuterClass .Terminal > =
67
- mutableMapOf ()
68
-
69
- for (terminal in terminalsList) {
70
- val terminalAlias = terminal.alias
71
- aliasToTerminalMap[terminalAlias] = terminal
72
- }
75
+ if (tasksList.isEmpty() || terminalsList.isEmpty()) {
76
+ backendTerminalManager.createNewSharedTerminal(" GitpodTerminal" , " Terminal" )
77
+ } else {
78
+ val aliasToTerminalMap: MutableMap <String , TerminalOuterClass .Terminal > =
79
+ mutableMapOf ()
73
80
74
- for (task in tasksList) {
75
- val terminalAlias = task.terminal
76
- val terminal = aliasToTerminalMap[terminalAlias]
81
+ for (terminal in terminalsList) {
82
+ val terminalAlias = terminal.alias
83
+ aliasToTerminalMap[terminalAlias] = terminal
84
+ }
77
85
78
- if (terminal != null ) {
79
- createSharedTerminal(terminal)
80
- }
81
- }
86
+ for (task in tasksList) {
87
+ val terminalAlias = task.terminal
88
+ val terminal = aliasToTerminalMap[terminalAlias]
89
+
90
+ if (terminal != null ) {
91
+ createSharedTerminal(terminal)
82
92
}
83
93
}
84
94
}
85
95
}
86
96
}
87
97
}
88
98
89
- private fun afterTerminalToolWindowGetsRegistered (action : () -> Unit ) {
99
+ private fun getTerminalToolWindowRegisteredEvent (): CompletableFuture <Void > {
100
+ val completableFuture = CompletableFuture <Void >()
101
+
90
102
debug(" Waiting for TerminalToolWindow to be registered..." )
91
103
val toolWindowManagerListener =
92
104
object : ToolWindowManagerListener {
@@ -96,19 +108,21 @@ class GitpodTerminalService(private val project: Project) {
96
108
) {
97
109
if (ids.contains(TerminalToolWindowFactory .TOOL_WINDOW_ID )) {
98
110
debug(" TerminalToolWindow got registered!" )
99
- action( )
111
+ completableFuture.complete( null )
100
112
}
101
113
}
102
114
}
103
115
104
116
project.messageBus
105
117
.connect()
106
118
.subscribe(ToolWindowManagerListener .TOPIC , toolWindowManagerListener)
119
+
120
+ return completableFuture
107
121
}
108
122
109
- private fun withSupervisorTasksList (action : (tasksList: List <TaskStatus >) -> Unit ) {
123
+ private fun getSupervisorTasksList (): CompletableFuture <List <TaskStatus >> {
124
+ val completableFuture = CompletableFuture <List <TaskStatus >>()
110
125
val taskStatusRequest = TasksStatusRequest .newBuilder().setObserve(true ).build()
111
-
112
126
val taskStatusResponseObserver =
113
127
object : StreamObserver <TasksStatusResponse > {
114
128
override fun onNext (response : TasksStatusResponse ) {
@@ -124,7 +138,7 @@ class GitpodTerminalService(private val project: Project) {
124
138
125
139
if (hasOpenedAllTasks) {
126
140
this .onCompleted()
127
- action (response.tasksList)
141
+ completableFuture.complete (response.tasksList)
128
142
}
129
143
}
130
144
@@ -138,38 +152,18 @@ class GitpodTerminalService(private val project: Project) {
138
152
" Got an error while trying to fetch tasks from Supervisor." ,
139
153
throwable
140
154
)
155
+ completableFuture.completeExceptionally(throwable)
141
156
}
142
157
}
143
158
144
159
statusServiceStub.tasksStatus(taskStatusRequest, taskStatusResponseObserver)
160
+
161
+ return completableFuture
145
162
}
146
163
147
- private fun withSupervisorTerminalsList (
148
- action : (terminalsList: List <TerminalOuterClass .Terminal >) -> Unit
149
- ) {
164
+ private fun getSupervisorTerminalsList (): Deferred <TerminalOuterClass .ListTerminalsResponse > {
150
165
val listTerminalsRequest = TerminalOuterClass .ListTerminalsRequest .newBuilder().build()
151
-
152
- val listTerminalsResponseObserver =
153
- object : StreamObserver <TerminalOuterClass .ListTerminalsResponse > {
154
- override fun onNext (response : TerminalOuterClass .ListTerminalsResponse ) {
155
- debug(" Got a list of Supervisor terminals: ${response.terminalsList} " )
156
- action(response.terminalsList)
157
- }
158
-
159
- override fun onError (throwable : Throwable ) {
160
- thisLogger()
161
- .error(
162
- " Got an error while getting the list of Supervisor terminals." ,
163
- throwable
164
- )
165
- }
166
-
167
- override fun onCompleted () {
168
- debug(" Successfully got the list of Supervisor terminals." )
169
- }
170
- }
171
-
172
- terminalServiceStub.list(listTerminalsRequest, listTerminalsResponseObserver)
166
+ return terminalServiceFutureStub.list(listTerminalsRequest).asDeferred()
173
167
}
174
168
175
169
private fun createSharedTerminal (supervisorTerminal : TerminalOuterClass .Terminal ) {
@@ -196,8 +190,9 @@ class GitpodTerminalService(private val project: Project) {
196
190
val shellTerminalWidget =
197
191
terminalView.widgets.find { widget ->
198
192
terminalView.toolWindow.contentManager.getContent(widget).tabName ==
199
- terminalRunnerId
200
- } as ShellTerminalWidget
193
+ terminalRunnerId
194
+ } as
195
+ ShellTerminalWidget
201
196
202
197
backendTerminalManager.shareTerminal(shellTerminalWidget, terminalRunnerId)
203
198
@@ -232,7 +227,10 @@ class GitpodTerminalService(private val project: Project) {
232
227
when {
233
228
response.hasTitle() -> {
234
229
debug(" Received terminal title: ${response.title} " )
235
- val terminalContent = terminalView.toolWindow.contentManager.getContent(shellTerminalWidget)
230
+ val terminalContent =
231
+ terminalView.toolWindow.contentManager.getContent(
232
+ shellTerminalWidget
233
+ )
236
234
terminalContent.putUserData(TITLE_KEY , response.title)
237
235
}
238
236
response.hasData() -> {
@@ -299,8 +297,6 @@ class GitpodTerminalService(private val project: Project) {
299
297
TerminalOuterClass .WriteTerminalRequest .newBuilder()
300
298
.setAlias(supervisorTerminal.alias)
301
299
302
- @Suppress(" EXPERIMENTAL_IS_NOT_ENABLED" )
303
- @OptIn(DelicateCoroutinesApi ::class )
304
300
val watchTerminalInputJob =
305
301
GlobalScope .launch {
306
302
withContext(Dispatchers .IO ) {
0 commit comments