,
+ title: String,
+ titleAttributes: TextAttributesKey
+ ) {
+ if (!tags.any()) {
+ return
+ }
+
+ appendSection(title) {
+ tags.forEach {
+ val subjectName = it.getSubjectName() ?: return@forEach
+
+ append("")
+ when (val link = it.getChildrenOfType().firstOrNull()) {
+ null -> appendStyledSpan(
+ DocumentationSettings.isSemanticHighlightingOfLinksEnabled(),
+ titleAttributes,
+ subjectName
+ )
+
+ else -> appendHyperlink(link)
+ }
+
+ append("
")
+ val elementDescription = markdownToHtml(it)
+ if (elementDescription.isNotBlank()) {
+ append(" - $elementDescription")
+ }
+ }
+ }
+ }
+
+ private fun StringBuilder.appendTag(tag: RsDocTag?, title: String) {
+ if (tag != null) {
+ appendSection(title) {
+ append(markdownToHtml(tag))
+ }
+ }
+ }
+
+ private fun markdownToHtml(comment: RsDocTag, allowSingleParagraph: Boolean = false): String {
+ val markdownTree = MarkdownParser(GFMFlavourDescriptor()).buildMarkdownTreeFromString(comment.getContent())
+ val markdownNode = MarkdownNode(markdownTree, null, comment)
+
+ // Avoid wrapping the entire converted contents in a
tag if it's just a single paragraph
+ val maybeSingleParagraph = markdownNode.children.singleOrNull { it.type != MarkdownTokenTypes.EOL }
+
+ val firstParagraphOmitted = when {
+ maybeSingleParagraph != null && !allowSingleParagraph -> {
+ maybeSingleParagraph.children.joinToString("") { if (it.text == "\n") " " else it.toHtml() }
+ }
+
+ else -> markdownNode.toHtml()
+ }
+
+ val topMarginOmitted = when {
+ firstParagraphOmitted.startsWith("
") -> firstParagraphOmitted.replaceFirst(
+ "
",
+ "
"
+ )
+
+ else -> firstParagraphOmitted
+ }
+
+ return topMarginOmitted
+ }
+
+ class MarkdownNode(val node: ASTNode, val parent: MarkdownNode?, val comment: RsDocTag) {
+ val children: List = node.children.map { MarkdownNode(it, this, comment) }
+ val endOffset: Int get() = node.endOffset
+ val startOffset: Int get() = node.startOffset
+ val type: IElementType get() = node.type
+ val text: String get() = comment.getContent().substring(startOffset, endOffset)
+ fun child(type: IElementType): MarkdownNode? = children.firstOrNull { it.type == type }
+ }
+
+ private fun MarkdownNode.visit(action: (MarkdownNode, () -> Unit) -> Unit) {
+ action(this) {
+ for (child in children) {
+ child.visit(action)
+ }
+ }
+ }
+
+ private fun MarkdownNode.toHtml(): String {
+ if (node.type == MarkdownTokenTypes.WHITE_SPACE) {
+ return text // do not trim trailing whitespace
+ }
+
+ var currentCodeFenceLang = "RuneScript"
+
+ val sb = StringBuilder()
+ visit { node, processChildren ->
+ fun wrapChildren(tag: String, newline: Boolean = false) {
+ sb.append("<$tag>")
+ processChildren()
+ sb.append("$tag>")
+ if (newline) sb.appendLine()
+ }
+
+ val nodeType = node.type
+ val nodeText = node.text
+ when (nodeType) {
+ MarkdownElementTypes.UNORDERED_LIST -> wrapChildren("ul", newline = true)
+ MarkdownElementTypes.ORDERED_LIST -> wrapChildren("ol", newline = true)
+ MarkdownElementTypes.LIST_ITEM -> wrapChildren("li")
+ MarkdownElementTypes.EMPH -> wrapChildren("em")
+ MarkdownElementTypes.STRONG -> wrapChildren("strong")
+ GFMElementTypes.STRIKETHROUGH -> wrapChildren("del")
+ MarkdownElementTypes.ATX_1 -> wrapChildren("h1")
+ MarkdownElementTypes.ATX_2 -> wrapChildren("h2")
+ MarkdownElementTypes.ATX_3 -> wrapChildren("h3")
+ MarkdownElementTypes.ATX_4 -> wrapChildren("h4")
+ MarkdownElementTypes.ATX_5 -> wrapChildren("h5")
+ MarkdownElementTypes.ATX_6 -> wrapChildren("h6")
+ MarkdownElementTypes.BLOCK_QUOTE -> wrapChildren("blockquote")
+ MarkdownElementTypes.PARAGRAPH -> {
+ sb.trimEnd()
+ wrapChildren("p", newline = true)
+ }
+
+ MarkdownElementTypes.CODE_SPAN -> {
+ val startDelimiter = node.child(MarkdownTokenTypes.BACKTICK)?.text
+ if (startDelimiter != null) {
+ val text = node.text.substring(startDelimiter.length).removeSuffix(startDelimiter)
+ sb.append("")
+ sb.appendHighlightedByLexerAndEncodedAsHtmlCodeSnippet(
+ DocumentationSettings.getInlineCodeHighlightingMode(),
+ comment.project,
+ RuneScript,
+ text
+ )
+ sb.append("
")
+ }
+ }
+
+ MarkdownElementTypes.CODE_BLOCK,
+ MarkdownElementTypes.CODE_FENCE -> {
+ sb.trimEnd()
+ sb.append("")
+ processChildren()
+ sb.append("
")
+ }
+
+ MarkdownTokenTypes.FENCE_LANG -> {
+ currentCodeFenceLang = nodeText
+ }
+
+ MarkdownElementTypes.SHORT_REFERENCE_LINK,
+ MarkdownElementTypes.FULL_REFERENCE_LINK -> {
+ val linkLabelNode = node.child(MarkdownElementTypes.LINK_LABEL)
+ val linkLabelContent = linkLabelNode?.children
+ ?.dropWhile { it.type == MarkdownTokenTypes.LBRACKET }
+ ?.dropLastWhile { it.type == MarkdownTokenTypes.RBRACKET }
+ if (linkLabelContent != null) {
+ val label = linkLabelContent.joinToString(separator = "") { it.text }
+ val linkText = node.child(MarkdownElementTypes.LINK_TEXT)?.toHtml() ?: label
+ val filteredLabel = label.split("/").last()
+ if (DumbService.isDumb(comment.project)) {
+ sb.append(filteredLabel)
+ } else {
+ comment.findDescendantOfType { it.text == linkText }
+ ?.references
+ ?.firstOrNull { it is RsDocReference }
+ ?.resolve()
+ ?.let { resolvedLinkElement ->
+ val link = if (resolvedLinkElement is RsLocalVariableExpression) {
+ "parameter/${label}/${comment.parentOfType()!!.startOffsetInParent}"
+ } else {
+ label
+ }
+ DocumentationManagerUtil.createHyperlink(
+ sb,
+ link,
+ highlightQualifiedName(
+ linkText,
+ getTargetLinkElementAttributes(resolvedLinkElement)
+ ),
+ false,
+ true
+ )
+ }
+ ?: sb.appendStyledSpan(
+ true,
+ CodeInsightColors.RUNTIME_ERROR,
+ filteredLabel
+ )
+ }
+ } else {
+ sb.append(node.text)
+ }
+ }
+
+ MarkdownElementTypes.INLINE_LINK -> {
+ val label = node.child(MarkdownElementTypes.LINK_TEXT)?.toHtml()
+ val destination = node.child(MarkdownElementTypes.LINK_DESTINATION)?.text
+ if (label != null && destination != null) {
+ sb.append("$label")
+ } else {
+ sb.append(node.text)
+ }
+ }
+
+ MarkdownTokenTypes.TEXT,
+ MarkdownTokenTypes.WHITE_SPACE,
+ MarkdownTokenTypes.COLON,
+ MarkdownTokenTypes.SINGLE_QUOTE,
+ MarkdownTokenTypes.DOUBLE_QUOTE,
+ MarkdownTokenTypes.LPAREN,
+ MarkdownTokenTypes.RPAREN,
+ MarkdownTokenTypes.LBRACKET,
+ MarkdownTokenTypes.RBRACKET,
+ MarkdownTokenTypes.EXCLAMATION_MARK,
+ GFMTokenTypes.CHECK_BOX,
+ GFMTokenTypes.GFM_AUTOLINK -> {
+ sb.append(nodeText)
+ }
+
+ MarkdownTokenTypes.CODE_LINE,
+ MarkdownTokenTypes.CODE_FENCE_CONTENT -> {
+ sb.appendHighlightedByLexerAndEncodedAsHtmlCodeSnippet(
+ when (DocumentationSettings.isHighlightingOfCodeBlocksEnabled()) {
+ true -> InlineCodeHighlightingMode.SEMANTIC_HIGHLIGHTING
+ false -> InlineCodeHighlightingMode.NO_HIGHLIGHTING
+ },
+ comment.project,
+ guessLanguage(currentCodeFenceLang) ?: RuneScript,
+ nodeText
+ )
+ }
+
+ MarkdownTokenTypes.EOL -> {
+ val parentType = node.parent?.type
+ if (parentType == MarkdownElementTypes.CODE_BLOCK || parentType == MarkdownElementTypes.CODE_FENCE) {
+ sb.append("\n")
+ } else {
+ sb.append(" ")
+ }
+ }
+
+ MarkdownTokenTypes.GT -> sb.append(">")
+ MarkdownTokenTypes.LT -> sb.append("<")
+
+ MarkdownElementTypes.LINK_TEXT -> {
+ val childrenWithoutBrackets = node.children.drop(1).dropLast(1)
+ for (child in childrenWithoutBrackets) {
+ sb.append(child.toHtml())
+ }
+ }
+
+ MarkdownTokenTypes.EMPH -> {
+ val parentNodeType = node.parent?.type
+ if (parentNodeType != MarkdownElementTypes.EMPH && parentNodeType != MarkdownElementTypes.STRONG) {
+ sb.append(node.text)
+ }
+ }
+
+ GFMTokenTypes.TILDE -> {
+ if (node.parent?.type != GFMElementTypes.STRIKETHROUGH) {
+ sb.append(node.text)
+ }
+ }
+
+ GFMElementTypes.TABLE -> {
+ val alignment: List = getTableAlignment(node)
+ var addedBody = false
+ sb.append("")
+
+ for (child in node.children) {
+ if (child.type == GFMElementTypes.HEADER) {
+ sb.append("")
+ processTableRow(sb, child, "th", alignment)
+ sb.append("")
+ } else if (child.type == GFMElementTypes.ROW) {
+ if (!addedBody) {
+ sb.append("")
+ addedBody = true
+ }
+
+ processTableRow(sb, child, "td", alignment)
+ }
+ }
+
+ if (addedBody) {
+ sb.append("")
+ }
+ sb.append("
")
+ }
+
+ else -> {
+ processChildren()
+ }
+ }
+ }
+ return sb.toString().trimEnd()
+ }
+
+ private fun StringBuilder.trimEnd() {
+ while (isNotEmpty() && this[length - 1] == ' ') {
+ deleteCharAt(length - 1)
+ }
+ }
+
+ private fun String.htmlEscape(): String = replace("&", "&").replace("<", "<").replace(">", ">")
+
+ private fun processTableRow(sb: StringBuilder, node: MarkdownNode, cellTag: String, alignment: List) {
+ sb.append("")
+ for ((i, child) in node.children.filter { it.type == GFMTokenTypes.CELL }.withIndex()) {
+ val alignValue = alignment.getOrElse(i) { "" }
+ val alignTag = if (alignValue.isEmpty()) "" else " align=\"$alignValue\""
+ sb.append("<$cellTag$alignTag>")
+ sb.append(child.toHtml())
+ sb.append("$cellTag>")
+ }
+ sb.append("
")
+ }
+
+ private fun getTableAlignment(node: MarkdownNode): List {
+ val separatorRow = node.child(GFMTokenTypes.TABLE_SEPARATOR)
+ ?: return emptyList()
+
+ return separatorRow.text.split('|').filterNot { it.isBlank() }.map {
+ val trimmed = it.trim()
+ val left = trimmed.startsWith(':')
+ val right = trimmed.endsWith(':')
+ if (left && right) "center"
+ else if (right) "right"
+ else if (left) "left"
+ else ""
+ }
+ }
+
+ private fun StringBuilder.appendStyledSpan(
+ doHighlighting: Boolean,
+ attributesKey: TextAttributesKey,
+ value: String?
+ ): StringBuilder {
+ if (doHighlighting) {
+ HtmlSyntaxInfoUtil.appendStyledSpan(
+ this,
+ attributesKey,
+ value,
+ DocumentationSettings.getHighlightingSaturation(true)
+ )
+ } else {
+ append(value)
+ }
+ return this
+ }
+
+ private fun StringBuilder.appendStyledSpan(
+ doHighlighting: Boolean,
+ attributes: TextAttributes,
+ value: String?
+ ): StringBuilder {
+ if (doHighlighting) {
+ HtmlSyntaxInfoUtil.appendStyledSpan(
+ this,
+ attributes,
+ value,
+ DocumentationSettings.getHighlightingSaturation(true)
+ )
+ } else {
+ append(value)
+ }
+ return this
+ }
+
+ private fun StringBuilder.appendHighlightedByLexerAndEncodedAsHtmlCodeSnippet(
+ highlightingMode: InlineCodeHighlightingMode,
+ project: Project,
+ language: Language,
+ codeSnippet: String
+ ): StringBuilder {
+ val codeSnippetBuilder = StringBuilder()
+ if (highlightingMode == InlineCodeHighlightingMode.SEMANTIC_HIGHLIGHTING) { // highlight code by lexer
+ HtmlSyntaxInfoUtil.appendHighlightedByLexerAndEncodedAsHtmlCodeSnippet(
+ codeSnippetBuilder,
+ project,
+ language,
+ codeSnippet,
+ false,
+ DocumentationSettings.getHighlightingSaturation(true)
+ )
+ } else {
+ codeSnippetBuilder.append(StringUtil.escapeXmlEntities(codeSnippet))
+ }
+ if (highlightingMode != InlineCodeHighlightingMode.NO_HIGHLIGHTING) {
+ // set code text color as editor default code color instead of doc component text color
+ val codeAttributes =
+ EditorColorsManager.getInstance().globalScheme.getAttributes(HighlighterColors.TEXT).clone()
+ codeAttributes.backgroundColor = null
+ appendStyledSpan(true, codeAttributes, codeSnippetBuilder.toString())
+ } else {
+ append(codeSnippetBuilder.toString())
+ }
+ return this
+ }
+
+ /**
+ * If highlighted links has the same color as highlighted inline code blocks they will be indistinguishable.
+ * In this case we should change link color to standard hyperlink color which we believe is apriori different.
+ */
+ private fun tuneAttributesForLink(attributes: TextAttributes): TextAttributes {
+ val globalScheme = EditorColorsManager.getInstance().globalScheme
+ if (attributes.foregroundColor == globalScheme.getAttributes(HighlighterColors.TEXT).foregroundColor
+ || attributes.foregroundColor == globalScheme.getAttributes(DefaultLanguageHighlighterColors.IDENTIFIER).foregroundColor
+ ) {
+ val tuned = attributes.clone()
+ if (ApplicationManager.getApplication().isUnitTestMode) {
+ tuned.foregroundColor =
+ globalScheme.getAttributes(CodeInsightColors.HYPERLINK_ATTRIBUTES).foregroundColor
+ } else {
+ tuned.foregroundColor = globalScheme.getColor(DefaultLanguageHighlighterColors.DOC_COMMENT_LINK)
+ }
+ return tuned
+ }
+ return attributes
+ }
+
+ private fun guessLanguage(name: String): Language? {
+ val lower = StringUtil.toLowerCase(name)
+ return Language.findLanguageByID(lower)
+ ?: Language.getRegisteredLanguages().firstOrNull { StringUtil.toLowerCase(it.id) == lower }
+ }
+}
+
+private inline fun StringBuilder.wrap(prefix: String, postfix: String, crossinline body: () -> Unit) {
+ append(prefix)
+ body()
+ append(postfix)
+}
+
+private inline fun StringBuilder.wrapTag(tag: String, crossinline body: () -> Unit) {
+ wrap("<$tag>", "$tag>", body)
+}
+
+private inline fun StringBuilder.wrapTag(tag: String, params: String, crossinline body: () -> Unit) {
+ wrap("<$tag $params>", "$tag>", body)
+}
+
+private fun textAttributesKeyForKtElement(element: PsiElement): HighlightInfoType? {
+ if (element is RsSymSymbol) {
+ return RsHighlightInfoTypeSemanticNames.CONFIG_REFERENCE
+ }
+ if (element is RsScript) {
+ return RsHighlightInfoTypeSemanticNames.SCRIPT_DECLARATION
+ }
+ if (element is RsLocalVariableExpression) {
+ return RsHighlightInfoTypeSemanticNames.LOCAL_VARIABLE
+ }
+ error("Missing element attribute: $element")
+}
+
+object RsHighlightInfoTypeSemanticNames {
+ val CONFIG_REFERENCE: HighlightInfoType = createSymbolTypeInfo(RsSyntaxHighlighterColors.CONFIG_REFERENCE)
+ val SCRIPT_DECLARATION: HighlightInfoType = createSymbolTypeInfo(RsSyntaxHighlighterColors.SCRIPT_DECLARATION)
+ val LOCAL_VARIABLE: HighlightInfoType = createSymbolTypeInfo(RsSyntaxHighlighterColors.LOCAL_VARIABLE)
+
+ private fun createSymbolTypeInfo(attributesKey: TextAttributesKey): HighlightInfoType {
+ return HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, attributesKey, false)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocumentationGenerator.kt b/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocumentationGenerator.kt
deleted file mode 100644
index d7a36df..0000000
--- a/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocumentationGenerator.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package io.runescript.plugin.ide.doc
-
-import com.intellij.lang.documentation.DocumentationSettings
-import com.intellij.openapi.editor.colors.TextAttributesKey
-import com.intellij.openapi.editor.richcopy.HtmlSyntaxInfoUtil
-import io.runescript.plugin.ide.highlight.RsSyntaxHighlighterColors
-
-class RsDocumentationGenerator {
-
- private val builder = StringBuilder()
-
- fun appendLParen() {
- appendHighlighted("(", RsSyntaxHighlighterColors.PARENTHESIS)
- }
-
- fun appendRParen() {
- appendHighlighted(")", RsSyntaxHighlighterColors.PARENTHESIS)
- }
-
- fun appendComma(text: String = ",") {
- appendHighlighted(text, RsSyntaxHighlighterColors.COMMA)
- }
-
- fun appendKeyword(keyword: String) {
- appendHighlighted(keyword, RsSyntaxHighlighterColors.KEYWORD)
- }
-
- fun appendHighlighted(keyword: String, attributes: TextAttributesKey) {
- @Suppress("UnstableApiUsage")
- HtmlSyntaxInfoUtil.appendStyledSpan(
- builder,
- attributes,
- keyword,
- DocumentationSettings.getHighlightingSaturation(false)
- )
- }
-
- fun appendColon(text: String = ":") {
- appendHighlighted(text, RsSyntaxHighlighterColors.COLON)
- }
-
- fun build(): String {
- return builder.toString()
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocumentationProvider.kt b/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocumentationProvider.kt
index 4720cf9..c323d81 100644
--- a/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocumentationProvider.kt
+++ b/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocumentationProvider.kt
@@ -1,9 +1,22 @@
package io.runescript.plugin.ide.doc
+import com.intellij.codeInsight.javadoc.JavaDocExternalFilter
import com.intellij.lang.documentation.AbstractDocumentationProvider
-import com.intellij.psi.PsiElement
+import com.intellij.lang.documentation.DocumentationMarkup.DEFINITION_END
+import com.intellij.lang.documentation.DocumentationMarkup.DEFINITION_START
+import com.intellij.lang.documentation.DocumentationSettings
+import com.intellij.openapi.editor.colors.TextAttributesKey
+import com.intellij.openapi.editor.richcopy.HtmlSyntaxInfoUtil
+import com.intellij.psi.*
+import com.intellij.psi.tree.TokenSet
+import com.intellij.psi.util.parentOfType
+import com.intellij.psi.util.siblings
+import com.intellij.psi.util.skipTokens
+import io.runescript.plugin.ide.doc.RsDocRenderer.renderRsDoc
import io.runescript.plugin.ide.highlight.RsSyntaxHighlighterColors
+import io.runescript.plugin.lang.doc.psi.api.RsDoc
import io.runescript.plugin.lang.psi.*
+import java.util.function.Consumer
class RsDocumentationProvider : AbstractDocumentationProvider() {
@@ -15,13 +28,18 @@ class RsDocumentationProvider : AbstractDocumentationProvider() {
return getDescription(element)
}
+ override fun generateHoverDoc(element: PsiElement, originalElement: PsiElement?): String? {
+ return getDescription(element)
+ }
+
private fun getDescription(element: PsiElement?): String? {
- if (element == null) {
- return null
- }
when (element) {
is RsLocalVariableExpression -> {
- val builder = createDocumentationGenerator()
+ val builder = StringBuilder()
+ val parameterDoc = findParamDoc(element)
+ if (!parameterDoc.isNullOrBlank()) {
+ builder.append(DEFINITION_START)
+ }
val type = when (val parent = element.parent) {
is RsParameter -> (parent.typeName ?: parent.arrayTypeLiteral)!!.text
is RsLocalVariableDeclarationStatement -> parent.defineType.text.substring(4)
@@ -34,41 +52,149 @@ class RsDocumentationProvider : AbstractDocumentationProvider() {
builder.appendHighlighted(element.text, RsSyntaxHighlighterColors.LOCAL_VARIABLE)
builder.appendColon(": ")
builder.appendKeyword(type)
- return builder.build()
+ if (!parameterDoc.isNullOrBlank()) {
+ builder.append(DEFINITION_END)
+ builder.append(parameterDoc)
+ }
+ return builder.toString()
}
+
is RsScript -> {
- val builder = createDocumentationGenerator()
- builder.appendHighlighted(element.qualifiedName, RsSyntaxHighlighterColors.SCRIPT_DECLARATION)
- builder.appendLParen()
- var appendComma = false
- element.parameterList?.parameterList?.forEach {
- if (appendComma) {
- builder.appendComma(", ")
- } else {
- appendComma = true
- }
- builder.appendKeyword((it.typeName ?: it.arrayTypeLiteral)!!.text)
- builder.appendHighlighted(" ${it.localVariableExpression.text}", RsSyntaxHighlighterColors.LOCAL_VARIABLE)
- }
- builder.appendRParen()
- appendComma = false
- builder.appendLParen()
- element.returnList?.typeNameList?.forEach {
- if (appendComma) {
- builder.appendComma(", ")
- } else {
- appendComma = true
- }
- builder.appendKeyword(it.text)
- }
- builder.appendRParen()
- return builder.build()
+ val builder = StringBuilder()
+
+ builder.append(DEFINITION_START)
+ builder.appendScriptDefinition(element)
+ builder.append(DEFINITION_END)
+
+ element.findDoc()?.let { builder.renderRsDoc(it.getDefaultSection()) }
+
+ return builder.toString()
}
else -> return null
}
}
- private fun createDocumentationGenerator() = RsDocumentationGenerator()
+ private fun findParamDoc(variable: RsLocalVariableExpression): String? {
+ if (variable.parent !is RsParameter) {
+ return null
+ }
+ val script = variable.parentOfType() ?: return null
+ val doc = script.findDoc() ?: return null
+ val sections = doc.getAllSections()
+ for (section in sections) {
+ val paramTags = section.findTagsByName("param")
+ if (paramTags.isEmpty()) {
+ continue
+ }
+ val paramTag = paramTags.firstOrNull { it.getSubjectName() == variable.nameLiteral.text }
+ if (paramTag != null) {
+ return buildString { renderRsDoc(paramTag) }
+ }
+ }
+ return null
+ }
+
+ private fun StringBuilder.appendScriptDefinition(script: RsScript) {
+ appendHighlighted(script.qualifiedName, RsSyntaxHighlighterColors.SCRIPT_DECLARATION)
+ appendLParen()
+ var appendComma = false
+ script.parameterList?.parameterList?.forEach {
+ if (appendComma) {
+ appendComma(", ")
+ } else {
+ appendComma = true
+ }
+ appendKeyword((it.typeName ?: it.arrayTypeLiteral)!!.text)
+ appendHighlighted(
+ " ${it.localVariableExpression.text}",
+ RsSyntaxHighlighterColors.LOCAL_VARIABLE
+ )
+ }
+ appendRParen()
+ appendComma = false
+ appendLParen()
+ script.returnList?.typeNameList?.forEach {
+ if (appendComma) {
+ appendComma(", ")
+ } else {
+ appendComma = true
+ }
+ appendKeyword(it.text)
+ }
+ appendRParen()
+ }
+
+ override fun collectDocComments(file: PsiFile, sink: Consumer) {
+ if (file !is RsFile) return
+ SyntaxTraverser.psiTraverser(file)
+ .filterIsInstance()
+ .forEach { sink.accept(it) }
+ }
+
+ override fun generateRenderedDoc(comment: PsiDocCommentBase): String? {
+ val docComment = comment as? RsDoc ?: return null
+ val result = StringBuilder().also {
+ it.renderRsDoc(docComment.getDefaultSection(), docComment.getAllSections())
+ }
+ return JavaDocExternalFilter.filterInternalDocInfo(result.toString())
+ }
+
+ override fun getDocumentationElementForLink(
+ psiManager: PsiManager,
+ link: String,
+ context: PsiElement
+ ): PsiElement? {
+ val parts = link.split("/")
+ if (parts.size < 2) {
+ return null
+ }
+ val typeName = parts[0]
+ val elementName = parts[1]
+ if (typeName == "parameter") {
+ // TODO(Walied): Find a better way to do this
+ val doc = context.containingFile.findElementAt(parts[2].toInt())?.parentOfType() ?: return null
+ val owner = doc.owner
+ return RsDocReference.resolve(context.project, owner, null, elementName).singleOrNull()
+ } else {
+ return RsDocReference.resolve(context.project, null, typeName, elementName).singleOrNull()
+ }
+ }
+}
+
+private fun StringBuilder.appendLParen() {
+ appendHighlighted("(", RsSyntaxHighlighterColors.PARENTHESIS)
+}
+
+private fun StringBuilder.appendRParen() {
+ appendHighlighted(")", RsSyntaxHighlighterColors.PARENTHESIS)
+}
+
+private fun StringBuilder.appendComma(text: String = ",") {
+ appendHighlighted(text, RsSyntaxHighlighterColors.COMMA)
+}
+
+private fun StringBuilder.appendKeyword(keyword: String) {
+ appendHighlighted(keyword, RsSyntaxHighlighterColors.KEYWORD)
+}
+
+private fun StringBuilder.appendHighlighted(keyword: String, attributes: TextAttributesKey) {
+ @Suppress("UnstableApiUsage")
+ HtmlSyntaxInfoUtil.appendStyledSpan(
+ this,
+ attributes,
+ keyword,
+ DocumentationSettings.getHighlightingSaturation(false)
+ )
+}
+
+private fun StringBuilder.appendColon(text: String = ":") {
+ appendHighlighted(text, RsSyntaxHighlighterColors.COLON)
+}
+
+fun RsScript.findDoc(): RsDoc? {
+ return siblings(false, withSelf = false)
+ .skipTokens(TokenSet.WHITE_SPACE)
+ .firstOrNull() as? RsDoc
}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/ide/formatter/blocks/RsBlockFactory.kt b/src/main/kotlin/io/runescript/plugin/ide/formatter/blocks/RsBlockFactory.kt
index e6d365b..0c66fd2 100644
--- a/src/main/kotlin/io/runescript/plugin/ide/formatter/blocks/RsBlockFactory.kt
+++ b/src/main/kotlin/io/runescript/plugin/ide/formatter/blocks/RsBlockFactory.kt
@@ -4,11 +4,16 @@ import com.intellij.formatting.Indent
import com.intellij.lang.ASTNode
import io.runescript.plugin.ide.formatter.RsFormatterContext
import io.runescript.plugin.ide.formatter.blocks.impl.*
+import io.runescript.plugin.lang.doc.lexer.RsDocTokens
import io.runescript.plugin.lang.psi.RsElementTypes
object RsBlockFactory {
fun create(context: RsFormatterContext, parent: RsBlock, node: ASTNode): RsBlock {
- val childIndent = parent.childIndent ?: Indent.getNoneIndent()
+ var childIndent = parent.childIndent ?: Indent.getNoneIndent()
+ if (node.elementType == RsDocTokens.TAG_NAME) childIndent = Indent.getNoneIndent()
+ if (node.elementType == RsDocTokens.LEADING_ASTERISK) childIndent = Indent.getSpaceIndent(1)
+ if (node.elementType == RsDocTokens.START) childIndent = Indent.getSpaceIndent(0)
+ if (node.elementType == RsDocTokens.END) childIndent = Indent.getSpaceIndent(1)
return when (node.elementType) {
RsElementTypes.SCRIPT -> RsScriptBlock(context, node)
RsElementTypes.BLOCK_STATEMENT -> RsBracedBlock(context, node)
diff --git a/src/main/kotlin/io/runescript/plugin/ide/formatter/lineIndent/RsLineIndentProvider.kt b/src/main/kotlin/io/runescript/plugin/ide/formatter/lineIndent/RsLineIndentProvider.kt
index 84eb8ea..df3dd0b 100644
--- a/src/main/kotlin/io/runescript/plugin/ide/formatter/lineIndent/RsLineIndentProvider.kt
+++ b/src/main/kotlin/io/runescript/plugin/ide/formatter/lineIndent/RsLineIndentProvider.kt
@@ -11,6 +11,7 @@ import com.intellij.psi.impl.source.codeStyle.lineIndent.JavaLikeLangLineIndentP
import com.intellij.psi.impl.source.codeStyle.lineIndent.JavaLikeLangLineIndentProvider.JavaLikeElement.*
import com.intellij.psi.tree.IElementType
import io.runescript.plugin.lang.RuneScript
+import io.runescript.plugin.lang.doc.lexer.RsDocTokens
import io.runescript.plugin.lang.psi.RsElementTypes
import io.runescript.plugin.lang.psi.RsTokenTypes
@@ -50,6 +51,8 @@ class RsLineIndentProvider : JavaLikeLangLineIndentProvider() {
RsElementTypes.IF to IfKeyword,
RsTokenTypes.BLOCK_COMMENT to BlockComment,
RsTokenTypes.LINE_COMMENT to LineComment,
+ RsDocTokens.START to DocBlockStart,
+ RsDocTokens.END to DocBlockEnd,
RsElementTypes.COMMA to Comma
)
}
diff --git a/src/main/kotlin/io/runescript/plugin/ide/highlight/RsColorSettingsPage.kt b/src/main/kotlin/io/runescript/plugin/ide/highlight/RsColorSettingsPage.kt
index 8c256ba..e57d4cb 100644
--- a/src/main/kotlin/io/runescript/plugin/ide/highlight/RsColorSettingsPage.kt
+++ b/src/main/kotlin/io/runescript/plugin/ide/highlight/RsColorSettingsPage.kt
@@ -77,6 +77,9 @@ class RsColorSettingsPage : ColorSettingsPage {
AttributesDescriptor(RsBundle.message("runescript.color.settings.description.string"), RsSyntaxHighlighterColors.STRING),
AttributesDescriptor(RsBundle.message("runescript.color.settings.description.string_tag"), RsSyntaxHighlighterColors.STRING_TAG),
AttributesDescriptor(RsBundle.message("runescript.color.settings.description.block_comment"), RsSyntaxHighlighterColors.BLOCK_COMMENT),
+ AttributesDescriptor(RsBundle.message("runescript.color.settings.description.doc_comment"), RsSyntaxHighlighterColors.DOC_COMMENT),
+ AttributesDescriptor(RsBundle.message("runescript.color.settings.description.doc_tag"), RsSyntaxHighlighterColors.DOC_COMMENT_TAG),
+ AttributesDescriptor(RsBundle.message("runescript.color.settings.description.doc_link"), RsSyntaxHighlighterColors.DOC_COMMENT_LINK),
AttributesDescriptor(RsBundle.message("runescript.color.settings.description.line_comment"), RsSyntaxHighlighterColors.LINE_COMMENT),
AttributesDescriptor(RsBundle.message("runescript.color.settings.description.operation_sign"), RsSyntaxHighlighterColors.OPERATION_SIGN),
AttributesDescriptor(RsBundle.message("runescript.color.settings.description.braces"), RsSyntaxHighlighterColors.BRACES),
@@ -104,6 +107,9 @@ class RsColorSettingsPage : ColorSettingsPage {
"RUNESCRIPT_STRING_TAG" to RsSyntaxHighlighterColors.STRING_TAG,
"RUNESCRIPT_BLOCK_COMMENT" to RsSyntaxHighlighterColors.BLOCK_COMMENT,
"RUNESCRIPT_LINE_COMMENT" to RsSyntaxHighlighterColors.LINE_COMMENT,
+ "RUNESCRIPT_DOC_COMMENT" to RsSyntaxHighlighterColors.DOC_COMMENT,
+ "RUNESCRIPT_DOC_TAG_NAME" to RsSyntaxHighlighterColors.DOC_COMMENT_TAG,
+ "RUNESCRIPT_DOC_COMMENT_LINK" to RsSyntaxHighlighterColors.DOC_COMMENT_LINK,
"RUNESCRIPT_OPERATION_SIGN" to RsSyntaxHighlighterColors.OPERATION_SIGN,
"RUNESCRIPT_BRACES" to RsSyntaxHighlighterColors.BRACES,
"RUNESCRIPT_SEMICOLON" to RsSyntaxHighlighterColors.SEMICOLON,
diff --git a/src/main/kotlin/io/runescript/plugin/ide/highlight/RsHighlightingLexer.kt b/src/main/kotlin/io/runescript/plugin/ide/highlight/RsHighlightingLexer.kt
new file mode 100644
index 0000000..8104ebc
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/ide/highlight/RsHighlightingLexer.kt
@@ -0,0 +1,14 @@
+package io.runescript.plugin.ide.highlight
+
+import com.intellij.lexer.LayeredLexer
+import com.intellij.psi.tree.IElementType
+import io.runescript.plugin.lang.doc.lexer.RsDocLexer
+import io.runescript.plugin.lang.lexer.RsLexerAdapter
+import io.runescript.plugin.lang.lexer.RsLexerInfo
+import io.runescript.plugin.lang.psi.RsTokenTypes
+
+class RsHighlightingLexer(lexerInfo: RsLexerInfo) : LayeredLexer(RsLexerAdapter(lexerInfo)) {
+ init {
+ registerSelfStoppingLayer(RsDocLexer(), arrayOf(RsTokenTypes.DOC_COMMENT), IElementType.EMPTY_ARRAY)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/ide/highlight/RsSyntaxHighlighter.kt b/src/main/kotlin/io/runescript/plugin/ide/highlight/RsSyntaxHighlighter.kt
index 89e9c18..91ba14c 100644
--- a/src/main/kotlin/io/runescript/plugin/ide/highlight/RsSyntaxHighlighter.kt
+++ b/src/main/kotlin/io/runescript/plugin/ide/highlight/RsSyntaxHighlighter.kt
@@ -5,10 +5,11 @@ import com.intellij.openapi.editor.colors.TextAttributesKey
import com.intellij.openapi.fileTypes.SyntaxHighlighterBase
import com.intellij.psi.tree.IElementType
import com.intellij.psi.tree.TokenSet
-import io.runescript.plugin.lang.lexer.RsLexerAdapter
+import io.runescript.plugin.lang.doc.lexer.RsDocTokens
import io.runescript.plugin.lang.lexer.RsLexerInfo
import io.runescript.plugin.lang.psi.RsElementTypes.*
import io.runescript.plugin.lang.psi.RsTokenTypes.BLOCK_COMMENT
+import io.runescript.plugin.lang.psi.RsTokenTypes.DOC_COMMENT
import io.runescript.plugin.lang.psi.RsTokenTypes.LINE_COMMENT
import io.runescript.plugin.lang.psi.RsTokenTypesSets.BRACES
import io.runescript.plugin.lang.psi.RsTokenTypesSets.BRACKETS
@@ -19,15 +20,16 @@ import io.runescript.plugin.lang.psi.RsTokenTypesSets.PARENS
class RsSyntaxHighlighter(private val lexerInfo: RsLexerInfo) : SyntaxHighlighterBase() {
override fun getHighlightingLexer(): Lexer {
- return RsLexerAdapter(lexerInfo)
+ return RsHighlightingLexer(lexerInfo)
}
override fun getTokenHighlights(tokenType: IElementType?): Array {
- return pack(attributes[tokenType])
+ return pack(attributes[tokenType], attributes2[tokenType])
}
companion object {
private val attributes = mutableMapOf()
+ private val attributes2 = mutableMapOf()
init {
attributes[IDENTIFIER] = RsSyntaxHighlighterColors.IDENTIFIER
@@ -46,6 +48,11 @@ class RsSyntaxHighlighter(private val lexerInfo: RsLexerInfo) : SyntaxHighlighte
attributes[COMMA] = RsSyntaxHighlighterColors.COMMA
fillMap(attributes, PARENS, RsSyntaxHighlighterColors.PARENTHESIS)
fillMap(attributes, BRACKETS, RsSyntaxHighlighterColors.BRACKETS)
+
+ fillMap(attributes, RsDocTokens.RSDOC_HIGHLIGHT_TOKENS, RsSyntaxHighlighterColors.DOC_COMMENT)
+ attributes[RsDocTokens.TAG_NAME] = RsSyntaxHighlighterColors.DOC_COMMENT
+ attributes2[RsDocTokens.TAG_NAME] = RsSyntaxHighlighterColors.DOC_COMMENT_TAG
+ attributes2[RsDocTokens.MARKDOWN_LINK] = RsSyntaxHighlighterColors.DOC_COMMENT_LINK
}
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/ide/highlight/RsSyntaxHighlighterColors.kt b/src/main/kotlin/io/runescript/plugin/ide/highlight/RsSyntaxHighlighterColors.kt
index 9a37082..9e14126 100644
--- a/src/main/kotlin/io/runescript/plugin/ide/highlight/RsSyntaxHighlighterColors.kt
+++ b/src/main/kotlin/io/runescript/plugin/ide/highlight/RsSyntaxHighlighterColors.kt
@@ -22,6 +22,11 @@ object RsSyntaxHighlighterColors {
private const val RUNESCRIPT_PARENTHESIS = "RUNESCRIPT_PARENTHESIS"
private const val RUNESCRIPT_BRACKETS = "RUNESCRIPT_BRACKETS"
+ // Documentation
+ private const val RUNESCRIPT_DOC_COMMENT = "RUNESCRIPT_DOC_COMMENT"
+ private const val RUNESCRIPT_DOC_COMMENT_TAG = "RUNESCRIPT_DOC_COMMENT_TAG"
+ private const val RUNESCRIPT_DOC_COMMENT_LINK = "RUNESCRIPT_DOC_COMMENT_LINK"
+
// Parser based attribute keys
private const val RUNESCRIPT_SCRIPT_DECLARATION = "RUNESCRIPT_SCRIPT_DECLARATION"
private const val RUNESCRIPT_CONSTANT = "RUNESCRIPT_CONSTANT"
@@ -50,6 +55,11 @@ object RsSyntaxHighlighterColors {
val PARENTHESIS = createTextAttributesKey(RUNESCRIPT_PARENTHESIS, DefaultLanguageHighlighterColors.PARENTHESES)
val BRACKETS = createTextAttributesKey(RUNESCRIPT_BRACKETS, DefaultLanguageHighlighterColors.BRACKETS)
+ // Documentation
+ val DOC_COMMENT = createTextAttributesKey(RUNESCRIPT_DOC_COMMENT, DefaultLanguageHighlighterColors.DOC_COMMENT)
+ val DOC_COMMENT_TAG = createTextAttributesKey(RUNESCRIPT_DOC_COMMENT_TAG, DefaultLanguageHighlighterColors.DOC_COMMENT_TAG)
+ val DOC_COMMENT_LINK = createTextAttributesKey(RUNESCRIPT_DOC_COMMENT_LINK, DefaultLanguageHighlighterColors.DOC_COMMENT_TAG_VALUE)
+
// Parser based attributes
val SCRIPT_DECLARATION = createTextAttributesKey(RUNESCRIPT_SCRIPT_DECLARATION, DefaultLanguageHighlighterColors.FUNCTION_DECLARATION)
val CONSTANT = createTextAttributesKey(RUNESCRIPT_CONSTANT, DefaultLanguageHighlighterColors.CONSTANT)
diff --git a/src/main/kotlin/io/runescript/plugin/ide/usages/RsReadWriteAccessDetector.kt b/src/main/kotlin/io/runescript/plugin/ide/usages/RsReadWriteAccessDetector.kt
index 753a758..1dd4810 100644
--- a/src/main/kotlin/io/runescript/plugin/ide/usages/RsReadWriteAccessDetector.kt
+++ b/src/main/kotlin/io/runescript/plugin/ide/usages/RsReadWriteAccessDetector.kt
@@ -3,6 +3,7 @@ package io.runescript.plugin.ide.usages
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
+import io.runescript.plugin.lang.doc.psi.impl.RsDocName
import io.runescript.plugin.lang.psi.*
import io.runescript.plugin.symbollang.psi.RsSymSymbol
import io.runescript.plugin.symbollang.psi.isVarFile
@@ -32,7 +33,8 @@ class RsReadWriteAccessDetector : ReadWriteAccessDetector() {
override fun getExpressionAccess(expression: PsiElement): Access {
require(expression is RsLocalVariableExpression
|| expression is RsScopedVariableExpression
- || expression is RsDynamicExpression)
+ || expression is RsDynamicExpression
+ || expression is RsDocName)
val parent = expression.parent
if (parent is RsAssignmentStatement || parent is RsLocalVariableDeclarationStatement) {
return Access.Write
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/NOTICE.txt b/src/main/kotlin/io/runescript/plugin/lang/doc/NOTICE.txt
new file mode 100644
index 0000000..d9f5e1d
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/NOTICE.txt
@@ -0,0 +1,3 @@
+This software includes code from IntelliJ IDEA Community Edition
+Copyright (C) JetBrains s.r.o.
+https://www.jetbrains.com/idea/
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/docPsiUtil.kt b/src/main/kotlin/io/runescript/plugin/lang/doc/docPsiUtil.kt
new file mode 100644
index 0000000..90febb7
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/docPsiUtil.kt
@@ -0,0 +1,49 @@
+package io.runescript.plugin.lang.doc
+
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiRecursiveElementWalkingVisitor
+import com.intellij.psi.util.PsiTreeUtil
+
+inline fun PsiElement.getChildrenOfType(): Array {
+ return PsiTreeUtil.getChildrenOfType(this, T::class.java) ?: arrayOf()
+}
+
+inline fun PsiElement.getChildOfType(): T? {
+ return PsiTreeUtil.getChildOfType(this, T::class.java)
+}
+
+inline fun PsiElement.getStrictParentOfType(): T? {
+ return PsiTreeUtil.getParentOfType(this, T::class.java, true)
+}
+
+inline fun PsiElement.getParentOfType(
+ strict: Boolean,
+ vararg stopAt: Class
+): T? {
+ return PsiTreeUtil.getParentOfType(this, T::class.java, strict, *stopAt)
+}
+
+inline fun PsiElement.findDescendantOfType(noinline predicate: (T) -> Boolean = { true }): T? {
+ return findDescendantOfType({ true }, predicate)
+}
+
+inline fun PsiElement.findDescendantOfType(
+ crossinline canGoInside: (PsiElement) -> Boolean,
+ noinline predicate: (T) -> Boolean = { true }
+): T? {
+ var result: T? = null
+ this.accept(object : PsiRecursiveElementWalkingVisitor() {
+ override fun visitElement(element: PsiElement) {
+ if (element is T && predicate(element)) {
+ result = element
+ stopWalking()
+ return
+ }
+
+ if (canGoInside(element)) {
+ super.visitElement(element)
+ }
+ }
+ })
+ return result
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/lexer/RsDocLexer.java b/src/main/kotlin/io/runescript/plugin/lang/doc/lexer/RsDocLexer.java
new file mode 100644
index 0000000..67ec6e7
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/lexer/RsDocLexer.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.runescript.plugin.lang.doc.lexer;
+
+import com.intellij.lexer.FlexAdapter;
+import com.intellij.lexer.MergingLexerAdapter;
+import com.intellij.psi.tree.TokenSet;
+
+import java.io.Reader;
+
+public class RsDocLexer extends MergingLexerAdapter {
+ private static final TokenSet RSDOC_TOKENS = TokenSet.create(RsDocTokens.TEXT, RsDocTokens.CODE_BLOCK_TEXT);
+
+ public RsDocLexer() {
+ super(new FlexAdapter(new _RsDocLexer((Reader) null)), RSDOC_TOKENS);
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/lexer/RsDocToken.java b/src/main/kotlin/io/runescript/plugin/lang/doc/lexer/RsDocToken.java
new file mode 100644
index 0000000..e056550
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/lexer/RsDocToken.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.runescript.plugin.lang.doc.lexer;
+
+import com.intellij.psi.tree.IElementType;
+import io.runescript.plugin.lang.RuneScript;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+public class RsDocToken extends IElementType {
+
+ public final int tokenId;
+
+ public RsDocToken(@NotNull @NonNls String debugName, int tokenId) {
+ super(debugName, RuneScript.INSTANCE);
+ this.tokenId = tokenId;
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/lexer/RsDocTokens.java b/src/main/kotlin/io/runescript/plugin/lang/doc/lexer/RsDocTokens.java
new file mode 100644
index 0000000..461a86d
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/lexer/RsDocTokens.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.runescript.plugin.lang.doc.lexer;
+
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.PsiBuilder;
+import com.intellij.lang.PsiBuilderFactory;
+import com.intellij.lang.PsiParser;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.tree.ILazyParseableElementType;
+import com.intellij.psi.tree.TokenSet;
+import io.runescript.plugin.lang.RuneScript;
+import io.runescript.plugin.lang.doc.parser.RsDocLinkParser;
+import io.runescript.plugin.lang.doc.parser.RsDocParser;
+import io.runescript.plugin.lang.doc.psi.impl.RsDocImpl;
+import org.jetbrains.annotations.Nullable;
+
+public interface RsDocTokens {
+ ILazyParseableElementType RSDOC = new ILazyParseableElementType("RSDoc", RuneScript.INSTANCE) {
+ @Override
+ public ASTNode parseContents(ASTNode chameleon) {
+ PsiElement parentElement = chameleon.getTreeParent().getPsi();
+ Project project = parentElement.getProject();
+ PsiBuilder builder = PsiBuilderFactory.getInstance().createBuilder(project, chameleon, new RsDocLexer(), getLanguage(),
+ chameleon.getText());
+ PsiParser parser = new RsDocParser();
+
+ return parser.parse(this, builder).getFirstChildNode();
+ }
+
+ @Nullable
+ @Override
+ public ASTNode createNode(CharSequence text) {
+ return new RsDocImpl(text);
+ }
+ };
+
+ int START_Id = 0;
+ int END_Id = 1;
+ int LEADING_ASTERISK_Id = 2;
+ int TEXT_Id = 3;
+ int CODE_BLOCK_TEXT_Id = 4;
+ int TAG_NAME_Id = 5;
+ int MARKDOWN_ESCAPED_CHAR_Id = 6;
+ int MARKDOWN_INLINE_LINK_Id = 7;
+
+ RsDocToken START = new RsDocToken("RSDOC_START", START_Id);
+ RsDocToken END = new RsDocToken("RSDOC_END", END_Id);
+ RsDocToken LEADING_ASTERISK = new RsDocToken("RSDOC_LEADING_ASTERISK", LEADING_ASTERISK_Id);
+
+ RsDocToken TEXT = new RsDocToken("RSDOC_TEXT", TEXT_Id);
+ RsDocToken CODE_BLOCK_TEXT = new RsDocToken("RSDOC_CODE_BLOCK_TEXT", CODE_BLOCK_TEXT_Id);
+
+ RsDocToken TAG_NAME = new RsDocToken("RSDOC_TAG_NAME", TAG_NAME_Id);
+ ILazyParseableElementType MARKDOWN_LINK = new ILazyParseableElementType("RSDOC_MARKDOWN_LINK", RuneScript.INSTANCE) {
+ @Override
+ public ASTNode parseContents(ASTNode chameleon) {
+ return RsDocLinkParser.parseMarkdownLink(this, chameleon);
+ }
+ };
+
+ RsDocToken MARKDOWN_ESCAPED_CHAR = new RsDocToken("RSDOC_MARKDOWN_ESCAPED_CHAR", MARKDOWN_ESCAPED_CHAR_Id);
+ RsDocToken MARKDOWN_INLINE_LINK = new RsDocToken("RSDOC_MARKDOWN_INLINE_LINK", MARKDOWN_INLINE_LINK_Id);
+ @SuppressWarnings("unused")
+ TokenSet RSDOC_HIGHLIGHT_TOKENS = TokenSet.create(START, END, LEADING_ASTERISK, TEXT, CODE_BLOCK_TEXT, MARKDOWN_LINK, MARKDOWN_ESCAPED_CHAR, MARKDOWN_INLINE_LINK);
+ TokenSet CONTENT_TOKENS = TokenSet.create(TEXT, CODE_BLOCK_TEXT, TAG_NAME, MARKDOWN_LINK, MARKDOWN_ESCAPED_CHAR, MARKDOWN_INLINE_LINK);
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocElementType.java b/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocElementType.java
new file mode 100644
index 0000000..e0a7170
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocElementType.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.runescript.plugin.lang.doc.parser;
+
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.tree.IElementType;
+import io.runescript.plugin.lang.RuneScript;
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.reflect.Constructor;
+
+public class RsDocElementType extends IElementType {
+ private final Constructor extends PsiElement> psiFactory;
+
+ public RsDocElementType(String debugName, @NotNull Class extends PsiElement> psiClass) {
+ super(debugName, RuneScript.INSTANCE);
+ try {
+ psiFactory = psiClass.getConstructor(ASTNode.class);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("Must have a constructor with ASTNode");
+ }
+ }
+
+ public PsiElement createPsi(ASTNode node) {
+ assert node.getElementType() == this;
+
+ try {
+ return psiFactory.newInstance(node);
+ } catch (Exception e) {
+ throw new RuntimeException("Error creating psi element for node", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocElementTypes.java b/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocElementTypes.java
new file mode 100644
index 0000000..41c447f
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocElementTypes.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.runescript.plugin.lang.doc.parser;
+
+import io.runescript.plugin.lang.doc.psi.impl.RsDocName;
+import io.runescript.plugin.lang.doc.psi.impl.RsDocSection;
+import io.runescript.plugin.lang.doc.psi.impl.RsDocTag;
+
+public class RsDocElementTypes {
+ public static final RsDocElementType RSDOC_SECTION = new RsDocElementType("RSDOC_SECTION", RsDocSection.class);
+ public static final RsDocElementType RSDOC_TAG = new RsDocElementType("RSDOC_TAG", RsDocTag.class);
+ public static final RsDocElementType RSDOC_NAME = new RsDocElementType("RSDOC_NAME", RsDocName.class);
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocKnownTag.kt b/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocKnownTag.kt
new file mode 100644
index 0000000..f9bc55b
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocKnownTag.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.runescript.plugin.lang.doc.parser
+
+enum class RsDocKnownTag(val isReferenceRequired: Boolean, val isSectionStart: Boolean) {
+ AUTHOR(false, false),
+ PARAM(true, false),
+ RETURN(false, false),
+ SEE(true, false),
+ SINCE(false, false),
+ SAMPLE(true, false);
+
+
+ companion object {
+ fun findByTagName(tagName: CharSequence): RsDocKnownTag? {
+ val name = if (tagName.startsWith('@')) {
+ tagName.subSequence(1, tagName.length)
+ } else tagName
+ try {
+ return valueOf(name.toString().uppercase())
+ } catch (ignored: IllegalArgumentException) {
+ }
+
+ return null
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocLinkParser.kt b/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocLinkParser.kt
new file mode 100644
index 0000000..d43c0f8
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocLinkParser.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.runescript.plugin.lang.doc.parser
+
+import com.intellij.lang.ASTNode
+import com.intellij.lang.PsiBuilder
+import com.intellij.lang.PsiBuilderFactory
+import com.intellij.lang.PsiParser
+import com.intellij.psi.tree.IElementType
+import io.runescript.plugin.ide.config.RsConfig
+import io.runescript.plugin.lang.lexer.RsLexerAdapter
+import io.runescript.plugin.lang.lexer.RsLexerInfo
+import io.runescript.plugin.lang.psi.RsElementTypes
+import io.runescript.plugin.lang.psi.RsTokenTypes
+
+/**
+ * Parses the contents of a Markdown link in RSDoc. Uses the standard Kotlin lexer.
+ */
+class RsDocLinkParser : PsiParser {
+ companion object {
+ @JvmStatic
+ fun parseMarkdownLink(root: IElementType, chameleon: ASTNode): ASTNode {
+ val parentElement = chameleon.treeParent.psi
+ val project = parentElement.project
+ val builder = PsiBuilderFactory.getInstance().createBuilder(
+ project,
+ chameleon,
+ RsLexerAdapter(RsLexerInfo(RsConfig.getPrimitiveTypes(project))),
+ root.language,
+ chameleon.text
+ )
+ val parser = RsDocLinkParser()
+
+ return parser.parse(root, builder).firstChildNode
+ }
+ }
+
+ override fun parse(root: IElementType, builder: PsiBuilder): ASTNode {
+ val rootMarker = builder.mark()
+ val hasLBracket = builder.tokenType == RsElementTypes.LBRACKET
+ if (hasLBracket) {
+ builder.advanceLexer()
+ }
+ parseQualifiedName(builder)
+ if (hasLBracket) {
+ if (!builder.eof() && builder.tokenType != RsElementTypes.RBRACKET) {
+ builder.error("Closing bracket expected")
+ while (!builder.eof() && builder.tokenType != RsElementTypes.RBRACKET) {
+ builder.advanceLexer()
+ }
+ }
+ if (builder.tokenType == RsElementTypes.RBRACKET) {
+ builder.advanceLexer()
+ }
+ } else {
+ if (!builder.eof()) {
+ builder.error("Expression expected")
+ while (!builder.eof()) {
+ builder.advanceLexer()
+ }
+ }
+ }
+ rootMarker.done(root)
+ return builder.treeBuilt
+ }
+
+ private fun parseQualifiedName(builder: PsiBuilder) {
+ // [domain/name] or [name]
+ var marker = builder.mark()
+ while (true) {
+ // don't highlight a word in a link as an error if it happens to be a Kotlin keyword
+ if (!isName(builder.tokenType)) {
+ marker.drop()
+ builder.error("Identifier expected")
+ break
+ }
+ builder.advanceLexer()
+ marker.done(RsDocElementTypes.RSDOC_NAME)
+ if (builder.tokenType != RsElementTypes.SLASH) {
+ break
+ }
+ marker = marker.precede()
+ builder.advanceLexer()
+ }
+ }
+
+ private fun isName(tokenType: IElementType?) = tokenType in RsTokenTypes.ID_TOKENS
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocParser.java b/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocParser.java
new file mode 100644
index 0000000..b52d6b5
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/parser/RsDocParser.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.runescript.plugin.lang.doc.parser;
+
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.PsiBuilder;
+import com.intellij.lang.PsiParser;
+import com.intellij.psi.tree.IElementType;
+import io.runescript.plugin.lang.doc.lexer.RsDocTokens;
+import org.jetbrains.annotations.NotNull;
+
+public class RsDocParser implements PsiParser {
+ @Override
+ @NotNull
+ public ASTNode parse(IElementType root, PsiBuilder builder) {
+ PsiBuilder.Marker rootMarker = builder.mark();
+ if (builder.getTokenType() == RsDocTokens.START) {
+ builder.advanceLexer();
+ }
+ PsiBuilder.Marker currentSectionMarker = builder.mark();
+
+ // todo: parse RSDoc tags, markdown, etc...
+ while (!builder.eof()) {
+ if (builder.getTokenType() == RsDocTokens.TAG_NAME) {
+ currentSectionMarker = parseTag(builder, currentSectionMarker);
+ } else if (builder.getTokenType() == RsDocTokens.END) {
+ if (currentSectionMarker != null) {
+ currentSectionMarker.done(RsDocElementTypes.RSDOC_SECTION);
+ currentSectionMarker = null;
+ }
+ builder.advanceLexer();
+ } else {
+ builder.advanceLexer();
+ }
+ }
+
+ if (currentSectionMarker != null) {
+ currentSectionMarker.done(RsDocElementTypes.RSDOC_SECTION);
+ }
+ rootMarker.done(root);
+ return builder.getTreeBuilt();
+ }
+
+ private static PsiBuilder.Marker parseTag(PsiBuilder builder, PsiBuilder.Marker currentSectionMarker) {
+ String tagName = builder.getTokenText();
+ RsDocKnownTag knownTag = RsDocKnownTag.Companion.findByTagName(tagName);
+ if (knownTag != null && knownTag.isSectionStart()) {
+ currentSectionMarker.done(RsDocElementTypes.RSDOC_SECTION);
+ currentSectionMarker = builder.mark();
+ }
+ PsiBuilder.Marker tagStart = builder.mark();
+ builder.advanceLexer();
+
+ while (!builder.eof() && !isAtEndOfTag(builder)) {
+ builder.advanceLexer();
+ }
+ tagStart.done(RsDocElementTypes.RSDOC_TAG);
+ return currentSectionMarker;
+ }
+
+ private static boolean isAtEndOfTag(PsiBuilder builder) {
+ if (builder.getTokenType() == RsDocTokens.END) {
+ return true;
+ }
+ if (builder.getTokenType() == RsDocTokens.LEADING_ASTERISK) {
+ int lookAheadCount = 1;
+ if (builder.lookAhead(1) == RsDocTokens.TEXT) {
+ lookAheadCount++;
+ }
+ if (builder.lookAhead(lookAheadCount) == RsDocTokens.TAG_NAME) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/psi/api/RsDoc.kt b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/api/RsDoc.kt
new file mode 100644
index 0000000..e256295
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/api/RsDoc.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+package io.runescript.plugin.lang.doc.psi.api
+
+import com.intellij.psi.PsiDocCommentBase
+import io.runescript.plugin.lang.doc.parser.RsDocKnownTag
+import io.runescript.plugin.lang.doc.psi.impl.RsDocSection
+import io.runescript.plugin.lang.psi.RsScript
+
+interface RsDoc : PsiDocCommentBase, RsDocElement {
+ override fun getOwner(): RsScript?
+ fun getDefaultSection(): RsDocSection
+ fun getAllSections(): List
+ fun findSectionByName(name: String): RsDocSection?
+ fun findSectionByTag(tag: RsDocKnownTag): RsDocSection?
+ fun findSectionByTag(tag: RsDocKnownTag, subjectName: String): RsDocSection?
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/psi/api/RsDocElement.java b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/api/RsDocElement.java
new file mode 100644
index 0000000..d080625
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/api/RsDocElement.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.runescript.plugin.lang.doc.psi.api;
+
+import com.intellij.psi.PsiElement;
+
+public interface RsDocElement extends PsiElement {
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocElementImpl.java b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocElementImpl.java
new file mode 100644
index 0000000..1b82b99
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocElementImpl.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.runescript.plugin.lang.doc.psi.impl;
+
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.Language;
+import io.runescript.plugin.lang.RuneScript;
+import io.runescript.plugin.lang.doc.psi.api.RsDocElement;
+import org.jetbrains.annotations.NotNull;
+
+public abstract class RsDocElementImpl extends ASTWrapperPsiElement implements RsDocElement {
+ @NotNull
+ @Override
+ public Language getLanguage() {
+ return RuneScript.INSTANCE;
+ }
+
+ @Override
+ public String toString() {
+ return getNode().getElementType().toString();
+ }
+
+ public RsDocElementImpl(@NotNull ASTNode node) {
+ super(node);
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocImpl.kt b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocImpl.kt
new file mode 100644
index 0000000..9e8213a
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocImpl.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+package io.runescript.plugin.lang.doc.psi.impl
+
+import com.intellij.lang.Language
+import com.intellij.psi.impl.source.tree.LazyParseablePsiElement
+import com.intellij.psi.tree.IElementType
+import com.intellij.psi.tree.TokenSet
+import com.intellij.psi.util.siblings
+import com.intellij.psi.util.skipTokens
+import io.runescript.plugin.lang.RuneScript
+import io.runescript.plugin.lang.doc.getChildOfType
+import io.runescript.plugin.lang.doc.getChildrenOfType
+import io.runescript.plugin.lang.doc.lexer.RsDocTokens
+import io.runescript.plugin.lang.doc.parser.RsDocKnownTag
+import io.runescript.plugin.lang.doc.psi.api.RsDoc
+import io.runescript.plugin.lang.psi.RsScript
+import io.runescript.plugin.lang.psi.RsTokenTypes
+
+class RsDocImpl(buffer: CharSequence?) : LazyParseablePsiElement(RsDocTokens.RSDOC, buffer), RsDoc {
+
+ override fun getLanguage(): Language = RuneScript
+
+ override fun toString(): String = node.elementType.toString()
+
+ override fun getTokenType(): IElementType = RsTokenTypes.DOC_COMMENT
+
+ override fun getOwner(): RsScript? {
+ return siblings(true, withSelf = false)
+ .skipTokens(TokenSet.WHITE_SPACE)
+ .firstOrNull() as? RsScript
+ }
+
+ override fun getDefaultSection(): RsDocSection = getChildOfType()!!
+
+ override fun getAllSections(): List =
+ getChildrenOfType().toList()
+
+ override fun findSectionByName(name: String): RsDocSection? =
+ getChildrenOfType().firstOrNull { it.name == name }
+
+ override fun findSectionByTag(tag: RsDocKnownTag): RsDocSection? =
+ findSectionByName(tag.name.lowercase())
+
+ override fun findSectionByTag(tag: RsDocKnownTag, subjectName: String): RsDocSection? =
+ getChildrenOfType().firstOrNull {
+ it.name == tag.name.lowercase() && it.getSubjectName() == subjectName
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocLink.kt b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocLink.kt
new file mode 100644
index 0000000..c17859c
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocLink.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.runescript.plugin.lang.doc.psi.impl
+
+import com.intellij.extapi.psi.ASTWrapperPsiElement
+import com.intellij.lang.ASTNode
+import com.intellij.openapi.util.TextRange
+import io.runescript.plugin.lang.doc.getStrictParentOfType
+
+class RsDocLink(node: ASTNode) : ASTWrapperPsiElement(node) {
+ fun getLinkText(): String = getLinkTextRange().substring(text)
+
+ fun getLinkTextRange(): TextRange {
+ val text = text
+ if (text.startsWith('[') && text.endsWith(']')) {
+ return TextRange(1, text.length - 1)
+ }
+ return TextRange(0, text.length)
+ }
+
+ /**
+ * If this link is the subject of a tag, returns the tag. Otherwise, returns null.
+ */
+ fun getTagIfSubject(): RsDocTag? {
+ val tag = getStrictParentOfType()
+ return if (tag != null && tag.getSubjectLink() == this) tag else null
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocName.kt b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocName.kt
new file mode 100644
index 0000000..69304b8
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocName.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.runescript.plugin.lang.doc.psi.impl;
+
+import com.intellij.extapi.psi.ASTWrapperPsiElement
+import com.intellij.lang.ASTNode
+import com.intellij.openapi.util.TextRange
+import com.intellij.psi.PsiReference
+import io.runescript.plugin.ide.doc.RsDocReference
+import io.runescript.plugin.lang.doc.getStrictParentOfType
+import io.runescript.plugin.lang.doc.psi.api.RsDoc
+import io.runescript.plugin.lang.psi.RsElementTypes
+
+/**
+ * A single part of a qualified name in the tag subject or link.
+ */
+class RsDocName(node: ASTNode) : ASTWrapperPsiElement(node) {
+
+ override fun getReference(): PsiReference? {
+ if (parent !is RsDocName) {
+ return RsDocReference(this)
+ }
+ return null
+ }
+
+ fun getContainingDoc(): RsDoc {
+ val rsdoc = getStrictParentOfType()
+ return rsdoc ?: throw IllegalStateException("RsDocName must be inside a RsDoc")
+ }
+
+ fun getContainingSection(): RsDocSection {
+ val rsdoc = getStrictParentOfType()
+ return rsdoc ?: throw IllegalStateException("RsDocName must be inside a RsDocSection")
+ }
+
+ /**
+ * Returns the range within the element containing the name (in other words,
+ * the range of the element excluding the qualifier and dot, if present).
+ */
+ fun getNameTextRange(): TextRange {
+ val dot = node.findChildByType(RsElementTypes.SLASH)
+ val textRange = textRange
+ val nameStart = if (dot != null) dot.textRange.endOffset - textRange.startOffset else 0
+ return TextRange(nameStart, textRange.length)
+ }
+
+ fun getNameText(): String = getNameTextRange().substring(text)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocSection.kt b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocSection.kt
new file mode 100644
index 0000000..65f1af7
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocSection.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.runescript.plugin.lang.doc.psi.impl
+
+import com.intellij.lang.ASTNode
+import io.runescript.plugin.lang.doc.getChildrenOfType
+
+/**
+ * The part of a doc comment which describes a single class, method or property
+ * produced by the element being documented. For example, the doc comment of a class
+ * can have sections for the class itself, its primary constructor and each of the
+ * properties defined in the primary constructor.
+ */
+class RsDocSection(node: ASTNode) : RsDocTag(node) {
+ /**
+ * Returns the name of the section (the name of the doc tag introducing the section,
+ * or null for the default section).
+ */
+ override fun getName(): String? =
+ (firstChild as? RsDocTag)?.name
+
+ override fun getSubjectName(): String? =
+ (firstChild as? RsDocTag)?.getSubjectName()
+
+ override fun getContent(): String =
+ (firstChild as? RsDocTag)?.getContent() ?: super.getContent()
+
+ fun findTagsByName(name: String): List {
+ return getChildrenOfType().filter { it.name == name }
+ }
+
+ fun findTagByName(name: String): RsDocTag? = findTagsByName(name).firstOrNull()
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocTag.kt b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocTag.kt
new file mode 100644
index 0000000..94bc30c
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/doc/psi/impl/RsDocTag.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2010-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.runescript.plugin.lang.doc.psi.impl
+
+import com.intellij.lang.ASTNode
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.psi.PsiElement
+import com.intellij.psi.TokenType
+import io.runescript.plugin.lang.doc.lexer.RsDocTokens
+import io.runescript.plugin.lang.doc.parser.RsDocElementTypes
+import io.runescript.plugin.lang.doc.parser.RsDocKnownTag
+
+open class RsDocTag(node: ASTNode) : RsDocElementImpl(node) {
+
+ /**
+ * Returns the name of this tag, not including the leading @ character.
+ *
+ * @return tag name or null if this tag represents the default section of a doc comment
+ * or the code has a syntax error.
+ */
+ override fun getName(): String? {
+ val tagName: PsiElement? = findChildByType(RsDocTokens.TAG_NAME)
+ if (tagName != null) {
+ return tagName.text.substring(1)
+ }
+ return null
+ }
+
+ /**
+ * Returns the name of the entity documented by this tag (for example, the name of the parameter
+ * for the @param tag), or null if this tag does not document any specific entity.
+ */
+ open fun getSubjectName(): String? = getSubjectLink()?.getLinkText()
+
+ fun getSubjectLink(): RsDocLink? {
+ val children = childrenAfterTagName()
+ if (hasSubject(children)) {
+ return children.firstOrNull()?.psi as? RsDocLink
+ }
+ return null
+ }
+
+ val knownTag: RsDocKnownTag?
+ get() {
+ return name?.let { RsDocKnownTag.findByTagName(it) }
+ }
+
+ private fun hasSubject(contentChildren: List): Boolean {
+ if (knownTag?.isReferenceRequired ?: false) {
+ return contentChildren.firstOrNull()?.elementType == RsDocTokens.MARKDOWN_LINK
+ }
+ return false
+ }
+
+ private fun childrenAfterTagName(): List =
+ node.getChildren(null)
+ .dropWhile { it.elementType == RsDocTokens.TAG_NAME }
+ .dropWhile { it.elementType == TokenType.WHITE_SPACE }
+
+ /**
+ * Returns the content of this tag (all text following the tag name and the subject if present,
+ * with leading asterisks removed).
+ */
+ open fun getContent(): String {
+ val builder = StringBuilder()
+ val codeBlockBuilder = StringBuilder()
+ var targetBuilder = builder
+
+ var contentStarted = false
+ var afterAsterisk = false
+ var indentedCodeBlock = false
+
+ fun isCodeBlock() = targetBuilder == codeBlockBuilder
+
+ fun startCodeBlock() {
+ targetBuilder = codeBlockBuilder
+ }
+
+ fun flushCodeBlock() {
+ if (isCodeBlock()) {
+ builder.append(trimCommonIndent(codeBlockBuilder, indentedCodeBlock))
+ codeBlockBuilder.setLength(0)
+ targetBuilder = builder
+ }
+ }
+
+ var children = childrenAfterTagName()
+ if (hasSubject(children)) {
+ children = children.drop(1)
+ }
+ for (node in children) {
+ val type = node.elementType
+ if (type == RsDocTokens.CODE_BLOCK_TEXT) {
+ //If first line of code block
+ if (!isCodeBlock())
+ indentedCodeBlock =
+ indentedCodeBlock || node.text.startsWith(indentationWhiteSpaces) || node.text.startsWith("\t")
+ startCodeBlock()
+ } else if (RsDocTokens.CONTENT_TOKENS.contains(type)) {
+ flushCodeBlock()
+ indentedCodeBlock = false
+ }
+
+ if (RsDocTokens.CONTENT_TOKENS.contains(type)) {
+ val isPlainContent = afterAsterisk && !isCodeBlock()
+ // If content not yet started and not part of indented code block
+ // and not inside fenced code block we should trim leading spaces
+ val trimLeadingSpaces = !(contentStarted || indentedCodeBlock) || isPlainContent
+
+ targetBuilder.append(if (trimLeadingSpaces) node.text.trimStart() else node.text)
+ contentStarted = true
+ afterAsterisk = false
+ }
+ if (type == RsDocTokens.LEADING_ASTERISK) {
+ afterAsterisk = true
+ }
+ if (type == TokenType.WHITE_SPACE && contentStarted) {
+ targetBuilder.append("\n".repeat(StringUtil.countNewLines(node.text)))
+ }
+ if (type == RsDocElementTypes.RSDOC_TAG) {
+ break
+ }
+ }
+
+ flushCodeBlock()
+
+ return builder.toString().trimEnd(' ', '\t')
+ }
+
+ private fun trimCommonIndent(builder: StringBuilder, prepend4WhiteSpaces: Boolean = false): String {
+ val lines = builder.toString().split('\n')
+ val minIndent = lines.filter { it.trim().isNotEmpty() }.minOfOrNull { it.calcIndent() } ?: 0
+ var processedLines = lines.map { it.drop(minIndent) }
+ if (prepend4WhiteSpaces)
+ processedLines =
+ processedLines.map { if (it.isNotBlank()) it.prependIndent(indentationWhiteSpaces) else it }
+ return processedLines.joinToString("\n")
+ }
+
+ private fun String.calcIndent() = indexOfFirst { !it.isWhitespace() }
+
+ companion object {
+ val indentationWhiteSpaces = " ".repeat(4)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/parser/RsParserDefinition.kt b/src/main/kotlin/io/runescript/plugin/lang/parser/RsParserDefinition.kt
index 3743f27..8396939 100644
--- a/src/main/kotlin/io/runescript/plugin/lang/parser/RsParserDefinition.kt
+++ b/src/main/kotlin/io/runescript/plugin/lang/parser/RsParserDefinition.kt
@@ -11,6 +11,9 @@ import com.intellij.psi.PsiFile
import com.intellij.psi.tree.IFileElementType
import com.intellij.psi.tree.TokenSet
import io.runescript.plugin.ide.config.RsConfig
+import io.runescript.plugin.lang.doc.lexer.RsDocTokens
+import io.runescript.plugin.lang.doc.parser.RsDocElementType
+import io.runescript.plugin.lang.doc.psi.impl.RsDocLink
import io.runescript.plugin.lang.lexer.RsLexerAdapter
import io.runescript.plugin.lang.lexer.RsLexerInfo
import io.runescript.plugin.lang.psi.RsElementTypes
@@ -40,8 +43,12 @@ class RsParserDefinition : ParserDefinition {
return RsTokenTypesSets.STRING_ELEMENTS
}
- override fun createElement(node: ASTNode?): PsiElement {
- return RsElementTypes.Factory.createElement(node)
+ override fun createElement(node: ASTNode): PsiElement {
+ return when (val elementType = node.elementType) {
+ is RsDocElementType -> elementType.createPsi(node)
+ RsDocTokens.MARKDOWN_LINK -> RsDocLink(node)
+ else -> RsElementTypes.Factory.createElement(node)
+ }
}
override fun createFile(viewProvider: FileViewProvider): PsiFile {
diff --git a/src/main/kotlin/io/runescript/plugin/lang/psi/RsBinaryExpression.kt b/src/main/kotlin/io/runescript/plugin/lang/psi/RsBinaryExpression.kt
index cd4cc1c..09b6a51 100644
--- a/src/main/kotlin/io/runescript/plugin/lang/psi/RsBinaryExpression.kt
+++ b/src/main/kotlin/io/runescript/plugin/lang/psi/RsBinaryExpression.kt
@@ -1,5 +1,8 @@
package io.runescript.plugin.lang.psi
+/**
+ * test
+ */
interface RsBinaryExpression : RsExpression {
val left: RsExpression
val right: RsExpression
diff --git a/src/main/kotlin/io/runescript/plugin/lang/psi/RsElementGenerator.kt b/src/main/kotlin/io/runescript/plugin/lang/psi/RsElementGenerator.kt
index 35dd2d9..4b465b9 100644
--- a/src/main/kotlin/io/runescript/plugin/lang/psi/RsElementGenerator.kt
+++ b/src/main/kotlin/io/runescript/plugin/lang/psi/RsElementGenerator.kt
@@ -10,6 +10,8 @@ import com.intellij.psi.util.PsiTreeUtil
import com.intellij.testFramework.LightVirtualFile
import io.runescript.plugin.ide.filetypes.RsFileType
import io.runescript.plugin.lang.RuneScript
+import io.runescript.plugin.lang.doc.findDescendantOfType
+import io.runescript.plugin.lang.doc.psi.impl.RsDocLink
object RsElementGenerator {
@@ -50,6 +52,11 @@ object RsElementGenerator {
return PsiTreeUtil.findChildOfType(literal, RsStringLiteralContent::class.java) as RsStringLiteralContent
}
+ fun createDocIdentifier(project: Project, name: String): PsiElement {
+ val element = createDummyFile(project, "/** [$name] */[proc,dummy]()()")
+ return element.findDescendantOfType()!!.firstChild.nextSibling
+ }
+
private fun createDummyFile(project: Project, text: String): PsiFile {
val factory = PsiFileFactory.getInstance(project) as PsiFileFactoryImpl
val name = "dummy.${RsFileType.defaultExtension}"
diff --git a/src/main/kotlin/io/runescript/plugin/lang/psi/RsFile.kt b/src/main/kotlin/io/runescript/plugin/lang/psi/RsFile.kt
index ffa360a..5ada789 100644
--- a/src/main/kotlin/io/runescript/plugin/lang/psi/RsFile.kt
+++ b/src/main/kotlin/io/runescript/plugin/lang/psi/RsFile.kt
@@ -3,21 +3,19 @@ package io.runescript.plugin.lang.psi
import com.intellij.extapi.psi.PsiFileBase
import com.intellij.openapi.fileTypes.FileType
import com.intellij.psi.FileViewProvider
+import com.intellij.psi.stubs.StubElement
import io.runescript.plugin.ide.filetypes.RsFileType
import io.runescript.plugin.lang.RuneScript
import io.runescript.plugin.lang.stubs.types.RsFileStubType
class RsFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, RuneScript) {
- init {
- init(RsFileStubType, RsFileStubType)
- }
-
override fun getFileType(): FileType {
return RsFileType
}
override fun toString(): String {
- return "RuneScript File"
+ return "RsFile: $name"
}
+
}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/psi/RsTokenTypes.kt b/src/main/kotlin/io/runescript/plugin/lang/psi/RsTokenTypes.kt
index 9ce9749..2e60884 100644
--- a/src/main/kotlin/io/runescript/plugin/lang/psi/RsTokenTypes.kt
+++ b/src/main/kotlin/io/runescript/plugin/lang/psi/RsTokenTypes.kt
@@ -1,8 +1,29 @@
package io.runescript.plugin.lang.psi
+import com.intellij.psi.tree.TokenSet
+import io.runescript.plugin.lang.doc.lexer.RsDocTokens
+
+
object RsTokenTypes {
+ val ID_TOKENS = TokenSet.create(
+ RsElementTypes.IDENTIFIER,
+ RsElementTypes.DEFINE_TYPE,
+ RsElementTypes.TYPE_LITERAL,
+ RsElementTypes.ARRAY_TYPE_LITERAL,
+ RsElementTypes.WHILE,
+ RsElementTypes.IF,
+ RsElementTypes.TRUE,
+ RsElementTypes.FALSE,
+ RsElementTypes.NULL,
+ RsElementTypes.SWITCH
+ )
+
@JvmField
val LINE_COMMENT = RsElementType("LINE_COMMENT")
+
@JvmField
val BLOCK_COMMENT = RsElementType("BLOCK_COMMENT")
+
+ @JvmField
+ val DOC_COMMENT = RsDocTokens.RSDOC
}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/psi/RsTokenTypesSets.kt b/src/main/kotlin/io/runescript/plugin/lang/psi/RsTokenTypesSets.kt
index 19046ad..6912fd2 100644
--- a/src/main/kotlin/io/runescript/plugin/lang/psi/RsTokenTypesSets.kt
+++ b/src/main/kotlin/io/runescript/plugin/lang/psi/RsTokenTypesSets.kt
@@ -2,6 +2,7 @@ package io.runescript.plugin.lang.psi
import com.intellij.psi.tree.TokenSet
import io.runescript.plugin.lang.psi.RsElementTypes.*
+import io.runescript.plugin.lang.psi.RsTokenTypes.DOC_COMMENT
import io.runescript.plugin.lang.psi.RsTokenTypes.BLOCK_COMMENT
import io.runescript.plugin.lang.psi.RsTokenTypes.LINE_COMMENT
@@ -52,6 +53,7 @@ object RsTokenTypesSets {
val COMMENTS = TokenSet.create(
LINE_COMMENT,
BLOCK_COMMENT,
+ DOC_COMMENT
)
val STRING_ELEMENTS = TokenSet.create(STRING_START, STRING_PART, STRING_TAG, STRING_END)
diff --git a/src/main/kotlin/io/runescript/plugin/lang/psi/manipulator/RsDocNameManipulator.kt b/src/main/kotlin/io/runescript/plugin/lang/psi/manipulator/RsDocNameManipulator.kt
new file mode 100644
index 0000000..14e1fff
--- /dev/null
+++ b/src/main/kotlin/io/runescript/plugin/lang/psi/manipulator/RsDocNameManipulator.kt
@@ -0,0 +1,16 @@
+package io.runescript.plugin.lang.psi.manipulator
+
+import com.intellij.openapi.util.TextRange
+import com.intellij.psi.AbstractElementManipulator
+import io.runescript.plugin.lang.doc.psi.impl.RsDocName
+import io.runescript.plugin.lang.psi.RsElementGenerator
+import io.runescript.plugin.lang.psi.RsStringLiteralContent
+
+class RsDocNameManipulator : AbstractElementManipulator() {
+
+ override fun handleContentChange(element: RsDocName, range: TextRange, newContent: String): RsDocName {
+ val newElement = RsElementGenerator.createDocIdentifier(element.project, newContent)
+ element.lastChild.replace(newElement)
+ return element
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/runescript/plugin/lang/psi/mixin/RsLocalVariableExpressionMixin.kt b/src/main/kotlin/io/runescript/plugin/lang/psi/mixin/RsLocalVariableExpressionMixin.kt
index c8a21db..4451ddb 100644
--- a/src/main/kotlin/io/runescript/plugin/lang/psi/mixin/RsLocalVariableExpressionMixin.kt
+++ b/src/main/kotlin/io/runescript/plugin/lang/psi/mixin/RsLocalVariableExpressionMixin.kt
@@ -10,6 +10,7 @@ import com.intellij.psi.stubs.IStubElementType
import com.intellij.psi.tree.IElementType
import com.intellij.psi.util.parentOfType
import com.intellij.refactoring.suggested.startOffset
+import io.runescript.plugin.ide.doc.findDoc
import io.runescript.plugin.lang.psi.*
import io.runescript.plugin.lang.psi.refs.RsLocalVariableReference
import io.runescript.plugin.lang.stubs.RsLocalVariableExpressionStub
@@ -21,7 +22,13 @@ abstract class RsLocalVariableExpressionMixin : StubBasedPsiElementBase()!!)
+ val script = parentOfType()!!
+ val doc = script.findDoc()
+ return if (doc != null) {
+ LocalSearchScope(arrayOf(script, doc))
+ } else {
+ LocalSearchScope(this)
+ }
}
override fun getReference(): PsiReference? {
diff --git a/src/main/kotlin/io/runescript/plugin/symbollang/psi/index/RsSymbolIndex.kt b/src/main/kotlin/io/runescript/plugin/symbollang/psi/index/RsSymbolIndex.kt
index 89004df..1e071d4 100644
--- a/src/main/kotlin/io/runescript/plugin/symbollang/psi/index/RsSymbolIndex.kt
+++ b/src/main/kotlin/io/runescript/plugin/symbollang/psi/index/RsSymbolIndex.kt
@@ -27,7 +27,7 @@ class RsSymbolIndex : StringStubIndexExtension() {
return configs.singleOrNull { it.containingFile.nameWithoutExtension == lookupType.literal }
}
- private val PsiFile.nameWithoutExtension: String
+ val PsiFile.nameWithoutExtension: String
get() {
val dot = name.lastIndexOf('.')
if (dot == -1) {
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index ca181cc..ad8cb14 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -29,6 +29,7 @@
+
@@ -39,6 +40,7 @@
+
@@ -53,6 +55,9 @@
+
+
+
@@ -63,7 +68,6 @@
-
diff --git a/src/main/resources/messages/RsBundle.properties b/src/main/resources/messages/RsBundle.properties
index 28f0851..f8c040b 100644
--- a/src/main/resources/messages/RsBundle.properties
+++ b/src/main/resources/messages/RsBundle.properties
@@ -18,6 +18,9 @@ runescript.color.settings.description.array_type_literal=Array type literals
runescript.color.settings.description.string=Strings
runescript.color.settings.description.string_tag=String tags
runescript.color.settings.description.block_comment=Block comments
+runescript.color.settings.description.doc_comment=Documentation comments
+runescript.color.settings.description.doc_tag=Documentation tags
+runescript.color.settings.description.doc_link=Documentation links
runescript.color.settings.description.line_comment=Line comments
runescript.color.settings.description.operation_sign=Operation signs
runescript.color.settings.description.braces=Braces
@@ -80,4 +83,13 @@ build.notification.jdk.not.found.content=Please ensure that JDK 17 or higher is
build.status.cancelled=Build cancelled
build.status.finished=Build finished
build.status.failed=Build failed
-build.status.running=Build running...
\ No newline at end of file
+build.status.running=Build running...
+
+# Documentation
+rsdoc.section.title.parameters=Params
+rsdoc.section.title.returns=Returns
+rsdoc.section.title.author=Authors
+rsdoc.section.title.since=Since
+rsdoc.section.title.samples=Samples
+rsdoc.section.title.see.also=See Also
+rsdoc.comment.unresolved=Unresolved
\ No newline at end of file