commit 70275b14e94dd07c36dbbc21f4738e3949d353c6 Author: me Date: Thu May 23 15:45:59 2024 +0800 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb897ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +.gradle +/.idea/ +/build/ +/target +/native/target/ +!/gradle +!**/src/main/**/build/ +!**/src/test/**/build/ +src/main/gen +src/main/resources/natives/ +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store +/src/test/resources/slint/lexer/output.txt +/src/test/resources/slint/parser/.txt +/idea-sandbox \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..110b2f0 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,140 @@ +plugins { + id("java") + id("org.jetbrains.kotlin.jvm") version "1.9.23" + id("org.jetbrains.intellij") version "1.17.2" + id("org.jetbrains.grammarkit") version "2022.3.2.2" + id("de.undercouch.download") version "5.6.0" +} +val slintVersion: String by project +//github url +val slintGithubUrl = "https://github.com/slint-ui/slint/releases/download/v$slintVersion" +//download dir +val slintViewerDownloadDir = "${layout.buildDirectory.get()}/slint-viewer" +//decompression path +val slintViewerBinaryDir = "${layout.buildDirectory.get()}/slint-viewer/$slintVersion" + +val grammarGeneratedRoot = "${layout.buildDirectory.get()}/generated/sources/grammar/" +//github filename-> decompression name +val slintViewerFilenames = arrayOf( + "slint-viewer-linux.tar.gz" to "slint-viewer-linux", + "slint-viewer-macos.tar.gz" to "slint-viewer-macos", + "slint-viewer-windows.zip" to "slint-viewer-windows.exe", +) + +sourceSets { + main { + java { + srcDirs(file("${grammarGeneratedRoot}/main/java")) + } + } +} +group = "me.zhouxi" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +intellij { + version.set("IC-2023.2.5") + sandboxDir.set("idea-sandbox") + plugins.set(listOf("java")) +} +dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + testCompileOnly("org.projectlombok:lombok:1.18.32") + testAnnotationProcessor("org.projectlombok:lombok:1.18.32") + implementation("org.jetbrains:grammar-kit:2022.3.2") +} +tasks { + buildPlugin { + //copy slint-viewer to plugin dir + from(slintViewerBinaryDir) { + into("/slint-viewer") + } + } + withType().configureEach { + if (name.endsWith("main()")) { + notCompatibleWithConfigurationCache("JavaExec created by IntelliJ") + } + } + + runIde { + //copy slint-viewer + doFirst { + copy { + from(slintViewerBinaryDir) + into("idea-sandbox/plugins/intellij-slint/slint-viewer") + } + } + } + + withType { + sourceCompatibility = "17" + targetCompatibility = "17" + } + withType { + kotlinOptions.jvmTarget = "17" + } + + patchPluginXml { + sinceBuild.set("232") + untilBuild.set("242.*") + } + + generateParser { + purgeOldFiles.set(true) + sourceFile.set(file("src/main/grammar/main.bnf")) + pathToParser.set("SlintParser") + targetRootOutputDir.set(file("${grammarGeneratedRoot}/main/java")) + pathToPsiRoot.set("psi") + } + generateLexer { + sourceFile.set(file("src/main/grammar/main.flex")) + targetOutputDir.set(file("${grammarGeneratedRoot}/main/java/me/zhouxi/slint/lang/lexer")) + purgeOldFiles.set(true) + } + + withType { + jvmArgs("-Djava.awt.headless=true") + } + + register("downloadSlintViewer") { + doFirst { + slintViewerFilenames.forEach { fileDesc -> + val (filename, renamed) = fileDesc + val src = "$slintGithubUrl/$filename" + val fileTarget = "$slintViewerDownloadDir/$filename" + download.run { + src(src) + dest(fileTarget) + } + copy { + val fileTree = if (filename.endsWith("zip")) { + zipTree(fileTarget) + } else { + tarTree(fileTarget) + } + from(fileTree) + //include executable file path + include("slint-viewer/slint-viewer*") + eachFile { + //rename to platform name + this.relativePath = RelativePath(true, renamed) + } + into(slintViewerBinaryDir) + } + } + } + } + signPlugin { + certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) + privateKey.set(System.getenv("PRIVATE_KEY")) + password.set(System.getenv("PRIVATE_KEY_PASSWORD")) + } + + publishPlugin { + token.set(System.getenv("PUBLISH_TOKEN")) + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..9f8bbbe --- /dev/null +++ b/gradle.properties @@ -0,0 +1,8 @@ +# Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib +kotlin.stdlib.default.dependency=false +# Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html +org.gradle.configuration-cache=false +# Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html +org.gradle.caching=false +# slint version +slintVersion=1.6.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..17655d0 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..1786ba2 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,8 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} + +rootProject.name = "intellij-slint" diff --git a/src/main/grammar/main.bnf b/src/main/grammar/main.bnf new file mode 100644 index 0000000..12dea24 --- /dev/null +++ b/src/main/grammar/main.bnf @@ -0,0 +1,568 @@ +{ + generate=[token-case="as-is" element-case="as-is"] + parserClass="me.zhouxi.slint.lang.parser.SlintParser" + implements="me.zhouxi.slint.lang.psi.SlintPsiElement" + extends="me.zhouxi.slint.lang.psi.SlintPsiElementImpl" + elementTypeHolderClass="me.zhouxi.slint.lang.psi.SlintTypes" + elementTypeClass="me.zhouxi.slint.lang.SlintElementType" + tokenTypeClass="me.zhouxi.slint.lang.SlintTokenType" + psiClassPrefix="Slint" + psiImplClassSuffix="Impl" + psiPackage="me.zhouxi.slint.lang.psi" + psiImplPackage="me.zhouxi.slint.lang.psi.impl" + tokens=[ + Comma = "," + FatArrow = "=>" + DoubleArrow = "<=>" + PlusEqual = "+=" + MinusEqual = "-=" + StarEqual = "*=" + DivEqual = "/=" + LessEqual = "<=" + GreaterEqual = ">=" + EqualEqual = "==" + NotEqual = "!=" + ColonEqual = ":=" + Arrow = "->" + OrOr = "||" + AndAnd = "&&" + LBrace = "{" + RBrace = "}" + LParent = "(" + RParent = ")" + LAngle = "<" + RAngle = ">" + LBracket = "[" + RBracket = "]" + Plus = "+" + Minus = "-" + Star = "*" + Div = "/" + Equal = "=" + Colon = ":" + Comma = "," + Semicolon = ";" + Bang = "!" + Dot = "." + Question = "?" + Dollar = "$" + At = "@" + Pipe = "|" + Percent = "%" + Whitespace = "regexp:(\s+)" + NumberLiteral = "regexp:\d+(\.(\d+)?)?([a-z]+|%)?" + Identifier = "regexp:^[a-zA-Z_][A-Za-z0-9\-_]*" + ColorLiteral = "regexp:#([a-zA-Z0-9]+)" + StringLiteral = 'regexp:(^"[^"\r\n]*")' + LineComment = 'regexp:^//[^\r\n]*' + BlockComment = 'regexp:/\*[\s\S]*?\*/' + + ] + extends(".*Expression")=Expression + implements(".*Keyword")=[ + "me.zhouxi.slint.lang.psi.SlintPsiKeywordIdentifier" + ] +} +// +Document ::= DocumentElement* +private recoverTopElement ::= !('component' | 'struct' | 'enum' | 'global'| 'export'|'import' ) +private DocumentElement ::= Import | Struct |Export | Enum | GlobalSingleton | Component { + recoverWhile=recoverTopElement +} +GlobalSingleton ::= GlobalKeyword ComponentName ComponentBody { + pin=1 + implements=["me.zhouxi.slint.lang.psi.SlintPsiNamedElement"] + mixin="me.zhouxi.slint.lang.psi.impl.SlintPsiNamedElementMixinImpl" +} +//import 定义 +Import ::= ImportKeyword ImportElement? ModuleLocation';'{ + pin=1 +} + +private ImportElement ::= '{' ImportedIdentifier (',' ImportedIdentifier)* ','? '}' FromKeyword { + pin=1 +} + +// ABc as Def +ImportedIdentifier ::= ReferenceIdentifier ImportAlias?{ + pin=1 + recoverWhile=AliasNameRecover +} +private AliasNameRecover::=!(','|'}'|';') + +private ImportAlias ::= AsKeyword InternalName { + pin=1 +} +//Struct 定义 +Struct ::= StructKeyword TypeName (':=')? StructBody { + pin=1 +} +private StructBody ::= '{' FieldDeclarations? '}'{ + pin=1 +} +//EnumDeclaration +Enum ::= EnumKeyword EnumName '{' (EnumValue (','EnumValue)*','? )? '}'{ + pin=1 + implements=["me.zhouxi.slint.lang.psi.SlintPsiNamedElement"] + mixin="me.zhouxi.slint.lang.psi.impl.SlintPsiNamedElementMixinImpl" +} +//--------------ExportsList ------------------------------------- +//ExportsList +Export ::= ExportKeyword ExportElement { + pin=1 +} +private ExportElement ::= ExportType | Struct| Component | ExportModule | GlobalSingleton|Enum + +ExportType ::= '{' ExportIdentifier (','ExportIdentifier)* ','? '}'{ + pin=1 +} +ExportIdentifier ::= ReferenceIdentifier ExportAlias?{ + +} +private ExportAlias ::= AsKeyword ExternalName{ + pin=1 +} +ExportModule ::= '*' FromKeyword ModuleLocation ';'{ + pin=1 +} +//---------- component ------------------------------------------------------------ +//Component ::= NewComponent +//| LegacyComponent +//Old syntax +//private LegacyComponent ::=(GlobalKeyword ComponentName ':=' ComponentName? ComponentBody)| ('global'? SubComponent) + +Component ::= ComponentKeyword ComponentName InheritDeclaration? ComponentBody { + pin=1 + implements=["me.zhouxi.slint.lang.psi.SlintPsiNamedElement"] + mixin="me.zhouxi.slint.lang.psi.impl.SlintPsiNamedElementMixinImpl" +} +//组件定义 +//private LegacyComponent ::= (ComponentName|NamedIdentifier ':=' ) ComponentBody +InheritDeclaration ::= InheritsKeyword ReferenceIdentifier { + pin=1 +} +ComponentBody ::= '{' ComponentElement* '}'{ + pin=1 +} +//组件元素定义 +private ComponentElement ::=ChildrenPlaceholder| PropertyDeclaration | CallbackDeclaration + | Function | PropertyAnimation | CallbackConnection | Transitions + | PropertyChanged + | States | TwoWayBinding | PropertyBinding | ConditionalElement + | RepetitionElement | SubComponent { + recoverWhile=recoverWhileForComponentBody + } +private recoverWhileForComponentBody::= !( + 'property'|'callback'|'changed' + |'states'|'transitions'|'pure' + |'function'|'public'|'protected'|'for'|'if'|'}'|';'|'@' + |GenericIdentifier|PropertyModifier) + +ChildrenPlaceholder ::= '@' 'children'{ + pin=1 +} +//--------------------------------PropertyDeclaration Start---------------------------------------------------- +// 属性定义 in property name: value / in property name <=> value +PropertyDeclaration ::= PropertyModifier? PropertyKeyword ('<' Type '>')? PropertyName(PropertyValue|PropertyTwoWayBindingValue|';'){ + pin=2 + implements=["me.zhouxi.slint.lang.psi.SlintPsiNamedElement"] + mixin="me.zhouxi.slint.lang.psi.impl.SlintPsiNamedElementMixinImpl" +} +private PropertyModifier ::= InKeyword|OutKeyword|InOutKeyword|PrivateKeyword +private PropertyValue::= ':' BindingStatement { + pin=1 +} +private PropertyTwoWayBindingValue ::= '<=>' QualifiedPropertyNameReference ';' { + pin=1 +} +//--------------------------------PropertyChanged Start---------------------------------------------------- +PropertyChanged ::= ChangedKeyword LocalVariable '=>' CodeBlock{ + pin=1 +} +//--------------------------------CallbackDeclaration Start---------------------------------------------------- +// 回调定义 pure callback abc()->int; callback abc; callback(..);callback()->type; +CallbackDeclaration ::= PureKeyword? CallbackKeyword FunctionName CallbackBinding? ';'{ + pin=2 + implements=["me.zhouxi.slint.lang.psi.SlintPsiNamedElement"] + mixin="me.zhouxi.slint.lang.psi.impl.SlintPsiNamedElementMixinImpl" +} +//回调参数定义 +CallbackBinding::= CallbackArgument | ('<=>'QualifiedPropertyNameReference) +//入参定义 +CallbackArgument ::= '(' (Type (','Type)* ','? )? ')' ReturnType?{ + pin=1 +} +private ReturnType ::= '->' (NamedType|ArrayType){ + pin=1 +} +//--------------------------------TwoWayBindingDeclaration Start---------------------------------------------------- +//组件双向绑定 +TwoWayBinding ::= ReferenceIdentifier '<=>' QualifiedPropertyNameReference ';' { + pin=2 +} + +//--------------------------------FunctionDeclaration Start---------------------------------------------------- +//函数定义 protected? pure? function f() +Function ::= FunctionModifiers? FunctionKeyword FunctionName FunctionArguments ReturnType? CodeBlock{ + pin=2 + implements=["me.zhouxi.slint.lang.psi.SlintPsiNamedElement"] + mixin="me.zhouxi.slint.lang.psi.impl.SlintPsiNamedElementMixinImpl" +} +private FunctionModifiers ::= ((PublicKeyword | ProtectedKeyword) PureKeyword?) | (PureKeyword (PublicKeyword | ProtectedKeyword)?) + +private FunctionArguments ::= '(' FieldDeclarations* ')'{ + pin=1 +} + +//--------------------------------CallbackConnectionDeclaration Start--------------------------------------------------- +//回调绑定定义 abc()=> {} +CallbackConnection ::= FunctionReference CallbackConnectionArguments? '=>' CodeBlock ';'?{ + pin=3 +} +private CallbackConnectionArguments ::= '(' (LocalVariable (',' LocalVariable)* ','?)? ')' + +//--------------------------------ConditionalElementDeclaration Start--------------------------------------------------- +ConditionalElement ::= IfKeyword Expression ':' SubComponent{ + pin=1 +} +RepetitionElement ::= ForKeyword RepetitionVariable InKeyword Expression ':' SubComponent { + pin=1 +} +private RepetitionVariable ::= LocalVariable RepetitionIndex?{ + pin=1 +} +RepetitionIndex ::= '[' LocalVariable ']'{ + pin=1 +} +//--------------------------------SubElementDeclaration Start--------------------------------------------------- +//子组件结构元素定义 +SubComponent ::= (PropertyName ':=')? ReferenceIdentifier ComponentBody{ + +} + +//--------------------------------TransitionsDeclaration Start--------------------------------------------------- +//过渡绑定 +Transitions ::= TransitionsKeyword '[' Transition* ']'{ + pin=1 +} +// +Transition ::= (InKeyword|OutKeyword) LocalVariable ':' '{' PropertyAnimation* '}'{ + pin=1 + recoverWhile=recoverForRBracket +} +//--------------------------------TransitionsDeclaration End--------------------------------------------------- +// in | out name : { } +States ::= StatesKeyword '[' State* ']'{ + pin=1 +} +// identifier [when] : { ... } +State ::= LocalVariable StateCondition? ':' '{' StateItem* '}' { + pin=3 + implements=["me.zhouxi.slint.lang.psi.SlintPsiNamedElement"] + mixin="me.zhouxi.slint.lang.psi.impl.SlintPsiNamedElementMixinImpl" + recoverWhile=recoverForRBracket +} +private recoverForRBracket::=!(']'|'}'|';'|GenericIdentifier) +StateCondition ::= WhenKeyword Expression { + pin=1 +} +//状态可以由transition propertyBinding 和 animationBinding组成 +private StateItem ::= QualifiedNamePropertyBinding | StateTransition +StateTransition ::= (InKeyword|OutKeyword) ':' '{' PropertyAnimation* '}'{ + pin=1 +} +//------------------------------------------------------------------------------------------ +//类型定义 +Type ::= NamedType | UnnamedType | ArrayType +private NamedType ::= QualifiedTypeNameReference +ArrayType ::= '[' Type ']' +UnnamedType ::= '{' FieldDeclarations* '}' +private FieldDeclarations ::= FieldDeclaration (',' FieldDeclaration)* ','? +private FieldDeclaration ::= PropertyName ':' Type + + +//代码块 +CodeBlock ::= '{' Statement* '}'{ + pin=1 +} +private Statement ::= (ReturnStatement|IfElseStatement|ExpressionStatement) ';'?{ + recoverWhile=recoverWhileStatement +} +ExpressionStatement ::= Expression (';' &Statement)? + +private recoverWhileStatement::=!(GenericIdentifier|';'|'}') + +ReturnStatement ::= 'return' (Expression)?{ + pin=1 +} +private IfElseStatement ::= IfStatement (ElseIfStatement)* ElseStatement?{ + pin=1 +} +IfStatement ::= IfKeyword Expression CodeBlock { + pin=1 +} +ElseIfStatement ::= ElseKeyword IfKeyword Expression CodeBlock{ + pin=2 +} +ElseStatement ::= ElseKeyword CodeBlock { + pin=1 +} +//动画定义 +PropertyAnimation ::= AnimateKeyword ('*'| (QualifiedPropertyNameReference (',' QualifiedPropertyNameReference)*)) '{' QualifiedNamePropertyBinding*'}'{ + pin=1 +} +//组件属性绑定 name: xxx ; name : {}; name : {} +//只有对象定义和表达式需要 ; +private QualifiedNamePropertyBinding::= QualifiedPropertyNameReference ':' BindingStatement{ + pin=2 +} +PropertyBinding ::= ReferenceIdentifier ':' BindingStatement{ + pin=2 +} +//优先尝试表达式解析 {}属于代码块 {name:xx}属于表达式,那么需要预测后面两个token,第二个token不是':'的情况下才是代码块 +//所以优先判断对象创建判断,然后进行代码块判断,最后进行表达式 +//代码块分号可选 +//表达式需要分号结尾 +BindingStatement ::=ObjectCreationExpressionWithSem|(CodeBlock ';'?)| ExpressionWithSem +//用于错误的直观化 +private ObjectCreationExpressionWithSem::=ObjectCreationExpression';'{ + pin=1 +} +private ExpressionWithSem::= Expression ';'{ + pin=1 +} + +//----------- +////////////////////////////////////////////////////////////////////////////////////////////// +//expression +Expression ::= AtExpression + | TernaryExpression + | UnaryExpression + | BinaryExpression + | AdditiveExpression + | MultiplicativeExpression + | ArrayAccessExpression + | ArrayCreationExpression + | ObjectCreationExpression + | AssignmentExpression + | PropertyExpression + | PrimaryExpression + { + recoverWhile=recoverForExpression + } +private recoverForExpression ::= !(Expression|GenericIdentifier|';') + +private BinaryExpression ::= SelfAssignExpression | ComparisonExpression | BooleanExpression +//加法表达式 +AdditiveExpression ::= Expression ('+'|'-') Expression{ + pin=2 +} +//乘法表达式 +MultiplicativeExpression ::= Expression ('*'|'/')Expression{ + pin=2 +} +//------------------------------AtExpression--------------------------------------- +AtExpression ::= '@' (LineGradient|RadialGradient|ImageUrl|Tr) { + pin=1 +} +LineGradient ::= 'linear-gradient' '(' LineGradientArgument ')' { + pin=1 +} +private LineGradientArgument ::= Expression (',' ColorStops)? { + pin=1 + recoverWhile=recoverForColorStops +} +private recoverForColorStops::=!(Expression|','|')') +RadialGradient ::= 'radial-gradient' '(' RadialGradientArgument ')' { + pin=1 +} +private RadialGradientArgument ::= 'circle' (',' ColorStops)?{ + pin=1 + recoverWhile=recoverForColorStops +} +ImageUrl ::= 'image-url' ImageUrlArgs { + pin=1 +} +private ImageUrlArgs ::= '(' RawStringLiteral NineSlice? ')'{ + pin=1 + recoverWhile=RP +} +NineSlice ::= ',' 'nine-slice' '('Numbers*')'{ +// pin=1 +} +private RP::=!(';'|'}'|')') + +ColorStops ::= ColorStop (','ColorStop)* ','? +ColorStop::= Expression Expression? + +Tr::='tr' '(' TrArg (','Expression)* ')'{ + pin=2 +} +TrArg::= ContextTr | Plurals | SimpleTrText +ContextTr ::= RawStringLiteral '=>' (Plurals|SimpleTrText){ + pin=2 +} +SimpleTrText::=RawStringLiteral +Plurals ::= RawStringLiteral '|' RawStringLiteral '%' Expression{ + pin=2 +} + +//----------------------------TernaryExpression----------------------------------------- +TernaryExpression ::= Expression '?' Expression ':' Expression +//----------------------------BooleanExpression----------------------------------------- +BooleanExpression ::= Expression ('&&' | '||') Expression{ + pin=2 +} + +ComparisonExpression ::= Expression ('>='|'<='|'=='|'!='|'>'|'<') Expression{ + pin=2 +} +AssignmentExpression ::= Expression '=' Expression + +SelfAssignExpression ::= Expression ('+='|'-='|'/='|'*='|'=') Expression + +UnaryExpression ::= ('!' | '+' | '-') Expression{ + pin=1 +} + +private PrimaryExpression::=ParenthesizedExpression|FunctionInvocationExpression|LiteralExpression + +ParenthesizedExpression ::= '(' Expression ')'{ + pin=2 +} +//fake PropertySuffixExpression::= Expression? '.' GenericIdentifier +PropertyExpression ::= Expression '.' GenericIdentifier { +// pin=2 extends=PropertySuffixExpression elementType=PropertySuffixExpression +} +FunctionInvocationExpression ::= Expression '(' InvocationArguments? ')'{ + extends=PropertyExpression +} + +InvocationArguments ::= Expression (','Expression)* ','?{ + recoverWhile=recoverOnRP +} +private recoverOnRP::=!(')') +ArrayAccessExpression ::=Expression '[' Expression ']' + +ArrayCreationExpression ::= '[' ArrayElements* ']'{ + pin=1 +} +private ArrayElements ::= Expression (','Expression)* ','? + + +LiteralExpression ::= Numbers | RawStringLiteral | GenericIdentifier | RawColorLiteral + +//------------------------------------------------- + +//------------------------------------------------- +ObjectCreationExpression ::= '{' ObjectPropertyBindings '}'{ + pin=2 +} + +private ObjectPropertyBindings ::= ObjectPropertyBinding (','ObjectPropertyBinding)* ','?{ + pin=1 +} +private ObjectPropertyBinding ::= PropertyName ':' Expression{ + pin=2 + recoverWhile=recover +} +private recover::=!(';'|'}'|',') +//-------------------------------For Keyword highlighting------------------------------------ +ComponentKeyword ::= 'component' +StructKeyword ::='struct' +EnumKeyword::='enum' +GlobalKeyword::='global' +ExportKeyword::='export' +ImportKeyword::='import' +AsKeyword::='as' +FromKeyword::='from' +InheritsKeyword::='inherits' +PropertyKeyword::='property' +CallbackKeyword::='callback' +StatesKeyword::='states' +TransitionsKeyword::='transitions' +PureKeyword::='pure' +FunctionKeyword::='function' +PublicKeyword::='public' +ProtectedKeyword::='protected' +ForKeyword::='for' +IfKeyword::='if' +ChangedKeyword::='changed' +InKeyword::='in' +WhenKeyword::='when' +ElseKeyword::='else' +AnimateKeyword::='animate' +OutKeyword::='out' +InOutKeyword::='in-out' +PrivateKeyword::='private' + +//---------NamedIdentifier ,简化PsiTree----------------------------------- +//noinspection BnfUnusedRule 用于标记命名节点对应的identifier +Named ::= PropertyName | TypeName |ExternalName | InternalName|ComponentName|FunctionName{ + pin=1 +} +{ + extends("PropertyName|TypeName|ComponentName|FunctionName")=Named +} +LocalVariable ::= GenericIdentifier{ + mixin="me.zhouxi.slint.lang.psi.impl.SlintPsiNamedElementMixinImpl" + implements=["me.zhouxi.slint.lang.psi.SlintPsiNamedElement"] +} +FunctionName ::= GenericIdentifier + +ComponentName ::=GenericIdentifier + +InternalName ::= GenericIdentifier{ + mixin="me.zhouxi.slint.lang.psi.impl.SlintPsiNamedElementMixinImpl" + implements=["me.zhouxi.slint.lang.psi.SlintPsiNamedElement"] +} + +PropertyName ::= GenericIdentifier + +TypeName ::= GenericIdentifier + +EnumName ::= GenericIdentifier + +EnumValue ::=GenericIdentifier + +ExternalName ::=GenericIdentifier{ + mixin="me.zhouxi.slint.lang.psi.impl.SlintPsiNamedElementMixinImpl" + implements=["me.zhouxi.slint.lang.psi.SlintPsiNamedElement"] +} + +ReferenceIdentifier::=GenericIdentifier{ + mixin="me.zhouxi.slint.lang.psi.impl.SlintReferencedIdentifierMixinImpl" +} + +//----------UnnamedIdentifier------------------ + +PropertyNameReference ::= GenericIdentifier{ + mixin="me.zhouxi.slint.lang.psi.impl.SlintReferencedIdentifierMixinImpl" +} +FunctionReference ::=GenericIdentifier{ + +} +QualifiedPropertyNameReference ::= PropertyNameReference ('.' PropertyNameReference)*{ + extends=PropertyNameReference +} +TypeNameReference::=GenericIdentifier + +QualifiedTypeNameReference ::= TypeNameReference ('.' TypeNameReference)*{ + extends=TypeNameReference +} + +ModuleLocation ::= RawStringLiteral{ + mixin="me.zhouxi.slint.lang.psi.impl.SlintReferencedIdentifierMixinImpl" +} +// +//noinspection BnfSuspiciousToken +private RawStringLiteral ::= StringLiteral + +//noinspection BnfSuspiciousToken +private Numbers ::= NumberLiteral + +//noinspection BnfSuspiciousToken +private GenericIdentifier::= Identifier + +//noinspection BnfSuspiciousToken +private RawColorLiteral ::= ColorLiteral diff --git a/src/main/grammar/main.flex b/src/main/grammar/main.flex new file mode 100644 index 0000000..28b5622 --- /dev/null +++ b/src/main/grammar/main.flex @@ -0,0 +1,93 @@ +package me.zhouxi.slint.lang.lexer; + +import com.intellij.lexer.FlexLexer; +import com.intellij.psi.tree.IElementType; + +import static com.intellij.psi.TokenType.BAD_CHARACTER; +import static com.intellij.psi.TokenType.WHITE_SPACE; +import static me.zhouxi.slint.lang.psi.SlintTypes.*; + +%% + +%{ + public SlintLexer() { + this((java.io.Reader)null); + } +%} + +%public +%class SlintLexer +%implements FlexLexer +%function advance +%type IElementType +%unicode + +WHITE_SPACE=\s+ +NUMBERLITERAL=[0-9]+(\.([0-9]+)?)?([a-z]+|%)? +IDENTIFIER=[a-zA-Z][A-Za-z0-9\-_]* +COLORLITERAL=#([a-zA-Z0-9]+) +LINECOMMENT=\/\/[^\r\n]* + +%xstate COMMENT STRINGLITERAL + +%% + { +{WHITE_SPACE} { return WHITE_SPACE; } +"/*" { yybegin(COMMENT);} +"\"" { yybegin(STRINGLITERAL);} +"," { return Comma; } +"=>" { return FatArrow; } +"<=>" { return DoubleArrow; } +"+=" { return PlusEqual; } +"-=" { return MinusEqual; } +"*=" { return StarEqual; } +"/=" { return DivEqual; } +"<=" { return LessEqual; } +">=" { return GreaterEqual; } +"==" { return EqualEqual; } +"!=" { return NotEqual; } +":=" { return ColonEqual; } +"->" { return Arrow; } +"||" { return OrOr; } +"&&" { return AndAnd; } +"{" { return LBrace; } +"}" { return RBrace; } +"(" { return LParent; } +")" { return RParent; } +"<" { return LAngle; } +">" { return RAngle; } +"[" { return LBracket; } +"]" { return RBracket; } +"+" { return Plus; } +"-" { return Minus; } +"*" { return Star; } +"/" { return Div; } +"=" { return Equal; } +":" { return Colon; } +";" { return Semicolon; } +"!" { return Bang; } +"." { return Dot; } +"?" { return Question; } +"$" { return Dollar; } +"@" { return At; } +"|" { return Pipe; } +"%" { return Percent; } +{NUMBERLITERAL} { return NumberLiteral; } +{IDENTIFIER} { return Identifier; } +{COLORLITERAL} { return ColorLiteral; } +{LINECOMMENT} { return LineComment; } +} + +{ + "*/" { yybegin(YYINITIAL);return BlockComment; } + [^*]+ {} + "*" {} +} +{ + "\\" { } + "\\\"" { } + "\"" { yybegin(YYINITIAL); return StringLiteral; } + [^\\\"\n]+ { } + [\n] {yybegin(YYINITIAL);} +} +[^] { return BAD_CHARACTER; } diff --git a/src/main/java/me/zhouxi/slint/lang/Slint.java b/src/main/java/me/zhouxi/slint/lang/Slint.java new file mode 100644 index 0000000..e690968 --- /dev/null +++ b/src/main/java/me/zhouxi/slint/lang/Slint.java @@ -0,0 +1,21 @@ +package me.zhouxi.slint.lang; + +import com.intellij.lang.Language; + +import javax.swing.*; + +import static com.intellij.openapi.util.IconLoader.getIcon; + +/** + * @author zhouxi 2024/4/30 + */ +public class Slint extends Language { + + public static final Slint INSTANCE = new Slint(); + + protected Slint() { + super("Slint"); + } + + public static final Icon ICON = getIcon("META-INF/pluginIcon.svg", Slint.class); +} diff --git a/src/main/java/me/zhouxi/slint/lang/SlintElementType.java b/src/main/java/me/zhouxi/slint/lang/SlintElementType.java new file mode 100644 index 0000000..58f8af9 --- /dev/null +++ b/src/main/java/me/zhouxi/slint/lang/SlintElementType.java @@ -0,0 +1,15 @@ +package me.zhouxi.slint.lang; + +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +public final class SlintElementType extends IElementType { + + + public SlintElementType(@NonNls @NotNull String rawKind) { + super(rawKind, Slint.INSTANCE); + } + + +} diff --git a/src/main/java/me/zhouxi/slint/lang/SlintParserDefinition.java b/src/main/java/me/zhouxi/slint/lang/SlintParserDefinition.java new file mode 100644 index 0000000..f227f00 --- /dev/null +++ b/src/main/java/me/zhouxi/slint/lang/SlintParserDefinition.java @@ -0,0 +1,62 @@ +package me.zhouxi.slint.lang; + +import com.intellij.lang.ASTNode; +import com.intellij.lang.ParserDefinition; +import com.intellij.lang.PsiParser; +import com.intellij.lexer.FlexAdapter; +import com.intellij.lexer.Lexer; +import com.intellij.openapi.project.Project; +import com.intellij.psi.FileViewProvider; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.tree.IFileElementType; +import com.intellij.psi.tree.TokenSet; +import me.zhouxi.slint.lang.lexer.SlintLexer; +import me.zhouxi.slint.lang.parser.SlintParser; +import me.zhouxi.slint.lang.psi.SlintFile; +import me.zhouxi.slint.lang.psi.SlintTypes; +import org.jetbrains.annotations.NotNull; + +import static me.zhouxi.slint.lang.psi.SlintTypes.*; + +/** + * @author zhouxi 2024/4/30 + */ +public class SlintParserDefinition implements ParserDefinition { + @Override + public @NotNull Lexer createLexer(Project project) { + return new FlexAdapter(new SlintLexer()); + } + + @Override + public @NotNull PsiParser createParser(Project project) { + return new SlintParser(); + } + + @Override + public @NotNull IFileElementType getFileNodeType() { + return FileType; + } + + public static final IFileElementType FileType = new IFileElementType("SlintFile",Slint.INSTANCE); + + @Override + public @NotNull TokenSet getCommentTokens() { + return TokenSet.create(LineComment, BlockComment); + } + + @Override + public @NotNull TokenSet getStringLiteralElements() { + return TokenSet.create(StringLiteral); + } + + @Override + public @NotNull PsiElement createElement(ASTNode node) { + return SlintTypes.Factory.createElement(node); + } + + @Override + public @NotNull PsiFile createFile(@NotNull FileViewProvider viewProvider) { + return new SlintFile(viewProvider); + } +} diff --git a/src/main/java/me/zhouxi/slint/lang/SlintTokenType.java b/src/main/java/me/zhouxi/slint/lang/SlintTokenType.java new file mode 100644 index 0000000..d0254a6 --- /dev/null +++ b/src/main/java/me/zhouxi/slint/lang/SlintTokenType.java @@ -0,0 +1,16 @@ +package me.zhouxi.slint.lang; + +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +/** + * @author zhouxi 2024/4/30 + */ +public class SlintTokenType extends IElementType { + + public SlintTokenType(@NonNls @NotNull String kind) { + super(kind, Slint.INSTANCE); + } + +} diff --git a/src/main/java/me/zhouxi/slint/lang/psi/SlintFile.java b/src/main/java/me/zhouxi/slint/lang/psi/SlintFile.java new file mode 100644 index 0000000..2d52b73 --- /dev/null +++ b/src/main/java/me/zhouxi/slint/lang/psi/SlintFile.java @@ -0,0 +1,23 @@ +package me.zhouxi.slint.lang.psi; + +import com.intellij.extapi.psi.PsiFileBase; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.psi.FileViewProvider; +import com.intellij.psi.PsiFile; +import me.zhouxi.slint.lang.Slint; +import org.jetbrains.annotations.NotNull; + +/** + * @author zhouxi 2024/4/30 + */ +public class SlintFile extends PsiFileBase implements PsiFile { + + public SlintFile(@NotNull FileViewProvider viewProvider) { + super(viewProvider, Slint.INSTANCE); + } + + @Override + public @NotNull FileType getFileType() { + return SlintFileType.INSTANCE; + } +} diff --git a/src/main/java/me/zhouxi/slint/lang/psi/SlintFileType.java b/src/main/java/me/zhouxi/slint/lang/psi/SlintFileType.java new file mode 100644 index 0000000..de2597c --- /dev/null +++ b/src/main/java/me/zhouxi/slint/lang/psi/SlintFileType.java @@ -0,0 +1,42 @@ +package me.zhouxi.slint.lang.psi; + +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.util.NlsContexts; +import com.intellij.openapi.util.NlsSafe; +import me.zhouxi.slint.lang.Slint; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * @author zhouxi 2024/4/30 + */ +public class SlintFileType extends LanguageFileType { + + public static final SlintFileType INSTANCE = new SlintFileType(); + + protected SlintFileType() { + super(Slint.INSTANCE); + } + + @Override + public @NonNls @NotNull String getName() { + return "Slint File"; + } + + @Override + public @NlsContexts.Label @NotNull String getDescription() { + return "Slint file dsl"; + } + + @Override + public @NlsSafe @NotNull String getDefaultExtension() { + return "slint"; + } + + @Override + public Icon getIcon() { + return Slint.ICON; + } +} diff --git a/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiElement.java b/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiElement.java new file mode 100644 index 0000000..ffbf9ce --- /dev/null +++ b/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiElement.java @@ -0,0 +1,9 @@ +package me.zhouxi.slint.lang.psi; + +import com.intellij.psi.PsiElement; + +public interface SlintPsiElement extends PsiElement { + + + +} diff --git a/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiElementImpl.java b/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiElementImpl.java new file mode 100644 index 0000000..000fdbc --- /dev/null +++ b/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiElementImpl.java @@ -0,0 +1,12 @@ +package me.zhouxi.slint.lang.psi; + +import com.intellij.extapi.psi.ASTWrapperPsiElement; +import com.intellij.lang.ASTNode; +import org.jetbrains.annotations.NotNull; + +public class SlintPsiElementImpl extends ASTWrapperPsiElement implements SlintPsiElement { + + public SlintPsiElementImpl(@NotNull ASTNode node) { + super(node); + } +} diff --git a/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiKeywordIdentifier.java b/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiKeywordIdentifier.java new file mode 100644 index 0000000..254faf5 --- /dev/null +++ b/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiKeywordIdentifier.java @@ -0,0 +1,4 @@ +package me.zhouxi.slint.lang.psi; + +public interface SlintPsiKeywordIdentifier extends SlintPsiElement{ +} diff --git a/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiNamedElement.java b/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiNamedElement.java new file mode 100644 index 0000000..8332770 --- /dev/null +++ b/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiNamedElement.java @@ -0,0 +1,11 @@ +package me.zhouxi.slint.lang.psi; + +import com.intellij.psi.NavigatablePsiElement; +import com.intellij.psi.PsiNameIdentifierOwner; + +public interface SlintPsiNamedElement extends SlintPsiElement, PsiNameIdentifierOwner, NavigatablePsiElement { + @Override + default boolean canNavigate() { + return true; + } +} diff --git a/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiReferencedIdentifier.java b/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiReferencedIdentifier.java new file mode 100644 index 0000000..b3be86e --- /dev/null +++ b/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiReferencedIdentifier.java @@ -0,0 +1,5 @@ +package me.zhouxi.slint.lang.psi; + +public interface SlintPsiReferencedIdentifier extends SlintPsiElement { + +} diff --git a/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiUtils.java b/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiUtils.java new file mode 100644 index 0000000..205ec07 --- /dev/null +++ b/src/main/java/me/zhouxi/slint/lang/psi/SlintPsiUtils.java @@ -0,0 +1,28 @@ +package me.zhouxi.slint.lang.psi; + +import java.util.List; + +public class SlintPsiUtils { + + public static final List InternalTypes = List.of( + "angle", + "bool", + "brush", + "color", + "duration", + "easing", + "float", + "image", + "int", + "length", + "percent", + "physical-length", + "relative-font-size", + "string" + ); + + public static boolean isInternalType(SlintPsiElement identifier) { + return InternalTypes.stream().anyMatch(identifier::textMatches); + } + +} diff --git a/src/main/java/me/zhouxi/slint/lang/psi/impl/SlintReferencedIdentifierMixinImpl.java b/src/main/java/me/zhouxi/slint/lang/psi/impl/SlintReferencedIdentifierMixinImpl.java new file mode 100644 index 0000000..396593c --- /dev/null +++ b/src/main/java/me/zhouxi/slint/lang/psi/impl/SlintReferencedIdentifierMixinImpl.java @@ -0,0 +1,27 @@ +package me.zhouxi.slint.lang.psi.impl; + +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiReference; +import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry; +import me.zhouxi.slint.lang.psi.SlintPsiElementImpl; +import me.zhouxi.slint.lang.psi.SlintPsiReferencedIdentifier; +import org.jetbrains.annotations.NotNull; + +public abstract class SlintReferencedIdentifierMixinImpl extends SlintPsiElementImpl implements SlintPsiReferencedIdentifier { + + public SlintReferencedIdentifierMixinImpl(@NotNull ASTNode node) { + super(node); + } + + + @Override + public PsiReference getReference() { + PsiReference[] references = getReferences(); + return references.length > 0 ? references[0] : null; + } + + @Override + public PsiReference @NotNull [] getReferences() { + return ReferenceProvidersRegistry.getReferencesFromProviders(this); + } +} diff --git a/src/main/java/me/zhouxi/slint/reference/SlintElementNameManipulator.java b/src/main/java/me/zhouxi/slint/reference/SlintElementNameManipulator.java new file mode 100644 index 0000000..4b7a2ff --- /dev/null +++ b/src/main/java/me/zhouxi/slint/reference/SlintElementNameManipulator.java @@ -0,0 +1,32 @@ +package me.zhouxi.slint.reference; + +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.AbstractElementManipulator; +import com.intellij.util.IncorrectOperationException; +import me.zhouxi.slint.lang.psi.SlintPsiReferencedIdentifier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static me.zhouxi.slint.lang.SlintElementFactoryKt.createIdentifier; + +/** + * @author zhouxi 2024/5/8 + */ +public class SlintElementNameManipulator extends AbstractElementManipulator { + @Override + public @Nullable SlintPsiReferencedIdentifier handleContentChange(@NotNull SlintPsiReferencedIdentifier element, @NotNull TextRange range, String newContent) throws IncorrectOperationException { + final var identifier = element.getFirstChild(); + if (identifier==null){ + throw new IncorrectOperationException("identifier doesn't exist"); + } + final var newIdentifier = createIdentifier(element.getProject(), newContent); + identifier.replace(newIdentifier); + return element; + } + + @Override + @NotNull + public TextRange getRangeInElement(@NotNull final SlintPsiReferencedIdentifier element) { + return new TextRange(0,element.getTextLength()); + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/Plugin.kt b/src/main/kotlin/me/zhouxi/slint/Plugin.kt new file mode 100644 index 0000000..0d58cf9 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/Plugin.kt @@ -0,0 +1,12 @@ +package me.zhouxi.slint + +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.openapi.extensions.PluginId +import java.nio.file.Path + +object Plugin { + val Id = PluginId.findId("me.zhouxi.intellij-slint") + + val Path: Path = PluginManagerCore.getPlugin(Id)!!.pluginPath + +} \ No newline at end of file diff --git a/src/main/kotlin/me/zhouxi/slint/brace/SlintPairedBraceMatcher.kt b/src/main/kotlin/me/zhouxi/slint/brace/SlintPairedBraceMatcher.kt new file mode 100644 index 0000000..d86dc88 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/brace/SlintPairedBraceMatcher.kt @@ -0,0 +1,30 @@ +package me.zhouxi.slint.brace + +import com.intellij.lang.BracePair +import com.intellij.lang.PairedBraceMatcher +import com.intellij.psi.PsiFile +import com.intellij.psi.tree.IElementType +import me.zhouxi.slint.lang.psi.SlintTypes + +class SlintPairedBraceMatcher : PairedBraceMatcher { + override fun getPairs(): Array { + return Pair + } + + override fun isPairedBracesAllowedBeforeType(lbraceType: IElementType, contextType: IElementType?): Boolean { + return true + } + + override fun getCodeConstructStart(file: PsiFile, openingBraceOffset: Int): Int { + return 0 + } + + companion object { + val Pair: Array = arrayOf( + BracePair(SlintTypes.LBrace, SlintTypes.RBrace, true), + BracePair(SlintTypes.LParent, SlintTypes.RParent, true), + BracePair(SlintTypes.LBracket, SlintTypes.RBracket, true), + BracePair(SlintTypes.LAngle, SlintTypes.RAngle, true) + ) + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/completion/SlintCompletionContributor.kt b/src/main/kotlin/me/zhouxi/slint/completion/SlintCompletionContributor.kt new file mode 100644 index 0000000..4062bca --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/completion/SlintCompletionContributor.kt @@ -0,0 +1,84 @@ +package me.zhouxi.slint.completion + +import com.intellij.codeInsight.completion.* +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.icons.AllIcons +import com.intellij.patterns.PlatformPatterns +import com.intellij.psi.util.childrenOfType +import com.intellij.psi.util.parentOfType +import com.intellij.util.ProcessingContext +import me.zhouxi.slint.lang.SlintParserDefinition +import me.zhouxi.slint.lang.psi.SlintComponent +import me.zhouxi.slint.lang.psi.SlintFile +import me.zhouxi.slint.lang.psi.SlintImport +import me.zhouxi.slint.lang.psi.SlintPsiUtils.InternalTypes +import me.zhouxi.slint.lang.psi.SlintTypes +import me.zhouxi.slint.lang.psi.utils.exportedElements +import me.zhouxi.slint.lang.psi.utils.inheritDeclaredElements + +class SlintCompletionContributor : CompletionContributor() { + init { + //文件级别 + extend( + CompletionType.BASIC, + PlatformPatterns.psiElement().withAncestor(4, PlatformPatterns.psiElement(SlintParserDefinition.FileType)), + object : CompletionProvider() { + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + result.addAllElements(TopKeywords) + } + }) + + //类型定义 + extend( + CompletionType.BASIC, + PlatformPatterns.psiElement().withAncestor(3, PlatformPatterns.psiElement(SlintTypes.Type)), + object : CompletionProvider() { + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + result.addAllElements(BasicTypes) + } + }) + + //componentBody + extend( + CompletionType.BASIC, + PlatformPatterns.psiElement().withAncestor(3, PlatformPatterns.psiElement(SlintTypes.ComponentBody)), + object : CompletionProvider() { + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + result.addAllElements(ElementKeywords) + val component = parameters.position.parentOfType() ?: return + val elements = component.inheritDeclaredElements() + for (element in elements) { + result.addElement(LookupElementBuilder.create(element).withIcon(AllIcons.Nodes.Property)) + } + val file = component.containingFile as SlintFile + val array = file.childrenOfType() + .mapNotNull { it.moduleLocation?.reference?.resolve() as SlintFile? } + .flatMap { it.exportedElements() } + for (element in array) { + result.addElement(LookupElementBuilder.create(element).withIcon(AllIcons.Nodes.Class)) + } + } + }) + } + +} + +val BasicTypes = InternalTypes.map { LookupElementBuilder.create(it) } +val TopKeywords = arrayOf("export", "component", "global", "enum", "struct").map { LookupElementBuilder.create(it) } +val ElementKeywords = arrayOf( + "in", "out", "in-out", "callback", + "property", "private", "changed", + "states", "transitions", "@children" +).map { LookupElementBuilder.create(it) } \ No newline at end of file diff --git a/src/main/kotlin/me/zhouxi/slint/formatter/SlintLineIndentProvider.kt b/src/main/kotlin/me/zhouxi/slint/formatter/SlintLineIndentProvider.kt new file mode 100644 index 0000000..d6e8eb7 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/formatter/SlintLineIndentProvider.kt @@ -0,0 +1,38 @@ +package me.zhouxi.slint.formatter + +import com.intellij.lang.Language +import com.intellij.psi.TokenType +import com.intellij.psi.impl.source.codeStyle.SemanticEditorPosition.SyntaxElement +import com.intellij.psi.impl.source.codeStyle.lineIndent.JavaLikeLangLineIndentProvider +import com.intellij.psi.tree.IElementType +import me.zhouxi.slint.lang.Slint +import me.zhouxi.slint.lang.psi.SlintTypes + +class SlintLineIndentProvider : JavaLikeLangLineIndentProvider() { + + override fun mapType(tokenType: IElementType): SyntaxElement? { + return SyntaxMap[tokenType] + } + + + override fun isSuitableForLanguage(language: Language): Boolean { + return language.isKindOf(Slint.INSTANCE) + } + + companion object { + val SyntaxMap: Map = java.util.Map.ofEntries( + java.util.Map.entry(SlintTypes.LBracket, JavaLikeElement.BlockOpeningBrace), + java.util.Map.entry(SlintTypes.RBracket, JavaLikeElement.BlockClosingBrace), + java.util.Map.entry(SlintTypes.LBrace, JavaLikeElement.BlockOpeningBrace), + java.util.Map.entry(SlintTypes.RBrace, JavaLikeElement.BlockClosingBrace), + java.util.Map.entry(TokenType.WHITE_SPACE, JavaLikeElement.Whitespace), + java.util.Map.entry(SlintTypes.Semicolon, JavaLikeElement.Semicolon), + java.util.Map.entry(SlintTypes.LineComment, JavaLikeElement.LineComment), + java.util.Map.entry(SlintTypes.BlockComment, JavaLikeElement.BlockComment), + java.util.Map.entry(SlintTypes.Colon, JavaLikeElement.Colon), + java.util.Map.entry(SlintTypes.Comma, JavaLikeElement.Comma), + java.util.Map.entry(SlintTypes.LParent, JavaLikeElement.LeftParenthesis), + java.util.Map.entry(SlintTypes.RParent, JavaLikeElement.RightParenthesis) + ) + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/highlight/Definitions.kt b/src/main/kotlin/me/zhouxi/slint/highlight/Definitions.kt new file mode 100644 index 0000000..2918fb1 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/highlight/Definitions.kt @@ -0,0 +1,58 @@ +package me.zhouxi.slint.highlight + +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors +import com.intellij.openapi.editor.HighlighterColors +import com.intellij.openapi.editor.colors.TextAttributesKey + +object Definitions { + @JvmField + val _KeyWord: TextAttributesKey = + TextAttributesKey.createTextAttributesKey("_KeyWord", DefaultLanguageHighlighterColors.KEYWORD) + + val _StringLiteral: TextAttributesKey = + TextAttributesKey.createTextAttributesKey("_StringLiteral", DefaultLanguageHighlighterColors.STRING) + + val _Comment: TextAttributesKey = + TextAttributesKey.createTextAttributesKey("_Comment", DefaultLanguageHighlighterColors.LINE_COMMENT) + + val _DeclaredIdentifier: TextAttributesKey = TextAttributesKey.createTextAttributesKey( + "_DeclaredIdentifier", DefaultLanguageHighlighterColors.INSTANCE_FIELD + ) + + val _BadCharacter: TextAttributesKey = + TextAttributesKey.createTextAttributesKey("BadCharacter", HighlighterColors.BAD_CHARACTER) + + val _NumberLiteral: TextAttributesKey = + TextAttributesKey.createTextAttributesKey("_NumberLiteral", DefaultLanguageHighlighterColors.NUMBER) + + val _SemiColon: TextAttributesKey = + TextAttributesKey.createTextAttributesKey("_SemiColon", DefaultLanguageHighlighterColors.SEMICOLON) + val _Error: TextAttributesKey = TextAttributesKey.createTextAttributesKey("_Error", HighlighterColors.BAD_CHARACTER) + + + private val BadCharacter = arrayOf(_BadCharacter) + + @JvmField + val KeyWord: Array = arrayOf(_KeyWord) + + @JvmField + val StringLiteral: Array = arrayOf(_StringLiteral) + + val DeclaredIdentifier: Array = arrayOf(_DeclaredIdentifier) + + @JvmField + val Comment: Array = arrayOf(_Comment) + + val Empty: Array = arrayOfNulls(0) + + @JvmField + val NumberLiteral: Array = arrayOf(_NumberLiteral) + + @JvmField + val Brace: Array = arrayOf(DefaultLanguageHighlighterColors.BRACES) + + @JvmField + val SemiColon: Array = arrayOf(_SemiColon) + + val Error: Array = arrayOf(_Error) +} diff --git a/src/main/kotlin/me/zhouxi/slint/highlight/KeywordHighlightAnnotator.kt b/src/main/kotlin/me/zhouxi/slint/highlight/KeywordHighlightAnnotator.kt new file mode 100644 index 0000000..9a70837 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/highlight/KeywordHighlightAnnotator.kt @@ -0,0 +1,45 @@ +package me.zhouxi.slint.highlight + +import com.intellij.lang.annotation.AnnotationHolder +import com.intellij.lang.annotation.Annotator +import com.intellij.lang.annotation.HighlightSeverity +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors +import com.intellij.psi.PsiElement +import me.zhouxi.slint.lang.psi.* + +class KeywordHighlightAnnotator : Annotator { + override fun annotate(element: PsiElement, holder: AnnotationHolder) { + if (element is SlintPsiKeywordIdentifier) { + holder.newSilentAnnotation(HighlightSeverity.INFORMATION) + .range(element) + .textAttributes(Definitions._KeyWord) + .create() + return + } + if (element is SlintTypeNameReference && SlintPsiUtils.isInternalType(element)) { + holder.newSilentAnnotation(HighlightSeverity.INFORMATION) + .range(element) + .textAttributes(Definitions._KeyWord) + .create() + return + } + if (element is SlintPropertyName) { + holder.newSilentAnnotation(HighlightSeverity.INFORMATION) + .range(element) + .textAttributes(DefaultLanguageHighlighterColors.INSTANCE_FIELD) + .create() + } + if (element is SlintPropertyBinding) { + holder.newSilentAnnotation(HighlightSeverity.INFORMATION) + .range(element.getReferenceIdentifier()) + .textAttributes(DefaultLanguageHighlighterColors.INSTANCE_FIELD) + .create() + } + if (element is SlintFunctionName || element is SlintFunctionReference) { + holder.newSilentAnnotation(HighlightSeverity.INFORMATION) + .range(element) + .textAttributes(DefaultLanguageHighlighterColors.INSTANCE_METHOD) + .create() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/zhouxi/slint/highlight/SlintSyntaxHighlighter.kt b/src/main/kotlin/me/zhouxi/slint/highlight/SlintSyntaxHighlighter.kt new file mode 100644 index 0000000..a8e4b8e --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/highlight/SlintSyntaxHighlighter.kt @@ -0,0 +1,37 @@ +package me.zhouxi.slint.highlight + +import com.intellij.lexer.FlexAdapter +import com.intellij.lexer.Lexer +import com.intellij.openapi.editor.colors.TextAttributesKey +import com.intellij.openapi.fileTypes.SyntaxHighlighterBase +import com.intellij.psi.tree.IElementType +import me.zhouxi.slint.lang.lexer.SlintLexer +import me.zhouxi.slint.lang.psi.SlintTypes + +class SlintSyntaxHighlighter : SyntaxHighlighterBase() { + override fun getHighlightingLexer(): Lexer { + return FlexAdapter(SlintLexer()) + } + + override fun getTokenHighlights(tokenType: IElementType): Array { + if (tokenType === SlintTypes.StringLiteral) { + return Definitions.StringLiteral + } + if (tokenType === SlintTypes.LineComment || tokenType === SlintTypes.BlockComment) { + return Definitions.Comment + } + if (tokenType === SlintTypes.NumberLiteral) { + return Definitions.NumberLiteral + } + if (tokenType === SlintTypes.LBrace || tokenType === SlintTypes.RBrace) { + return Definitions.Brace + } + if (tokenType === SlintTypes.DoubleArrow) { + return Definitions.KeyWord + } + if (tokenType === SlintTypes.Semicolon) { + return Definitions.SemiColon + } + return TextAttributesKey.EMPTY_ARRAY + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/lang/SlintElementFactory.kt b/src/main/kotlin/me/zhouxi/slint/lang/SlintElementFactory.kt new file mode 100644 index 0000000..313278f --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/lang/SlintElementFactory.kt @@ -0,0 +1,18 @@ +package me.zhouxi.slint.lang + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFileFactory +import me.zhouxi.slint.lang.psi.SlintComponent +import me.zhouxi.slint.lang.psi.SlintFile + +/** + * @author zhouxi 2024/5/8 + */ + +fun createIdentifier(project: Project?, text: String): PsiElement { + val factory = PsiFileFactory.getInstance(project) + val file = factory.createFileFromText("dummy.slint", Slint.INSTANCE, "component $text{}") as SlintFile + return file.findChildByClass(SlintComponent::class.java)!!.componentName!!.identifier +} + diff --git a/src/main/kotlin/me/zhouxi/slint/lang/psi/impl/SlintPsiNamedElementMixinImpl.kt b/src/main/kotlin/me/zhouxi/slint/lang/psi/impl/SlintPsiNamedElementMixinImpl.kt new file mode 100644 index 0000000..6e0be9a --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/lang/psi/impl/SlintPsiNamedElementMixinImpl.kt @@ -0,0 +1,39 @@ +package me.zhouxi.slint.lang.psi.impl + +import com.intellij.lang.ASTNode +import com.intellij.openapi.util.NlsSafe +import com.intellij.psi.PsiElement +import com.intellij.util.IncorrectOperationException +import me.zhouxi.slint.lang.createIdentifier +import me.zhouxi.slint.lang.psi.SlintNamed +import me.zhouxi.slint.lang.psi.SlintPsiElementImpl +import me.zhouxi.slint.lang.psi.SlintPsiNamedElement + +/** + * @author zhouxi 2024/5/15 + */ +abstract class SlintPsiNamedElementMixinImpl(node: ASTNode) : SlintPsiElementImpl(node), + SlintPsiNamedElement { + override fun getNameIdentifier(): PsiElement? { + return findChildByClass(SlintNamed::class.java) + } + + override fun getName(): String? { + return nameIdentifier?.text ?: this.text + } + + override fun canNavigate(): Boolean { + return true + } + + override fun getTextOffset(): Int { + return nameIdentifier?.textOffset ?: super.getTextOffset() + } + + @Throws(IncorrectOperationException::class) + override fun setName(name: @NlsSafe String): PsiElement { + val element = nameIdentifier ?: throw IncorrectOperationException() + element.replace(createIdentifier(project, name)) + return this + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/lang/psi/utils/SlintFileUtils.kt b/src/main/kotlin/me/zhouxi/slint/lang/psi/utils/SlintFileUtils.kt new file mode 100644 index 0000000..2cf4bc2 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/lang/psi/utils/SlintFileUtils.kt @@ -0,0 +1,43 @@ +package me.zhouxi.slint.lang.psi.utils + +import com.intellij.psi.PsiElement +import com.intellij.psi.util.childrenOfType +import me.zhouxi.slint.lang.psi.* + + +fun SlintExport.exportedElements(): List { + val list = mutableListOf() + this.exportType?.exportIdentifierList?.forEach { + if (it.externalName == null) { + val component = it.referenceIdentifier.reference?.resolve() as SlintComponent? + component?.let { list.add(component) } + } else { + list.add(it.externalName!!) + } + } + this.component?.let { list.add(it) } + this.globalSingleton?.let { list.add(it) } + val file = this.exportModule?.moduleLocation?.reference?.resolve() as SlintFile? ?: return list + val exports = file.childrenOfType() + //TODO recursion error + exports.forEach { list.addAll(it.exportedElements()) } + return list +} + +fun SlintImport.importedElements(): List { + val list = mutableListOf() + this.importedIdentifierList.forEach { identifier -> + list.add(identifier.referenceIdentifier) + identifier.internalName?.let { list.add(it) } + } + return list +} + +fun SlintFile.importedElements(): List { + return this.childrenOfType().flatMap { it.importedElements() } +} + +fun SlintFile.exportedElements(): List { + return this.childrenOfType().flatMap { it.exportedElements() } + +} diff --git a/src/main/kotlin/me/zhouxi/slint/lang/psi/utils/SlintPsiTreeUtils.kt b/src/main/kotlin/me/zhouxi/slint/lang/psi/utils/SlintPsiTreeUtils.kt new file mode 100644 index 0000000..06ea356 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/lang/psi/utils/SlintPsiTreeUtils.kt @@ -0,0 +1,132 @@ +package me.zhouxi.slint.lang.psi.utils + +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import me.zhouxi.slint.lang.psi.* +import java.util.function.Function +import java.util.function.Predicate + + +fun resolveComponent(element: SlintReferenceIdentifier?): SlintComponent? { + if (element == null) { + return null + } + val maybeComponent = element.reference?.resolve() + if (maybeComponent is SlintComponent) { + return maybeComponent + } + return resolveReferencedComponent(maybeComponent ?: return null) +} + +fun resolveComponent(element: SlintInternalName): SlintComponent? { + //内部名字解析引用 + val resolve = element.reference?.resolve() + if (resolve is SlintComponent) { + return resolve + } + //InternalName解析不到东西,换成External试一下 + if (resolve == null) { + val externalName = PsiTreeUtil.getPrevSiblingOfType(element, SlintExternalName::class.java) + if (externalName == null) { + val externalName1 = PsiTreeUtil.getNextSiblingOfType(element, SlintExternalName::class.java) ?: return null + return resolveReferencedComponent(externalName1) + } + return resolveReferencedComponent(externalName) + } + return resolveReferencedComponent(resolve) +} + +fun resolveComponent(element: SlintExternalName): SlintComponent? { + val resolve = element.reference?.resolve() + if (resolve is SlintComponent) { + return resolve + } + //InternalName解析不到东西,换成External试一下 + if (resolve == null) { + val internalName = PsiTreeUtil.getPrevSiblingOfType(element, SlintInternalName::class.java) + if (internalName == null) { + val internalName1 = PsiTreeUtil.getNextSiblingOfType(element, SlintInternalName::class.java) ?: return null + return resolveReferencedComponent(internalName1) + } + return resolveReferencedComponent(internalName) + } + return resolveReferencedComponent(resolve) +} + +fun resolveReferencedComponent(element: PsiElement?): SlintComponent? { + if (element == null) { + return null + } + when (element) { + is SlintComponentName -> return element.parent as SlintComponent? + is SlintComponent -> return element + is SlintReferenceIdentifier -> return resolveComponent(element) + is SlintInternalName -> return resolveComponent(element) + is SlintExternalName -> return resolveComponent(element) + } + return null +} + +fun searchProperty( + component: PsiElement?, + predicate: Predicate +): SlintPropertyDeclaration? { + if (component is SlintSubComponent) { + return searchProperty(component, predicate) + } + if (component is SlintComponent) { + return searchElementInParents(component, predicate) { it.componentBody?.propertyDeclarationList } + } + return null +} + +fun searchProperty( + subComponent: SlintSubComponent?, + predicate: Predicate +): SlintPropertyDeclaration? { + val component = resolveComponent(subComponent?.referenceIdentifier) ?: return null + return searchElementInParents(component, predicate) { it.componentBody?.propertyDeclarationList } +} + +fun searchCallback( + subComponent: SlintSubComponent?, + predicate: Predicate +): SlintCallbackDeclaration? { + val component = subComponent?.referenceIdentifier?.reference?.resolve() ?: return null + return searchElementInParents( + component as SlintComponent, + predicate + ) { it.componentBody?.callbackDeclarationList } +} + +fun searchElementInParents( + component: SlintComponent?, + predicate: Predicate, + function: Function?> +): T? { + if (component == null) { + return null + } + val properties = function.apply(component) ?: arrayListOf() + for (property in properties) { + if (predicate.test(property)) { + return property + } + } + val inheritComponent = resolveReferencedComponent(component.inheritDeclaration?.referenceIdentifier) + return searchElementInParents(inheritComponent, predicate, function) +} + +fun SlintComponent.currentDeclaredElements(): List { + val properties = this.componentBody?.propertyDeclarationList ?: emptyList() + val callbacks = this.componentBody?.callbackDeclarationList ?: emptyList() + return properties + callbacks +} + +fun SlintComponent.inheritDeclaredElements(): List { + val properties = this.componentBody?.propertyDeclarationList ?: emptyList() + val callbacks = this.componentBody?.callbackDeclarationList ?: emptyList() + val inheritedComponent = resolveReferencedComponent(this.inheritDeclaration?.referenceIdentifier) + ?: return properties + callbacks + return properties + callbacks + inheritedComponent.inheritDeclaredElements() +} \ No newline at end of file diff --git a/src/main/kotlin/me/zhouxi/slint/preview/CommandOptions.kt b/src/main/kotlin/me/zhouxi/slint/preview/CommandOptions.kt new file mode 100644 index 0000000..cfc61e3 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/preview/CommandOptions.kt @@ -0,0 +1,11 @@ +package me.zhouxi.slint.preview + +import com.intellij.execution.configurations.LocatableRunConfigurationOptions + +/** + * @author zhouxi 2024/5/16 + */ +class CommandOptions : LocatableRunConfigurationOptions() { + + var arguments by string() +} diff --git a/src/main/kotlin/me/zhouxi/slint/preview/PreviewAction.kt b/src/main/kotlin/me/zhouxi/slint/preview/PreviewAction.kt new file mode 100644 index 0000000..3d77906 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/preview/PreviewAction.kt @@ -0,0 +1,15 @@ +package me.zhouxi.slint.preview + +import com.intellij.execution.RunManager +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent + +class PreviewAction : AnAction() { + override fun actionPerformed(e: AnActionEvent) { + val manager = RunManager.getInstance(e.project!!) +// e.si +// manager.createConfiguration() + + + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/preview/PreviewConfigurationFactory.kt b/src/main/kotlin/me/zhouxi/slint/preview/PreviewConfigurationFactory.kt new file mode 100644 index 0000000..585efa4 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/preview/PreviewConfigurationFactory.kt @@ -0,0 +1,26 @@ +package me.zhouxi.slint.preview + +import com.intellij.execution.configurations.ConfigurationFactory +import com.intellij.execution.configurations.ConfigurationType +import com.intellij.execution.configurations.RunConfiguration +import com.intellij.openapi.components.BaseState +import com.intellij.openapi.project.Project + +/** + * @author zhouxi 2024/5/16 + */ +class PreviewConfigurationFactory(type: ConfigurationType) : ConfigurationFactory(type) { + override fun getOptionsClass(): Class { + return CommandOptions::class.java + } + + override fun getId(): String { + return PreviewConfigurationType.id + } + + override fun createTemplateConfiguration( + project: Project + ): RunConfiguration { + return PreviewRunConfiguration(project, this, "Slint Viewer") + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/preview/PreviewConfigurationType.kt b/src/main/kotlin/me/zhouxi/slint/preview/PreviewConfigurationType.kt new file mode 100644 index 0000000..a8d22a0 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/preview/PreviewConfigurationType.kt @@ -0,0 +1,22 @@ +package me.zhouxi.slint.preview + +import com.intellij.execution.configurations.ConfigurationTypeBase +import com.intellij.openapi.util.NotNullLazyValue +import me.zhouxi.slint.lang.Slint + +/** + * @author zhouxi 2024/5/16 + */ +object PreviewConfigurationType : ConfigurationTypeBase( + "SlintViewerConfiguration", + "Slint viewer", + "Slint component preview", + NotNullLazyValue.createValue { Slint.ICON } +) { + init { + addFactory(PreviewConfigurationFactory(this)) + } + + val factory: PreviewConfigurationFactory + get() = this.configurationFactories[0] as PreviewConfigurationFactory +} diff --git a/src/main/kotlin/me/zhouxi/slint/preview/PreviewRunConfiguration.kt b/src/main/kotlin/me/zhouxi/slint/preview/PreviewRunConfiguration.kt new file mode 100644 index 0000000..57de692 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/preview/PreviewRunConfiguration.kt @@ -0,0 +1,71 @@ +package me.zhouxi.slint.preview + +import com.intellij.execution.ExecutionException +import com.intellij.execution.Executor +import com.intellij.execution.configurations.* +import com.intellij.execution.process.ColoredProcessHandler +import com.intellij.execution.process.ProcessHandler +import com.intellij.execution.process.ProcessTerminatedListener +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.openapi.options.SettingsEditor +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.InvalidDataException +import com.intellij.util.io.BaseOutputReader +import org.apache.commons.lang3.StringUtils +import org.jdom.Element + +/** + * @author zhouxi 2024/5/16 + */ +class PreviewRunConfiguration(project: Project, factory: ConfigurationFactory?, name: String?) : + LocatableConfigurationBase(project, factory!!, name) { + public override fun getOptions(): CommandOptions { + return super.getOptions() as CommandOptions + } + + override fun getConfigurationEditor(): SettingsEditor { + return SlintSettingEditor() + } + + + override fun suggestedName(): String? { + println(options.arguments) + return suggestedName() + } + + @Throws(InvalidDataException::class) + override fun readExternal(element: Element) { + super.readExternal(element) + val attribute = element.getAttributeValue("RunArguments") + if (StringUtils.isNoneBlank(attribute)) { + options.arguments = attribute + } + } + + override fun writeExternal(element: Element) { + super.writeExternal(element) + if (StringUtils.isNoneBlank(options.arguments)) { + element.setAttribute("RunArguments", options.arguments) + } + } + + + override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState { + return object : CommandLineState(environment) { + @Throws(ExecutionException::class) + override fun startProcess(): ProcessHandler { + val args = options.arguments?.split(" ")?.filter { it.isNotBlank() }?.toTypedArray() ?: arrayOf() + val commandLine = GeneralCommandLine( + SlintViewer.executable(), *args + ) + val processHandler = object : ColoredProcessHandler(commandLine) { + override fun readerOptions(): BaseOutputReader.Options { + return BaseOutputReader.Options.forMostlySilentProcess() + } + } + ProcessTerminatedListener.attach(processHandler) + return processHandler + } + } + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/preview/PreviewRunConfigurationProducer.kt b/src/main/kotlin/me/zhouxi/slint/preview/PreviewRunConfigurationProducer.kt new file mode 100644 index 0000000..fc31632 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/preview/PreviewRunConfigurationProducer.kt @@ -0,0 +1,40 @@ +package me.zhouxi.slint.preview + +import com.intellij.execution.actions.ConfigurationContext +import com.intellij.execution.actions.LazyRunConfigurationProducer +import com.intellij.execution.configurations.ConfigurationFactory +import com.intellij.openapi.util.Ref +import com.intellij.psi.PsiElement +import me.zhouxi.slint.lang.psi.SlintFile + +/** + * @author zhouxi 2024/5/16 + */ +class PreviewRunConfigurationProducer : LazyRunConfigurationProducer() { + override fun getConfigurationFactory(): ConfigurationFactory { + return PreviewConfigurationType.factory + } + + override fun setupConfigurationFromContext( + configuration: PreviewRunConfiguration, + context: ConfigurationContext, + sourceElement: Ref + ): Boolean { + val file = sourceElement.get().containingFile + if (file !is SlintFile) { + return false + } + configuration.options.arguments = "${file.virtualFile.path} --auto-reload" + configuration.name = file.name + return true + } + + + override fun isConfigurationFromContext( + configuration: PreviewRunConfiguration, + context: ConfigurationContext + ): Boolean { + val path = context.psiLocation?.context?.containingFile?.virtualFile?.path ?: return false + return configuration.options.arguments?.contains(path) == true + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/preview/PreviewRunLineMarkerContributor.kt b/src/main/kotlin/me/zhouxi/slint/preview/PreviewRunLineMarkerContributor.kt new file mode 100644 index 0000000..f7f863b --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/preview/PreviewRunLineMarkerContributor.kt @@ -0,0 +1,22 @@ +package me.zhouxi.slint.preview + +import com.intellij.execution.lineMarker.ExecutorAction +import com.intellij.execution.lineMarker.RunLineMarkerContributor +import com.intellij.psi.PsiElement +import me.zhouxi.slint.lang.Slint +import me.zhouxi.slint.lang.psi.SlintComponentName + +/** + * @author zhouxi 2024/5/16 + */ +class PreviewRunLineMarkerContributor : RunLineMarkerContributor() { + override fun getInfo(element: PsiElement): Info? { + if (element.parent is SlintComponentName) { + return Info( + Slint.ICON, null, + *ExecutorAction.getActions(1) + ) + } + return null + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/preview/SlintSettingEditor.kt b/src/main/kotlin/me/zhouxi/slint/preview/SlintSettingEditor.kt new file mode 100644 index 0000000..c95beae --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/preview/SlintSettingEditor.kt @@ -0,0 +1,47 @@ +package me.zhouxi.slint.preview + + +import com.intellij.openapi.options.ConfigurationException +import com.intellij.openapi.options.SettingsEditor +import com.intellij.ui.components.JBTextField +import com.intellij.ui.dsl.builder.* +import java.awt.Desktop +import java.awt.Font +import java.net.URI +import javax.swing.JComponent + +/** + * @author zhouxi 2024/5/16 + */ +class SlintSettingEditor : SettingsEditor() { + + private val commandInput = JBTextField() + + override fun resetEditorFrom(config: PreviewRunConfiguration) { + commandInput.text = config.options.arguments + } + + @Throws(ConfigurationException::class) + override fun applyEditorTo(config: PreviewRunConfiguration) { + config.options.arguments = commandInput.text + } + + override fun createEditor(): JComponent { + return panel { + separator() + row { + label("Slint viewer arguments").also { + it.component.font = it.component.font.deriveFont(Font.BOLD) + } + } + row { + cell(commandInput).columns(COLUMNS_LARGE).align(Align.FILL) + } + row { + link("Github document") { + Desktop.getDesktop().browse(URI("https://github.com/slint-ui/slint/tree/master/tools/viewer")) + } + } + } + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/preview/SlintViewer.kt b/src/main/kotlin/me/zhouxi/slint/preview/SlintViewer.kt new file mode 100644 index 0000000..ed0795e --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/preview/SlintViewer.kt @@ -0,0 +1,27 @@ +package me.zhouxi.slint.preview + +import com.intellij.openapi.util.SystemInfo +import me.zhouxi.slint.Plugin +import java.nio.file.Paths + +/** + * @author zhouxi 2024/5/17 + */ +object SlintViewer { + + private val executable = if (SystemInfo.isWindows) { + "slint-viewer-windows.exe" + } else if (SystemInfo.isLinux) { + "slint-viewer-linux" + } else if (SystemInfo.isMac) { + "slint-viewer-macos" + } else { + throw RuntimeException("Unsupported OS") + } + + fun executable(): String { + return Paths.get(Plugin.Path.toAbsolutePath().toString(), "slint-viewer", executable).toString() + } + + +} diff --git a/src/main/kotlin/me/zhouxi/slint/reference/SlintReferenceContributor.kt b/src/main/kotlin/me/zhouxi/slint/reference/SlintReferenceContributor.kt new file mode 100644 index 0000000..79b5bd0 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/reference/SlintReferenceContributor.kt @@ -0,0 +1,47 @@ +package me.zhouxi.slint.reference + +import com.intellij.patterns.PlatformPatterns.psiElement +import com.intellij.patterns.StandardPatterns.or +import com.intellij.psi.PsiReferenceContributor +import com.intellij.psi.PsiReferenceRegistrar +import me.zhouxi.slint.lang.psi.SlintTypes.* +import me.zhouxi.slint.reference.provider.ComponentReferenceProvider +import me.zhouxi.slint.reference.provider.ModuleLocationReferenceProvider +import me.zhouxi.slint.reference.provider.PropertyReferenceProvider + +/** + * @author zhouxi 2024/5/17 + */ +class SlintReferenceContributor : PsiReferenceContributor() { + override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) { + //component Reference + registrar.registerReferenceProvider( + psiElement(ReferenceIdentifier) + .withParent( + or( + psiElement(SubComponent), + psiElement(Component), + psiElement(InheritDeclaration), + psiElement(ImportedIdentifier), + psiElement(ExportIdentifier) + ) + ), + ComponentReferenceProvider() + ) + //moduleLocation Reference + registrar.registerReferenceProvider( + psiElement(ModuleLocation), + ModuleLocationReferenceProvider() + ) + //property binding + registrar.registerReferenceProvider( + psiElement().withParent( + or( + psiElement(PropertyBinding), + psiElement(ComponentBody) + ) + ), + PropertyReferenceProvider() + ) + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/reference/provider/ComponentReferenceProvider.kt b/src/main/kotlin/me/zhouxi/slint/reference/provider/ComponentReferenceProvider.kt new file mode 100644 index 0000000..3751fb1 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/reference/provider/ComponentReferenceProvider.kt @@ -0,0 +1,56 @@ +package me.zhouxi.slint.reference.provider + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiReference +import com.intellij.psi.PsiReferenceBase +import com.intellij.psi.PsiReferenceProvider +import com.intellij.psi.util.childrenOfType +import com.intellij.psi.util.parentOfType +import com.intellij.util.ProcessingContext +import me.zhouxi.slint.lang.psi.* +import me.zhouxi.slint.lang.psi.utils.exportedElements +import me.zhouxi.slint.lang.psi.utils.importedElements + +/** + * @author zhouxi 2024/5/17 + */ +class ComponentReferenceProvider : PsiReferenceProvider() { + override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array { + return arrayOf(SlintComponentNameReference(element as SlintReferenceIdentifier)) + } + + class SlintComponentNameReference(element: SlintReferenceIdentifier) : PsiReferenceBase(element) { + override fun resolve(): PsiElement? { + val file = element.containingFile as SlintFile + //优先查找当前文件内的组件定义 + val component = file.childrenOfType() + .firstOrNull { it.componentName?.textMatches(element) == true } + if (component != null) { + return component + } + // TODO psiTreeUtils + //然后是导出的组件 maybe component or externalName + val externalElement = file.exportedElements().firstOrNull { it.textMatches(element) } + if (externalElement != null) { + return externalElement + } + //maybe internalName or referencedIdentifier; + val element = file.importedElements().firstOrNull { it.textMatches(element) } ?: return null + if (element is SlintPsiReferencedIdentifier) { + val location = element.parentOfType()?.moduleLocation ?: return null + val targetFile = location.reference?.resolve() as SlintFile? ?: return null + return targetFile.exportedElements() + .firstOrNull { it.nameIdentifier?.textMatches(element) == true } + } + return element + } + + override fun getVariants(): Array { + val file = element.containingFile as SlintFile + val array: Array = file.childrenOfType() + .mapNotNull { it.moduleLocation?.reference?.resolve() as SlintFile? } + .flatMap { it.exportedElements() }.toTypedArray() + return array + } + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/reference/provider/InternalNameReferenceProvider.kt b/src/main/kotlin/me/zhouxi/slint/reference/provider/InternalNameReferenceProvider.kt new file mode 100644 index 0000000..b4cbb17 --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/reference/provider/InternalNameReferenceProvider.kt @@ -0,0 +1,29 @@ +package me.zhouxi.slint.reference.provider + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiReference +import com.intellij.psi.PsiReferenceBase +import com.intellij.psi.PsiReferenceProvider +import com.intellij.psi.util.parentOfType +import com.intellij.util.ProcessingContext +import me.zhouxi.slint.lang.psi.SlintFile +import me.zhouxi.slint.lang.psi.SlintImport +import me.zhouxi.slint.lang.psi.SlintInternalName +import me.zhouxi.slint.lang.psi.utils.exportedElements + +/** + * @author zhouxi 2024/5/17 + */ +class InternalNameReferenceProvider : PsiReferenceProvider() { + override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array { + return arrayOf(InternalNameReference(element as SlintInternalName)) + } + + class InternalNameReference(element: SlintInternalName) : PsiReferenceBase(element) { + override fun resolve(): PsiElement? { + val slintImport = element.parentOfType() ?: return null + val slintFile = slintImport.moduleLocation?.reference?.resolve() as? SlintFile ?: return null + return slintFile.exportedElements().firstOrNull { it.textMatches(element) } + } + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/reference/provider/ModuleLocationReferenceProvider.kt b/src/main/kotlin/me/zhouxi/slint/reference/provider/ModuleLocationReferenceProvider.kt new file mode 100644 index 0000000..fa8d3ed --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/reference/provider/ModuleLocationReferenceProvider.kt @@ -0,0 +1,31 @@ +package me.zhouxi.slint.reference.provider + +import com.intellij.openapi.util.TextRange +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.psi.* +import com.intellij.util.ProcessingContext +import me.zhouxi.slint.lang.psi.SlintModuleLocation + +/** + * @author zhouxi 2024/5/17 + */ +class ModuleLocationReferenceProvider : PsiReferenceProvider() { + override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array { + return arrayOf(ModuleLocationReference(element)) + } + + class ModuleLocationReference(element: PsiElement) : PsiReferenceBase(element) { + override fun resolve(): PsiElement? { + val location = element as SlintModuleLocation + val filename = location.stringLiteral.text + if (filename.length < 3) return null + val directory = element.containingFile.originalFile.virtualFile?.parent ?: return null + val file = VfsUtil.findRelativeFile(directory, filename.substring(1, filename.length - 1)) ?: return null + return PsiManager.getInstance(element.project).findFile(file) + } + + override fun calculateDefaultRangeInElement(): TextRange { + return TextRange(1, element.textLength - 1) + } + } +} diff --git a/src/main/kotlin/me/zhouxi/slint/reference/provider/PropertyReferenceProvider.kt b/src/main/kotlin/me/zhouxi/slint/reference/provider/PropertyReferenceProvider.kt new file mode 100644 index 0000000..0affc5e --- /dev/null +++ b/src/main/kotlin/me/zhouxi/slint/reference/provider/PropertyReferenceProvider.kt @@ -0,0 +1,27 @@ +package me.zhouxi.slint.reference.provider + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiReference +import com.intellij.psi.PsiReferenceBase +import com.intellij.psi.PsiReferenceProvider +import com.intellij.psi.util.parentOfType +import com.intellij.psi.util.parentOfTypes +import com.intellij.util.ProcessingContext +import me.zhouxi.slint.lang.psi.SlintComponent +import me.zhouxi.slint.lang.psi.SlintPropertyBinding +import me.zhouxi.slint.lang.psi.SlintSubComponent +import me.zhouxi.slint.lang.psi.utils.searchProperty + +class PropertyReferenceProvider : PsiReferenceProvider() { + override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array { + return arrayOf(PropertyReference(element)) + } + + class PropertyReference(element: PsiElement) : PsiReferenceBase(element) { + override fun resolve(): PsiElement? { + val binding = element.parentOfType() ?: return null + val parent = binding.parentOfTypes(SlintSubComponent::class, SlintComponent::class) ?: return null + return searchProperty(parent) { it.propertyName?.textMatches(element) == true } + } + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml new file mode 100644 index 0000000..2a0f180 --- /dev/null +++ b/src/main/resources/META-INF/plugin.xml @@ -0,0 +1,56 @@ + + + me.zhouxi.intellij-slint + + intellij-slint + + + + + + com.intellij.modules.platform + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/pluginIcon.svg b/src/main/resources/META-INF/pluginIcon.svg new file mode 100644 index 0000000..91c3021 --- /dev/null +++ b/src/main/resources/META-INF/pluginIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/icons/slint-logo-small-light.png b/src/main/resources/icons/slint-logo-small-light.png new file mode 100644 index 0000000..366c0a0 Binary files /dev/null and b/src/main/resources/icons/slint-logo-small-light.png differ diff --git a/src/test/java/LexerTest.java b/src/test/java/LexerTest.java new file mode 100644 index 0000000..1cebf33 --- /dev/null +++ b/src/test/java/LexerTest.java @@ -0,0 +1,55 @@ +import com.intellij.lexer.FlexAdapter; +import com.intellij.lexer.Lexer; +import com.intellij.testFramework.LexerTestCase; +import lombok.extern.slf4j.Slf4j; +import me.zhouxi.slint.lang.lexer.SlintLexer; +import org.jetbrains.annotations.NotNull; +import org.junit.Rule; +import org.junit.rules.Stopwatch; +import org.junit.runner.Description; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; + +/** + * @author zhouxi 2024/4/30 + */ +@Slf4j +public class LexerTest extends LexerTestCase { + + @Override + protected Lexer createLexer() { + return new FlexAdapter(new SlintLexer()); + } + + @Override + protected String getDirPath() { + return "src/test/resources/slint"; + } + + + public void test() throws IOException { + var source = Files.readString(Paths.get("src/test/resources/slint/main.slint")); + doTest(source); + } + + protected @NotNull String getPathToTestDataFile(String extension) { + return "src/test/resources/slint/lexer/output" + extension; + } + + + @Rule + public Stopwatch stopwatch = new Stopwatch() { + @Override + protected void succeeded(long nanos, Description description) { + log.info("{} succeeded, time taken: {} ms", description.getMethodName(), TimeUnit.NANOSECONDS.toMillis(nanos)); + } + + @Override + protected void failed(long nanos, Throwable e, Description description) { + log.info("{} failed, time taken: {} ms", description.getMethodName(), TimeUnit.NANOSECONDS.toMillis(nanos)); + } + }; +} diff --git a/src/test/java/ParserTest.java b/src/test/java/ParserTest.java new file mode 100644 index 0000000..7866921 --- /dev/null +++ b/src/test/java/ParserTest.java @@ -0,0 +1,33 @@ +import com.intellij.testFramework.ParsingTestCase; +import me.zhouxi.slint.lang.SlintParserDefinition; +import org.apache.commons.lang3.time.StopWatch; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * @author zhouxi 2024/4/30 + */ +public class ParserTest extends ParsingTestCase { + public ParserTest() { + super("", ".slint", new SlintParserDefinition()); + } + + @Override + protected @NotNull String getTestDataPath() { + return "src/test/resources/slint/parser"; + } + + public void test() throws IOException { + final var path = Paths.get("src/test/resources/slint/main.slint"); + var source = Files.readString(path); + StopWatch started = StopWatch.createStarted(); + for (int i = 0; i < 50000; i++) { + doCodeTest(source); + } + started.stop(); + System.out.println(started.getNanoTime()/50000); + } +} diff --git a/src/test/resources/slint/main.slint b/src/test/resources/slint/main.slint new file mode 100644 index 0000000..d17cba0 --- /dev/null +++ b/src/test/resources/slint/main.slint @@ -0,0 +1,5 @@ + /* dsadas */ + /* dsadas */ + "dasdasd\"" + "dada" +" \ No newline at end of file