language injections
This commit is contained in:
parent
431e09830f
commit
f2220b7546
3 changed files with 222 additions and 0 deletions
|
@ -0,0 +1,72 @@
|
||||||
|
package com.falsepattern.zigbrains.zig.injection
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral
|
||||||
|
import com.falsepattern.zigbrains.zig.psi.ZigTypes
|
||||||
|
import com.falsepattern.zigbrains.zig.psi.getMultilineContent
|
||||||
|
import com.intellij.lang.Language
|
||||||
|
import com.intellij.lang.injection.MultiHostRegistrar
|
||||||
|
import com.intellij.lang.injection.general.Injection
|
||||||
|
import com.intellij.lang.injection.general.LanguageInjectionPerformer
|
||||||
|
import com.intellij.openapi.util.TextRange
|
||||||
|
import com.intellij.psi.PsiComment
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiLanguageInjectionHost
|
||||||
|
|
||||||
|
class ZigLanguageInjectionPerformer : LanguageInjectionPerformer {
|
||||||
|
override fun isPrimary() = false
|
||||||
|
|
||||||
|
override fun performInjection(registrar: MultiHostRegistrar, injection: Injection, context: PsiElement): Boolean {
|
||||||
|
if (context !is PsiLanguageInjectionHost)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val language = injection.injectedLanguage ?: return false
|
||||||
|
|
||||||
|
val ranges: List<TextRange> = if (context is ZigStringLiteral) {
|
||||||
|
context.contentRanges
|
||||||
|
} else if (context is PsiComment) {
|
||||||
|
val comment = context as PsiComment
|
||||||
|
when (comment.tokenType) {
|
||||||
|
ZigTypes.LINE_COMMENT -> comment.text.getMultilineContent("//")
|
||||||
|
ZigTypes.DOC_COMMENT -> comment.text.getMultilineContent("///")
|
||||||
|
ZigTypes.CONTAINER_DOC_COMMENT -> comment.text.getMultilineContent("//!")
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
injectIntoStringMultiRanges(
|
||||||
|
registrar,
|
||||||
|
context,
|
||||||
|
ranges,
|
||||||
|
language,
|
||||||
|
injection.prefix,
|
||||||
|
injection.suffix
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun injectIntoStringMultiRanges(
|
||||||
|
registrar: MultiHostRegistrar,
|
||||||
|
context: PsiLanguageInjectionHost,
|
||||||
|
ranges: List<TextRange>,
|
||||||
|
language: Language,
|
||||||
|
prefix: String,
|
||||||
|
suffix: String
|
||||||
|
) {
|
||||||
|
if (ranges.isEmpty())
|
||||||
|
return
|
||||||
|
|
||||||
|
registrar.startInjecting(language)
|
||||||
|
|
||||||
|
if (ranges.size == 1) {
|
||||||
|
registrar.addPlace(prefix, suffix, context, ranges.first())
|
||||||
|
} else {
|
||||||
|
registrar.addPlace(prefix, null, context, ranges.first())
|
||||||
|
for (range in ranges.subList(1, ranges.size - 1)) {
|
||||||
|
registrar.addPlace(null, null, context, range)
|
||||||
|
}
|
||||||
|
registrar.addPlace(null, suffix, context, ranges.last())
|
||||||
|
}
|
||||||
|
registrar.doneInjecting()
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
package com.falsepattern.zigbrains.zig.injection
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.zig.ZigFileType
|
||||||
|
import com.falsepattern.zigbrains.zig.injection.InjectTriState.*
|
||||||
|
import com.falsepattern.zigbrains.zig.psi.ZigContainerMembers
|
||||||
|
import com.falsepattern.zigbrains.zig.psi.ZigPrimaryTypeExpr
|
||||||
|
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral
|
||||||
|
import com.falsepattern.zigbrains.zig.psi.getTextRangeBounds
|
||||||
|
import com.falsepattern.zigbrains.zig.psi.indentSize
|
||||||
|
import com.falsepattern.zigbrains.zig.util.escape
|
||||||
|
import com.falsepattern.zigbrains.zig.util.prefixWithTextBlockEscape
|
||||||
|
import com.intellij.openapi.util.NlsSafe
|
||||||
|
import com.intellij.openapi.util.TextRange
|
||||||
|
import com.intellij.psi.AbstractElementManipulator
|
||||||
|
import com.intellij.psi.PsiFileFactory
|
||||||
|
|
||||||
|
class ZigStringElementManipulator: AbstractElementManipulator<ZigStringLiteral>() {
|
||||||
|
override fun handleContentChange(
|
||||||
|
element: ZigStringLiteral,
|
||||||
|
range: TextRange,
|
||||||
|
newContent: String?
|
||||||
|
): ZigStringLiteral {
|
||||||
|
val originalContext = element.text!!
|
||||||
|
val isMultiline = element.isMultiline
|
||||||
|
val prefix = "const x = \n";
|
||||||
|
val suffix = "\n;"
|
||||||
|
val sbFactory: (Int) -> StringBuilder = {
|
||||||
|
val sb = StringBuilder(prefix.length + suffix.length + it)
|
||||||
|
sb.append(prefix)
|
||||||
|
sb
|
||||||
|
}
|
||||||
|
val replacement = if (isMultiline) {
|
||||||
|
replaceMultilineContent(element, newContent, range, originalContext, sbFactory)
|
||||||
|
} else {
|
||||||
|
replaceQuotedContent(element, range, newContent, originalContext, sbFactory)
|
||||||
|
}
|
||||||
|
replacement.append(suffix)
|
||||||
|
val fileFactory = PsiFileFactory.getInstance(element.project)
|
||||||
|
val dummy = fileFactory.createFileFromText(fileName, ZigFileType, replacement)
|
||||||
|
val stringLiteral = dummy
|
||||||
|
.firstChild
|
||||||
|
.let {it as ZigContainerMembers}
|
||||||
|
.containerDeclarationsList
|
||||||
|
.first()
|
||||||
|
.declList
|
||||||
|
.first()
|
||||||
|
.globalVarDecl!!
|
||||||
|
.expr
|
||||||
|
.let { it as ZigPrimaryTypeExpr }
|
||||||
|
.stringLiteral!!
|
||||||
|
return element.replace(stringLiteral) as ZigStringLiteral
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun getRangeInElement(element: ZigStringLiteral) =
|
||||||
|
getTextRangeBounds(element.contentRanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val fileName = "dummy." + ZigFileType.defaultExtension
|
||||||
|
|
||||||
|
private fun ZigStringElementManipulator.replaceQuotedContent(
|
||||||
|
element: ZigStringLiteral,
|
||||||
|
range: TextRange,
|
||||||
|
newContent: String?,
|
||||||
|
originalContext: String,
|
||||||
|
sbFactory: (Int) -> StringBuilder
|
||||||
|
): StringBuilder {
|
||||||
|
val elementRange = getRangeInElement(element)
|
||||||
|
val prefixStart = elementRange.startOffset
|
||||||
|
val prefixEnd = range.startOffset
|
||||||
|
val suffixStart = range.endOffset
|
||||||
|
val suffixEnd = elementRange.endOffset
|
||||||
|
val escaped = newContent?.escape()
|
||||||
|
val result = sbFactory(2 + (prefixEnd - prefixStart) + (escaped?.length ?: 0) + (suffixEnd - suffixStart))
|
||||||
|
result.append('"')
|
||||||
|
result.append(originalContext.subSequence(prefixStart, prefixEnd))
|
||||||
|
if (escaped != null) {
|
||||||
|
result.append(escaped)
|
||||||
|
}
|
||||||
|
result.append(originalContext.subSequence(suffixStart, suffixEnd))
|
||||||
|
result.append('"')
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class InjectTriState {
|
||||||
|
NotYet,
|
||||||
|
Incomplete,
|
||||||
|
Complete
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceMultilineContent(
|
||||||
|
element: ZigStringLiteral,
|
||||||
|
newContent: String?,
|
||||||
|
range: TextRange,
|
||||||
|
originalContext: String,
|
||||||
|
sbFactory: (Int) -> StringBuilder
|
||||||
|
): StringBuilder {
|
||||||
|
val contentRanges = element.contentRanges
|
||||||
|
val contentBuilder = StringBuilder(contentRanges.sumOf { it.length } + (newContent?.length ?: 0))
|
||||||
|
var injectState = NotYet
|
||||||
|
var i = 0
|
||||||
|
val contentIter = contentRanges.iterator()
|
||||||
|
while (injectState === NotYet && contentIter.hasNext()) {
|
||||||
|
val contentRange = contentIter.next()
|
||||||
|
val intersection = contentRange.intersection(range)
|
||||||
|
if (intersection == null) {
|
||||||
|
contentBuilder.append(originalContext, contentRange.startOffset, contentRange.endOffset)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
contentBuilder.append(originalContext, contentRange.startOffset, intersection.startOffset)
|
||||||
|
contentBuilder.append(newContent)
|
||||||
|
if (intersection.endOffset < contentRange.endOffset) {
|
||||||
|
contentBuilder.append(originalContext, intersection.endOffset, contentRange.endOffset)
|
||||||
|
injectState = Complete
|
||||||
|
} else {
|
||||||
|
injectState = Incomplete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (injectState === Incomplete && contentIter.hasNext()) {
|
||||||
|
val contentRange = contentIter.next()
|
||||||
|
val intersection = contentRange.intersection(range)
|
||||||
|
if (intersection == null) {
|
||||||
|
contentBuilder.append(originalContext, contentRange.startOffset, contentRange.endOffset)
|
||||||
|
} else if (intersection.endOffset < contentRange.endOffset) {
|
||||||
|
contentBuilder.append(originalContext, intersection.endOffset, contentRange.endOffset)
|
||||||
|
injectState = Complete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (contentIter.hasNext()) {
|
||||||
|
val contentRange = contentIter.next()
|
||||||
|
contentBuilder.append(originalContext, contentRange.startOffset, contentRange.endOffset)
|
||||||
|
}
|
||||||
|
return contentBuilder.prefixWithTextBlockEscape(
|
||||||
|
indent = element.indentSize,
|
||||||
|
marker = "\\\\",
|
||||||
|
indentFirst = false,
|
||||||
|
prefixFirst = true,
|
||||||
|
newLineAfter = false,
|
||||||
|
sbFactory
|
||||||
|
)
|
||||||
|
}
|
|
@ -48,6 +48,14 @@
|
||||||
<className>com.falsepattern.zigbrains.zig.intentions.MakeStringQuoted</className>
|
<className>com.falsepattern.zigbrains.zig.intentions.MakeStringQuoted</className>
|
||||||
<category>Zig intentions</category>
|
<category>Zig intentions</category>
|
||||||
</intentionAction>
|
</intentionAction>
|
||||||
|
|
||||||
|
<!-- Language injection -->
|
||||||
|
<lang.elementManipulator
|
||||||
|
forClass="com.falsepattern.zigbrains.zig.psi.ZigStringLiteral"
|
||||||
|
implementationClass="com.falsepattern.zigbrains.zig.injection.ZigStringElementManipulator"/>
|
||||||
|
<languageInjectionPerformer
|
||||||
|
language="Zig"
|
||||||
|
implementationClass="com.falsepattern.zigbrains.zig.injection.ZigLanguageInjectionPerformer"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
<!-- endregion Zig -->
|
<!-- endregion Zig -->
|
||||||
|
|
||||||
|
@ -87,4 +95,5 @@
|
||||||
implementationClass="com.falsepattern.zigbrains.zon.highlighting.ZonSyntaxHighlighterFactory"/>
|
implementationClass="com.falsepattern.zigbrains.zon.highlighting.ZonSyntaxHighlighterFactory"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
<!-- endregion Zon -->
|
<!-- endregion Zon -->
|
||||||
|
|
||||||
</idea-plugin>
|
</idea-plugin>
|
Loading…
Add table
Reference in a new issue