diff --git a/CHANGELOG.md b/CHANGELOG.md index e224f26..55732a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # intellij-runescript Changelog ## [Unreleased] +### Added +- Add documentation support based on the KDoc. + ### Fixed - Fix regular expressions not being allowed in hook transmits list. - Fix invalid symbol file inspection reporting an error for "commands.sym". diff --git a/README.md b/README.md index 0e08135..3cbce9b 100644 --- a/README.md +++ b/README.md @@ -36,4 +36,5 @@ To install the plugin, follow the following steps: ## Acknowledgements -The [IntelliJ Rust](https://github.com/intellij-rust/intellij-rust) plugin was used for reference. \ No newline at end of file +The [IntelliJ Rust](https://github.com/intellij-rust/intellij-rust) plugin was used for reference. +The [Kotlin Compiler](https://github.com/JetBrains/Kotlin) plugin was used for reference. \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 63ce368..ee601b4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,6 +27,7 @@ kotlin { sourceSets { main { java.srcDirs("src/main/gen") + java.srcDirs("src/main/kotlin") } } diff --git a/src/main/gen/io/runescript/plugin/lang/doc/lexer/_RsDocLexer.java b/src/main/gen/io/runescript/plugin/lang/doc/lexer/_RsDocLexer.java new file mode 100644 index 0000000..d1988b5 --- /dev/null +++ b/src/main/gen/io/runescript/plugin/lang/doc/lexer/_RsDocLexer.java @@ -0,0 +1,928 @@ +// Generated by JFlex 1.9.1 http://jflex.de/ (tweaked for IntelliJ platform) +// source: RuneScriptDoc.flex + +package io.runescript.plugin.lang.doc.lexer; + +import com.intellij.lexer.FlexLexer; +import com.intellij.psi.TokenType; +import com.intellij.psi.tree.IElementType; +import com.intellij.util.text.CharArrayUtil; +import java.lang.Character; +import io.runescript.plugin.lang.doc.lexer.RsDocTokens; +import io.runescript.plugin.lang.doc.parser.RsDocKnownTag; + + +class _RsDocLexer implements FlexLexer { + + /** This character denotes the end of file */ + public static final int YYEOF = -1; + + /** initial size of the lookahead buffer */ + private static final int ZZ_BUFFERSIZE = 16384; + + /** lexical states */ + public static final int YYINITIAL = 0; + public static final int LINE_BEGINNING = 2; + public static final int CONTENTS_BEGINNING = 4; + public static final int TAG_BEGINNING = 6; + public static final int TAG_TEXT_BEGINNING = 8; + public static final int CONTENTS = 10; + public static final int CODE_BLOCK = 12; + public static final int CODE_BLOCK_LINE_BEGINNING = 14; + public static final int CODE_BLOCK_CONTENTS_BEGINNING = 16; + public static final int INDENTED_CODE_BLOCK = 18; + + /** + * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l + * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l + * at the beginning of a line + * l is of the form l = 2*k, k a non negative integer + */ + private static final int ZZ_LEXSTATE[] = { + 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, + 8, 8, 6, 6 + }; + + /** + * Top-level table for translating characters to character classes + */ + private static final int [] ZZ_CMAP_TOP = zzUnpackcmap_top(); + + private static final String ZZ_CMAP_TOP_PACKED_0 = + "\1\0\1\u0100\1\u0200\1\u0300\1\u0400\1\u0500\1\u0600\1\u0700"+ + "\1\u0800\1\u0900\1\u0a00\1\u0b00\1\u0c00\1\u0d00\1\u0e00\1\u0f00"+ + "\1\u1000\1\u0100\1\u1100\1\u1200\1\u1300\1\u0100\1\u1400\1\u1500"+ + "\1\u1600\1\u1700\1\u1800\1\u1900\1\u1a00\1\u1b00\1\u0100\1\u1c00"+ + "\1\u1d00\1\u1e00\12\u1f00\1\u2000\1\u2100\1\u2200\1\u1f00\1\u2300"+ + "\1\u2400\2\u1f00\31\u0100\1\u1b00\121\u0100\1\u2500\4\u0100\1\u2600"+ + "\1\u0100\1\u2700\1\u2800\1\u2900\1\u2a00\1\u2b00\1\u2c00\53\u0100"+ + "\1\u2d00\10\u2e00\31\u1f00\1\u0100\1\u2f00\1\u3000\1\u0100\1\u3100"+ + "\1\u3200\1\u3300\1\u3400\1\u3500\1\u3600\1\u3700\1\u3800\1\u3900"+ + "\1\u0100\1\u3a00\1\u3b00\1\u3c00\1\u3d00\1\u3e00\1\u3f00\1\u4000"+ + "\1\u4100\1\u4200\1\u4300\1\u4400\1\u4500\1\u4600\1\u4700\1\u4800"+ + "\1\u4900\1\u4a00\1\u4b00\1\u4c00\1\u4d00\1\u1f00\1\u4e00\1\u4f00"+ + "\1\u5000\1\u5100\3\u0100\1\u5200\1\u5300\1\u5400\12\u1f00\4\u0100"+ + "\1\u5500\17\u1f00\2\u0100\1\u5600\41\u1f00\2\u0100\1\u5700\1\u5800"+ + "\2\u1f00\1\u5900\1\u5a00\27\u0100\1\u5b00\4\u0100\1\u5c00\1\u5d00"+ + "\42\u1f00\1\u0100\1\u5e00\1\u5f00\11\u1f00\1\u6000\27\u1f00\1\u6100"+ + "\1\u6200\1\u6300\1\u6400\11\u1f00\1\u6500\1\u6600\5\u1f00\1\u6700"+ + "\1\u6800\2\u1f00\1\u6900\1\u1f00\1\u6a00\21\u1f00\246\u0100\1\u6b00"+ + "\20\u0100\1\u6c00\1\u6d00\25\u0100\1\u6e00\34\u0100\1\u6f00\14\u1f00"+ + "\2\u0100\1\u7000\5\u1f00\23\u0100\1\u7100\u0dec\u1f00"; + + private static int [] zzUnpackcmap_top() { + int [] result = new int[4352]; + int offset = 0; + offset = zzUnpackcmap_top(ZZ_CMAP_TOP_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackcmap_top(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + + + /** + * Second-level tables for translating characters to character classes + */ + private static final int [] ZZ_CMAP_BLOCKS = zzUnpackcmap_blocks(); + + private static final String ZZ_CMAP_BLOCKS_PACKED_0 = + "\11\0\1\1\1\2\1\3\1\4\1\3\22\0\1\5"+ + "\3\0\1\6\3\0\1\7\1\10\1\11\1\0\1\12"+ + "\1\0\1\12\1\13\12\14\1\12\5\0\1\15\32\6"+ + "\1\16\1\17\1\20\1\0\1\6\1\21\32\6\3\0"+ + "\1\22\6\0\1\3\34\0\4\6\4\0\1\6\12\0"+ + "\1\6\4\0\1\6\5\0\27\6\1\0\37\6\1\0"+ + "\u01ca\6\4\0\14\6\16\0\5\6\7\0\1\6\1\0"+ + "\1\6\201\0\5\6\1\0\2\6\2\0\4\6\1\0"+ + "\1\6\6\0\1\6\1\0\3\6\1\0\1\6\1\0"+ + "\24\6\1\0\123\6\1\0\213\6\10\0\246\6\1\0"+ + "\46\6\2\0\1\6\6\0\51\6\6\0\1\6\100\0"+ + "\33\6\4\0\4\6\30\0\1\6\24\0\53\6\43\0"+ + "\2\6\1\0\143\6\1\0\1\6\17\0\2\6\7\0"+ + "\2\6\12\0\3\6\2\0\1\6\20\0\1\6\1\0"+ + "\36\6\35\0\131\6\13\0\1\6\30\0\41\6\11\0"+ + "\2\6\4\0\1\6\3\0\30\6\4\0\1\6\11\0"+ + "\1\6\3\0\1\6\27\0\31\6\7\0\13\6\65\0"+ + "\25\6\1\0\22\6\74\0\66\6\3\0\1\6\22\0"+ + "\1\6\7\0\12\6\17\0\20\6\4\0\10\6\2\0"+ + "\2\6\2\0\26\6\1\0\7\6\1\0\1\6\3\0"+ + "\4\6\3\0\1\6\20\0\1\6\15\0\2\6\1\0"+ + "\3\6\16\0\4\6\7\0\2\6\10\0\6\6\4\0"+ + "\2\6\2\0\26\6\1\0\7\6\1\0\2\6\1\0"+ + "\2\6\1\0\2\6\37\0\4\6\1\0\1\6\23\0"+ + "\3\6\20\0\11\6\1\0\3\6\1\0\26\6\1\0"+ + "\7\6\1\0\2\6\1\0\5\6\3\0\1\6\22\0"+ + "\1\6\17\0\2\6\17\0\1\6\7\0\1\6\13\0"+ + "\10\6\2\0\2\6\2\0\26\6\1\0\7\6\1\0"+ + "\2\6\1\0\5\6\3\0\1\6\36\0\2\6\1\0"+ + "\3\6\17\0\1\6\21\0\1\6\1\0\6\6\3\0"+ + "\3\6\1\0\4\6\3\0\2\6\1\0\1\6\1\0"+ + "\2\6\3\0\2\6\3\0\3\6\3\0\14\6\26\0"+ + "\1\6\50\0\1\6\13\0\10\6\1\0\3\6\1\0"+ + "\27\6\1\0\20\6\3\0\1\6\32\0\3\6\5\0"+ + "\2\6\36\0\1\6\4\0\10\6\1\0\3\6\1\0"+ + "\27\6\1\0\12\6\1\0\5\6\3\0\1\6\40\0"+ + "\1\6\1\0\2\6\17\0\2\6\21\0\11\6\1\0"+ + "\3\6\1\0\51\6\2\0\1\6\20\0\1\6\5\0"+ + "\3\6\10\0\3\6\30\0\6\6\5\0\22\6\3\0"+ + "\30\6\1\0\11\6\1\0\1\6\2\0\7\6\72\0"+ + "\60\6\1\0\2\6\13\0\10\6\72\0\2\6\1\0"+ + "\1\6\1\0\5\6\1\0\30\6\1\0\1\6\1\0"+ + "\12\6\1\0\2\6\11\0\1\6\2\0\5\6\1\0"+ + "\1\6\25\0\4\6\40\0\1\6\77\0\10\6\1\0"+ + "\44\6\33\0\5\6\163\0\53\6\24\0\1\6\20\0"+ + "\6\6\4\0\4\6\3\0\1\6\3\0\2\6\7\0"+ + "\3\6\4\0\15\6\14\0\1\6\21\0\46\6\1\0"+ + "\1\6\5\0\1\6\2\0\53\6\1\0\115\6\1\0"+ + "\4\6\2\0\7\6\1\0\1\6\1\0\4\6\2\0"+ + "\51\6\1\0\4\6\2\0\41\6\1\0\4\6\2\0"+ + "\7\6\1\0\1\6\1\0\4\6\2\0\17\6\1\0"+ + "\71\6\1\0\4\6\2\0\103\6\45\0\20\6\20\0"+ + "\126\6\2\0\6\6\3\0\u016c\6\2\0\21\6\1\0"+ + "\32\6\5\0\113\6\3\0\13\6\7\0\15\6\1\0"+ + "\4\6\16\0\22\6\16\0\22\6\16\0\15\6\1\0"+ + "\3\6\17\0\64\6\43\0\1\6\3\0\2\6\103\0"+ + "\131\6\7\0\5\6\2\0\42\6\1\0\1\6\5\0"+ + "\106\6\12\0\37\6\61\0\36\6\2\0\5\6\13\0"+ + "\54\6\4\0\32\6\66\0\27\6\11\0\65\6\122\0"+ + "\1\6\135\0\57\6\21\0\7\6\67\0\36\6\15\0"+ + "\2\6\12\0\54\6\32\0\44\6\51\0\3\6\12\0"+ + "\44\6\2\0\11\6\7\0\53\6\2\0\3\6\51\0"+ + "\4\6\1\0\6\6\1\0\2\6\3\0\1\6\5\0"+ + "\300\6\100\0\26\6\2\0\6\6\2\0\46\6\2\0"+ + "\6\6\2\0\10\6\1\0\1\6\1\0\1\6\1\0"+ + "\1\6\1\0\37\6\2\0\65\6\1\0\7\6\1\0"+ + "\1\6\3\0\3\6\1\0\7\6\3\0\4\6\2\0"+ + "\6\6\4\0\15\6\5\0\3\6\1\0\7\6\53\0"+ + "\2\3\25\0\2\6\23\0\1\6\34\0\1\6\15\0"+ + "\1\6\20\0\15\6\3\0\40\6\102\0\1\6\4\0"+ + "\1\6\2\0\12\6\1\0\1\6\3\0\5\6\6\0"+ + "\1\6\1\0\1\6\1\0\1\6\1\0\4\6\1\0"+ + "\13\6\2\0\4\6\5\0\5\6\4\0\1\6\21\0"+ + "\51\6\u0177\0\57\6\1\0\57\6\1\0\205\6\6\0"+ + "\4\6\3\0\2\6\14\0\46\6\1\0\1\6\5\0"+ + "\1\6\2\0\70\6\7\0\1\6\20\0\27\6\11\0"+ + "\7\6\1\0\7\6\1\0\7\6\1\0\7\6\1\0"+ + "\7\6\1\0\7\6\1\0\7\6\1\0\7\6\120\0"+ + "\1\6\325\0\3\6\31\0\11\6\7\0\5\6\2\0"+ + "\5\6\4\0\126\6\6\0\3\6\1\0\132\6\1\0"+ + "\4\6\5\0\53\6\1\0\136\6\21\0\40\6\60\0"+ + "\u010d\6\3\0\215\6\103\0\56\6\2\0\15\6\3\0"+ + "\20\6\12\0\2\6\24\0\57\6\20\0\37\6\2\0"+ + "\120\6\47\0\11\6\2\0\147\6\2\0\65\6\2\0"+ + "\11\6\52\0\15\6\1\0\3\6\1\0\4\6\1\0"+ + "\27\6\25\0\1\6\7\0\64\6\16\0\62\6\76\0"+ + "\6\6\3\0\1\6\1\0\2\6\13\0\34\6\12\0"+ + "\27\6\31\0\35\6\7\0\57\6\34\0\1\6\20\0"+ + "\5\6\1\0\12\6\12\0\5\6\1\0\51\6\27\0"+ + "\3\6\1\0\10\6\24\0\27\6\3\0\1\6\3\0"+ + "\62\6\1\0\1\6\3\0\2\6\2\0\5\6\2\0"+ + "\1\6\1\0\1\6\30\0\3\6\2\0\13\6\7\0"+ + "\3\6\14\0\6\6\2\0\6\6\2\0\6\6\11\0"+ + "\7\6\1\0\7\6\1\0\53\6\1\0\16\6\6\0"+ + "\163\6\35\0\244\6\14\0\27\6\4\0\61\6\4\0"+ + "\u0100\3\156\6\2\0\152\6\46\0\7\6\14\0\5\6"+ + "\5\0\1\6\1\0\12\6\1\0\15\6\1\0\5\6"+ + "\1\0\1\6\1\0\2\6\1\0\2\6\1\0\154\6"+ + "\41\0\153\6\22\0\100\6\2\0\66\6\50\0\15\6"+ + "\66\0\2\6\30\0\3\6\31\0\1\6\6\0\5\6"+ + "\1\0\207\6\7\0\1\6\34\0\32\6\4\0\1\6"+ + "\1\0\32\6\13\0\131\6\3\0\6\6\2\0\6\6"+ + "\2\0\6\6\2\0\3\6\3\0\2\6\3\0\2\6"+ + "\31\0\14\6\1\0\32\6\1\0\23\6\1\0\2\6"+ + "\1\0\17\6\2\0\16\6\42\0\173\6\105\0\65\6"+ + "\u010b\0\35\6\3\0\61\6\57\0\40\6\15\0\36\6"+ + "\5\0\46\6\12\0\36\6\2\0\44\6\4\0\10\6"+ + "\1\0\5\6\52\0\236\6\22\0\44\6\4\0\44\6"+ + "\4\0\50\6\10\0\64\6\234\0\67\6\11\0\26\6"+ + "\12\0\10\6\230\0\6\6\2\0\1\6\1\0\54\6"+ + "\1\0\2\6\3\0\1\6\2\0\27\6\12\0\27\6"+ + "\11\0\37\6\101\0\23\6\1\0\2\6\12\0\26\6"+ + "\12\0\32\6\106\0\70\6\6\0\2\6\100\0\1\6"+ + "\17\0\4\6\1\0\3\6\1\0\35\6\52\0\35\6"+ + "\3\0\35\6\43\0\10\6\1\0\34\6\33\0\66\6"+ + "\12\0\26\6\12\0\23\6\15\0\22\6\156\0\111\6"+ + "\67\0\63\6\15\0\63\6\15\0\44\6\u015c\0\52\6"+ + "\6\0\2\6\116\0\35\6\12\0\1\6\10\0\26\6"+ + "\152\0\25\6\33\0\27\6\14\0\65\6\113\0\55\6"+ + "\40\0\31\6\32\0\44\6\35\0\1\6\2\0\1\6"+ + "\10\0\43\6\3\0\1\6\14\0\60\6\16\0\4\6"+ + "\25\0\1\6\1\0\1\6\43\0\22\6\1\0\31\6"+ + "\124\0\7\6\1\0\1\6\1\0\4\6\1\0\17\6"+ + "\1\0\12\6\7\0\57\6\46\0\10\6\2\0\2\6"+ + "\2\0\26\6\1\0\7\6\1\0\2\6\1\0\5\6"+ + "\3\0\1\6\22\0\1\6\14\0\5\6\236\0\65\6"+ + "\22\0\4\6\24\0\3\6\36\0\60\6\24\0\2\6"+ + "\1\0\1\6\270\0\57\6\51\0\4\6\44\0\60\6"+ + "\24\0\1\6\73\0\53\6\15\0\1\6\107\0\33\6"+ + "\345\0\54\6\164\0\100\6\37\0\10\6\2\0\1\6"+ + "\2\0\10\6\1\0\2\6\1\0\30\6\17\0\1\6"+ + "\1\0\1\6\136\0\10\6\2\0\47\6\20\0\1\6"+ + "\1\0\1\6\34\0\1\6\12\0\50\6\7\0\1\6"+ + "\25\0\1\6\13\0\56\6\23\0\1\6\42\0\71\6"+ + "\7\0\11\6\1\0\45\6\21\0\1\6\61\0\36\6"+ + "\160\0\7\6\1\0\2\6\1\0\46\6\25\0\1\6"+ + "\31\0\6\6\1\0\2\6\1\0\40\6\16\0\1\6"+ + "\u0147\0\23\6\275\0\1\6\54\0\4\6\37\0\232\6"+ + "\146\0\157\6\21\0\304\6\274\0\57\6\321\0\107\6"+ + "\271\0\71\6\7\0\37\6\161\0\36\6\22\0\60\6"+ + "\20\0\4\6\37\0\25\6\5\0\23\6\260\0\100\6"+ + "\200\0\113\6\5\0\1\6\102\0\15\6\100\0\2\6"+ + "\1\0\1\6\34\0\370\6\10\0\326\6\52\0\11\6"+ + "\367\0\37\6\61\0\3\6\21\0\4\6\10\0\u018c\6"+ + "\4\0\153\6\5\0\15\6\3\0\11\6\7\0\12\6"+ + "\146\0\125\6\1\0\107\6\1\0\2\6\2\0\1\6"+ + "\2\0\2\6\2\0\4\6\1\0\14\6\1\0\1\6"+ + "\1\0\7\6\1\0\101\6\1\0\4\6\2\0\10\6"+ + "\1\0\7\6\1\0\34\6\1\0\4\6\1\0\5\6"+ + "\1\0\1\6\3\0\7\6\1\0\u0154\6\2\0\31\6"+ + "\1\0\31\6\1\0\37\6\1\0\31\6\1\0\37\6"+ + "\1\0\31\6\1\0\37\6\1\0\31\6\1\0\37\6"+ + "\1\0\31\6\1\0\10\6\64\0\55\6\12\0\7\6"+ + "\20\0\1\6\u0171\0\54\6\23\0\306\6\73\0\104\6"+ + "\7\0\1\6\u0164\0\1\6\117\0\4\6\1\0\33\6"+ + "\1\0\2\6\1\0\1\6\2\0\1\6\1\0\12\6"+ + "\1\0\4\6\1\0\1\6\1\0\1\6\6\0\1\6"+ + "\4\0\1\6\1\0\1\6\1\0\1\6\1\0\3\6"+ + "\1\0\2\6\1\0\1\6\2\0\1\6\1\0\1\6"+ + "\1\0\1\6\1\0\1\6\1\0\1\6\1\0\2\6"+ + "\1\0\1\6\2\0\4\6\1\0\7\6\1\0\4\6"+ + "\1\0\4\6\1\0\1\6\1\0\12\6\1\0\21\6"+ + "\5\0\3\6\1\0\5\6\1\0\21\6\104\0\336\6"+ + "\42\0\65\6\13\0\336\6\2\0\u0182\6\16\0\u0131\6"+ + "\37\0\36\6\342\0\113\6\265\0"; + + private static int [] zzUnpackcmap_blocks() { + int [] result = new int[29184]; + int offset = 0; + offset = zzUnpackcmap_blocks(ZZ_CMAP_BLOCKS_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackcmap_blocks(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + + /** + * Translates DFA states to action switch labels. + */ + private static final int [] ZZ_ACTION = zzUnpackAction(); + + private static final String ZZ_ACTION_PACKED_0 = + "\11\0\3\1\1\2\1\3\2\4\1\5\6\2\1\6"+ + "\1\7\2\2\1\10\1\11\1\10\1\12\2\10\1\0"+ + "\1\13\1\0\1\4\3\0\1\14\2\0\1\15\4\0"+ + "\1\16\1\4\3\0\1\17\1\0\1\20\1\0\1\21"+ + "\1\0\1\3\1\0\2\22\1\23\3\0\1\24\1\23"; + + private static int [] zzUnpackAction() { + int [] result = new int[69]; + int offset = 0; + offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAction(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + + + /** + * Translates a state to a row index in the transition table + */ + private static final int [] ZZ_ROWMAP = zzUnpackRowMap(); + + private static final String ZZ_ROWMAP_PACKED_0 = + "\0\0\0\23\0\46\0\71\0\114\0\137\0\162\0\205"+ + "\0\230\0\253\0\276\0\321\0\253\0\344\0\367\0\u010a"+ + "\0\u011d\0\u0130\0\u0143\0\u0156\0\u0169\0\276\0\u017c\0\u018f"+ + "\0\u01a2\0\u01b5\0\u01c8\0\253\0\u01db\0\276\0\u01ee\0\u0201"+ + "\0\u0214\0\276\0\253\0\u0227\0\u023a\0\u024d\0\u0260\0\u0273"+ + "\0\253\0\u0286\0\u0299\0\u02ac\0\u02bf\0\u02d2\0\u02e5\0\u02f8"+ + "\0\253\0\u030b\0\u031e\0\u0331\0\u0344\0\u0357\0\u036a\0\253"+ + "\0\u037d\0\253\0\u0390\0\u030b\0\u03a3\0\u024d\0\u0273\0\u024d"+ + "\0\u03b6\0\u03c9\0\u03dc\0\u03ef\0\253"; + + private static int [] zzUnpackRowMap() { + int [] result = new int[69]; + int offset = 0; + offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackRowMap(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length() - 1; + while (i < l) { + int high = packed.charAt(i++) << 16; + result[j++] = high | packed.charAt(i++); + } + return j; + } + + /** + * The transition table of the DFA + */ + private static final int [] ZZ_TRANS = zzUnpacktrans(); + + private static final String ZZ_TRANS_PACKED_0 = + "\11\12\1\13\1\12\1\14\7\12\1\15\1\16\1\17"+ + "\1\15\1\17\1\20\3\15\1\21\4\15\1\22\1\23"+ + "\1\15\1\24\1\25\1\15\1\16\1\17\1\15\1\17"+ + "\1\20\3\15\1\26\3\15\1\27\1\22\1\23\1\15"+ + "\1\24\1\25\1\15\2\30\1\15\2\30\1\31\2\15"+ + "\1\26\4\15\1\32\5\15\2\30\1\15\2\30\3\15"+ + "\1\26\4\15\1\33\5\15\1\16\1\17\1\15\1\17"+ + "\1\20\3\15\1\26\4\15\1\22\1\23\1\15\1\24"+ + "\1\25\1\34\2\35\1\34\2\35\3\34\1\36\12\34"+ + "\2\35\1\34\2\35\3\34\1\37\7\34\1\40\1\41"+ + "\1\34\2\35\1\34\2\35\3\34\1\36\7\34\1\40"+ + "\1\41\34\0\1\42\1\0\1\43\20\0\1\44\12\0"+ + "\1\16\1\17\1\0\2\17\16\0\2\17\1\0\2\17"+ + "\16\0\2\17\1\0\1\17\1\45\26\0\1\21\1\0"+ + "\1\43\7\0\6\46\1\47\7\46\1\0\1\46\1\50"+ + "\2\46\16\0\1\51\1\0\1\51\23\0\1\52\23\0"+ + "\1\53\6\0\1\54\15\0\2\30\1\0\2\30\23\0"+ + "\1\31\3\0\1\31\1\0\1\31\14\0\1\55\22\0"+ + "\1\56\15\0\2\35\1\0\2\35\26\0\1\37\1\0"+ + "\1\43\30\0\1\57\23\0\1\60\11\0\1\61\12\0"+ + "\2\17\1\0\1\17\1\62\15\0\16\46\1\0\1\46"+ + "\1\50\10\46\1\47\3\46\1\47\1\63\1\47\1\46"+ + "\1\0\1\46\1\64\11\46\1\65\6\46\1\0\1\46"+ + "\1\50\2\46\21\0\1\66\23\0\1\66\6\0\1\54"+ + "\5\0\1\54\14\0\1\55\3\0\1\55\1\67\1\55"+ + "\3\0\1\70\10\0\1\56\3\0\1\56\1\71\1\56"+ + "\3\0\1\72\23\0\1\73\23\0\1\73\1\0\2\17"+ + "\1\0\1\17\1\74\15\0\6\46\1\75\7\46\1\0"+ + "\1\46\1\50\2\46\7\76\1\65\6\76\1\0\1\76"+ + "\1\77\2\76\10\65\1\100\5\65\1\101\4\65\2\66"+ + "\3\0\16\66\6\0\1\102\22\0\1\103\15\0\1\73"+ + "\1\104\1\0\2\73\15\0\6\46\1\75\3\46\1\75"+ + "\1\46\1\75\1\46\1\0\1\46\1\64\2\46\10\101"+ + "\1\105\12\101\6\0\1\102\3\0\1\102\1\0\1\102"+ + "\3\0\1\70\10\0\1\103\3\0\1\103\1\0\1\103"+ + "\3\0\1\72\3\0\1\104\2\0\2\104\15\0"; + + private static int [] zzUnpacktrans() { + int [] result = new int[1026]; + int offset = 0; + offset = zzUnpacktrans(ZZ_TRANS_PACKED_0, offset, result); + return result; + } + + private static int zzUnpacktrans(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + value--; + do result[j++] = value; while (--count > 0); + } + return j; + } + + + /* error codes */ + private static final int ZZ_UNKNOWN_ERROR = 0; + private static final int ZZ_NO_MATCH = 1; + private static final int ZZ_PUSHBACK_2BIG = 2; + + /* error messages for the codes above */ + private static final String[] ZZ_ERROR_MSG = { + "Unknown internal scanner error", + "Error: could not match input", + "Error: pushback value was too large" + }; + + /** + * ZZ_ATTRIBUTE[aState] contains the attributes of state {@code aState} + */ + private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute(); + + private static final String ZZ_ATTRIBUTE_PACKED_0 = + "\11\0\1\11\2\1\1\11\16\1\1\11\5\1\1\0"+ + "\1\11\1\0\1\1\3\0\1\11\2\0\1\1\4\0"+ + "\1\11\1\1\3\0\1\1\1\0\1\11\1\0\1\11"+ + "\1\0\1\1\1\0\3\1\3\0\1\1\1\11"; + + private static int [] zzUnpackAttribute() { + int [] result = new int[69]; + int offset = 0; + offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAttribute(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + + /** the input device */ + private java.io.Reader zzReader; + + /** the current state of the DFA */ + private int zzState; + + /** the current lexical state */ + private int zzLexicalState = YYINITIAL; + + /** this buffer contains the current text to be matched and is + the source of the yytext() string */ + private CharSequence zzBuffer = ""; + + /** the textposition at the last accepting state */ + private int zzMarkedPos; + + /** the current text position in the buffer */ + private int zzCurrentPos; + + /** startRead marks the beginning of the yytext() string in the buffer */ + private int zzStartRead; + + /** endRead marks the last character in the buffer, that has been read + from input */ + private int zzEndRead; + + /** zzAtEOF == true <=> the scanner is at the EOF */ + private boolean zzAtEOF; + + /** Number of newlines encountered up to the start of the matched text. */ + @SuppressWarnings("unused") + private int yyline; + + /** Number of characters from the last newline up to the start of the matched text. */ + @SuppressWarnings("unused") + protected int yycolumn; + + /** Number of characters up to the start of the matched text. */ + @SuppressWarnings("unused") + private long yychar; + + /** Whether the scanner is currently at the beginning of a line. */ + @SuppressWarnings("unused") + private boolean zzAtBOL = true; + + /** Whether the user-EOF-code has already been executed. */ + private boolean zzEOFDone; + + /* user code: */ + public _RsDocLexer() { + this((java.io.Reader)null); + } + + private boolean isLastToken() { + return zzMarkedPos == zzBuffer.length(); + } + + private boolean yytextContainLineBreaks() { + return CharArrayUtil.containLineBreaks(zzBuffer, zzStartRead, zzMarkedPos); + } + + private boolean nextIsNotWhitespace() { + return zzMarkedPos <= zzBuffer.length() && !Character.isWhitespace(zzBuffer.charAt(zzMarkedPos + 1)); + } + + private boolean prevIsNotWhitespace() { + return zzMarkedPos != 0 && !Character.isWhitespace(zzBuffer.charAt(zzMarkedPos - 1)); + } + + + /** + * Creates a new scanner + * + * @param in the java.io.Reader to read input from. + */ + _RsDocLexer(java.io.Reader in) { + this.zzReader = in; + } + + + /** Returns the maximum size of the scanner buffer, which limits the size of tokens. */ + private int zzMaxBufferLen() { + return Integer.MAX_VALUE; + } + + /** Whether the scanner buffer can grow to accommodate a larger token. */ + private boolean zzCanGrow() { + return true; + } + + /** + * Translates raw input code points to DFA table row + */ + private static int zzCMap(int input) { + int offset = input & 255; + return offset == input ? ZZ_CMAP_BLOCKS[offset] : ZZ_CMAP_BLOCKS[ZZ_CMAP_TOP[input >> 8] | offset]; + } + + public final int getTokenStart() { + return zzStartRead; + } + + public final int getTokenEnd() { + return getTokenStart() + yylength(); + } + + public void reset(CharSequence buffer, int start, int end, int initialState) { + zzBuffer = buffer; + zzCurrentPos = zzMarkedPos = zzStartRead = start; + zzAtEOF = false; + zzAtBOL = true; + zzEndRead = end; + yybegin(initialState); + } + + /** + * Refills the input buffer. + * + * @return {@code false}, iff there was new input. + * + * @exception java.io.IOException if any I/O-Error occurs + */ + private boolean zzRefill() throws java.io.IOException { + return true; + } + + + /** + * Returns the current lexical state. + */ + public final int yystate() { + return zzLexicalState; + } + + + /** + * Enters a new lexical state + * + * @param newState the new lexical state + */ + public final void yybegin(int newState) { + zzLexicalState = newState; + } + + + /** + * Returns the text matched by the current regular expression. + */ + public final CharSequence yytext() { + return zzBuffer.subSequence(zzStartRead, zzMarkedPos); + } + + + /** + * Returns the character at position {@code pos} from the + * matched text. + * + * It is equivalent to yytext().charAt(pos), but faster + * + * @param pos the position of the character to fetch. + * A value from 0 to yylength()-1. + * + * @return the character at position pos + */ + public final char yycharat(int pos) { + return zzBuffer.charAt(zzStartRead+pos); + } + + + /** + * Returns the length of the matched text region. + */ + public final int yylength() { + return zzMarkedPos-zzStartRead; + } + + + /** + * Reports an error that occurred while scanning. + * + * In a wellformed scanner (no or only correct usage of + * yypushback(int) and a match-all fallback rule) this method + * will only be called with things that "Can't Possibly Happen". + * If this method is called, something is seriously wrong + * (e.g. a JFlex bug producing a faulty scanner etc.). + * + * Usual syntax/scanner level error handling should be done + * in error fallback rules. + * + * @param errorCode the code of the errormessage to display + */ + private void zzScanError(int errorCode) { + String message; + try { + message = ZZ_ERROR_MSG[errorCode]; + } + catch (ArrayIndexOutOfBoundsException e) { + message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR]; + } + + throw new Error(message); + } + + + /** + * Pushes the specified amount of characters back into the input stream. + * + * They will be read again by then next call of the scanning method + * + * @param number the number of characters to be read again. + * This number must not be greater than yylength()! + */ + public void yypushback(int number) { + if ( number > yylength() ) + zzScanError(ZZ_PUSHBACK_2BIG); + + zzMarkedPos -= number; + } + + + /** + * Contains user EOF-code, which will be executed exactly once, + * when the end of file is reached + */ + private void zzDoEOF() { + if (!zzEOFDone) { + zzEOFDone = true; + + return; + } + } + + + /** + * Resumes scanning until the next regular expression is matched, + * the end of input is encountered or an I/O-Error occurs. + * + * @return the next token + * @exception java.io.IOException if any I/O-Error occurs + */ + public IElementType advance() throws java.io.IOException + { + int zzInput; + int zzAction; + + // cached fields: + int zzCurrentPosL; + int zzMarkedPosL; + int zzEndReadL = zzEndRead; + CharSequence zzBufferL = zzBuffer; + + int [] zzTransL = ZZ_TRANS; + int [] zzRowMapL = ZZ_ROWMAP; + int [] zzAttrL = ZZ_ATTRIBUTE; + + while (true) { + zzMarkedPosL = zzMarkedPos; + + zzAction = -1; + + zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; + + zzState = ZZ_LEXSTATE[zzLexicalState]; + + // set up zzAction for empty match case: + int zzAttributes = zzAttrL[zzState]; + if ( (zzAttributes & 1) == 1 ) { + zzAction = zzState; + } + + + zzForAction: { + while (true) { + + if (zzCurrentPosL < zzEndReadL) { + zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL); + zzCurrentPosL += Character.charCount(zzInput); + } + else if (zzAtEOF) { + zzInput = YYEOF; + break zzForAction; + } + else { + // store back cached positions + zzCurrentPos = zzCurrentPosL; + zzMarkedPos = zzMarkedPosL; + boolean eof = zzRefill(); + // get translated positions and possibly new buffer + zzCurrentPosL = zzCurrentPos; + zzMarkedPosL = zzMarkedPos; + zzBufferL = zzBuffer; + zzEndReadL = zzEndRead; + if (eof) { + zzInput = YYEOF; + break zzForAction; + } + else { + zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL); + zzCurrentPosL += Character.charCount(zzInput); + } + } + int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMap(zzInput) ]; + if (zzNext == -1) break zzForAction; + zzState = zzNext; + + zzAttributes = zzAttrL[zzState]; + if ( (zzAttributes & 1) == 1 ) { + zzAction = zzState; + zzMarkedPosL = zzCurrentPosL; + if ( (zzAttributes & 8) == 8 ) break zzForAction; + } + + } + } + + // store back cached position + zzMarkedPos = zzMarkedPosL; + + if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { + zzAtEOF = true; + zzDoEOF(); + return null; + } + else { + switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { + case 1: + { return TokenType.BAD_CHARACTER; + } + // fall through + case 21: break; + case 2: + { yybegin(CONTENTS); + return RsDocTokens.TEXT; + } + // fall through + case 22: break; + case 3: + { if(yystate() == CONTENTS_BEGINNING) { + yybegin(INDENTED_CODE_BLOCK); + return RsDocTokens.CODE_BLOCK_TEXT; + } + } + // fall through + case 23: break; + case 4: + { if (yytextContainLineBreaks()) { + yybegin(LINE_BEGINNING); + return TokenType.WHITE_SPACE; + } else { + yybegin(yystate() == CONTENTS_BEGINNING ? CONTENTS_BEGINNING : CONTENTS); + return RsDocTokens.TEXT; // internal white space + } + } + // fall through + case 24: break; + case 5: + { yybegin(CONTENTS_BEGINNING); + return RsDocTokens.LEADING_ASTERISK; + } + // fall through + case 25: break; + case 6: + { if (yytextContainLineBreaks()) { + yybegin(LINE_BEGINNING); + } + return TokenType.WHITE_SPACE; + } + // fall through + case 26: break; + case 7: + { yybegin(TAG_TEXT_BEGINNING); + return RsDocTokens.MARKDOWN_LINK; + } + // fall through + case 27: break; + case 8: + { yybegin(yystate() == INDENTED_CODE_BLOCK ? INDENTED_CODE_BLOCK : CODE_BLOCK); + return RsDocTokens.CODE_BLOCK_TEXT; + } + // fall through + case 28: break; + case 9: + { if (yytextContainLineBreaks()) { + yybegin(yystate() == INDENTED_CODE_BLOCK ? LINE_BEGINNING : CODE_BLOCK_LINE_BEGINNING); + return TokenType.WHITE_SPACE; + } + return RsDocTokens.CODE_BLOCK_TEXT; + } + // fall through + case 29: break; + case 10: + { yybegin(CODE_BLOCK_CONTENTS_BEGINNING); + return RsDocTokens.LEADING_ASTERISK; + } + // fall through + case 30: break; + case 11: + { if (isLastToken()) return RsDocTokens.END; + else return RsDocTokens.TEXT; + } + // fall through + case 31: break; + case 12: + { yybegin(CONTENTS); + return RsDocTokens.MARKDOWN_ESCAPED_CHAR; + } + // fall through + case 32: break; + case 13: + { RsDocKnownTag tag = RsDocKnownTag.Companion.findByTagName(zzBuffer.subSequence(zzStartRead, zzMarkedPos)); + yybegin(tag != null && tag.isReferenceRequired() ? TAG_BEGINNING : TAG_TEXT_BEGINNING); + return RsDocTokens.TAG_NAME; + } + // fall through + case 33: break; + case 14: + { yybegin(CONTENTS_BEGINNING); + return RsDocTokens.START; + } + // fall through + case 34: break; + case 15: + { yybegin(CODE_BLOCK_LINE_BEGINNING); + return RsDocTokens.TEXT; + } + // fall through + case 35: break; + case 16: + { yybegin(TAG_TEXT_BEGINNING); + return RsDocTokens.MARKDOWN_LINK; + } + // fall through + case 36: break; + case 17: + { yybegin(CONTENTS); + return RsDocTokens.MARKDOWN_LINK; + } + // fall through + case 37: break; + case 18: + // lookahead expression with fixed lookahead length + zzMarkedPos = Character.offsetByCodePoints + (zzBufferL, zzMarkedPos, -1); + { yybegin(CONTENTS); + return RsDocTokens.MARKDOWN_LINK; + } + // fall through + case 38: break; + case 19: + { yybegin(CONTENTS); + return RsDocTokens.MARKDOWN_INLINE_LINK; + } + // fall through + case 39: break; + case 20: + // lookahead expression with fixed base length + zzMarkedPos = Character.offsetByCodePoints + (zzBufferL, zzStartRead, 3); + { // Code fence end + yybegin(CONTENTS); + return RsDocTokens.TEXT; + } + // fall through + case 40: break; + default: + zzScanError(ZZ_NO_MATCH); + } + } + } + } + + +} diff --git a/src/main/gen/io/runescript/plugin/lang/lexer/_RsLexer.java b/src/main/gen/io/runescript/plugin/lang/lexer/_RsLexer.java index e79c821..df604f7 100644 --- a/src/main/gen/io/runescript/plugin/lang/lexer/_RsLexer.java +++ b/src/main/gen/io/runescript/plugin/lang/lexer/_RsLexer.java @@ -28,6 +28,7 @@ class _RsLexer implements FlexLexer { public static final int STRING = 2; public static final int STRING_INTERPOLATION = 4; public static final int BLOCK_COMMENT = 6; + public static final int DOC_COMMENT = 8; /** * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l @@ -36,7 +37,7 @@ class _RsLexer implements FlexLexer { * l is of the form l = 2*k, k a non negative integer */ private static final int ZZ_LEXSTATE[] = { - 0, 0, 1, 1, 0, 0, 2, 2 + 0, 0, 1, 1, 0, 0, 2, 2, 2, 2 }; /** @@ -113,11 +114,12 @@ private static int zzUnpackcmap_blocks(String packed, int offset, int [] result) "\2\20\1\21\1\22\1\23\1\24\1\25\1\26\1\27"+ "\1\30\11\16\1\31\1\32\1\33\1\34\1\35\1\36"+ "\1\37\1\1\2\40\4\20\1\41\1\0\2\16\1\42"+ - "\1\43\4\16\1\44\4\16\7\0\1\35\1\45\2\0"+ - "\2\46\1\20\12\16\7\0\1\35\1\47\1\20\1\50"+ - "\1\16\1\51\1\52\1\16\1\53\1\16\1\54\1\16"+ - "\1\55\1\16\6\0\2\16\1\56\1\16\1\57\1\0"+ - "\1\35\2\16\1\60\1\0\1\16\1\61\1\16\1\62"; + "\1\43\4\16\1\44\4\16\7\0\1\35\1\45\1\0"+ + "\1\46\2\47\1\20\12\16\7\0\1\35\1\50\1\20"+ + "\1\51\1\16\1\52\1\53\1\16\1\54\1\16\1\55"+ + "\1\16\1\56\1\16\6\0\2\16\1\57\1\16\1\60"+ + "\1\0\1\35\2\16\1\61\1\0\1\16\1\62\1\16"+ + "\1\63"; private static int [] zzUnpackAction() { int [] result = new int[132]; @@ -325,9 +327,9 @@ private static int zzUnpacktrans(String packed, int offset, int [] result) { private static final String ZZ_ATTRIBUTE_PACKED_0 = "\3\0\12\11\1\1\1\11\6\1\1\11\1\1\1\11"+ "\1\1\3\11\11\1\4\11\1\1\1\11\2\1\1\11"+ - "\6\1\1\0\2\1\2\11\11\1\7\0\2\11\2\0"+ - "\1\11\14\1\7\0\1\1\1\11\1\1\1\11\12\1"+ - "\6\0\5\1\1\0\4\1\1\0\4\1"; + "\6\1\1\0\2\1\2\11\11\1\7\0\2\11\1\0"+ + "\1\1\1\11\14\1\7\0\1\1\1\11\1\1\1\11"+ + "\12\1\6\0\5\1\1\0\4\1\1\0\4\1"; private static int [] zzUnpackAttribute() { int [] result = new int[132]; @@ -655,10 +657,17 @@ else if (zzAtEOF) { zzAtEOF = true; switch (zzLexicalState) { case BLOCK_COMMENT: { - popState(); - return RsTokenTypes.BLOCK_COMMENT; + int state = yystate(); + popState(); + return state == DOC_COMMENT ? RsTokenTypes.DOC_COMMENT : RsTokenTypes.BLOCK_COMMENT; } // fall though case 133: break; + case DOC_COMMENT: { + int state = yystate(); + popState(); + return state == DOC_COMMENT ? RsTokenTypes.DOC_COMMENT : RsTokenTypes.BLOCK_COMMENT; + } // fall though + case 134: break; default: return null; } @@ -669,67 +678,67 @@ else if (zzAtEOF) { { return TokenType.BAD_CHARACTER; } // fall through - case 51: break; + case 52: break; case 2: { return TokenType.WHITE_SPACE; } // fall through - case 52: break; + case 53: break; case 3: { return EXCEL; } // fall through - case 53: break; + case 54: break; case 4: { pushState(STRING); return STRING_START; } // fall through - case 54: break; + case 55: break; case 5: { return DOLLAR; } // fall through - case 55: break; + case 56: break; case 6: { return PERCENT; } // fall through - case 56: break; + case 57: break; case 7: { return AMPERSAND; } // fall through - case 57: break; + case 58: break; case 8: { return LPAREN; } // fall through - case 58: break; + case 59: break; case 9: { return RPAREN; } // fall through - case 59: break; + case 60: break; case 10: { return STAR; } // fall through - case 60: break; + case 61: break; case 11: { return PLUS; } // fall through - case 61: break; + case 62: break; case 12: { return COMMA; } // fall through - case 62: break; + case 63: break; case 13: { return MINUS; } // fall through - case 63: break; + case 64: break; case 14: { CharSequence lexeme = yytext(); for (String typeName: getTypeNames()) { @@ -769,37 +778,37 @@ else if (zzAtEOF) { return IDENTIFIER; } // fall through - case 64: break; + case 65: break; case 15: { return SLASH; } // fall through - case 65: break; + case 66: break; case 16: { return INTEGER; } // fall through - case 66: break; + case 67: break; case 17: { return COLON; } // fall through - case 67: break; + case 68: break; case 18: { return SEMICOLON; } // fall through - case 68: break; + case 69: break; case 19: { return LT; } // fall through - case 69: break; + case 70: break; case 20: { return EQUAL; } // fall through - case 70: break; + case 71: break; case 21: { if (yystate() == STRING_INTERPOLATION) { popState(); @@ -809,153 +818,159 @@ else if (zzAtEOF) { } } // fall through - case 71: break; + case 72: break; case 22: { return LBRACKET; } // fall through - case 72: break; + case 73: break; case 23: { return RBRACKET; } // fall through - case 73: break; + case 74: break; case 24: { return CARET; } // fall through - case 74: break; + case 75: break; case 25: { return LBRACE; } // fall through - case 75: break; + case 76: break; case 26: { return BAR; } // fall through - case 76: break; + case 77: break; case 27: { return RBRACE; } // fall through - case 77: break; + case 78: break; case 28: { return TILDE; } // fall through - case 78: break; + case 79: break; case 29: { return STRING_PART; } // fall through - case 79: break; + case 80: break; case 30: { popState(); return STRING_END; } // fall through - case 80: break; + case 81: break; case 31: { pushState(STRING_INTERPOLATION); return STRING_INTERPOLATION_START; } // fall through - case 81: break; + case 82: break; case 32: { } // fall through - case 82: break; + case 83: break; case 33: { pushState(BLOCK_COMMENT); } // fall through - case 83: break; + case 84: break; case 34: { return LTE; } // fall through - case 84: break; + case 85: break; case 35: { return GTE; } // fall through - case 85: break; + case 86: break; case 36: { return IF; } // fall through - case 86: break; + case 87: break; case 37: - { popState(); - return RsTokenTypes.BLOCK_COMMENT; + { int state = yystate(); + popState(); + return state == DOC_COMMENT ? RsTokenTypes.DOC_COMMENT : RsTokenTypes.BLOCK_COMMENT; } // fall through - case 87: break; + case 88: break; case 38: - { return LINE_COMMENT; + { pushState(DOC_COMMENT); } // fall through - case 88: break; + case 89: break; case 39: - { return STRING_TAG; + { return LINE_COMMENT; } // fall through - case 89: break; + case 90: break; case 40: - { return RsTokenTypes.BLOCK_COMMENT; + { return STRING_TAG; } // fall through - case 90: break; + case 91: break; case 41: - { return CALC; + { return RsTokenTypes.BLOCK_COMMENT; } // fall through - case 91: break; + case 92: break; case 42: - { return CASE; + { return CALC; } // fall through - case 92: break; + case 93: break; case 43: - { return ELSE; + { return CASE; } // fall through - case 93: break; + case 94: break; case 44: - { return NULL; + { return ELSE; } // fall through - case 94: break; + case 95: break; case 45: - { return TRUE; + { return NULL; } // fall through - case 95: break; + case 96: break; case 46: - { return FALSE; + { return TRUE; } // fall through - case 96: break; + case 97: break; case 47: - { return WHILE; + { return FALSE; } // fall through - case 97: break; + case 98: break; case 48: - { return RETURN; + { return WHILE; } // fall through - case 98: break; + case 99: break; case 49: - { return DEFAULT; + { return RETURN; } // fall through - case 99: break; + case 100: break; case 50: + { return DEFAULT; + } + // fall through + case 101: break; + case 51: { return COORDGRID; } // fall through - case 100: break; + case 102: break; default: zzScanError(ZZ_NO_MATCH); } diff --git a/src/main/grammars/RuneScript.flex b/src/main/grammars/RuneScript.flex index 1efbef3..8a564d8 100644 --- a/src/main/grammars/RuneScript.flex +++ b/src/main/grammars/RuneScript.flex @@ -59,18 +59,20 @@ IMG_TAG = "" CLOSE_TAG = "" INCOMPLETE_TAG = "<"(shad|col|str|u|img)"=" -%x STRING, STRING_INTERPOLATION, BLOCK_COMMENT +%x STRING, STRING_INTERPOLATION, BLOCK_COMMENT, DOC_COMMENT %% - { + { "*/" { + int state = yystate(); popState(); - return RsTokenTypes.BLOCK_COMMENT; + return state == DOC_COMMENT ? RsTokenTypes.DOC_COMMENT : RsTokenTypes.BLOCK_COMMENT; } <> { + int state = yystate(); popState(); - return RsTokenTypes.BLOCK_COMMENT; + return state == DOC_COMMENT ? RsTokenTypes.DOC_COMMENT : RsTokenTypes.BLOCK_COMMENT; } [\s\S] { } } @@ -79,6 +81,7 @@ INCOMPLETE_TAG = "<"(shad|col|str|u|img)"=" // Comments "/**/" { return RsTokenTypes.BLOCK_COMMENT; } +"/**" { pushState(DOC_COMMENT); } "/*" { pushState(BLOCK_COMMENT); } {LINE_COMMENT} { return LINE_COMMENT; } diff --git a/src/main/grammars/RuneScriptDoc.NOTICE.txt b/src/main/grammars/RuneScriptDoc.NOTICE.txt new file mode 100644 index 0000000..d9f5e1d --- /dev/null +++ b/src/main/grammars/RuneScriptDoc.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/grammars/RuneScriptDoc.flex b/src/main/grammars/RuneScriptDoc.flex new file mode 100644 index 0000000..8740ba9 --- /dev/null +++ b/src/main/grammars/RuneScriptDoc.flex @@ -0,0 +1,214 @@ +package io.runescript.plugin.lang.doc.lexer; + +import com.intellij.lexer.FlexLexer; +import com.intellij.psi.TokenType; +import com.intellij.psi.tree.IElementType; +import com.intellij.util.text.CharArrayUtil; +import java.lang.Character; +import io.runescript.plugin.lang.doc.lexer.RsDocTokens; +import io.runescript.plugin.lang.doc.parser.RsDocKnownTag; + +%% + +%unicode +%class _RsDocLexer +%implements FlexLexer + +%{ + public _RsDocLexer() { + this((java.io.Reader)null); + } + + private boolean isLastToken() { + return zzMarkedPos == zzBuffer.length(); + } + + private boolean yytextContainLineBreaks() { + return CharArrayUtil.containLineBreaks(zzBuffer, zzStartRead, zzMarkedPos); + } + + private boolean nextIsNotWhitespace() { + return zzMarkedPos <= zzBuffer.length() && !Character.isWhitespace(zzBuffer.charAt(zzMarkedPos + 1)); + } + + private boolean prevIsNotWhitespace() { + return zzMarkedPos != 0 && !Character.isWhitespace(zzBuffer.charAt(zzMarkedPos - 1)); + } +%} + +%function advance +%type IElementType +%eof{ + return; +%eof} + +%state LINE_BEGINNING +%state CONTENTS_BEGINNING +%state TAG_BEGINNING +%state TAG_TEXT_BEGINNING +%state CONTENTS +%state CODE_BLOCK +%state CODE_BLOCK_LINE_BEGINNING +%state CODE_BLOCK_CONTENTS_BEGINNING +%state INDENTED_CODE_BLOCK + +WHITE_SPACE_CHAR =[\ \t\f\n] +NOT_WHITE_SPACE_CHAR=[^\ \t\f\n] + +DIGIT=[0-9] +ALPHA=[:jletter:] +TAG_NAME={ALPHA}({ALPHA}|{DIGIT})* +IDENTIFIER={ALPHA}({ALPHA}|{DIGIT}|".")* +QUALIFIED_NAME_START={ALPHA} +QUALIFIED_NAME_CHAR={ALPHA}|{DIGIT}|[\.:,] +QUALIFIED_NAME={QUALIFIED_NAME_START}{QUALIFIED_NAME_CHAR}* +CODE_LINK=\[({QUALIFIED_NAME}\/)?{QUALIFIED_NAME}\] +CODE_FENCE_START=("```" | "~~~").* +CODE_FENCE_END=("```" | "~~~") + +%% + + + "/**" { yybegin(CONTENTS_BEGINNING); + return RsDocTokens.START; } +"*"+ "/" { if (isLastToken()) return RsDocTokens.END; + else return RsDocTokens.TEXT; } + + "*"+ { yybegin(CONTENTS_BEGINNING); + return RsDocTokens.LEADING_ASTERISK; } + + "@"{TAG_NAME} { + RsDocKnownTag tag = RsDocKnownTag.Companion.findByTagName(zzBuffer.subSequence(zzStartRead, zzMarkedPos)); + yybegin(tag != null && tag.isReferenceRequired() ? TAG_BEGINNING : TAG_TEXT_BEGINNING); + return RsDocTokens.TAG_NAME; +} + + { + {WHITE_SPACE_CHAR}+ { + if (yytextContainLineBreaks()) { + yybegin(LINE_BEGINNING); + } + return TokenType.WHITE_SPACE; + } + + /* Example: @return[x] The return value of function x + ^^^ + */ + {CODE_LINK} { yybegin(TAG_TEXT_BEGINNING); + return RsDocTokens.MARKDOWN_LINK; } + + /* Example: @param aaa The value of aaa + ^^^ + */ + {QUALIFIED_NAME} { + yybegin(TAG_TEXT_BEGINNING); + return RsDocTokens.MARKDOWN_LINK; + } + + [^\n] { + yybegin(CONTENTS); + return RsDocTokens.TEXT; + } +} + + { + {WHITE_SPACE_CHAR}+ { + if (yytextContainLineBreaks()) { + yybegin(LINE_BEGINNING); + } + return TokenType.WHITE_SPACE; + } + + /* Example: @return[x] The return value of function x + ^^^ + */ + {CODE_LINK} { yybegin(CONTENTS); + return RsDocTokens.MARKDOWN_LINK; } + + [^\n] { + yybegin(CONTENTS); + return RsDocTokens.TEXT; + } +} + + { + + ([\ ]{4}[\ ]*)|([\t]+) { + if(yystate() == CONTENTS_BEGINNING) { + yybegin(INDENTED_CODE_BLOCK); + return RsDocTokens.CODE_BLOCK_TEXT; + } + } + + {WHITE_SPACE_CHAR}+ { + if (yytextContainLineBreaks()) { + yybegin(LINE_BEGINNING); + return TokenType.WHITE_SPACE; + } else { + yybegin(yystate() == CONTENTS_BEGINNING ? CONTENTS_BEGINNING : CONTENTS); + return RsDocTokens.TEXT; // internal white space + } + } + + "\\"[\[\]] { + yybegin(CONTENTS); + return RsDocTokens.MARKDOWN_ESCAPED_CHAR; + } + + "[" [^\[]* "](" [^)]* ")" { + yybegin(CONTENTS); + return RsDocTokens.MARKDOWN_INLINE_LINK; + } + + {CODE_FENCE_START} { + yybegin(CODE_BLOCK_LINE_BEGINNING); + return RsDocTokens.TEXT; + } + + /* We're only interested in parsing links that can become code references, + meaning they contain only identifier characters and characters that can be + used in type declarations. No brackets, backticks, asterisks or anything like that. + Also if a link is followed by [ or (, then its destination is a regular HTTP + link and not a Kotlin identifier, so we don't need to do our parsing and resolution. */ + {CODE_LINK} / [^\(\[] { + yybegin(CONTENTS); + return RsDocTokens.MARKDOWN_LINK; + } + + [^\n] { + yybegin(CONTENTS); + return RsDocTokens.TEXT; + } +} + + { + "*"+ { + yybegin(CODE_BLOCK_CONTENTS_BEGINNING); + return RsDocTokens.LEADING_ASTERISK; + } +} + + { + {CODE_FENCE_END} / [ \t\f]* [\n] [ \t\f]* { + // Code fence end + yybegin(CONTENTS); + return RsDocTokens.TEXT; + } +} + + { + {WHITE_SPACE_CHAR}+ { + if (yytextContainLineBreaks()) { + yybegin(yystate() == INDENTED_CODE_BLOCK ? LINE_BEGINNING : CODE_BLOCK_LINE_BEGINNING); + return TokenType.WHITE_SPACE; + } + return RsDocTokens.CODE_BLOCK_TEXT; + } + + [^\n] { + yybegin(yystate() == INDENTED_CODE_BLOCK ? INDENTED_CODE_BLOCK : CODE_BLOCK); + return RsDocTokens.CODE_BLOCK_TEXT; + } +} + +[\s\S] { return TokenType.BAD_CHARACTER; } \ No newline at end of file diff --git a/src/main/kotlin/io/runescript/plugin/ide/RsCommenter.kt b/src/main/kotlin/io/runescript/plugin/ide/RsCommenter.kt index 6cdbb0c..cb476c7 100644 --- a/src/main/kotlin/io/runescript/plugin/ide/RsCommenter.kt +++ b/src/main/kotlin/io/runescript/plugin/ide/RsCommenter.kt @@ -3,6 +3,7 @@ package io.runescript.plugin.ide import com.intellij.lang.CodeDocumentationAwareCommenter import com.intellij.psi.PsiComment import com.intellij.psi.tree.IElementType +import io.runescript.plugin.lang.doc.psi.api.RsDoc import io.runescript.plugin.lang.psi.RsTokenTypes class RsCommenter : CodeDocumentationAwareCommenter { @@ -36,22 +37,22 @@ class RsCommenter : CodeDocumentationAwareCommenter { } override fun getDocumentationCommentTokenType(): IElementType? { - return null + return RsTokenTypes.DOC_COMMENT } - override fun getDocumentationCommentPrefix(): String? { - return null + override fun getDocumentationCommentPrefix(): String { + return "/**" } - override fun getDocumentationCommentLinePrefix(): String? { - return null + override fun getDocumentationCommentLinePrefix(): String { + return "*" } - override fun getDocumentationCommentSuffix(): String? { - return null + override fun getDocumentationCommentSuffix(): String { + return "*/" } override fun isDocumentationComment(element: PsiComment?): Boolean { - return false + return element is RsDoc } } \ No newline at end of file diff --git a/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocReference.kt b/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocReference.kt new file mode 100644 index 0000000..b21e642 --- /dev/null +++ b/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocReference.kt @@ -0,0 +1,81 @@ +package io.runescript.plugin.ide.doc + +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementResolveResult +import com.intellij.psi.PsiPolyVariantReferenceBase +import com.intellij.psi.ResolveResult +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.StubIndex +import io.runescript.plugin.lang.doc.psi.impl.RsDocName +import io.runescript.plugin.lang.psi.RsScript +import io.runescript.plugin.lang.stubs.index.RsClientScriptIndex +import io.runescript.plugin.lang.stubs.index.RsCommandScriptIndex +import io.runescript.plugin.lang.stubs.index.RsProcScriptIndex +import io.runescript.plugin.symbollang.psi.RsSymSymbol +import io.runescript.plugin.symbollang.psi.index.RsSymbolIndex +import io.runescript.plugin.symbollang.psi.index.RsSymbolIndex.Companion.nameWithoutExtension + +class RsDocReference(element: RsDocName) : PsiPolyVariantReferenceBase(element) { + + override fun multiResolve(incompleteCode: Boolean): Array { + val typeName = (element.firstChild as? RsDocName)?.text + val elementName = element.lastChild.text + return resolve(element.project, element.getContainingDoc().owner, typeName, elementName) + .map { PsiElementResolveResult(it) } + .toTypedArray() + } + + override fun getVariants(): Array = emptyArray() + + override fun isSoft() = false + + override fun getRangeInElement(): TextRange { + return element.getNameTextRange() + } + + override fun getCanonicalText(): String { + return element.getNameText() + } + + companion object { + fun resolve(project: Project, owner: RsScript?, typeName: String?, elementName: String): Array { + if (typeName == null) { + return owner + ?.parameterList + ?.parameterList + ?.map { it.localVariableExpression } + ?.filter { it.nameLiteral.text == elementName } + ?.toTypedArray() + ?: emptyArray() + } + val scriptIndexKey = when (typeName) { + "command" -> RsCommandScriptIndex.KEY + "clientscript" -> RsClientScriptIndex.KEY + "proc" -> RsProcScriptIndex.KEY + else -> null + } + if (scriptIndexKey != null) { + val scripts = StubIndex.getElements( + scriptIndexKey, + elementName, + project, + GlobalSearchScope.allScope(project), + RsScript::class.java + ) + return scripts.toTypedArray() + } + val configs = StubIndex.getElements( + RsSymbolIndex.KEY, + elementName, + project, + GlobalSearchScope.allScope(project), + RsSymSymbol::class.java + ) + return configs + .filter { it.containingFile.nameWithoutExtension == typeName } + .toTypedArray() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocRenderer.NOTICE.txt b/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocRenderer.NOTICE.txt new file mode 100644 index 0000000..d9f5e1d --- /dev/null +++ b/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocRenderer.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/ide/doc/RsDocRenderer.kt b/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocRenderer.kt new file mode 100644 index 0000000..8cd493b --- /dev/null +++ b/src/main/kotlin/io/runescript/plugin/ide/doc/RsDocRenderer.kt @@ -0,0 +1,710 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package io.runescript.plugin.ide.doc + +import com.intellij.codeInsight.daemon.impl.HighlightInfoType +import com.intellij.codeInsight.documentation.DocumentationManagerUtil +import com.intellij.codeInsight.javadoc.JavaDocInfoGeneratorFactory +import com.intellij.lang.Language +import com.intellij.lang.documentation.DocumentationMarkup.* +import com.intellij.lang.documentation.DocumentationSettings +import com.intellij.lang.documentation.DocumentationSettings.InlineCodeHighlightingMode +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors +import com.intellij.openapi.editor.HighlighterColors +import com.intellij.openapi.editor.colors.CodeInsightColors +import com.intellij.openapi.editor.colors.EditorColorsManager +import com.intellij.openapi.editor.colors.TextAttributesKey +import com.intellij.openapi.editor.markup.TextAttributes +import com.intellij.openapi.editor.richcopy.HtmlSyntaxInfoUtil +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.util.parentOfType +import io.runescript.plugin.ide.RsBundle +import io.runescript.plugin.ide.highlight.RsSyntaxHighlighterColors +import io.runescript.plugin.lang.RuneScript +import io.runescript.plugin.lang.doc.findDescendantOfType +import io.runescript.plugin.lang.doc.getChildrenOfType +import io.runescript.plugin.lang.doc.psi.api.RsDoc +import io.runescript.plugin.lang.doc.psi.impl.RsDocLink +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 +import io.runescript.plugin.lang.psi.RsLocalVariableExpression +import io.runescript.plugin.lang.psi.RsScript +import io.runescript.plugin.symbollang.psi.RsSymSymbol +import org.intellij.markdown.IElementType +import org.intellij.markdown.MarkdownElementTypes +import org.intellij.markdown.MarkdownTokenTypes +import org.intellij.markdown.ast.ASTNode +import org.intellij.markdown.flavours.gfm.GFMElementTypes +import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor +import org.intellij.markdown.flavours.gfm.GFMTokenTypes +import org.intellij.markdown.parser.MarkdownParser +import org.jetbrains.annotations.Nls + +object RsDocRenderer { + + private fun StringBuilder.appendRsDocContent(docComment: RsDocTag): StringBuilder = + append(markdownToHtml(docComment, allowSingleParagraph = true)) + + private fun StringBuilder.appendRsDocSections(sections: List) { + fun findTagsByName(name: String) = + sequence { sections.forEach { yieldAll(it.findTagsByName(name)) } } + + fun findTagByName(name: String) = findTagsByName(name).firstOrNull() + + val paramTags = findTagsByName("param").filter { it.getSubjectName() != null } + appendTagList( + paramTags, + RsBundle.message("rsdoc.section.title.parameters"), + RsSyntaxHighlighterColors.LOCAL_VARIABLE + ) + + appendTag(findTagByName("return"), RsBundle.message("rsdoc.section.title.returns")) + + appendAuthors(findTagsByName("author")) + appendTag(findTagByName("since"), RsBundle.message("rsdoc.section.title.since")) + + appendSeeAlso(findTagsByName("see")) + + val sampleTags = findTagsByName("sample").filter { it.getSubjectLink() != null } + appendSamplesList(sampleTags) + } + + fun StringBuilder.renderRsDoc( + contentTag: RsDocTag, + sections: List = if (contentTag is RsDocSection) listOf(contentTag) else emptyList() + ) { + val computedContent = buildString { appendRsDocContent(contentTag) } + if (computedContent.isNotBlank()) { + append(CONTENT_START) + append(computedContent) + append(CONTENT_END) + } + + append(SECTIONS_START) + appendRsDocSections(sections) + append(SECTIONS_END) + } + + private fun StringBuilder.appendHyperlink(rsDocLink: RsDocLink) { + val linkText = rsDocLink.getLinkText() + if (DumbService.isDumb(rsDocLink.project)) { + append(linkText) + } else { + DocumentationManagerUtil.createHyperlink( + this, + linkText, + highlightQualifiedName(linkText, getTargetLinkElementAttributes(rsDocLink.getTargetElement())), + false, + true + ) + } + } + + private fun getTargetLinkElementAttributes(element: PsiElement?): TextAttributes { + return element + ?.let { textAttributesKeyForKtElement(it)?.attributesKey } + ?.let { getTargetLinkElementAttributes(it) } + ?: TextAttributes().apply { + foregroundColor = + EditorColorsManager.getInstance().globalScheme.getColor(DefaultLanguageHighlighterColors.DOC_COMMENT_LINK) + } + } + + private fun getTargetLinkElementAttributes(key: TextAttributesKey): TextAttributes { + return tuneAttributesForLink(EditorColorsManager.getInstance().globalScheme.getAttributes(key)) + } + + private fun highlightQualifiedName(qualifiedName: String, lastSegmentAttributes: TextAttributes): String { + val linkComponents = qualifiedName.split("/") + val elementName = linkComponents.last() + return buildString { + appendStyledSpan( + DocumentationSettings.isSemanticHighlightingOfLinksEnabled(), + lastSegmentAttributes, + elementName + ) + } + } + + private fun RsDocLink.getTargetElement(): PsiElement? { + return getChildrenOfType().last().references.firstOrNull { it is RsDocReference }?.resolve() + } + + @Nls + fun generateJavadoc(psiMethod: PsiMethod): String { + val javaDocInfoGenerator = JavaDocInfoGeneratorFactory.create(psiMethod.project, psiMethod) + val builder = StringBuilder() + @Suppress("HardCodedStringLiteral") + if (javaDocInfoGenerator.generateDocInfoCore(builder, false)) { + val renderedJava = builder.toString() + return renderedJava.removeRange( + renderedJava.indexOf(DEFINITION_START), + renderedJava.indexOf(DEFINITION_END) + ) // Cut off light method signature + } + return "" + } + + private fun PsiElement.extractExampleText() = text + + private fun trimCommonIndent(text: String): String { + fun String.leadingIndent() = indexOfFirst { !it.isWhitespace() } + + val lines = text.split('\n') + val minIndent = lines.filter { it.trim().isNotEmpty() }.minOfOrNull(String::leadingIndent) ?: 0 + return lines.joinToString("\n") { it.drop(minIndent) } + } + + private fun StringBuilder.appendSection(title: String, content: StringBuilder.() -> Unit) { + append(SECTION_HEADER_START, title, ":", SECTION_SEPARATOR) + content() + append(SECTION_END) + } + + private fun StringBuilder.appendSamplesList(sampleTags: Sequence) { + if (!sampleTags.any()) return + + appendSection(RsBundle.message("rsdoc.section.title.samples")) { + sampleTags.forEach { + it.getSubjectLink()?.let { subjectLink -> + append("

") + this@appendSamplesList.appendHyperlink(subjectLink) + wrapTag("pre") { + wrapTag("code") { + if (DumbService.isDumb(subjectLink.project)) { + append("// " + RsBundle.message("rsdoc.comment.unresolved")) + } else { + val codeSnippet = when (val target = subjectLink.getTargetElement()) { + null -> "// " + RsBundle.message("rsdoc.comment.unresolved") + else -> trimCommonIndent(target.extractExampleText()).htmlEscape() + } + this@appendSamplesList.appendHighlightedByLexerAndEncodedAsHtmlCodeSnippet( + when (DocumentationSettings.isHighlightingOfCodeBlocksEnabled()) { + true -> InlineCodeHighlightingMode.SEMANTIC_HIGHLIGHTING + false -> InlineCodeHighlightingMode.NO_HIGHLIGHTING + }, + subjectLink.project, + RuneScript, + codeSnippet + ) + } + } + } + } + } + } + } + + private fun StringBuilder.appendSeeAlso(seeTags: Sequence) { + if (!seeTags.any()) return + + val iterator = seeTags.iterator() + + appendSection(RsBundle.message("rsdoc.section.title.see.also")) { + while (iterator.hasNext()) { + val tag = iterator.next() + val subjectName = tag.getSubjectName() + val link = tag.getChildrenOfType().lastOrNull() + when { + link != null -> this.appendHyperlink(link) + subjectName != null -> DocumentationManagerUtil.createHyperlink( + this, + subjectName, + subjectName, + false, + true + ) + + else -> append(tag.getContent()) + } + if (iterator.hasNext()) { + append(",
") + } + } + } + } + + private fun StringBuilder.appendAuthors(authorTags: Sequence) { + if (!authorTags.any()) return + + val iterator = authorTags.iterator() + + appendSection(RsBundle.message("rsdoc.section.title.author")) { + while (iterator.hasNext()) { + append(iterator.next().getContent()) + if (iterator.hasNext()) { + append(", ") + } + } + } + } + + private fun StringBuilder.appendTagList( + tags: Sequence, + 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("") + 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("") + } + 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>", "", body) +} + +private inline fun StringBuilder.wrapTag(tag: String, params: String, crossinline body: () -> Unit) { + wrap("<$tag $params>", "", 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 psiFactory; + + public RsDocElementType(String debugName, @NotNull Class 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