Skip to content

Commit 8a80c12

Browse files
committed
fix(devin-lang): Improve code block parsing and injection logic #101
1 parent d804142 commit 8a80c12

File tree

3 files changed

+43
-40
lines changed

3 files changed

+43
-40
lines changed

exts/devin-lang/src/main/kotlin/cc/unitmesh/language/DevInLanguageInjector.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ class DevInLanguageInjector : LanguageInjector {
2525
val text = languageIdentifier?.text ?: return
2626
val language = CodeFenceLanguageGuesser.guessLanguageForInjection(text) ?: return
2727

28-
val elements = host.getContents()
29-
if (elements.size < 2) {
28+
val contentList = CodeBlockElement.obtainFenceContent(host) ?: return
29+
if (contentList.isEmpty()) {
3030
return
3131
}
3232

3333
injectAsOnePlace(host, language, registrar)
3434
}
3535

3636
private fun injectAsOnePlace(host: CodeBlockElement, language: Language, registrar: InjectedLanguagePlaces) {
37-
val elements = CodeBlockElement.obtainFenceContent(host, withWhitespaces = true) ?: return
37+
val elements = CodeBlockElement.obtainFenceContent(host) ?: return
3838

3939
val first = elements.first()
4040
val last = elements.last()

exts/devin-lang/src/main/kotlin/cc/unitmesh/language/parser/CodeBlockElement.kt

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.intellij.psi.LiteralTextEscaper
1010
import com.intellij.psi.PsiElement
1111
import com.intellij.psi.PsiLanguageInjectionHost
1212
import com.intellij.psi.impl.source.tree.injected.InjectionBackgroundSuppressor
13+
import com.intellij.psi.templateLanguages.OuterLanguageElement
1314
import com.intellij.psi.tree.IElementType
1415
import com.intellij.psi.util.*
1516

@@ -32,36 +33,43 @@ class CodeBlockElement(node: ASTNode) : ASTWrapperPsiElement(node), PsiLanguageI
3233
return findChildByType(DevInTypes.LANGUAGE_ID)
3334
}
3435

35-
fun getContents(): List<PsiElement> {
36-
return obtainFenceContent(this, withWhitespaces = false) ?: return emptyList()
36+
fun getContents(): PsiElement? {
37+
return findChildByType(DevInTypes.CODE_CONTENTS)
3738
}
3839

3940
companion object {
40-
fun obtainFenceContent(element: CodeBlockElement, withWhitespaces: Boolean): List<PsiElement>? {
41-
return when {
42-
withWhitespaces -> CachedValuesManager.getCachedValue(element) {
43-
CachedValueProvider.Result.create(getContent(element, true), element)
44-
}
45-
else -> CachedValuesManager.getCachedValue(element) {
46-
CachedValueProvider.Result.create(getContent(element, false), element)
47-
}
41+
fun obtainFenceContent(element: CodeBlockElement): List<PsiElement>? {
42+
return CachedValuesManager.getCachedValue(element) {
43+
CachedValueProvider.Result.create(getContent(element), element)
4844
}
4945
}
5046

51-
private fun getContent(element: CodeBlockElement, withWhitespaces: Boolean): List<PsiElement>? {
52-
val codeContents = element.children.firstOrNull { it.elementType == DevInTypes.CODE_CONTENTS } ?: return null
47+
private fun getContent(host: CodeBlockElement): List<PsiElement>? {
48+
val children = host.firstChild
49+
?.siblings(forward = true, withSelf = true) ?: return null
50+
51+
val elements =
52+
children.filter {
53+
it !is OuterLanguageElement
54+
&& (it.node.elementType == DevInTypes.CODE_CONTENTS || it == DevInTypes.NEWLINE)
55+
}
56+
.toList()
5357

54-
val psiElements = PsiTreeUtil.collectElements(codeContents) {
55-
it.elementType == DevInTypes.CODE_CONTENT
56-
}.toMutableList()
58+
if (elements.isNotEmpty() && elements.first() == DevInTypes.NEWLINE) {
59+
elements.drop(1)
60+
}
61+
if (elements.isNotEmpty() && elements.last() == DevInTypes.NEWLINE) {
62+
elements.dropLast(1)
63+
}
5764

58-
return psiElements.toList()
65+
return elements.takeIf { it.isNotEmpty() }
5966
}
6067

6168
fun obtainRelevantTextRange(element: CodeBlockElement): TextRange {
62-
val elements = obtainFenceContent(element, withWhitespaces = true) ?: return getEmptyRange(element)
69+
val elements = obtainFenceContent(element) ?: return getEmptyRange(element)
6370
val first = elements.first()
6471
val last = elements.last()
72+
6573
return TextRange.create(first.startOffsetInParent, last.startOffsetInParent + last.textLength)
6674
}
6775

@@ -82,27 +90,26 @@ class CodeBlockLiteralTextEscaper(host: CodeBlockElement) : LiteralTextEscaper<C
8290
override fun isOneLine(): Boolean = false;
8391

8492
override fun decode(rangeInsideHost: TextRange, outChars: StringBuilder): Boolean {
85-
val elements = CodeBlockElement.obtainFenceContent(myHost, withWhitespaces = false) ?: return true
93+
val elements = CodeBlockElement.obtainFenceContent(myHost) ?: return true
8694
for (element in elements) {
8795
val intersected = rangeInsideHost.intersection(element.textRangeInParent) ?: continue
8896
outChars.append(intersected.substring(myHost.text))
8997
}
98+
9099
return true
91100
}
92101

93102
override fun getOffsetInHost(offsetInDecoded: Int, rangeInsideHost: TextRange): Int {
94-
val elements = CodeBlockElement.obtainFenceContent(myHost, withWhitespaces = false) ?: return -1
103+
val elements = CodeBlockElement.obtainFenceContent(myHost) ?: return -1
95104
var cur = 0
96105
for (element in elements) {
97106
val intersected = rangeInsideHost.intersection(element.textRangeInParent)
98107
if (intersected == null || intersected.isEmpty) continue
99108
if (cur + intersected.length == offsetInDecoded) {
100109
return intersected.startOffset + intersected.length
101-
}
102-
else if (cur == offsetInDecoded) {
110+
} else if (cur == offsetInDecoded) {
103111
return intersected.startOffset
104-
}
105-
else if (cur < offsetInDecoded && cur + intersected.length > offsetInDecoded) {
112+
} else if (cur < offsetInDecoded && cur + intersected.length > offsetInDecoded) {
106113
return intersected.startOffset + (offsetInDecoded - cur)
107114
}
108115
cur += intersected.length
@@ -114,8 +121,7 @@ class CodeBlockLiteralTextEscaper(host: CodeBlockElement) : LiteralTextEscaper<C
114121
val result = intersected.startOffset + (offsetInDecoded - (cur - intersected.length))
115122
return if (rangeInsideHost.startOffset <= result && result <= rangeInsideHost.endOffset) {
116123
result
117-
}
118-
else -1
124+
} else -1
119125
}
120126

121127
override fun getRelevantTextRange(): TextRange {

exts/devin-lang/src/main/kotlin/cc/unitmesh/language/parser/DevInParserDefinition.kt

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import cc.unitmesh.language.DevInLanguage
44
import cc.unitmesh.language.lexer.DevInLexerAdapter
55
import cc.unitmesh.language.psi.DevInFile
66
import cc.unitmesh.language.psi.DevInTypes
7+
import com.intellij.extapi.psi.ASTWrapperPsiElement
78
import com.intellij.lang.ASTNode
89
import com.intellij.lang.ParserDefinition
910
import com.intellij.lang.PsiParser
@@ -19,9 +20,7 @@ import org.jetbrains.annotations.NotNull
1920

2021
internal class DevInParserDefinition : ParserDefinition {
2122
@NotNull
22-
override fun createLexer(project: Project?): Lexer {
23-
return DevInLexerAdapter()
24-
}
23+
override fun createLexer(project: Project?): Lexer = DevInLexerAdapter()
2524

2625
@NotNull
2726
override fun getCommentTokens(): TokenSet = TokenSet.EMPTY
@@ -30,19 +29,13 @@ internal class DevInParserDefinition : ParserDefinition {
3029
override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY
3130

3231
@NotNull
33-
override fun createParser(project: Project?): PsiParser {
34-
return DevInParser()
35-
}
32+
override fun createParser(project: Project?): PsiParser = DevInParser()
3633

3734
@NotNull
38-
override fun getFileNodeType(): IFileElementType {
39-
return FILE
40-
}
35+
override fun getFileNodeType(): IFileElementType = FILE
4136

4237
@NotNull
43-
override fun createFile(@NotNull viewProvider: FileViewProvider): PsiFile {
44-
return DevInFile(viewProvider)
45-
}
38+
override fun createFile(@NotNull viewProvider: FileViewProvider): PsiFile = DevInFile(viewProvider)
4639

4740
@NotNull
4841
override fun createElement(node: ASTNode?): PsiElement {
@@ -51,6 +44,10 @@ internal class DevInParserDefinition : ParserDefinition {
5144
return CodeBlockElement(node)
5245
}
5346

47+
if (elementType == DevInTypes.CODE_CONTENTS) {
48+
return ASTWrapperPsiElement(node)
49+
}
50+
5451
return DevInTypes.Factory.createElement(node)
5552
}
5653

0 commit comments

Comments
 (0)