Skip to content

Commit a6ba2b9

Browse files
committed
feat: add inlay hints
1 parent 119a476 commit a6ba2b9

5 files changed

Lines changed: 131 additions & 54 deletions

File tree

src/main/kotlin/com/github/xepozz/crontab/ide/CronScheduleDescriber.kt

Lines changed: 54 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,77 +3,78 @@ package com.github.xepozz.crontab.ide
33
object CronScheduleDescriber {
44
fun asHumanReadable(cron: String): String {
55
when (cron) {
6-
"@hourly" -> return "Every hour"
7-
"@daily" -> return "Every day"
8-
"@weekly" -> return "Every week"
9-
"@monthly" -> return "Every month"
10-
"@yearly" -> return "Every year"
11-
"@annually" -> return "Every year"
12-
"@reboot" -> return "After machine boot"
6+
"@hourly" -> return "every hour"
7+
"@daily" -> return "every day"
8+
"@weekly" -> return "every week"
9+
"@monthly" -> return "every month"
10+
"@yearly" -> return "every year"
11+
"@annually" -> return "every year"
12+
"@reboot" -> return "after machine boot"
1313
}
1414

1515
val fields = cron.trim().split("\\s+".toRegex())
16-
if (fields.size != 5) {
17-
throw IllegalArgumentException("Invalid cron expression. Expected 5 fields, got ${fields.size} in: \"$cron\"")
18-
}
19-
20-
val (minute, hour, dayOfMonth, month, dayOfWeek) = fields
2116

22-
return buildString {
23-
append("Runs ")
17+
// val (minute, hour, dayOfMonth, month, dayOfWeek) = fields
18+
val minute = fields.getOrElse(0, { "*" })
19+
val hour = fields.getOrElse(1, { "*" })
20+
val dayOfMonth = fields.getOrElse(2, { "*" })
21+
val month = fields.getOrElse(3, { "*" })
22+
val dayOfWeek = fields.getOrElse(4, { "*" })
2423

24+
return buildList {
2525
// Minute
26-
append(
27-
when (minute) {
28-
"*" -> "every minute"
26+
add(
27+
when {
28+
minute == "*" -> "every minute"
29+
minute.matches(Regex("\\*/\\d+")) -> "every ${minute.split("/").last()} minute"
2930
else -> "at minute $minute"
3031
}
3132
)
3233

3334
// Hour
34-
append(
35-
when (hour) {
36-
"*" -> ""
37-
else -> " past hour $hour"
35+
add(
36+
when {
37+
hour == "*" -> "every hour"
38+
hour.matches(Regex("\\*/\\d+")) -> "every ${hour.split("/").last()} hour"
39+
else -> "past hour $hour"
3840
}
3941
)
4042

41-
append(" ")
42-
4343
// Day of month
44-
append(
45-
when (dayOfMonth) {
46-
"*" -> "every day"
47-
else -> "on day $dayOfMonth of the month"
48-
}
49-
)
50-
51-
append(" ")
44+
if (dayOfMonth != "*") {
45+
add(
46+
when {
47+
dayOfMonth.matches(Regex("\\*/\\d+")) -> "every ${dayOfMonth.split("/").last()} day"
48+
else -> "on day $dayOfMonth of the month"
49+
}
50+
)
51+
}
5252

5353
// Month
54-
append(
55-
when (month) {
56-
"*" -> "in every month"
57-
else -> "in $month"
58-
}
59-
)
60-
61-
append(" ")
54+
if (month != "*") {
55+
add(
56+
when {
57+
month.matches(Regex("\\*/\\d+")) -> "every ${month.split("/").last()} month"
58+
else -> "in $month"
59+
}
60+
)
61+
}
6262

6363
// Day of week
64-
append(
65-
when (dayOfWeek) {
66-
"*" -> ""
67-
"0", "7" -> "on Sundays"
68-
"1" -> "on Mondays"
69-
"2" -> "on Tuesdays"
70-
"3" -> "on Wednesdays"
71-
"4" -> "on Thursdays"
72-
"5" -> "on Fridays"
73-
"6" -> "on Saturdays"
74-
else -> "on specific days ($dayOfWeek)"
75-
}
76-
)
77-
}
64+
if (dayOfWeek != "*") {
65+
add(
66+
when (dayOfWeek) {
67+
"0", "7" -> "on Sundays"
68+
"1" -> "on Mondays"
69+
"2" -> "on Tuesdays"
70+
"3" -> "on Wednesdays"
71+
"4" -> "on Thursdays"
72+
"5" -> "on Fridays"
73+
"6" -> "on Saturdays"
74+
else -> "on specific days ($dayOfWeek)"
75+
}
76+
)
77+
}
78+
}.joinToString(" ")
7879
}
7980
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.github.xepozz.crontab.ide
2+
3+
import com.github.xepozz.crontab.language.psi.CrontabCronExpression
4+
import com.github.xepozz.crontab.language.psi.CrontabElementFactory
5+
import com.github.xepozz.crontab.language.psi.CrontabSchedule
6+
import com.intellij.codeInsight.hints.declarative.HintFormat
7+
import com.intellij.codeInsight.hints.declarative.InlayHintsCollector
8+
import com.intellij.codeInsight.hints.declarative.InlayHintsProvider
9+
import com.intellij.codeInsight.hints.declarative.InlayTreeSink
10+
import com.intellij.codeInsight.hints.declarative.InlineInlayPosition
11+
import com.intellij.codeInsight.hints.declarative.SharedBypassCollector
12+
import com.intellij.openapi.editor.Editor
13+
import com.intellij.psi.PsiElement
14+
import com.intellij.psi.PsiFile
15+
import com.intellij.psi.util.PsiTreeUtil
16+
import com.intellij.psi.util.endOffset
17+
18+
class CrontabInlayHintsProvider : InlayHintsProvider {
19+
override fun createCollector(
20+
file: PsiFile,
21+
editor: Editor
22+
): InlayHintsCollector {
23+
return object : SharedBypassCollector {
24+
override fun collectFromElement(element: PsiElement, sink: InlayTreeSink) {
25+
val crontabFile = CrontabElementFactory.createFile(element.project, element.text)
26+
val expression = crontabFile.children.getOrNull(0) as? CrontabCronExpression ?: return
27+
if (expression.schedule.text != element.text) {
28+
return
29+
}
30+
31+
val schedules = PsiTreeUtil.findChildrenOfType(crontabFile, CrontabSchedule::class.java)
32+
// println("schedules: ${schedules}")
33+
34+
if (schedules.isNotEmpty()) {
35+
36+
val offset = when {
37+
element.nextSibling.text in arrayOf("\"", "'", "`") -> element.nextSibling.endOffset
38+
else -> element.endOffset
39+
}
40+
sink.addPresentation(
41+
position = InlineInlayPosition(offset, false, 100),
42+
hintFormat = HintFormat.default,
43+
) {
44+
text(CronScheduleDescriber.asHumanReadable(element.text))
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.github.xepozz.crontab.ide
2+
3+
import com.intellij.codeInsight.hints.declarative.InlayHintsProviderFactory
4+
import com.intellij.codeInsight.hints.declarative.InlayProviderInfo
5+
import com.intellij.lang.Language
6+
7+
class CrontabInlayHintsProviderFactory : InlayHintsProviderFactory {
8+
override fun getProviderInfo(
9+
language: Language,
10+
providerId: String
11+
) = InlayProviderInfo(
12+
provider = CrontabInlayHintsProvider(),
13+
providerId = providerId,
14+
options = emptySet(),
15+
isEnabledByDefault = true,
16+
providerName = CrontabInlayHintsProvider::class.java.name
17+
)
18+
19+
20+
override fun getProvidersForLanguage(language: Language) = listOf(getProviderInfo(language, "crontab"))
21+
22+
override fun getSupportedLanguages() = emptySet<Language>()
23+
}

src/main/kotlin/com/github/xepozz/crontab/language/psi/CrontabElementFactory.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.github.xepozz.crontab.language.psi
33
import com.github.xepozz.crontab.language.CrontabFile
44
import com.github.xepozz.crontab.language.CrontabFileType
55
import com.intellij.openapi.project.Project
6+
import com.intellij.psi.PsiFileFactory
67
import com.intellij.psi.util.PsiTreeUtil
78

89
object CrontabElementFactory {
@@ -25,7 +26,7 @@ object CrontabElementFactory {
2526

2627
fun createFile(project: Project, text: String): CrontabFile {
2728
val name = "dummy.crontab"
28-
return com.intellij.psi.PsiFileFactory.getInstance(project)
29+
return PsiFileFactory.getInstance(project)
2930
.createFileFromText(name, CrontabFileType.INSTANCE, text) as CrontabFile
3031
}
3132
}

src/main/resources/META-INF/plugin.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
<resource-bundle>messages.MyBundle</resource-bundle>
1111

1212
<extensions defaultExtensionNs="com.intellij">
13+
<codeInsight.declarativeInlayProviderFactory
14+
implementation="com.github.xepozz.crontab.ide.CrontabInlayHintsProviderFactory"/>
1315
<fileType
1416
name="Crontab File"
1517
implementationClass="com.github.xepozz.crontab.language.CrontabFileType"

0 commit comments

Comments
 (0)