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>
|
||||
<category>Zig intentions</category>
|
||||
</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>
|
||||
<!-- endregion Zig -->
|
||||
|
||||
|
@ -87,4 +95,5 @@
|
|||
implementationClass="com.falsepattern.zigbrains.zon.highlighting.ZonSyntaxHighlighterFactory"/>
|
||||
</extensions>
|
||||
<!-- endregion Zon -->
|
||||
|
||||
</idea-plugin>
|
Loading…
Add table
Reference in a new issue