Skip to content

Commit ef67d2c

Browse files
committed
feat(intellij): added initial Robot Framework file templates and better syntax highlighting support based on a customized TextMate lexer/parser
1 parent ef0cf54 commit ef67d2c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1051
-293
lines changed

intellij-client/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pluginVersion = 0.104.0
88

99
# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
1010
pluginSinceBuild = 243
11-
pluginUntilBuild = 243.*
11+
pluginUntilBuild =
1212

1313

1414
# IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension

intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/BundledHelpers.kt

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package dev.robotcode.robotcode4ij
2+
3+
import com.intellij.execution.configurations.GeneralCommandLine
4+
import com.intellij.execution.util.ExecUtil
5+
import com.intellij.openapi.application.ApplicationManager
6+
import com.intellij.openapi.application.PathManager
7+
import com.intellij.openapi.diagnostic.thisLogger
8+
import com.intellij.openapi.project.Project
9+
import com.intellij.openapi.util.Key
10+
import com.jetbrains.python.sdk.pythonSdk
11+
import java.nio.file.Path
12+
import kotlin.io.path.Path
13+
import kotlin.io.path.exists
14+
import kotlin.io.path.isRegularFile
15+
import kotlin.io.path.pathString
16+
17+
class RobotCodeHelpers {
18+
companion object {
19+
val basePath: Path = PathManager.getPluginsDir().resolve("robotcode4ij").resolve("data")
20+
val bundledPath: Path = basePath.resolve("bundled")
21+
val toolPath: Path = bundledPath.resolve("tool")
22+
val robotCodePath: Path = toolPath.resolve("robotcode")
23+
val checkRobotVersion: Path = toolPath.resolve("utils").resolve("check_robot_version.py")
24+
25+
val PYTHON_AND_ROBOT_OK_KEY = Key.create<Boolean?>("ROBOTCODE_PYTHON_AND_ROBOT_OK")
26+
}
27+
}
28+
29+
fun Project.checkPythonAndRobotVersion(reset: Boolean = false): Boolean {
30+
if (!reset && this.getUserData(RobotCodeHelpers.PYTHON_AND_ROBOT_OK_KEY) == true) {
31+
return true
32+
}
33+
34+
val result = ApplicationManager.getApplication().executeOnPooledThread<Boolean> {
35+
36+
val pythonInterpreter = this.pythonSdk?.homePath
37+
38+
if (pythonInterpreter == null) {
39+
thisLogger().info("No Python Interpreter defined for project '${this.name}'")
40+
return@executeOnPooledThread false
41+
}
42+
43+
if (!Path(pythonInterpreter).exists()) {
44+
thisLogger().warn("Python Interpreter $pythonInterpreter not exists")
45+
return@executeOnPooledThread false
46+
}
47+
48+
if (!Path(pythonInterpreter).isRegularFile()) {
49+
thisLogger().warn("Python Interpreter $pythonInterpreter is not a regular file")
50+
return@executeOnPooledThread false
51+
}
52+
53+
thisLogger().info("Use Python Interpreter $pythonInterpreter for project '${this.name}'")
54+
55+
val res = ExecUtil.execAndGetOutput(
56+
GeneralCommandLine(
57+
pythonInterpreter, "-u", "-c", "import sys; print(sys.version_info[:2]>=(3,8))"
58+
), timeoutInMilliseconds = 5000
59+
)
60+
if (res.exitCode != 0 || res.stdout.trim() != "True") {
61+
thisLogger().warn("Invalid python version")
62+
return@executeOnPooledThread false
63+
}
64+
65+
val res1 = ExecUtil.execAndGetOutput(
66+
GeneralCommandLine(pythonInterpreter, "-u", RobotCodeHelpers.checkRobotVersion.pathString),
67+
timeoutInMilliseconds = 5000
68+
)
69+
if (res1.exitCode != 0 || res1.stdout.trim() != "True") {
70+
thisLogger().warn("Invalid Robot Framework version")
71+
return@executeOnPooledThread false
72+
}
73+
74+
return@executeOnPooledThread true
75+
76+
}.get()
77+
78+
this.putUserData(RobotCodeHelpers.PYTHON_AND_ROBOT_OK_KEY, result)
79+
80+
return result
81+
}
82+
83+
84+
fun Project.buildRobotCodeCommandLine(
85+
args: Array<String> = arrayOf(),
86+
profiles: Array<String> = arrayOf(),
87+
extraArgs: Array<String> = arrayOf(),
88+
format: String = "",
89+
noColor: Boolean = true,
90+
noPager: Boolean = true
91+
): GeneralCommandLine {
92+
if (!this.checkPythonAndRobotVersion()) {
93+
throw IllegalArgumentException("PythonSDK is not defined or robot version is not valid for project ${this.name}")
94+
}
95+
96+
val pythonInterpreter = this.pythonSdk?.homePath
97+
val commandLine = GeneralCommandLine(
98+
pythonInterpreter,
99+
"-u",
100+
"-X",
101+
"utf8",
102+
RobotCodeHelpers.robotCodePath.pathString,
103+
*(if (format.isNotEmpty()) arrayOf("--format", format) else arrayOf()),
104+
*(if (noColor) arrayOf("--no-color") else arrayOf()),
105+
*(if (noPager) arrayOf("--no-pager") else arrayOf()),
106+
*profiles.flatMap { listOf("--profile", it) }.toTypedArray(),
107+
*extraArgs,
108+
*args
109+
).withWorkDirectory(this.basePath).withCharset(Charsets.UTF_8)
110+
111+
return commandLine
112+
}

intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/RobotCodeParserDefinition.kt

Lines changed: 0 additions & 84 deletions
This file was deleted.

intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/RobotCodePostStartupActivity.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,22 @@ import com.intellij.platform.backend.workspace.workspaceModel
77
import com.intellij.platform.workspace.jps.entities.ModuleEntity
88
import com.intellij.platform.workspace.storage.EntityChange
99
import dev.robotcode.robotcode4ij.lsp.langServerManager
10+
import dev.robotcode.robotcode4ij.testing.testManger
1011
import kotlinx.coroutines.flow.collect
1112
import kotlinx.coroutines.flow.onEach
1213

1314
class RobotCodePostStartupActivity : ProjectActivity {
1415
override suspend fun execute(project: Project) {
15-
project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, RobotCodeVirtualFileListener(project))
16-
1716
project.langServerManager.start()
17+
project.testManger.refresh()
1818

19+
project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, RobotCodeVirtualFileListener(project))
1920
project.workspaceModel.eventLog.onEach {
2021
val moduleChanges = it.getChanges(ModuleEntity::class.java)
2122
if (moduleChanges.filterIsInstance<EntityChange.Replaced<ModuleEntity>>().isNotEmpty()) {
23+
project.checkPythonAndRobotVersion(true)
2224
project.langServerManager.restart()
25+
project.testManger.refresh()
2326
}
2427
}.collect()
2528
}

intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/RobotCodeRunConfiguration.kt

Lines changed: 0 additions & 12 deletions
This file was deleted.

intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/RobotCodeTextMateBundleProvider.kt

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package dev.robotcode.robotcode4ij
2+
3+
import com.intellij.util.containers.Interner
4+
import org.jetbrains.plugins.textmate.TextMateService
5+
import org.jetbrains.plugins.textmate.language.TextMateLanguageDescriptor
6+
import org.jetbrains.plugins.textmate.language.syntax.TextMateSyntaxTable
7+
8+
9+
object TextMateBundleHolder {
10+
private val interner = Interner.createWeakInterner<CharSequence>()
11+
12+
val descriptor: TextMateLanguageDescriptor by lazy {
13+
14+
val reader = TextMateService.getInstance().readBundle(RobotCodeHelpers.basePath)
15+
?: throw IllegalStateException("Failed to read robotcode textmate bundle")
16+
17+
val syntaxTable = TextMateSyntaxTable()
18+
19+
val grammarIterator = reader.readGrammars().iterator()
20+
while (grammarIterator.hasNext()) {
21+
val grammar = grammarIterator.next()
22+
val rootScopeName = syntaxTable.loadSyntax(grammar.plist.value, interner) ?: continue
23+
if (rootScopeName == "source.robotframework") {
24+
val syntax = syntaxTable.getSyntax(rootScopeName)
25+
return@lazy TextMateLanguageDescriptor(rootScopeName, syntax)
26+
}
27+
}
28+
29+
throw IllegalStateException("Failed to find robotcode textmate in bundle")
30+
}
31+
32+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package dev.robotcode.robotcode4ij.actions
2+
3+
import com.intellij.ide.actions.CreateFileFromTemplateAction
4+
import com.intellij.ide.actions.CreateFileFromTemplateDialog
5+
import com.intellij.ide.fileTemplates.FileTemplateManager
6+
import com.intellij.openapi.project.DumbAware
7+
import com.intellij.openapi.project.Project
8+
import com.intellij.psi.PsiDirectory
9+
import dev.robotcode.robotcode4ij.RobotIcons
10+
import dev.robotcode.robotcode4ij.RobotResourceFileType
11+
import dev.robotcode.robotcode4ij.RobotSuiteFileType
12+
13+
class RobotCreateFileAction : CreateFileFromTemplateAction(
14+
"Robot Framework File", "Robot Framework file",
15+
RobotIcons
16+
.Suite
17+
),
18+
DumbAware {
19+
override fun buildDialog(project: Project, directory: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) {
20+
builder.setTitle("New Robot Framework File")
21+
FileTemplateManager.getInstance(project)
22+
.allTemplates
23+
.forEach {
24+
if (it.extension == RobotSuiteFileType.defaultExtension) {
25+
builder.addKind(it.name, RobotIcons.Suite, it.name)
26+
} else if (it.extension == RobotResourceFileType.defaultExtension) {
27+
builder.addKind(it.name, RobotIcons.Resource, it.name)
28+
}
29+
}
30+
builder
31+
.addKind("Suite file", RobotIcons.Suite, "Robot Suite File")
32+
.addKind("Resource file", RobotIcons.Resource, "Robot Resource File")
33+
34+
}
35+
36+
override fun getActionName(directory: PsiDirectory?, newName: String, templateName: String?): String {
37+
return "Create Robot Framework File"
38+
}
39+
40+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package dev.robotcode.robotcode4ij.editor
2+
3+
import com.intellij.psi.impl.cache.CommentTokenSetProvider
4+
import com.intellij.psi.tree.IElementType
5+
import dev.robotcode.robotcode4ij.psi.RobotTextMateElementType
6+
7+
val COMMENT_SCOPES = setOf(
8+
"comment.line.robotframework",
9+
"comment.line.rest.robotframework",
10+
"comment.block.robotframework",
11+
)
12+
13+
class RobotCodeCommentTokenSetProvider : CommentTokenSetProvider {
14+
override fun isInComments(elementType: IElementType?): Boolean {
15+
val scopeName = (elementType as? RobotTextMateElementType)?.element?.scope?.scopeName
16+
return COMMENT_SCOPES.contains(scopeName)
17+
}
18+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package dev.robotcode.robotcode4ij.editor
2+
3+
import com.intellij.lang.Commenter
4+
5+
class RobotCodeCommenter : Commenter {
6+
override fun getLineCommentPrefix(): String {
7+
return "#"
8+
}
9+
10+
override fun getBlockCommentPrefix(): String? {
11+
return null
12+
}
13+
14+
override fun getBlockCommentSuffix(): String? {
15+
return null
16+
}
17+
18+
override fun getCommentedBlockCommentPrefix(): String? {
19+
return null
20+
}
21+
22+
override fun getCommentedBlockCommentSuffix(): String? {
23+
return null
24+
}
25+
}

0 commit comments

Comments
 (0)