Skip to content

Commit d7fae98

Browse files
committed
feat(shire): merge local agent language shire into AutoDev #379
1 parent 5e6a4a2 commit d7fae98

File tree

7 files changed

+352
-0
lines changed

7 files changed

+352
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package cc.unitmesh.devti.envior
2+
3+
import com.intellij.json.JsonUtil
4+
import com.intellij.json.psi.*
5+
6+
fun JsonProperty.valueAsString(obj: JsonObject): String? {
7+
val value = JsonUtil.getPropertyValueOfType(obj, name, JsonLiteral::class.java)
8+
return when (value) {
9+
is JsonStringLiteral -> value.value
10+
is JsonBooleanLiteral -> value.value.toString()
11+
else -> value?.text
12+
}
13+
}
14+
15+
fun JsonObject.findString(name: String): String? {
16+
val property = findProperty(name) ?: return null
17+
return property.valueAsString(this)
18+
}
19+
20+
fun JsonObject.findNumber(name: String): Number? {
21+
val property = findProperty(name) ?: return null
22+
return JsonUtil.getPropertyValueOfType(this, name, JsonNumberLiteral::class.java)?.value
23+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package cc.unitmesh.devti.envior
2+
3+
import com.intellij.json.psi.JsonFile
4+
import com.intellij.json.psi.JsonObject
5+
import com.intellij.openapi.project.DumbService
6+
import com.intellij.openapi.project.Project
7+
import com.intellij.psi.PsiFile
8+
import com.intellij.psi.PsiManager
9+
import com.intellij.psi.search.GlobalSearchScope
10+
import com.intellij.util.indexing.FileBasedIndex
11+
12+
object ShireEnvReader {
13+
const val DEFAULT_ENV_NAME = "development"
14+
/**
15+
* This function attempts to retrieve a JSON file associated with a given environment name within the specified scope and project.
16+
*
17+
* @param envName The name of the environment for which to find the associated JSON file.
18+
* @param scope The GlobalSearchScope to limit the search for the JSON file.
19+
* @param project The Project within which to search for the JSON file.
20+
*
21+
* @return A JsonFile object if a file with the environment name is found, or null if no such file exists within the given scope and project.
22+
*/
23+
private fun getEnvJsonFile(
24+
envName: String,
25+
scope: GlobalSearchScope,
26+
project: Project,
27+
): JsonFile? {
28+
return DumbService.getInstance(project).runReadActionInSmartMode<JsonFile?> {
29+
FileBasedIndex.getInstance().getContainingFiles(ShireEnvironmentIndex.id(), envName, scope)
30+
.firstOrNull()
31+
?.let {
32+
(PsiManager.getInstance(project).findFile(it) as? JsonFile)
33+
}
34+
}
35+
}
36+
37+
fun getEnvObject(
38+
envName: String,
39+
scope: GlobalSearchScope,
40+
project: Project,
41+
): JsonObject? {
42+
val psiFile = getEnvJsonFile(envName, scope, project)
43+
val envObject = getEnvObject(envName, psiFile)
44+
return envObject
45+
}
46+
47+
/**
48+
* Read Shire env file object
49+
*/
50+
fun getEnvObject(envName: String, psiFile: PsiFile?): JsonObject? {
51+
val rootObject = (psiFile as? JsonFile)?.topLevelValue as? JsonObject ?: return null
52+
return rootObject.propertyList.firstOrNull { it.name == envName }?.value as? JsonObject
53+
}
54+
55+
fun fetchEnvironmentVariables(envName: String, scope: GlobalSearchScope): List<Set<String>> {
56+
return FileBasedIndex.getInstance().getValues(
57+
ShireEnvironmentIndex.id(),
58+
envName,
59+
scope
60+
)
61+
}
62+
63+
fun getAllEnvironments(project: Project, scope: GlobalSearchScope): Collection<String> {
64+
try {
65+
return DumbService.getInstance(project).runReadActionInSmartMode<Collection<String>> {
66+
val index = FileBasedIndex.getInstance()
67+
index.getAllKeys(ShireEnvironmentIndex.id(), project).stream()
68+
.filter {
69+
it != ShireEnvironmentIndex.MODEL_LIST && index.getContainingFiles(
70+
ShireEnvironmentIndex.id(),
71+
it,
72+
scope
73+
).isNotEmpty()
74+
}
75+
.toList()
76+
}
77+
} catch (e: Exception) {
78+
return emptyList()
79+
}
80+
}
81+
82+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package cc.unitmesh.devti.envior
2+
3+
import com.intellij.json.JsonUtil
4+
import com.intellij.json.psi.*
5+
import com.intellij.openapi.util.TextRange
6+
7+
object ShireEnvVariableFiller {
8+
private fun getVariableValue(jsonObject: JsonObject, name: String, processVars: Map<String, String>): String? {
9+
val value = JsonUtil.getPropertyValueOfType(jsonObject, name, JsonLiteral::class.java)
10+
val jsonResult = getValueAsString(value)
11+
if (jsonResult != null) {
12+
return jsonResult
13+
}
14+
15+
return processVars[name]
16+
}
17+
18+
private fun getValueAsString(value: JsonLiteral?): String? {
19+
return when (value) {
20+
is JsonStringLiteral -> value.value
21+
is JsonBooleanLiteral -> value.value.toString()
22+
else -> value?.text
23+
}
24+
}
25+
26+
fun fillVariables(
27+
messageBody: String,
28+
variables: List<Set<String>>,
29+
obj: JsonObject?,
30+
processVars: Map<String, String>
31+
): String {
32+
if (obj == null) return messageBody
33+
if (variables.isEmpty()) return messageBody
34+
35+
val envRanges = collectVariablesRangesInMessageBody(messageBody)
36+
37+
val result = StringBuilder(messageBody.length)
38+
var lastVariableRangeEndOffset = 0
39+
40+
for (variableRange in envRanges) {
41+
result.append(messageBody as CharSequence, lastVariableRangeEndOffset, variableRange.startOffset)
42+
val variableValue = getVariableValue(obj, getVariableKey(variableRange, messageBody), processVars)
43+
44+
result.append(variableValue)
45+
lastVariableRangeEndOffset = variableRange.endOffset
46+
}
47+
48+
result.append(messageBody as CharSequence, lastVariableRangeEndOffset, messageBody.length)
49+
val sb = result.toString()
50+
return sb
51+
}
52+
53+
private fun getVariableKey(variableRange: TextRange, messageBody: String) =
54+
variableRange.substring(messageBody).removePrefix("\${").removeSuffix("}")
55+
56+
private fun collectVariablesRangesInMessageBody(body: String): List<TextRange> {
57+
val ranges = mutableListOf<TextRange>()
58+
var startIndex = 0
59+
60+
while (startIndex < body.length) {
61+
val openBraceIndex = body.indexOf("\${", startIndex)
62+
val closeBraceIndex = body.indexOf("}", openBraceIndex)
63+
64+
if (openBraceIndex == -1 || closeBraceIndex == -1) {
65+
break
66+
}
67+
68+
val range = TextRange(openBraceIndex, closeBraceIndex + 1)
69+
val contentInsideBraces = body.substring(openBraceIndex + 2, closeBraceIndex)
70+
71+
if (contentInsideBraces.isNotBlank()) {
72+
ranges.add(range)
73+
}
74+
75+
startIndex = closeBraceIndex + 1
76+
}
77+
78+
return ranges
79+
}
80+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package cc.unitmesh.devti.envior
2+
3+
import com.intellij.json.psi.*
4+
import com.intellij.openapi.util.text.StringUtil
5+
import com.intellij.util.indexing.*
6+
import com.intellij.util.io.DataExternalizer
7+
import com.intellij.util.io.EnumeratorStringDescriptor
8+
import com.intellij.util.io.KeyDescriptor
9+
10+
11+
class ShireEnvironmentIndex : FileBasedIndexExtension<String, Set<String>>() {
12+
companion object {
13+
val SHIRE_ENV_ID: ID<String, Set<String>> = ID.create("shire.environment")
14+
const val MODEL_TITLE = "title"
15+
const val MODEL_LIST = "models"
16+
17+
fun id(): ID<String, Set<String>> {
18+
return SHIRE_ENV_ID
19+
}
20+
}
21+
22+
override fun getValueExternalizer(): DataExternalizer<Set<String>> = ShireStringsExternalizer()
23+
override fun getVersion(): Int = 2
24+
override fun getInputFilter(): FileBasedIndex.InputFilter = ShireEnvironmentInputFilter()
25+
override fun dependsOnFileContent(): Boolean = true
26+
override fun getName(): ID<String, Set<String>> = SHIRE_ENV_ID
27+
override fun getKeyDescriptor(): KeyDescriptor<String> = EnumeratorStringDescriptor.INSTANCE
28+
29+
override fun getIndexer(): DataIndexer<String, Set<String>, FileContent> {
30+
return DataIndexer { inputData: FileContent ->
31+
val file = inputData.psiFile
32+
require(file is JsonFile) { AssertionError() }
33+
34+
val variablesFromFile = getVariablesFromFile(file)
35+
variablesFromFile
36+
}
37+
}
38+
39+
private fun getVariablesFromFile(file: JsonFile): Map<String, Set<String>> {
40+
val result: MutableMap<String, Set<String>> = HashMap()
41+
when (val topLevelValue = file.topLevelValue) {
42+
is JsonObject -> {
43+
for (property in topLevelValue.propertyList) {
44+
when (val value = property.value) {
45+
is JsonObject -> {
46+
result[property.name] = readEnvVariables(value, file.name)
47+
}
48+
49+
is JsonArray -> {
50+
// the prop key should be "models"
51+
if (property.name != MODEL_LIST) {
52+
continue
53+
}
54+
55+
// the child elements of the array are objects, which should have prop call "name"
56+
val envVariables = value.children
57+
.filterIsInstance<JsonObject>()
58+
.mapNotNull { obj ->
59+
val name = obj.findProperty(MODEL_TITLE)?.value?.text
60+
name?.let { StringUtil.unquoteString(it) }
61+
}
62+
.toSet()
63+
64+
result[property.name] = envVariables
65+
}
66+
}
67+
}
68+
}
69+
}
70+
71+
return result
72+
}
73+
74+
private fun readEnvVariables(obj: JsonObject, fileName: String): Set<String> {
75+
val properties = obj.propertyList
76+
return if (properties.isEmpty()) {
77+
emptySet()
78+
} else {
79+
val set = properties.stream()
80+
.map { property ->
81+
StringUtil.nullize(property.name)
82+
}
83+
.toList()
84+
.mapNotNull { it }
85+
.toSet()
86+
87+
set
88+
}
89+
}
90+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package cc.unitmesh.devti.envior
2+
3+
import com.intellij.json.JsonFileType
4+
import com.intellij.openapi.fileTypes.FileType
5+
import com.intellij.openapi.vfs.VirtualFile
6+
import com.intellij.util.indexing.DefaultFileTypeSpecificInputFilter
7+
8+
class ShireEnvironmentInputFilter : DefaultFileTypeSpecificInputFilter(*arrayOf<FileType>(JsonFileType.INSTANCE)) {
9+
override fun acceptInput(file: VirtualFile): Boolean {
10+
return super.acceptInput(file) && isShireEnvFile(file)
11+
}
12+
13+
private fun isShireEnvFile(file: VirtualFile?): Boolean {
14+
return file?.name?.endsWith(".shireEnv.json") ?: false
15+
}
16+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package cc.unitmesh.devti.envior
2+
3+
import com.intellij.util.io.DataExternalizer
4+
import java.io.DataInput
5+
import java.io.DataOutput
6+
7+
class ShireStringsExternalizer : DataExternalizer<Set<String>> {
8+
override fun save(out: DataOutput, value: Set<String>) {
9+
out.writeInt(value.size)
10+
for (s in value) {
11+
out.writeUTF(s)
12+
}
13+
}
14+
15+
override fun read(input: DataInput): Set<String> {
16+
val size = input.readInt()
17+
val result: MutableSet<String> = HashSet(size)
18+
for (i in 0 until size) {
19+
result.add(input.readUTF())
20+
}
21+
22+
return result
23+
}
24+
25+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package cc.unitmesh.devti.envior.llm
2+
3+
import com.intellij.json.psi.JsonArray
4+
import com.intellij.json.psi.JsonFile
5+
import com.intellij.json.psi.JsonObject
6+
import com.intellij.openapi.application.runReadAction
7+
import com.intellij.openapi.project.Project
8+
import com.intellij.psi.PsiManager
9+
import com.intellij.psi.search.GlobalSearchScope
10+
import com.intellij.util.indexing.FileBasedIndex
11+
import cc.unitmesh.devti.envior.ShireEnvironmentIndex
12+
import cc.unitmesh.devti.envior.valueAsString
13+
14+
class LlmEnv {
15+
companion object {
16+
private fun configFromFile(modelName: String, psiFile: JsonFile?): JsonObject? {
17+
val rootObject = psiFile?.topLevelValue as? JsonObject ?: return null
18+
val envObject = rootObject.propertyList.firstOrNull { it.name == ShireEnvironmentIndex.MODEL_LIST }?.value as? JsonArray
19+
return envObject?.children?.firstOrNull {
20+
it is JsonObject && it.findProperty(ShireEnvironmentIndex.MODEL_TITLE)?.valueAsString(it) == modelName
21+
} as? JsonObject
22+
}
23+
24+
fun configFromFile(modelName: String, scope: GlobalSearchScope, project: Project): JsonObject? {
25+
val jsonFile = runReadAction {
26+
FileBasedIndex.getInstance().getContainingFiles(ShireEnvironmentIndex.id(), ShireEnvironmentIndex.MODEL_LIST, scope)
27+
.firstOrNull()
28+
?.let {
29+
(PsiManager.getInstance(project).findFile(it) as? JsonFile)
30+
}
31+
}
32+
33+
return configFromFile(modelName, jsonFile)
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)