Skip to content

Commit 4ecf2b4

Browse files
authored
Implement DocumentationProvider (#24)
* Implement DocumentationProvider - added C3DocumentationProvider for hover docs for function declarations and variable declarations * Fixed function docs not displaying correctly in default module - added documentation for const declarations - fixed function docs not displaying correctly in default module - added highlighting for doc comments - added basic completion for doc comments - added check for existing function parameters for doc comments - updated regex for pattern matching in doc comments
1 parent c8a2522 commit 4ecf2b4

File tree

11 files changed

+465
-14
lines changed

11 files changed

+465
-14
lines changed

src/main/java/org/c3lang/intellij/annotation/C3Annotator.kt

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,34 @@ package org.c3lang.intellij.annotation
33
import com.intellij.lang.annotation.AnnotationHolder
44
import com.intellij.lang.annotation.Annotator
55
import com.intellij.lang.annotation.HighlightSeverity
6+
import com.intellij.psi.PsiComment
67
import com.intellij.psi.PsiElement
8+
import com.intellij.psi.util.elementType
9+
import org.c3lang.intellij.C3ParserDefinition
710
import org.c3lang.intellij.psi.C3CallExpr
811

9-
class C3Annotator : Annotator {
12+
class C3Annotator : Annotator
13+
{
1014

11-
override fun annotate(element: PsiElement, holder: AnnotationHolder) {
12-
when (element) {
15+
override fun annotate(element: PsiElement, holder: AnnotationHolder)
16+
{
17+
when (element)
18+
{
1319
is C3CallExpr -> annotateMissingCallables(element, holder)
20+
is PsiComment ->
21+
{
22+
if (element.elementType == C3ParserDefinition.DOC_COMMENT) annotateDocComment(element, holder)
23+
}
1424
}
1525

1626
org.c3lang.intellij.C3Annotator.INSTANCE.annotate(element, holder)
1727
}
1828

1929
private fun annotateMissingCallables(
20-
callExpr: C3CallExpr,
21-
holder: AnnotationHolder
22-
) {
30+
callExpr: C3CallExpr,
31+
holder: AnnotationHolder
32+
)
33+
{
2334
// val pathIdentExpr = callExpr.expr as? C3PathIdentExpr ?: return
2435
// val nameIdentElement = pathIdentExpr.pathIdent.nameIdentElement ?: return
2536
// val callName = nameIdentElement.text
@@ -44,19 +55,23 @@ class C3Annotator : Annotator {
4455
// }
4556
}
4657

47-
private fun AnnotationHolder.error(message: String, element: PsiElement) {
58+
private fun AnnotationHolder.error(message: String, element: PsiElement)
59+
{
4860
return newAnnotation(HighlightSeverity.ERROR, message).range(element.textRange).create()
4961
}
5062

51-
private fun AnnotationHolder.warning(message: String, element: PsiElement) {
63+
private fun AnnotationHolder.warning(message: String, element: PsiElement)
64+
{
5265
return newAnnotation(HighlightSeverity.WARNING, message).range(element.textRange).create()
5366
}
5467

55-
private fun AnnotationHolder.weakWarning(message: String, element: PsiElement) {
68+
private fun AnnotationHolder.weakWarning(message: String, element: PsiElement)
69+
{
5670
return newAnnotation(HighlightSeverity.WEAK_WARNING, message).range(element.textRange).create()
5771
}
5872

59-
private fun AnnotationHolder.info(message: String, element: PsiElement) {
73+
private fun AnnotationHolder.info(message: String, element: PsiElement)
74+
{
6075
return newAnnotation(HighlightSeverity.INFORMATION, message).range(element.textRange).create()
6176
}
6277

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package org.c3lang.intellij.annotation
2+
3+
import ai.grazie.text.range
4+
import com.intellij.lang.annotation.AnnotationHolder
5+
import com.intellij.lang.annotation.HighlightSeverity
6+
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
7+
import com.intellij.openapi.util.TextRange
8+
import com.intellij.psi.PsiComment
9+
import com.intellij.psi.PsiWhiteSpace
10+
import org.c3lang.intellij.psi.C3FuncDefinition
11+
12+
internal fun annotateDocComment(element: PsiComment, holder: AnnotationHolder)
13+
{
14+
annotateDocTags(element, holder)
15+
annotateParamTags(element, holder)
16+
}
17+
18+
private fun annotateDocTags(element: PsiComment, holder: AnnotationHolder)
19+
{
20+
val regex = Regex("@(param|return(!)?|deprecated|require|ensure|pure)")
21+
val commentText = element.text
22+
val commentStart = element.textRange.startOffset
23+
24+
regex.findAll(commentText).forEach { match ->
25+
val range = TextRange(commentStart + match.range.first, commentStart + match.range.last + 1)
26+
27+
holder.newSilentAnnotation(HighlightSeverity.INFORMATION)
28+
.range(range)
29+
.textAttributes(DOC_COMMENT_TAG)
30+
.create()
31+
}
32+
}
33+
34+
private fun getUnderlyingFunction(comment: PsiComment): C3FuncDefinition?
35+
{
36+
var next = comment.nextSibling
37+
38+
while (next is PsiWhiteSpace || next is PsiComment)
39+
{
40+
next = next.nextSibling
41+
}
42+
43+
if (next.firstChild is C3FuncDefinition) return next.firstChild as C3FuncDefinition
44+
return null
45+
}
46+
47+
private fun annotateParamTags(element: PsiComment, holder: AnnotationHolder)
48+
{
49+
val function = getUnderlyingFunction(element)
50+
val args = arrayListOf<String>()
51+
52+
if (function != null)
53+
{
54+
function.funcDef.fnParameterList.parameterList?.paramDeclList?.forEach {
55+
args.add(it.parameter.name!!)
56+
}
57+
}
58+
59+
val regex = Regex("@param\\s+((\\[(in|&in|out|&out|inout|&inout)])\\s+)?(\\w+)(\\s+:\\s+(\"((?:[^\"\\\\]|\\\\.)*)\"|`((?:[^`\\\\]|\\\\.)*)`))?")
60+
val commentText = element.text
61+
val commentStart = element.textRange.startOffset
62+
63+
val nonMatchingLines = commentText.lines().filter { it.trim().startsWith("@param") && !regex.matches(it.trim()) }
64+
65+
nonMatchingLines.forEach {
66+
holder.newAnnotation(HighlightSeverity.ERROR, "Invalid syntax")
67+
.range(TextRange(commentStart + it.range.start, commentStart + it.range.endInclusive + 1))
68+
.create()
69+
}
70+
71+
regex.findAll(commentText).forEach { match ->
72+
val contract = match.groups[1]
73+
val name = match.groups[4]!!
74+
val description = match.groups[6]
75+
76+
if (!args.contains(name.value))
77+
{
78+
val range = TextRange(commentStart + match.range.first, commentStart + match.range.last + 1)
79+
80+
holder.newAnnotation(HighlightSeverity.ERROR, "Argument missing in function")
81+
.range(range)
82+
.create()
83+
84+
return@forEach
85+
}
86+
87+
if (contract != null)
88+
{
89+
val contractRange = TextRange(commentStart + contract.range.first, commentStart + contract.range.last + 1)
90+
91+
holder.newSilentAnnotation(HighlightSeverity.INFORMATION)
92+
.range(contractRange)
93+
.textAttributes(DefaultLanguageHighlighterColors.CONSTANT)
94+
.create()
95+
}
96+
97+
if (description != null)
98+
{
99+
val descriptionRange = TextRange(commentStart + description.range.first, commentStart + description.range.last + 1)
100+
101+
holder.newSilentAnnotation(HighlightSeverity.INFORMATION)
102+
.range(descriptionRange)
103+
.textAttributes(DefaultLanguageHighlighterColors.STRING)
104+
.create()
105+
}
106+
}
107+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.c3lang.intellij.annotation
2+
3+
import com.intellij.openapi.editor.colors.TextAttributesKey
4+
import java.awt.Font
5+
6+
val DOC_COMMENT_TAG = TextAttributesKey.createTextAttributesKey("C3_DOC_COMMENT_TAG").apply {
7+
defaultAttributes.fontType = Font.BOLD or Font.ITALIC // kotlin bitwise-or
8+
}

src/main/java/org/c3lang/intellij/completion/C3CompletionContributor.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import com.intellij.codeInsight.completion.CompletionContributor
44
import com.intellij.codeInsight.completion.CompletionInitializationContext
55
import com.intellij.codeInsight.completion.CompletionType
66
import com.intellij.patterns.PlatformPatterns.psiElement
7-
import org.c3lang.intellij.psi.C3ModuleDefinition
87

9-
class C3CompletionContributor : CompletionContributor() {
10-
init {
8+
class C3CompletionContributor : CompletionContributor()
9+
{
10+
init
11+
{
1112
val pattern = psiElement()/*.inside(C3ModuleDefinition::class.java)*/
1213

1314
extend(CompletionType.BASIC, pattern, FunctionCompletionContributor)
@@ -17,9 +18,11 @@ class C3CompletionContributor : CompletionContributor() {
1718
extend(CompletionType.BASIC, pattern, FaultCompletionContributor)
1819
extend(CompletionType.BASIC, pattern, TailExprCompletionContributor)
1920
extend(CompletionType.BASIC, pattern, InitializerListCompletionContributor)
21+
extend(CompletionType.BASIC, pattern, DocCommentCompletionContributor)
2022
}
2123

22-
override fun beforeCompletion(context: CompletionInitializationContext) {
24+
override fun beforeCompletion(context: CompletionInitializationContext)
25+
{
2326
// path
2427
context.dummyIdentifier = DUMMY_IDENTIFIER
2528
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.c3lang.intellij.completion
2+
3+
import com.intellij.codeInsight.completion.CompletionParameters
4+
import com.intellij.codeInsight.completion.CompletionProvider
5+
import com.intellij.codeInsight.completion.CompletionResultSet
6+
import com.intellij.codeInsight.lookup.LookupElementBuilder
7+
import com.intellij.patterns.PlatformPatterns.or
8+
import com.intellij.patterns.PlatformPatterns.psiElement
9+
import com.intellij.util.ProcessingContext
10+
import org.c3lang.intellij.C3ParserDefinition
11+
12+
object DocCommentCompletionContributor : CompletionProvider<CompletionParameters>()
13+
{
14+
private val pattern = or(
15+
psiElement().inside(psiElement().withElementType(C3ParserDefinition.DOC_COMMENT)),
16+
)
17+
18+
override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet)
19+
{
20+
if (!pattern.accepts(parameters.position) && !pattern.accepts(parameters.originalPosition))
21+
{
22+
return
23+
}
24+
25+
listOf(
26+
"@param",
27+
"@return",
28+
"@return!",
29+
"@deprecated",
30+
"@require",
31+
"@ensure",
32+
"@pure",
33+
"[in]",
34+
"[&in]",
35+
"[out]",
36+
"[&out]",
37+
"[inout]",
38+
"[&inout]"
39+
).forEach {
40+
result.addElement(LookupElementBuilder.create(it))
41+
}
42+
}
43+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.c3lang.intellij.docs
2+
3+
import com.intellij.lang.documentation.AbstractDocumentationProvider
4+
import com.intellij.psi.PsiElement
5+
import org.c3lang.intellij.psi.C3ConstDeclarationStmt
6+
import org.c3lang.intellij.psi.C3FuncDef
7+
import org.c3lang.intellij.psi.C3LocalDeclAfterType
8+
9+
class C3DocumentationProvider : AbstractDocumentationProvider()
10+
{
11+
override fun generateDoc(element: PsiElement?, originalElement: PsiElement?): String?
12+
{
13+
if (element is C3FuncDef) return generateFuncDefDoc(element)
14+
if (element is C3LocalDeclAfterType) return generateVarDeclDoc(element)
15+
if (element is C3ConstDeclarationStmt) return generateConstDeclDoc(element)
16+
17+
return null
18+
}
19+
20+
override fun generateHoverDoc(element: PsiElement, originalElement: PsiElement?): String?
21+
{
22+
return generateDoc(element, originalElement)
23+
}
24+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.c3lang.intellij.docs
2+
3+
import com.intellij.lang.documentation.DocumentationMarkup
4+
import com.intellij.openapi.project.Project
5+
import com.intellij.psi.presentation.java.SymbolPresentationUtil
6+
import org.c3lang.intellij.psi.C3ConstDeclarationStmt
7+
8+
internal fun generateConstDeclDoc(element: C3ConstDeclarationStmt): String
9+
{
10+
val name = element.name ?: "Error getting name"
11+
val type = element.type?.text ?: "Error getting type"
12+
val value = element.expr?.text ?: "Error getting value"
13+
val file = SymbolPresentationUtil.getFilePathPresentation(element.containingFile)
14+
15+
return renderFullDoc(file, name, value, type, element.project)
16+
}
17+
18+
private fun renderFullDoc(file: String, name: String, value: String, type: String, project: Project): String
19+
{
20+
val builder = StringBuilder()
21+
appendDefinition("const $type $name = $value", project, builder)
22+
builder.append(DocumentationMarkup.SECTIONS_START)
23+
appendFileSection(file, builder)
24+
builder.append(DocumentationMarkup.SECTIONS_END)
25+
26+
return builder.toString()
27+
}

0 commit comments

Comments
 (0)