Skip to content

Commit

Permalink
feat(intellij): added initial Robot Framework file templates and bett…
Browse files Browse the repository at this point in the history
…er syntax highlighting support based on a customized TextMate lexer/parser
  • Loading branch information
d-biehl committed Jan 2, 2025
1 parent ef0cf54 commit ef67d2c
Show file tree
Hide file tree
Showing 43 changed files with 1,051 additions and 293 deletions.
2 changes: 1 addition & 1 deletion intellij-client/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pluginVersion = 0.104.0

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


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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package dev.robotcode.robotcode4ij

import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.util.ExecUtil
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.jetbrains.python.sdk.pythonSdk
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.exists
import kotlin.io.path.isRegularFile
import kotlin.io.path.pathString

class RobotCodeHelpers {
companion object {
val basePath: Path = PathManager.getPluginsDir().resolve("robotcode4ij").resolve("data")
val bundledPath: Path = basePath.resolve("bundled")
val toolPath: Path = bundledPath.resolve("tool")
val robotCodePath: Path = toolPath.resolve("robotcode")
val checkRobotVersion: Path = toolPath.resolve("utils").resolve("check_robot_version.py")

val PYTHON_AND_ROBOT_OK_KEY = Key.create<Boolean?>("ROBOTCODE_PYTHON_AND_ROBOT_OK")
}
}

fun Project.checkPythonAndRobotVersion(reset: Boolean = false): Boolean {
if (!reset && this.getUserData(RobotCodeHelpers.PYTHON_AND_ROBOT_OK_KEY) == true) {
return true
}

val result = ApplicationManager.getApplication().executeOnPooledThread<Boolean> {

val pythonInterpreter = this.pythonSdk?.homePath

if (pythonInterpreter == null) {
thisLogger().info("No Python Interpreter defined for project '${this.name}'")
return@executeOnPooledThread false
}

if (!Path(pythonInterpreter).exists()) {
thisLogger().warn("Python Interpreter $pythonInterpreter not exists")
return@executeOnPooledThread false
}

if (!Path(pythonInterpreter).isRegularFile()) {
thisLogger().warn("Python Interpreter $pythonInterpreter is not a regular file")
return@executeOnPooledThread false
}

thisLogger().info("Use Python Interpreter $pythonInterpreter for project '${this.name}'")

val res = ExecUtil.execAndGetOutput(
GeneralCommandLine(
pythonInterpreter, "-u", "-c", "import sys; print(sys.version_info[:2]>=(3,8))"
), timeoutInMilliseconds = 5000
)
if (res.exitCode != 0 || res.stdout.trim() != "True") {
thisLogger().warn("Invalid python version")
return@executeOnPooledThread false
}

val res1 = ExecUtil.execAndGetOutput(
GeneralCommandLine(pythonInterpreter, "-u", RobotCodeHelpers.checkRobotVersion.pathString),
timeoutInMilliseconds = 5000
)
if (res1.exitCode != 0 || res1.stdout.trim() != "True") {
thisLogger().warn("Invalid Robot Framework version")
return@executeOnPooledThread false
}

return@executeOnPooledThread true

}.get()

this.putUserData(RobotCodeHelpers.PYTHON_AND_ROBOT_OK_KEY, result)

return result
}


fun Project.buildRobotCodeCommandLine(
args: Array<String> = arrayOf(),
profiles: Array<String> = arrayOf(),
extraArgs: Array<String> = arrayOf(),
format: String = "",
noColor: Boolean = true,
noPager: Boolean = true
): GeneralCommandLine {
if (!this.checkPythonAndRobotVersion()) {
throw IllegalArgumentException("PythonSDK is not defined or robot version is not valid for project ${this.name}")
}

val pythonInterpreter = this.pythonSdk?.homePath
val commandLine = GeneralCommandLine(
pythonInterpreter,
"-u",
"-X",
"utf8",
RobotCodeHelpers.robotCodePath.pathString,
*(if (format.isNotEmpty()) arrayOf("--format", format) else arrayOf()),
*(if (noColor) arrayOf("--no-color") else arrayOf()),
*(if (noPager) arrayOf("--no-pager") else arrayOf()),
*profiles.flatMap { listOf("--profile", it) }.toTypedArray(),
*extraArgs,
*args
).withWorkDirectory(this.basePath).withCharset(Charsets.UTF_8)

return commandLine
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ import com.intellij.platform.backend.workspace.workspaceModel
import com.intellij.platform.workspace.jps.entities.ModuleEntity
import com.intellij.platform.workspace.storage.EntityChange
import dev.robotcode.robotcode4ij.lsp.langServerManager
import dev.robotcode.robotcode4ij.testing.testManger
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach

class RobotCodePostStartupActivity : ProjectActivity {
override suspend fun execute(project: Project) {
project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, RobotCodeVirtualFileListener(project))

project.langServerManager.start()
project.testManger.refresh()

project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, RobotCodeVirtualFileListener(project))
project.workspaceModel.eventLog.onEach {
val moduleChanges = it.getChanges(ModuleEntity::class.java)
if (moduleChanges.filterIsInstance<EntityChange.Replaced<ModuleEntity>>().isNotEmpty()) {
project.checkPythonAndRobotVersion(true)
project.langServerManager.restart()
project.testManger.refresh()
}
}.collect()
}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package dev.robotcode.robotcode4ij

import com.intellij.util.containers.Interner
import org.jetbrains.plugins.textmate.TextMateService
import org.jetbrains.plugins.textmate.language.TextMateLanguageDescriptor
import org.jetbrains.plugins.textmate.language.syntax.TextMateSyntaxTable


object TextMateBundleHolder {
private val interner = Interner.createWeakInterner<CharSequence>()

val descriptor: TextMateLanguageDescriptor by lazy {

val reader = TextMateService.getInstance().readBundle(RobotCodeHelpers.basePath)
?: throw IllegalStateException("Failed to read robotcode textmate bundle")

val syntaxTable = TextMateSyntaxTable()

val grammarIterator = reader.readGrammars().iterator()
while (grammarIterator.hasNext()) {
val grammar = grammarIterator.next()
val rootScopeName = syntaxTable.loadSyntax(grammar.plist.value, interner) ?: continue
if (rootScopeName == "source.robotframework") {
val syntax = syntaxTable.getSyntax(rootScopeName)
return@lazy TextMateLanguageDescriptor(rootScopeName, syntax)
}
}

throw IllegalStateException("Failed to find robotcode textmate in bundle")
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package dev.robotcode.robotcode4ij.actions

import com.intellij.ide.actions.CreateFileFromTemplateAction
import com.intellij.ide.actions.CreateFileFromTemplateDialog
import com.intellij.ide.fileTemplates.FileTemplateManager
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDirectory
import dev.robotcode.robotcode4ij.RobotIcons
import dev.robotcode.robotcode4ij.RobotResourceFileType
import dev.robotcode.robotcode4ij.RobotSuiteFileType

class RobotCreateFileAction : CreateFileFromTemplateAction(
"Robot Framework File", "Robot Framework file",
RobotIcons
.Suite
),
DumbAware {
override fun buildDialog(project: Project, directory: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) {
builder.setTitle("New Robot Framework File")
FileTemplateManager.getInstance(project)
.allTemplates
.forEach {
if (it.extension == RobotSuiteFileType.defaultExtension) {
builder.addKind(it.name, RobotIcons.Suite, it.name)
} else if (it.extension == RobotResourceFileType.defaultExtension) {
builder.addKind(it.name, RobotIcons.Resource, it.name)
}
}
builder
.addKind("Suite file", RobotIcons.Suite, "Robot Suite File")
.addKind("Resource file", RobotIcons.Resource, "Robot Resource File")

}

override fun getActionName(directory: PsiDirectory?, newName: String, templateName: String?): String {
return "Create Robot Framework File"
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.robotcode.robotcode4ij.editor

import com.intellij.psi.impl.cache.CommentTokenSetProvider
import com.intellij.psi.tree.IElementType
import dev.robotcode.robotcode4ij.psi.RobotTextMateElementType

val COMMENT_SCOPES = setOf(
"comment.line.robotframework",
"comment.line.rest.robotframework",
"comment.block.robotframework",
)

class RobotCodeCommentTokenSetProvider : CommentTokenSetProvider {
override fun isInComments(elementType: IElementType?): Boolean {
val scopeName = (elementType as? RobotTextMateElementType)?.element?.scope?.scopeName
return COMMENT_SCOPES.contains(scopeName)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package dev.robotcode.robotcode4ij.editor

import com.intellij.lang.Commenter

class RobotCodeCommenter : Commenter {
override fun getLineCommentPrefix(): String {
return "#"
}

override fun getBlockCommentPrefix(): String? {
return null
}

override fun getBlockCommentSuffix(): String? {
return null
}

override fun getCommentedBlockCommentPrefix(): String? {
return null
}

override fun getCommentedBlockCommentSuffix(): String? {
return null
}
}
Loading

0 comments on commit ef67d2c

Please sign in to comment.