@@ -4,11 +4,21 @@ import com.intellij.execution.configurations.GeneralCommandLine
4
4
import com.intellij.execution.util.ExecUtil
5
5
import com.intellij.openapi.application.ApplicationManager
6
6
import com.intellij.openapi.application.PathManager
7
+ import com.intellij.openapi.components.Service
8
+ import com.intellij.openapi.components.service
7
9
import com.intellij.openapi.diagnostic.thisLogger
8
10
import com.intellij.openapi.project.Project
9
11
import com.intellij.openapi.project.modules
10
12
import com.intellij.openapi.util.Key
11
13
import com.jetbrains.python.sdk.pythonSdk
14
+ import dev.robotcode.robotcode4ij.lsp.langServerManager
15
+ import dev.robotcode.robotcode4ij.testing.testManger
16
+ import kotlinx.coroutines.CoroutineScope
17
+ import kotlinx.coroutines.Dispatchers
18
+ import kotlinx.coroutines.ExperimentalCoroutinesApi
19
+ import kotlinx.coroutines.Job
20
+ import kotlinx.coroutines.delay
21
+ import kotlinx.coroutines.launch
12
22
import java.nio.file.Path
13
23
import kotlin.io.path.Path
14
24
import kotlin.io.path.exists
@@ -23,7 +33,7 @@ class RobotCodeHelpers {
23
33
val robotCodePath: Path = toolPath.resolve(" robotcode" )
24
34
val checkRobotVersion: Path = toolPath.resolve(" utils" ).resolve(" check_robot_version.py" )
25
35
26
- val PYTHON_AND_ROBOT_OK_KEY = Key .create<Boolean ?>(" ROBOTCODE_PYTHON_AND_ROBOT_OK" )
36
+ val PYTHON_AND_ROBOT_OK_KEY = Key .create<CheckPythonAndRobotVersionResult ?>(" ROBOTCODE_PYTHON_AND_ROBOT_OK" )
27
37
}
28
38
}
29
39
@@ -34,28 +44,43 @@ val Project.robotPythonSdk: com.intellij.openapi.projectRoots.Sdk?
34
44
}
35
45
}
36
46
37
- fun Project.checkPythonAndRobotVersion (reset : Boolean = false): Boolean {
38
- if (! reset && this .getUserData(RobotCodeHelpers .PYTHON_AND_ROBOT_OK_KEY ) == true ) {
39
- return true
47
+ enum class CheckPythonAndRobotVersionResult (val errorMessage : String? = null ) {
48
+ OK (null ),
49
+ NO_PYTHON (" No Python interpreter is configured for the project." ),
50
+ INVALID_PYTHON (" The configured Python interpreter is invalid." ),
51
+ INVALID_PYTHON_VERSION (" The Python version configured for the project is too old. Minimum required version is 3.9." ),
52
+ INVALID_ROBOT (" The Robot Framework version is invalid or not installed. Version 5.0 or higher is required." )
53
+ }
54
+
55
+ fun Project.resetPythonAndRobotVersionCache () {
56
+ this .putUserData(RobotCodeHelpers .PYTHON_AND_ROBOT_OK_KEY , null )
57
+ }
58
+
59
+ fun Project.checkPythonAndRobotVersion (reset : Boolean = false): CheckPythonAndRobotVersionResult {
60
+ if (! reset) {
61
+ val cachedResult = this .getUserData(RobotCodeHelpers .PYTHON_AND_ROBOT_OK_KEY )
62
+ if (cachedResult != null ) {
63
+ return cachedResult
64
+ }
40
65
}
41
66
42
- val result = ApplicationManager .getApplication().executeOnPooledThread<Boolean > {
67
+ val result = ApplicationManager .getApplication().executeOnPooledThread<CheckPythonAndRobotVersionResult > {
43
68
44
69
val pythonInterpreter = this .robotPythonSdk?.homePath
45
70
46
71
if (pythonInterpreter == null ) {
47
72
thisLogger().info(" No Python Interpreter defined for project '${this .name} '" )
48
- return @executeOnPooledThread false
73
+ return @executeOnPooledThread CheckPythonAndRobotVersionResult . NO_PYTHON
49
74
}
50
75
51
76
if (! Path (pythonInterpreter).exists()) {
52
77
thisLogger().warn(" Python Interpreter $pythonInterpreter not exists" )
53
- return @executeOnPooledThread false
78
+ return @executeOnPooledThread CheckPythonAndRobotVersionResult . INVALID_PYTHON
54
79
}
55
80
56
81
if (! Path (pythonInterpreter).isRegularFile()) {
57
82
thisLogger().warn(" Python Interpreter $pythonInterpreter is not a regular file" )
58
- return @executeOnPooledThread false
83
+ return @executeOnPooledThread CheckPythonAndRobotVersionResult . INVALID_PYTHON
59
84
}
60
85
61
86
thisLogger().info(" Use Python Interpreter $pythonInterpreter for project '${this .name} '" )
@@ -67,7 +92,7 @@ fun Project.checkPythonAndRobotVersion(reset: Boolean = false): Boolean {
67
92
)
68
93
if (res.exitCode != 0 || res.stdout.trim() != " True" ) {
69
94
thisLogger().warn(" Invalid python version" )
70
- return @executeOnPooledThread false
95
+ return @executeOnPooledThread CheckPythonAndRobotVersionResult . INVALID_PYTHON_VERSION
71
96
}
72
97
73
98
val res1 = ExecUtil .execAndGetOutput(
@@ -76,10 +101,10 @@ fun Project.checkPythonAndRobotVersion(reset: Boolean = false): Boolean {
76
101
)
77
102
if (res1.exitCode != 0 || res1.stdout.trim() != " True" ) {
78
103
thisLogger().warn(" Invalid Robot Framework version" )
79
- return @executeOnPooledThread false
104
+ return @executeOnPooledThread CheckPythonAndRobotVersionResult . INVALID_ROBOT
80
105
}
81
106
82
- return @executeOnPooledThread true
107
+ return @executeOnPooledThread CheckPythonAndRobotVersionResult . OK
83
108
84
109
}.get()
85
110
@@ -88,6 +113,7 @@ fun Project.checkPythonAndRobotVersion(reset: Boolean = false): Boolean {
88
113
return result
89
114
}
90
115
116
+ class InvalidPythonOrRobotVersionException (message : String ) : Exception(message)
91
117
92
118
fun Project.buildRobotCodeCommandLine (
93
119
args : Array <String > = arrayOf(),
@@ -97,8 +123,8 @@ fun Project.buildRobotCodeCommandLine(
97
123
noColor : Boolean = true,
98
124
noPager : Boolean = true
99
125
): GeneralCommandLine {
100
- if (! this .checkPythonAndRobotVersion()) {
101
- throw IllegalArgumentException (" PythonSDK is not defined or robot version is not valid for project ${this .name} " )
126
+ if (this .checkPythonAndRobotVersion() != CheckPythonAndRobotVersionResult . OK ) {
127
+ throw InvalidPythonOrRobotVersionException (" PythonSDK is not defined or robot version is not valid for project ${this .name} " )
102
128
}
103
129
104
130
val pythonInterpreter = this .robotPythonSdk?.homePath
@@ -118,3 +144,50 @@ fun Project.buildRobotCodeCommandLine(
118
144
119
145
return commandLine
120
146
}
147
+
148
+ @Service(Service .Level .PROJECT )
149
+ private class RobotCodeRestartManager (private val project : Project ) {
150
+ companion object {
151
+ private const val DEBOUNCE_DELAY = 500L
152
+ }
153
+
154
+ private var refreshJob: Job ? = null
155
+
156
+ fun restart (reset : Boolean = false) {
157
+ project.checkPythonAndRobotVersion(reset)
158
+ project.langServerManager.restart()
159
+ project.testManger.refreshDebounced()
160
+ }
161
+
162
+ @OptIn(ExperimentalCoroutinesApi ::class )
163
+ private val restartScope = CoroutineScope (Dispatchers .IO .limitedParallelism(1 ))
164
+
165
+ fun restartDebounced (reset : Boolean = false) {
166
+ if (! project.isOpen || project.isDisposed) {
167
+ return
168
+ }
169
+
170
+ refreshJob?.cancel()
171
+
172
+ refreshJob = restartScope.launch {
173
+ delay(DEBOUNCE_DELAY )
174
+ restart(reset)
175
+ refreshJob = null
176
+ }
177
+ }
178
+
179
+ fun cancelRestart () {
180
+ refreshJob?.cancel()
181
+ refreshJob = null
182
+ }
183
+ }
184
+
185
+ fun Project.restartAll (reset : Boolean = false, debounced : Boolean = true) {
186
+ val service = this .service<RobotCodeRestartManager >()
187
+ if (debounced) {
188
+ service.restartDebounced(reset)
189
+ } else {
190
+ service.cancelRestart()
191
+ service.restart(reset)
192
+ }
193
+ }
0 commit comments