├── .github └── workflows │ ├── build.yml │ ├── release.yml │ └── verify.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── renovate.json ├── settings.gradle.kts └── src ├── grammar ├── gn.bnf └── gn.flex ├── main ├── java │ └── com │ │ └── google │ │ └── idea │ │ └── gn │ │ ├── GnAnnotator.kt │ │ ├── GnBraceMatcher.kt │ │ ├── GnColorSettingsPage.kt │ │ ├── GnColors.kt │ │ ├── GnCommenter.kt │ │ ├── GnCompletionContributor.kt │ │ ├── GnDocumentationProvider.kt │ │ ├── GnEnterDelegate.kt │ │ ├── GnFilePattern.kt │ │ ├── GnFileType.kt │ │ ├── GnFoldingBuilder.kt │ │ ├── GnFormattingBuilder.kt │ │ ├── GnIcons.kt │ │ ├── GnKeys.kt │ │ ├── GnLabel.kt │ │ ├── GnLanguage.kt │ │ ├── GnLexerAdapter.kt │ │ ├── GnParserDefinition.kt │ │ ├── GnQuoteHandler.kt │ │ ├── GnSyntaxHighlighter.kt │ │ ├── GnSyntaxHighlighterFactory.kt │ │ ├── GnTypedHandler.kt │ │ ├── actions │ │ └── NewBuildFileAction.kt │ │ ├── completion │ │ ├── CompletionIdentifier.kt │ │ ├── FileCompletionProvider.kt │ │ ├── IdentifierCompletionProvider.kt │ │ └── IdentifierCompletionResultSet.kt │ │ ├── config │ │ ├── GnConfigurableBase.kt │ │ ├── GnProjectConfigurable.kt │ │ ├── GnSettingsService.kt │ │ ├── GnSettingsServiceImpl.kt │ │ └── layout.kt │ │ ├── manipulators │ │ └── GnStringExprManipulator.kt │ │ ├── psi │ │ ├── Builtin.kt │ │ ├── ElementType.kt │ │ ├── Function.kt │ │ ├── FunctionVariable.kt │ │ ├── GnBinaryExpr.kt │ │ ├── GnCompositeType.kt │ │ ├── GnFile.kt │ │ ├── GnFormatBlock.kt │ │ ├── GnPsiUtil.kt │ │ ├── GnValue.kt │ │ ├── Target.kt │ │ ├── TemplateFunction.kt │ │ ├── TemplateVariable.kt │ │ ├── TokenType.kt │ │ ├── Variable.kt │ │ ├── Visitor.kt │ │ ├── builtin │ │ │ ├── BuiltinTargetFunction.kt │ │ │ ├── BuiltinVariable.kt │ │ │ ├── DeclareArgs.kt │ │ │ ├── Defined.kt │ │ │ ├── FilterExclude.kt │ │ │ ├── FilterInclude.kt │ │ │ ├── Foreach.kt │ │ │ ├── ForwardVariablesFrom.kt │ │ │ ├── GetEnv.kt │ │ │ ├── GetLabelInfo.kt │ │ │ ├── GetPathInfo.kt │ │ │ ├── GetTargetOutputs.kt │ │ │ ├── Import.kt │ │ │ ├── NoOpFunctions.kt │ │ │ ├── ProcessFileTemplate.kt │ │ │ ├── ReadFile.kt │ │ │ ├── RebasePath.kt │ │ │ ├── SetDefaultToolchain.kt │ │ │ ├── SplitList.kt │ │ │ ├── StringJoin.kt │ │ │ ├── StringReplace.kt │ │ │ ├── StringSplit.kt │ │ │ ├── Target.kt │ │ │ ├── Template.kt │ │ │ └── util.kt │ │ ├── impl │ │ │ ├── GnBinaryExprImpl.kt │ │ │ ├── GnIdentifierImpl.kt │ │ │ └── GnLiteralReferenceImpl.kt │ │ ├── reference │ │ │ ├── GnCallIdentifierReference.kt │ │ │ └── GnLabelReference.kt │ │ └── scope │ │ │ ├── BlockScope.kt │ │ │ ├── BuiltinScope.kt │ │ │ ├── FileScope.kt │ │ │ ├── Scope.kt │ │ │ └── TemplateScope.kt │ │ └── util │ │ └── PathUtils.kt └── resources │ ├── META-INF │ ├── plugin.xml │ └── pluginIcon.svg │ ├── fileTemplates │ └── internal │ │ ├── Gn File.gn.ft │ │ └── Gn File.gn.html │ └── icons │ └── gn.svg └── test ├── java └── com │ └── google │ └── idea │ └── gn │ ├── AnnotatorTest.kt │ ├── AutoCompleteTest.kt │ ├── BaseTest.kt │ ├── BuiltinFuncTest.kt │ ├── ExprTest.kt │ ├── FilePatternTest.kt │ ├── FoldingTest.kt │ ├── GnLabelTest.kt │ ├── ParseTest.kt │ ├── ReferencesTest.kt │ ├── TemplateVariablesTest.kt │ └── util │ ├── Extensions.kt │ ├── GnCodeInsightTestCase.kt │ └── PatternGatherer.kt └── testData ├── project ├── build │ └── rules.gni └── src │ ├── BUILD.gn │ ├── lib │ ├── BUILD.gn │ └── lib.cc │ └── test │ ├── BUILD.gn │ └── FoldingTest.gn ├── simpleParseCheck.gn └── simpleParseCheck.txt /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | 7 | jobs: 8 | 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | 15 | - uses: actions/checkout@v4 16 | 17 | - uses: gradle/wrapper-validation-action@v2.1.2 18 | 19 | - uses: actions/setup-java@v4 20 | with: 21 | distribution: zulu 22 | java-version: 17 23 | 24 | - name: Setup Gradle 25 | uses: gradle/actions/setup-gradle@v3 26 | with: 27 | gradle-home-cache-cleanup: true 28 | 29 | - name: Build plugin 30 | run: ./gradlew buildPlugin -PenableWarningAsError=true 31 | 32 | - name: Prepare Plugin Artifact 33 | id: artifact 34 | shell: bash 35 | run: | 36 | cd ./build/distributions 37 | FILENAME=`ls *.zip` 38 | unzip "$FILENAME" -d content 39 | 40 | echo "filename=${FILENAME:0:-4}" >> $GITHUB_OUTPUT 41 | 42 | - name: Upload artifact 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: ${{ steps.artifact.outputs.filename }} 46 | path: ./build/distributions/content/*/* 47 | 48 | test: 49 | name: Test 50 | needs: [ build ] 51 | runs-on: ubuntu-latest 52 | 53 | steps: 54 | 55 | - uses: actions/checkout@v4 56 | 57 | - uses: actions/setup-java@v4 58 | with: 59 | distribution: zulu 60 | java-version: 17 61 | 62 | - name: Setup Gradle 63 | uses: gradle/gradle-build-action@v3 64 | with: 65 | gradle-home-cache-cleanup: true 66 | 67 | - name: Run Tests 68 | run: ./gradlew check 69 | 70 | - name: Collect Tests Result 71 | if: ${{ failure() }} 72 | uses: actions/upload-artifact@v4 73 | with: 74 | name: tests-result 75 | path: ${{ github.workspace }}/build/reports/tests 76 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | tag: 6 | description: 'Tag' 7 | required: true 8 | 9 | jobs: 10 | 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | 17 | - uses: actions/checkout@v4 18 | with: 19 | ref: ${{ github.event.inputs.tag }} 20 | fetch-depth: 0 21 | 22 | - name: Validate tag 23 | shell: bash 24 | run: | 25 | describe_tags=$(git describe --tags) 26 | if [[ ! "$describe_tags" =~ ^[0-9]+\.[0-9]+\.[0-9]$ ]]; then 27 | echo "Tag $describe_tags is not a valid tag name" 28 | exit 1 29 | fi 30 | echo "Building tag $describe_tags" 31 | 32 | - uses: actions/setup-java@v4 33 | with: 34 | distribution: zulu 35 | java-version: 17 36 | 37 | - name: Setup Gradle 38 | uses: gradle/actions/setup-gradle@v3 39 | with: 40 | cache-read-only: true 41 | gradle-home-cache-cleanup: true 42 | 43 | - name: Build plugin 44 | run: ./gradlew buildPlugin 45 | 46 | - name: Prepare Plugin Artifact 47 | id: artifact 48 | shell: bash 49 | run: | 50 | cd ./build/distributions 51 | FILENAME=`ls *.zip` 52 | unzip "$FILENAME" -d content 53 | 54 | echo "filename=${FILENAME:0:-4}" >> $GITHUB_OUTPUT 55 | 56 | - name: Upload artifact 57 | uses: actions/upload-artifact@v4 58 | with: 59 | name: ${{ steps.artifact.outputs.filename }} 60 | path: ./build/distributions/content/*/* 61 | -------------------------------------------------------------------------------- /.github/workflows/verify.yml: -------------------------------------------------------------------------------- 1 | name: Verify 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Build"] 6 | types: 7 | - completed 8 | 9 | workflow_dispatch: 10 | 11 | schedule: 12 | # every First Saturday of the month at 00:00 UTC 13 | - cron: "0 0 1-7 * 6" 14 | 15 | jobs: 16 | 17 | verify: 18 | name: Verify 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | # Free GitHub Actions Environment Disk Space 23 | - name: Maximize Build Space 24 | uses: jlumbroso/free-disk-space@main 25 | with: 26 | tool-cache: false 27 | large-packages: false 28 | 29 | - uses: actions/checkout@v4 30 | 31 | - uses: actions/setup-java@v4 32 | with: 33 | distribution: zulu 34 | java-version: 17 35 | 36 | - name: Setup Gradle 37 | uses: gradle/actions/setup-gradle@v3 38 | with: 39 | # Disable cache write since this workflow since this workflow is executed after the build workflow 40 | cache-read-only: true 41 | gradle-home-cache-cleanup: true 42 | 43 | - name: Set Plugin Verifier Home Dir 44 | id: properties 45 | run: | 46 | echo "pluginVerifierHomeDir=~/.pluginVerifier" >> $GITHUB_OUTPUT 47 | 48 | # Cache Plugin Verifier IDEs 49 | - name: Setup Plugin Verifier IDEs Cache 50 | uses: actions/cache@v4 51 | with: 52 | path: ${{ steps.properties.outputs.pluginVerifierHomeDir }}/ides 53 | key: plugin-verifier-${{ hashFiles('build/listProductsReleases.txt') }} 54 | 55 | # Run Verify Plugin task and IntelliJ Plugin Verifier tool 56 | - name: Run Plugin Verification tasks 57 | run: ./gradlew runPluginVerifier -Dplugin.verifier.home.dir=${{ steps.properties.outputs.pluginVerifierHomeDir }} 58 | 59 | 60 | # Collect Plugin Verifier Result 61 | - name: Collect Plugin Verifier Result 62 | if: ${{ always() }} 63 | uses: actions/upload-artifact@v4 64 | with: 65 | name: pluginVerifier-result 66 | path: ${{ github.workspace }}/build/reports/pluginVerifier 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | src/gen 4 | tmp 5 | 6 | # Ignore Gradle GUI config 7 | gradle-app.setting 8 | 9 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 10 | !gradle-wrapper.jar 11 | 12 | # Cache of project 13 | .gradletasknamecache 14 | 15 | .idea 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # IntelliJ GN Plugin Changelog 4 | 5 | ## [Unreleased] 6 | 7 | ## [0.1.5] - 2024-04-06 8 | 9 | ### Changed 10 | 11 | - Change minimum supported IntelliJ version to 2022.3 12 | 13 | ## [0.1.4] - 2023-12-04 14 | 15 | ### Changed 16 | 17 | - Change minimum supported IntelliJ version to 2022.1 18 | - Implement AbstractElementManipulator of GnStringExprImpl to not crash on 19 | rename refactoring 20 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of 9 | experience, education, socio-economic status, nationality, personal appearance, 10 | race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or reject 41 | comments, commits, code, wiki edits, issues, and other contributions that are 42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 43 | contributor for other behaviors that they deem inappropriate, threatening, 44 | offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | This Code of Conduct also applies outside the project spaces when the Project 56 | Steward has a reasonable belief that an individual's behavior may have a 57 | negative impact on the project or its community. 58 | 59 | ## Conflict Resolution 60 | 61 | We do not believe that all conflict is bad; healthy debate and disagreement 62 | often yield positive results. However, it is never okay to be disrespectful or 63 | to engage in behavior that violates the project’s code of conduct. 64 | 65 | If you see someone violating the code of conduct, you are encouraged to address 66 | the behavior directly with those involved. Many issues can be resolved quickly 67 | and easily, and this gives people more control over the outcome of their 68 | dispute. If you are unable to resolve the matter for any reason, or if the 69 | behavior is threatening or harassing, report it. We are dedicated to providing 70 | an environment where participants feel welcome and safe. 71 | 72 | Reports should be directed to *[PROJECT STEWARD NAME(s) AND EMAIL(s)]*, the 73 | Project Steward(s) for *[PROJECT NAME]*. It is the Project Steward’s duty to 74 | receive and address reported violations of the code of conduct. They will then 75 | work with a committee consisting of representatives from the Open Source 76 | Programs Office and the Google Open Source Strategy team. If for any reason you 77 | are uncomfortable reaching out to the Project Steward, please email 78 | opensource@google.com. 79 | 80 | We will investigate every complaint, but you may not receive a direct response. 81 | We will use our discretion in determining when and how to follow up on reported 82 | incidents, which may range from not taking action to permanent expulsion from 83 | the project and project-sponsored spaces. We will notify the accused of the 84 | report and provide them an opportunity to discuss it before any action is taken. 85 | The identity of the reporter will be omitted from the details of the report 86 | supplied to the accused. In potentially harmful situations, such as ongoing 87 | harassment or threats to anyone's safety, we may take action without notice. 88 | 89 | ## Attribution 90 | 91 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 92 | available at 93 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 94 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google/conduct/). 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Google LLC. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GN Plugin for Intellij IDEs 2 | 3 | This plugin adds support to edit `.gn` and `.gni` files to Intellij IDEs. 4 | 5 | ## Currently supported 6 | 7 | - Basic syntax highlighting 8 | - *Very* basic target parsing 9 | - Click-through navigation for resolved label references 10 | - *Very* basic formatting 11 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.grammarkit.tasks.GenerateLexerTask 2 | import org.jetbrains.grammarkit.tasks.GenerateParserTask 3 | import org.jetbrains.changelog.Changelog 4 | import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile 5 | 6 | val versionDetails: groovy.lang.Closure by extra 7 | 8 | plugins { 9 | id("java") 10 | alias(libs.plugins.kotlin) 11 | alias(libs.plugins.gradleIntellijPlugin) 12 | alias(libs.plugins.grammerKit) 13 | alias(libs.plugins.changelog) 14 | alias(libs.plugins.gitVersion) 15 | } 16 | 17 | val gitDetails = versionDetails() 18 | version = if (gitDetails.isCleanTag) { 19 | gitDetails.version 20 | } else { 21 | // eg. 0.1.4-dev.12345678 22 | providers.gradleProperty("intellijGnVersion").get().ifEmpty { 23 | throw IllegalStateException("intellijGnVersion must be set in gradle.properties") 24 | } + "-dev." + gitDetails.gitHash 25 | } 26 | 27 | java.sourceCompatibility = JavaVersion.VERSION_17 28 | java.targetCompatibility = JavaVersion.VERSION_17 29 | 30 | repositories { 31 | mavenCentral() 32 | } 33 | 34 | dependencies { 35 | testImplementation(libs.kotlinTest) 36 | testImplementation(libs.kotlinTestJdk7) 37 | testImplementation(platform(libs.junitBom)) 38 | testRuntimeOnly(libs.junitPlatformLauncher) { 39 | because("Only needed to run tests in a version of IntelliJ IDEA that bundles older versions") 40 | } 41 | testRuntimeOnly(libs.junitJupiterEngine) 42 | testRuntimeOnly(libs.jnintVintageEngine) 43 | } 44 | 45 | grammarKit { 46 | grammarKitRelease.set("2021.1.2") 47 | } 48 | 49 | task("generateLexerTask", GenerateLexerTask::class) { 50 | // source flex file 51 | sourceFile.set(file("src/grammar/gn.flex")) 52 | 53 | // target directory for lexer 54 | targetOutputDir = project.layout.projectDirectory.dir("src/gen/com/google/idea/gn") 55 | 56 | // if set, plugin will remove a lexer output file before generating new one. Default: false 57 | purgeOldFiles = true 58 | } 59 | 60 | task("generateParserTask", GenerateParserTask::class) { 61 | // source bnf file 62 | sourceFile.set(file("src/grammar/gn.bnf")) 63 | 64 | // optional, task-specific root for the generated files. Default: none 65 | targetRootOutputDir = project.layout.projectDirectory.dir("src/gen") 66 | 67 | // path to a parser file, relative to the targetRoot 68 | pathToParser = "/com/google/idea/gn/parser/GnParser.java" 69 | 70 | // path to a directory with generated psi files, relative to the targetRoot 71 | pathToPsiRoot = "/com/google/idea/gn/psi" 72 | 73 | // if set, plugin will remove a parser output file and psi output directory before generating new ones. Default: false 74 | purgeOldFiles = true 75 | } 76 | 77 | intellij { 78 | version = "2022.3" 79 | sandboxDir = "tmp/sandbox" 80 | } 81 | 82 | changelog { 83 | groups.empty() 84 | } 85 | 86 | tasks.named("compileKotlin") { 87 | setDependsOn(listOf(tasks.named("generateLexerTask"), tasks.named("generateParserTask"))) 88 | } 89 | 90 | tasks.withType { 91 | val enableWarningAsError = project.findProperty("enableWarningAsError")?.toString()?.toBoolean() ?: false 92 | if (enableWarningAsError) { 93 | var compilerArgs = options.compilerArgs 94 | if (compilerArgs == null) { 95 | compilerArgs = mutableListOf() 96 | } 97 | compilerArgs.add("-Werror") 98 | options.compilerArgs = compilerArgs 99 | } 100 | } 101 | 102 | tasks.withType { 103 | val enableWarningAsError = project.findProperty("enableWarningAsError")?.toString()?.toBoolean() ?: false 104 | kotlinOptions.jvmTarget = "17" 105 | if (enableWarningAsError) { 106 | kotlinOptions.allWarningsAsErrors = true 107 | } 108 | } 109 | 110 | sourceSets { 111 | main { 112 | java { 113 | srcDir("src/gen") 114 | } 115 | } 116 | } 117 | 118 | tasks.withType { 119 | useJUnitPlatform() 120 | } 121 | 122 | val intellijSinceBuild = "223" 123 | val intellijUntilBuild = "" 124 | 125 | tasks.patchPluginXml { 126 | sinceBuild = intellijSinceBuild 127 | untilBuild = intellijUntilBuild 128 | val changelog = project.changelog // local variable for configuration cache compatibility 129 | // Get the latest available change notes from the changelog file 130 | changeNotes = providers.gradleProperty("intellijGnVersion").map { pluginVersion -> 131 | with(changelog) { 132 | renderItem( 133 | (getOrNull(pluginVersion) ?: getUnreleased()) 134 | .withHeader(false) 135 | .withEmptySections(false), 136 | Changelog.OutputType.HTML, 137 | ) 138 | } 139 | } 140 | } 141 | 142 | tasks.publishPlugin { 143 | token = providers.environmentVariable("ORG_GRADLE_PROJECT_intellijPublishToken") 144 | } 145 | 146 | // Helper build task to create a local updatePlugins.xml file to serve updates 147 | // locally. 148 | task("serverPlugins") { 149 | dependsOn(tasks.named("buildPlugin")) 150 | group = "intellij" 151 | doLast { 152 | File(layout.buildDirectory.asFile.get(), "distributions/updatePlugins.xml").writeText(""" 153 | 154 | < 155 | GN 156 | Experimental GN plugin for intellij 157 | 158 | 159 | 160 | """.trimIndent()) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | pluginRepositoryUrl = https://github.com/google/intellij-gn-plugin 2 | intellijGnVersion = 0.1.5 3 | 4 | # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib 5 | kotlin.stdlib.default.dependency = false 6 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | 3 | # plugins 4 | kotlin = "1.7.0" 5 | gradleIntellijPlugin = "1.17.3" 6 | grammerKit = "2022.3.2.2" 7 | changelog = "2.2.0" 8 | gitVersion = "3.0.0" 9 | 10 | junit = "5.10.2" 11 | 12 | [libraries] 13 | 14 | # test only 15 | kotlinTest = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" } 16 | kotlinTestJdk7 = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk7", version.ref = "kotlin" } 17 | 18 | junitBom = { module = "org.junit:junit-bom", version.ref = "junit" } 19 | junitPlatformLauncher = { group = "org.junit.platform", name = "junit-platform-launcher" } 20 | junitJupiterEngine = { group = "org.junit.jupiter", name = "junit-jupiter-engine" } 21 | jnintVintageEngine = { group = "org.junit.vintage", name = "junit-vintage-engine" } 22 | 23 | [plugins] 24 | 25 | kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 26 | gradleIntellijPlugin = { id = "org.jetbrains.intellij", version.ref = "gradleIntellijPlugin" } 27 | grammerKit = { id = "org.jetbrains.grammarkit", version.ref = "grammerKit" } 28 | changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } 29 | gitVersion = { id = "com.palantir.git-version", version.ref = "gitVersion" } 30 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/intellij-gn-plugin/829d0cdee71dcc42a72e5d3c6f724526676bdf9b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "packageRules": [ 7 | { 8 | "description": "Kotlin version should be managed in coordinate with Intellij Platform minimum version", 9 | "matchDatasources": ["maven"], 10 | "matchDepPatterns": ["^org.jetbrains.kotlin:kotlin"], 11 | "enabled": false 12 | }, 13 | { 14 | "description": "Kotlin version should be managed in coordinate with Intellij Platform minimum version", 15 | "matchDatasources": ["maven"], 16 | "matchDepPatterns": ["^org.jetbrains.kotlin.jvm"], 17 | "enabled": false 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "gn" 2 | -------------------------------------------------------------------------------- /src/grammar/gn.bnf: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | { 6 | parserClass = "com.google.idea.gn.parser.GnParser" 7 | 8 | extends = "com.intellij.extapi.psi.ASTWrapperPsiElement" 9 | 10 | 11 | psiClassPrefix = "Gn" 12 | psiImplClassSuffix = "Impl" 13 | psiPackage = "com.google.idea.gn.psi" 14 | psiImplPackage = "com.google.idea.gn.psi.impl" 15 | 16 | elementTypeHolderClass = "com.google.idea.gn.psi.Types" 17 | elementTypeClass = "com.google.idea.gn.psi.ElementType" 18 | tokenTypeClass = "com.google.idea.gn.psi.TokenType" 19 | 20 | extends(".*expr")=expr 21 | tokens = [ 22 | comment="regexp:#.*" 23 | newline="regexp:\n" 24 | ] 25 | } 26 | 27 | /* 28 | 29 | File = StatementList . 30 | 31 | Statement = Assignment | Call | Condition . 32 | LValue = identifier | ArrayAccess | ScopeAccess . 33 | Assignment = LValue AssignOp Expr . 34 | Call = identifier "(" [ ExprList ] ")" [ Block ] . 35 | Condition = "if" "(" Expr ")" Block 36 | [ "else" ( Condition | Block ) ] . 37 | Block = "{" StatementList "}" . 38 | StatementList = { Statement } . 39 | 40 | ArrayAccess = identifier "[" Expr "]" . 41 | ScopeAccess = identifier "." identifier . 42 | Expr = UnaryExpr | Expr BinaryOp Expr . 43 | UnaryExpr = PrimaryExpr | UnaryOp UnaryExpr . 44 | PrimaryExpr = identifier | integer | string | Call 45 | | ArrayAccess | ScopeAccess | Block 46 | | "(" Expr ")" 47 | | "[" [ ExprList [ "," ] ] "]" . 48 | ExprList = Expr { "," Expr } . 49 | 50 | AssignOp = "=" | "+=" | "-=" . 51 | UnaryOp = "!" . 52 | BinaryOp = "+" | "-" // highest priority 53 | | "<" | "<=" | ">" | ">=" 54 | | "==" | "!=" 55 | | "&&" 56 | | "||" . // lowest priority 57 | */ 58 | 59 | file ::= statement-list 60 | private statement-recover ::= !(newline | CBRACKET | CPAREN | CBRACE | COMMA) 61 | statement ::= call | condition | assignment { 62 | pin(".*")=1 63 | } 64 | lvalue ::= array-access | scope-access | id 65 | assignment ::= lvalue assign-op expr { 66 | pin="lvalue" 67 | } 68 | call ::= id OPAREN expr-list CPAREN [block] 69 | 70 | condition ::= IF OPAREN expr CPAREN block [else-condition] { 71 | pin(".*")=1 72 | } 73 | else-condition ::= ELSE ( condition | block ) { 74 | pin(".*")=1 75 | } 76 | block ::= OBRACE statement-list CBRACE 77 | statement-list ::= ( statement )* { 78 | recoverWhile="statement-recover" 79 | } 80 | array-access ::= id OBRACKET expr CBRACKET 81 | scope-access ::= id DOT id 82 | 83 | 84 | expr ::= and_expr | or_expr | equality-group | compare-group | add-group | unary_expr | primary_expr | literal_expr { 85 | recoverWhile="statement-recover" 86 | } 87 | 88 | private add-group ::= plus_expr | minus_expr 89 | private compare-group ::= lt_expr | le_expr | gt_expr | ge_expr 90 | private equality-group ::= equal_expr | not-equal_expr 91 | 92 | unary_expr ::= UNARY_NOT primary_expr 93 | plus_expr ::= expr PLUS expr { 94 | implements="GnBinaryExpr" 95 | mixin="GnBinaryExprImpl" 96 | } 97 | minus_expr ::= expr MINUS expr { 98 | implements="GnBinaryExpr" 99 | mixin="GnBinaryExprImpl" 100 | } 101 | lt_expr ::= expr LESSER expr { 102 | implements="GnBinaryExpr" 103 | mixin="GnBinaryExprImpl" 104 | } 105 | le_expr ::= expr LESSER_EQUAL expr { 106 | implements="GnBinaryExpr" 107 | mixin="GnBinaryExprImpl" 108 | } 109 | gt_expr ::= expr GREATER expr { 110 | implements="GnBinaryExpr" 111 | mixin="GnBinaryExprImpl" 112 | } 113 | ge_expr ::= expr GREATER_EQUAL expr { 114 | implements="GnBinaryExpr" 115 | mixin="GnBinaryExprImpl" 116 | } 117 | equal_expr ::= expr EQUAL_EQUAL expr { 118 | implements="GnBinaryExpr" 119 | mixin="GnBinaryExprImpl" 120 | } 121 | and_expr ::= expr AND expr { 122 | implements="GnBinaryExpr" 123 | mixin="GnBinaryExprImpl" 124 | } 125 | or_expr ::= expr OR expr { 126 | implements="GnBinaryExpr" 127 | mixin="GnBinaryExprImpl" 128 | } 129 | not-equal_expr ::= expr NOT_EQUAL expr { 130 | implements="GnBinaryExpr" 131 | mixin="GnBinaryExprImpl" 132 | } 133 | paren_expr ::= OPAREN expr CPAREN 134 | 135 | string_literal_expr ::= STRING_LITERAL 136 | string_hex ::= DOLLAR HEX_BYTE 137 | string_ident ::= DOLLAR id 138 | string_expand ::= DOLLAR OBRACE (array-access | scope-access | id) CBRACE 139 | string_inner ::= string_literal_expr | string_expand | string_hex | string_ident 140 | string_expr ::= QUOTE (string_inner)* QUOTE { 141 | mixin="GnLiteralReferenceImpl" 142 | } 143 | 144 | literal_expr ::= string_expr | INTEGRAL_LITERAL | TRUE | FALSE 145 | 146 | primary_expr ::= literal_expr | call | array-access | scope-access | block | paren_expr | collection | id 147 | 148 | 149 | collection ::= OBRACKET expr-list [COMMA] CBRACKET 150 | expr-list ::= [expr (COMMA expr)*] 151 | 152 | assign-op ::= EQUAL | PLUS_EQUAL | MINUS_EQUAL 153 | id ::= IDENTIFIER { 154 | mixin = "GnIdentifierImpl" 155 | } 156 | -------------------------------------------------------------------------------- /src/grammar/gn.flex: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn; 6 | 7 | import com.intellij.lexer.FlexLexer; 8 | import com.intellij.psi.tree.IElementType; 9 | import com.intellij.psi.TokenType; 10 | import static com.google.idea.gn.psi.Types.*; 11 | 12 | import static com.intellij.psi.TokenType.BAD_CHARACTER; 13 | import static com.intellij.psi.TokenType.WHITE_SPACE; 14 | 15 | %% 16 | 17 | %class GnLexer 18 | %implements FlexLexer 19 | %unicode 20 | %function advance 21 | %type IElementType 22 | 23 | 24 | WHITE_SPACE=\s+ 25 | 26 | // Reserved words 27 | IF="if" 28 | ELSE="else" 29 | 30 | // Operators 31 | OBRACE="{" 32 | CBRACE="}" 33 | DOT="." 34 | COMMA="," 35 | OPAREN="(" 36 | CPAREN=")" 37 | OBRACKET="[" 38 | CBRACKET="]" 39 | EQUAL_EQUAL="==" 40 | PLUS_EQUAL="+=" 41 | MINUS_EQUAL="-=" 42 | LESSER_EQUAL="<=" 43 | GREATER_EQUAL=">=" 44 | NOT_EQUAL="!=" 45 | EQUAL="=" 46 | UNARY_NOT="!" 47 | PLUS="+" 48 | MINUS="-" 49 | LESSER="<" 50 | GREATER=">" 51 | AND="&&" 52 | OR="||" 53 | DOLLAR="$" 54 | QUOTE=\" 55 | 56 | // Literals 57 | TRUE="true" 58 | FALSE="false" 59 | 60 | STRING_LITERAL=((\\.)|[^\$\"\n\\])+ 61 | INTEGRAL_LITERAL=[-]?[0-9]+ 62 | HEX_BYTE="0x"[0-9a-fA-F]{2} 63 | IDENTIFIER=[a-zA-Z_][a-zA-Z_0-9]* 64 | COMMENT="#".* 65 | 66 | %state STRING 67 | %state STRING_EXPR 68 | %state STRING_DOLLAR 69 | 70 | %% 71 | 72 | { 73 | {WHITE_SPACE} { return WHITE_SPACE; } 74 | {IF} { return IF; } 75 | {ELSE} { return ELSE; } 76 | {OBRACE} { return OBRACE; } 77 | {CBRACE} { if(yystate() == STRING_EXPR) { yybegin(STRING); } return CBRACE; } 78 | {DOT} { return DOT; } 79 | {COMMA} { return COMMA; } 80 | {OPAREN} { return OPAREN; } 81 | {CPAREN} { return CPAREN; } 82 | {OBRACKET} { return OBRACKET; } 83 | {CBRACKET} { return CBRACKET; } 84 | {EQUAL_EQUAL} { return EQUAL_EQUAL; } 85 | {PLUS_EQUAL} { return PLUS_EQUAL; } 86 | {MINUS_EQUAL} { return MINUS_EQUAL; } 87 | {LESSER_EQUAL} { return LESSER_EQUAL; } 88 | {GREATER_EQUAL} { return GREATER_EQUAL; } 89 | {NOT_EQUAL} { return NOT_EQUAL; } 90 | {EQUAL} { return EQUAL; } 91 | {UNARY_NOT} { return UNARY_NOT; } 92 | {PLUS} { return PLUS; } 93 | {MINUS} { return MINUS; } 94 | {LESSER} { return LESSER; } 95 | {GREATER} { return GREATER; } 96 | {AND} { return AND; } 97 | {OR} { return OR; } 98 | {TRUE} { return TRUE; } 99 | {FALSE} { return FALSE; } 100 | {QUOTE} { yybegin(STRING); return QUOTE; } 101 | {INTEGRAL_LITERAL} { return INTEGRAL_LITERAL; } 102 | {IDENTIFIER} { return IDENTIFIER; } 103 | {COMMENT} { return COMMENT; } 104 | [^] { if(yystate() == STRING_EXPR){ yybegin(STRING); } return BAD_CHARACTER; } 105 | } 106 | 107 | 108 | { 109 | {HEX_BYTE} { yybegin(STRING); return HEX_BYTE; } 110 | {OBRACE} { yybegin(STRING_EXPR); return OBRACE; } 111 | {IDENTIFIER} { yybegin(STRING); return IDENTIFIER; } 112 | [^] { yybegin(STRING); return BAD_CHARACTER; } 113 | } 114 | 115 | { 116 | {QUOTE} { yybegin(YYINITIAL); return QUOTE; } 117 | {STRING_LITERAL} { return STRING_LITERAL; } 118 | {DOLLAR} { yybegin(STRING_DOLLAR); return DOLLAR; } 119 | {IDENTIFIER} { return IDENTIFIER; } 120 | [^] { yybegin(YYINITIAL); return BAD_CHARACTER; } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnAnnotator.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.google.idea.gn.psi.* 7 | import com.google.idea.gn.psi.builtin.BuiltinTargetFunction 8 | import com.google.idea.gn.psi.reference.GnCallIdentifierReference 9 | import com.google.idea.gn.psi.scope.FileScope 10 | import com.intellij.lang.annotation.AnnotationHolder 11 | import com.intellij.lang.annotation.Annotator 12 | import com.intellij.lang.annotation.HighlightSeverity 13 | import com.intellij.psi.PsiElement 14 | 15 | class GnAnnotator : Annotator { 16 | 17 | var file: GnFile? = null 18 | 19 | private fun getCallIdentifierColor(identifier: GnId): GnColors? { 20 | (identifier.reference as? GnCallIdentifierReference)?.let { 21 | if (it.isTemplate()) { 22 | return GnColors.TEMPLATE 23 | } 24 | } 25 | return Builtin.FUNCTIONS[identifier.text]?.let { 26 | if (it is BuiltinTargetFunction) { 27 | GnColors.TARGET_FUNCTION 28 | } else { 29 | GnColors.BUILTIN_FUNCTION 30 | } 31 | } 32 | } 33 | 34 | private fun annotateIdentifier(identifier: GnId, holder: AnnotationHolder) { 35 | val parent = identifier.parent 36 | val color = if (parent is GnCall) { 37 | getCallIdentifierColor(identifier) 38 | } else { 39 | GnColors.VARIABLE 40 | } 41 | color?.let { 42 | holder.newSilentAnnotation(HighlightSeverity.INFORMATION) 43 | .textAttributes(it.textAttributesKey).create() 44 | } 45 | } 46 | 47 | private fun prepareFile(newFile: GnFile) { 48 | val visitor = Visitor(FileScope(), object : Visitor.VisitorDelegate() { 49 | override val observeConditions: Boolean 50 | get() = false 51 | override val callTemplates: Boolean 52 | get() = true 53 | }) 54 | newFile.accept(visitor) 55 | file = newFile 56 | } 57 | 58 | override fun annotate(element: PsiElement, holder: AnnotationHolder) { 59 | if (holder.currentAnnotationSession.file != file) { 60 | (holder.currentAnnotationSession.file as? GnFile)?.let { prepareFile(it) } 61 | } 62 | when (element) { 63 | is GnId -> annotateIdentifier(element, holder) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnBraceMatcher.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.google.idea.gn.psi.Types 7 | import com.intellij.lang.BracePair 8 | import com.intellij.lang.PairedBraceMatcher 9 | import com.intellij.psi.PsiFile 10 | import com.intellij.psi.TokenType 11 | import com.intellij.psi.tree.IElementType 12 | 13 | class GnBraceMatcher : PairedBraceMatcher { 14 | override fun getPairs(): Array { 15 | return PAIRS 16 | } 17 | 18 | override fun isPairedBracesAllowedBeforeType(lbraceType: IElementType, 19 | contextType: IElementType?): Boolean { 20 | return TokenType.WHITE_SPACE == contextType || 21 | isCloseBraceToken(contextType) 22 | } 23 | 24 | override fun getCodeConstructStart(file: PsiFile, openingBraceOffset: Int): Int { 25 | return openingBraceOffset 26 | } 27 | 28 | companion object { 29 | private val PAIRS = arrayOf( 30 | BracePair(Types.OBRACKET, Types.CBRACKET, true), 31 | BracePair(Types.OPAREN, Types.CPAREN, true), 32 | BracePair(Types.OBRACE, Types.CBRACE, true)) 33 | 34 | private fun isCloseBraceToken(type: IElementType?): Boolean { 35 | for (pair in PAIRS) { 36 | if (type === pair.rightBraceType) { 37 | return true 38 | } 39 | } 40 | return false 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnColorSettingsPage.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn 6 | 7 | import com.intellij.openapi.editor.colors.TextAttributesKey 8 | import com.intellij.openapi.fileTypes.SyntaxHighlighter 9 | import com.intellij.openapi.options.colors.AttributesDescriptor 10 | import com.intellij.openapi.options.colors.ColorDescriptor 11 | import com.intellij.openapi.options.colors.ColorSettingsPage 12 | import com.intellij.util.containers.toArray 13 | import javax.swing.Icon 14 | 15 | class GnColorSettingsPage : ColorSettingsPage { 16 | override fun getHighlighter(): SyntaxHighlighter = GnSyntaxHighlighter() 17 | 18 | override fun getIcon(): Icon? = GnIcons.FILE 19 | 20 | override fun getAttributeDescriptors(): Array = DESCRIPTORS 21 | 22 | override fun getColorDescriptors(): Array = emptyArray() 23 | 24 | override fun getDisplayName(): String = "Gn" 25 | 26 | override fun getDemoText(): String = """ 27 | # This is a comment 28 | import("//build.gni") 29 | 30 | template("my_template") { 31 | a = "value" 32 | b = { 33 | c = 1 34 | d = false 35 | } 36 | if (b.d) { 37 | foo = "bar${"$"}a" 38 | } 39 | group(target_name) { 40 | forward_variables_from(invoker, "*") 41 | } 42 | } 43 | 44 | ("hello") { 45 | deps = ["//lib:my_lib"] 46 | } 47 | """.trimIndent() 48 | 49 | override fun getAdditionalHighlightingTagToDescriptorMap(): MutableMap? = mutableMapOf( 50 | "function" to GnColors.BUILTIN_FUNCTION.textAttributesKey, 51 | "target" to GnColors.TARGET_FUNCTION.textAttributesKey, 52 | "template" to GnColors.TEMPLATE.textAttributesKey, 53 | "var" to GnColors.VARIABLE.textAttributesKey 54 | ) 55 | 56 | companion object { 57 | val DESCRIPTORS: Array = GnColors.values().sortedBy { it.name } 58 | .map { it.attributesDescriptor }.toArray( 59 | emptyArray()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnColors.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn 6 | 7 | import com.intellij.openapi.editor.colors.TextAttributesKey 8 | import com.intellij.openapi.options.colors.AttributesDescriptor 9 | import com.intellij.openapi.editor.DefaultLanguageHighlighterColors as Default 10 | 11 | enum class GnColors(humanName: String, default: TextAttributesKey? = null) { 12 | BUILTIN_FUNCTION("Function", Default.PREDEFINED_SYMBOL), 13 | TARGET_FUNCTION("Builtin Target rule", Default.KEYWORD), 14 | TEMPLATE("Template call", Default.FUNCTION_CALL), 15 | COMMENT("Comment", Default.LINE_COMMENT), 16 | STRING("String", Default.STRING), 17 | VARIABLE("Variable", Default.INSTANCE_FIELD), 18 | KEYWORD("Keyword", Default.KEYWORD), 19 | BRACES("Braces and Operators//Braces", Default.BRACES), 20 | BRACKETS("Braces and Operators//Brackets", Default.BRACKETS), 21 | OPERATOR("Braces and Operators//Operator", Default.OPERATION_SIGN), 22 | PARENS("Braces and Operators//Parentheses", Default.PARENTHESES), 23 | DOT("Braces and Operators//Dot", Default.DOT), 24 | INT("Integral", Default.NUMBER); 25 | 26 | val textAttributesKey = TextAttributesKey.createTextAttributesKey("com.google.idea.gn.$name", 27 | default) 28 | val attributesDescriptor = AttributesDescriptor(humanName, textAttributesKey) 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnCommenter.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.intellij.lang.Commenter 7 | 8 | class GnCommenter : Commenter { 9 | override fun getCommentedBlockCommentPrefix(): String? = null 10 | 11 | override fun getCommentedBlockCommentSuffix(): String? = null 12 | 13 | override fun getBlockCommentPrefix(): String? = null 14 | 15 | override fun getBlockCommentSuffix(): String? = null 16 | 17 | override fun getLineCommentPrefix(): String? = "#" 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnCompletionContributor.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.google.idea.gn.completion.FileCompletionProvider 7 | import com.google.idea.gn.completion.IdentifierCompletionProvider 8 | import com.google.idea.gn.psi.GnFile 9 | import com.google.idea.gn.psi.Types 10 | import com.google.idea.gn.psi.builtin.BuiltinVariable 11 | import com.google.idea.gn.psi.builtin.Import 12 | import com.intellij.codeInsight.completion.CompletionContributor 13 | import com.intellij.codeInsight.completion.CompletionInitializationContext 14 | import com.intellij.codeInsight.completion.CompletionType 15 | import com.intellij.patterns.PlatformPatterns 16 | import com.intellij.patterns.PlatformPatterns.psiElement 17 | import com.intellij.patterns.StandardPatterns 18 | import com.intellij.util.text.Matcher 19 | 20 | class GnCompletionContributor : CompletionContributor() { 21 | 22 | companion object { 23 | const val DUMMY_ID = "gn_dummy_completion_" 24 | } 25 | 26 | 27 | override fun beforeCompletion(context: CompletionInitializationContext) { 28 | context.dummyIdentifier = DUMMY_ID 29 | context.replacementOffset = 0 30 | } 31 | 32 | init { 33 | // Complete deps. 34 | extend(CompletionType.BASIC, 35 | psiElement(Types.STRING_LITERAL) 36 | .withAncestor(10, 37 | psiElement(Types.ASSIGNMENT).withFirstNonWhitespaceChild( 38 | psiElement(Types.LVALUE) 39 | .withText(StandardPatterns.or( 40 | PlatformPatterns.string() 41 | .oneOf(BuiltinVariable.DEPS.identifierName, 42 | BuiltinVariable.PUBLIC_DEPS.identifierName, 43 | BuiltinVariable.DATA_DEPS.identifierName, 44 | BuiltinVariable.ALL_DEPENDENT_CONFIGS.identifierName, 45 | BuiltinVariable.PUBLIC_CONFIGS.identifierName 46 | ), 47 | // "Heuristic" for template variables that might use deps 48 | PlatformPatterns.string().endsWith("_deps") 49 | )) 50 | ) 51 | ), FileCompletionProvider( 52 | Matcher { name: String -> name == GnFile.BUILD_FILE }, 53 | skipFileName = true, parseTargets = true) 54 | ) 55 | 56 | // Complete imports. 57 | extend(CompletionType.BASIC, psiElement(Types.STRING_LITERAL) 58 | .withSuperParent(5, psiElement(Types.CALL) 59 | .withFirstNonWhitespaceChild(psiElement(Types.ID).withText( 60 | Import.NAME))), 61 | FileCompletionProvider( 62 | Matcher { name: String -> name.endsWith(GnFile.GNI) })) 63 | 64 | // Complete sources. 65 | extend(CompletionType.BASIC, psiElement(Types.STRING_LITERAL).withAncestor(10, 66 | psiElement(Types.ASSIGNMENT) 67 | .withFirstChild( 68 | psiElement(Types.LVALUE).withText(BuiltinVariable.SOURCES.identifierName))), 69 | FileCompletionProvider(Matcher { true })) 70 | 71 | // Complete identifiers. 72 | extend(CompletionType.BASIC, psiElement(Types.IDENTIFIER), IdentifierCompletionProvider()) 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnDocumentationProvider.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn 6 | 7 | import com.google.idea.gn.psi.GnId 8 | import com.google.idea.gn.util.getPathLabel 9 | import com.intellij.lang.documentation.DocumentationProvider 10 | import com.intellij.psi.PsiElement 11 | 12 | class GnDocumentationProvider : DocumentationProvider { 13 | 14 | private fun getQuickIdentifierNavigateInfo(element: PsiElement, originalElement: GnId): String? { 15 | val file = element.containingFile 16 | val originalFile = originalElement.containingFile 17 | if (file != originalFile) { 18 | return getPathLabel(file) 19 | } 20 | return null 21 | } 22 | 23 | override fun getQuickNavigateInfo(element: PsiElement?, originalElement: PsiElement?): String? = 24 | when { 25 | element != null && originalElement is GnId -> getQuickIdentifierNavigateInfo(element, 26 | originalElement) 27 | else -> null 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnEnterDelegate.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.intellij.codeInsight.editorActions.enter.EnterBetweenBracesAndBracketsDelegate 7 | 8 | class GnEnterDelegate : EnterBetweenBracesAndBracketsDelegate() 9 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnFilePattern.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | class GnFilePattern(pattern: String) { 7 | val regex: Regex 8 | 9 | init { 10 | val replace = listOf("\\b", "*") 11 | val parts = mutableListOf("^") 12 | var patt: CharSequence = pattern 13 | while (patt.isNotEmpty()) { 14 | val x = patt.findAnyOf(replace) 15 | 16 | val sub = x?.let { 17 | patt.subSequence(0, x.first) 18 | } ?: patt 19 | if (sub.isNotEmpty()) { 20 | parts.add(Regex.escape(sub.toString())) 21 | } 22 | 23 | patt = when (x?.second) { 24 | "\\b" -> { 25 | parts.add("(.*\\/)?") 26 | patt.subSequence(x.first + 2, patt.length) 27 | } 28 | "*" -> { 29 | parts.add(".*") 30 | patt.subSequence(x.first + 1, patt.length) 31 | } 32 | else -> { 33 | "" 34 | } 35 | } 36 | } 37 | parts.add("$") 38 | regex = Regex(parts.joinToString("")) 39 | } 40 | 41 | fun matches(chars: CharSequence): Boolean = regex.matches(chars) 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnFileType.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.intellij.openapi.fileTypes.LanguageFileType 7 | import javax.swing.Icon 8 | 9 | object GnFileType : LanguageFileType(GnLanguage) { 10 | override fun getName(): String { 11 | return "GN" 12 | } 13 | 14 | override fun getDescription(): String { 15 | return "GN Build" 16 | } 17 | 18 | override fun getDefaultExtension(): String { 19 | return "gn" 20 | } 21 | 22 | override fun getIcon(): Icon? { 23 | return GnIcons.FILE 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnFoldingBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.google.idea.gn 2 | 3 | import com.google.idea.gn.psi.impl.GnBlockImpl 4 | import com.google.idea.gn.psi.impl.GnCollectionImpl 5 | import com.intellij.lang.ASTNode 6 | import com.intellij.lang.folding.FoldingBuilderEx 7 | import com.intellij.lang.folding.FoldingDescriptor 8 | import com.intellij.openapi.editor.Document 9 | import com.intellij.openapi.project.DumbAware 10 | import com.intellij.openapi.util.TextRange 11 | import com.intellij.psi.PsiElement 12 | import com.intellij.psi.util.PsiTreeUtil 13 | import java.util.* 14 | 15 | class GnFoldingBuilder : FoldingBuilderEx(), DumbAware { 16 | override fun buildFoldRegions(root: PsiElement, document: Document, quick: Boolean): Array { 17 | val descriptors: MutableList = ArrayList() 18 | 19 | //Block 20 | val blocks = PsiTreeUtil.findChildrenOfType(root, GnBlockImpl::class.java) 21 | for (block in blocks) { 22 | val first = block.firstChild 23 | val last = block.lastChild 24 | val newRange = TextRange(first.textRange.endOffset, last.textRange.startOffset) 25 | if (newRange.length > 0) { 26 | descriptors.add(FoldingDescriptor(block, newRange)) 27 | } 28 | } 29 | 30 | // Collections 31 | val collections = PsiTreeUtil.findChildrenOfType(root, GnCollectionImpl::class.java) 32 | for (collection in collections) { 33 | val first = collection.firstChild 34 | val last = collection.lastChild 35 | val newRange = TextRange(first.textRange.endOffset, last.textRange.startOffset) 36 | if (newRange.length > 0) { 37 | descriptors.add(FoldingDescriptor(collection, newRange)) 38 | } 39 | } 40 | return descriptors.toTypedArray() 41 | } 42 | 43 | override fun getPlaceholderText(node: ASTNode): String? { 44 | return "..." 45 | } 46 | 47 | override fun isCollapsedByDefault(node: ASTNode): Boolean { 48 | return false 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnFormattingBuilder.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.google.idea.gn.psi.GnFormatBlock 7 | import com.intellij.formatting.* 8 | import com.intellij.lang.ASTNode 9 | import com.intellij.openapi.util.TextRange 10 | import com.intellij.psi.PsiFile 11 | import com.intellij.psi.codeStyle.CodeStyleSettings 12 | 13 | class GnFormattingBuilder : FormattingModelBuilder { 14 | override fun createModel(formattingContext: FormattingContext): FormattingModel { 15 | val element = formattingContext.psiElement 16 | val settings = formattingContext.codeStyleSettings 17 | return FormattingModelProvider 18 | .createFormattingModelForPsiFile(element.containingFile, 19 | GnFormatBlock(element.node, 20 | Alignment.createAlignment(), 21 | createSpaceBuilder(settings)), 22 | settings) 23 | } 24 | 25 | override fun getRangeAffectingIndent(file: PsiFile, offset: Int, elementAtOffset: ASTNode): TextRange? { 26 | return null 27 | } 28 | 29 | companion object { 30 | private fun createSpaceBuilder(settings: CodeStyleSettings): SpacingBuilder { 31 | return SpacingBuilder(settings, 32 | GnLanguage) // .around(Types.ASSIGN_OP).spacing(1, 1, 0, false, 0) 33 | // .between(Types.CPAREN, Types.BLOCK).spacing(1, 1, 0, false, 0) 34 | // .between(Types.STATEMENT, Types.STATEMENT).spacing(0, 0, 1, false, 1) 35 | // .afterInside(Types.COMMA, Types.COLLECTION).spacing(0, 0, 1, false, 0) 36 | // .after(Types.OBRACE).spacing(0, 0, 1, false, 1) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnIcons.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.intellij.openapi.util.IconLoader 7 | 8 | object GnIcons { 9 | @JvmField 10 | val FILE = IconLoader.getIcon("/icons/gn.svg", GnIcons.javaClass) 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnKeys.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn 6 | 7 | import com.google.idea.gn.completion.CompletionIdentifier 8 | import com.google.idea.gn.completion.FileCompletionProvider 9 | import com.google.idea.gn.psi.Function 10 | import com.google.idea.gn.psi.TemplateFunction 11 | import com.intellij.openapi.util.Key 12 | 13 | object GnKeys { 14 | val LABEL_COMPLETION_TYPE: Key = Key( 15 | "com.google.idea.gn.LABEL_COMPLETION_TYPE") 16 | val IDENTIFIER_COMPLETION_TYPE: Key = Key( 17 | "com.google.idea.gn.IDENTIFIER_COMPLETION_TYPE") 18 | val CALL_RESOLVED_FUNCTION: Key = Key("com.google.idea.gn.CALL_RESOLVED_FUNCTION") 19 | val TEMPLATE_INSTALLED_FUNCTION: Key = Key( 20 | "com.google.idea.gn.TEMPLATE_INSTALLED_FUNCTION") 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnLabel.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.google.idea.gn.util.getPathLabel 7 | import com.intellij.openapi.util.text.StringUtil 8 | import com.intellij.psi.PsiFile 9 | import java.io.StringWriter 10 | import java.util.regex.Pattern 11 | 12 | class GnLabel private constructor() { 13 | private var targetField: String? = null 14 | val target: String? 15 | get() = if (targetField == null && parts.isNotEmpty()) { 16 | parts[parts.size - 1] 17 | } else targetField 18 | 19 | private fun writeDir(writer: StringWriter) { 20 | if (isAbsolute) { 21 | writer.write("//") 22 | } 23 | writer.write(java.lang.String.join("/", *parts)) 24 | } 25 | 26 | fun toFullyQualified(): GnLabel { 27 | val ret = GnLabel() 28 | ret.isAbsolute = isAbsolute 29 | ret.parts = parts 30 | ret.targetField = target 31 | ret.toolchain = toolchain 32 | return ret 33 | } 34 | 35 | fun dropToolChain(): GnLabel { 36 | val ret = GnLabel() 37 | ret.isAbsolute = isAbsolute 38 | ret.parts = parts 39 | ret.targetField = targetField 40 | ret.toolchain = null 41 | return ret 42 | } 43 | 44 | override fun toString(): String { 45 | val writer = StringWriter() 46 | writeDir(writer) 47 | targetField?.let { 48 | if (it.isNotEmpty()) { 49 | writer.write(":") 50 | writer.write(it) 51 | } 52 | } 53 | toolchain?.let { 54 | if (it.isNotEmpty()) { 55 | writer.write("(") 56 | writer.write(it) 57 | writer.write(")") 58 | } 59 | } 60 | return writer.toString() 61 | } 62 | 63 | var isAbsolute = false 64 | private set 65 | var parts: Array = emptyArray() 66 | private set 67 | 68 | var toolchain: String? = null 69 | private set 70 | 71 | val dirString: String 72 | get() { 73 | val writer = StringWriter() 74 | writeDir(writer) 75 | return writer.toString() 76 | } 77 | 78 | fun toAbsolute(file: PsiFile): GnLabel? { 79 | if (isAbsolute) { 80 | return this 81 | } 82 | val ret = parse(getPathLabel(file)) ?: return null 83 | ret.isAbsolute = true 84 | ret.toolchain = toolchain 85 | ret.targetField = target 86 | ret.parts = ret.parts.plus(parts) 87 | return ret 88 | } 89 | 90 | companion object { 91 | private val PATH_PATTERN = Pattern.compile( 92 | """(//)?([a-zA-Z0-9$ \-_./]*)(:[a-zA-Z0-9_\-$.]*)?(\([a-zA-Z0-9_\-$./:]*\))?""") 93 | 94 | @kotlin.jvm.JvmStatic 95 | fun parse(path: String?): GnLabel? { 96 | if (path.isNullOrEmpty()) { 97 | return null 98 | } 99 | val out = GnLabel() 100 | val m = PATH_PATTERN.matcher(path) 101 | if (!m.matches()) { 102 | return null 103 | } 104 | if (m.group(1) != null) { 105 | out.isAbsolute = true 106 | } 107 | out.parts = StringUtil.split(m.group(2), "/", true, true).toTypedArray() 108 | out.targetField = m.group(3)?.substring(1) 109 | 110 | if (out.parts.isEmpty() && out.targetField == null) { 111 | return null 112 | } 113 | out.toolchain = m.group(4) 114 | if (out.toolchain != null) { 115 | out.toolchain = out.toolchain!!.substring(1, out.toolchain!!.length - 1) 116 | } 117 | return out 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnLanguage.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.intellij.lang.Language 7 | 8 | object GnLanguage : Language("gn") 9 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnLexerAdapter.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.intellij.lexer.FlexAdapter 7 | import java.io.Reader 8 | 9 | class GnLexerAdapter : FlexAdapter(GnLexer(null as Reader?)) 10 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnParserDefinition.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.google.idea.gn.parser.GnParser 7 | import com.google.idea.gn.psi.GnFile 8 | import com.google.idea.gn.psi.Types 9 | import com.intellij.lang.ASTNode 10 | import com.intellij.lang.ParserDefinition 11 | import com.intellij.lang.PsiParser 12 | import com.intellij.lexer.Lexer 13 | import com.intellij.openapi.project.Project 14 | import com.intellij.psi.FileViewProvider 15 | import com.intellij.psi.PsiElement 16 | import com.intellij.psi.PsiFile 17 | import com.intellij.psi.TokenType 18 | import com.intellij.psi.tree.IFileElementType 19 | import com.intellij.psi.tree.TokenSet 20 | 21 | class GnParserDefinition : ParserDefinition { 22 | override fun createLexer(project: Project): Lexer { 23 | return GnLexerAdapter() 24 | } 25 | 26 | override fun createParser(project: Project): PsiParser { 27 | return GnParser() 28 | } 29 | 30 | override fun getFileNodeType(): IFileElementType { 31 | return FILE 32 | } 33 | 34 | override fun getCommentTokens(): TokenSet { 35 | return COMMENTS 36 | } 37 | 38 | override fun getWhitespaceTokens(): TokenSet { 39 | return WHITE_SPACES 40 | } 41 | 42 | override fun getStringLiteralElements(): TokenSet { 43 | return TokenSet.EMPTY 44 | } 45 | 46 | override fun createElement(node: ASTNode): PsiElement { 47 | return Types.Factory.createElement(node) 48 | } 49 | 50 | override fun createFile(viewProvider: FileViewProvider): PsiFile { 51 | return GnFile(viewProvider) 52 | } 53 | 54 | companion object { 55 | val WHITE_SPACES = TokenSet.create(TokenType.WHITE_SPACE) 56 | val COMMENTS = TokenSet.create(Types.COMMENT) 57 | val STRING_EXPR = TokenSet.create(Types.QUOTE, Types.STRING_LITERAL) 58 | val FILE = IFileElementType(GnLanguage) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnQuoteHandler.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler 7 | 8 | class GnQuoteHandler : SimpleTokenSetQuoteHandler(GnParserDefinition.STRING_EXPR) 9 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnSyntaxHighlighter.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.google.idea.gn.psi.Types 7 | import com.intellij.lexer.Lexer 8 | import com.intellij.openapi.editor.colors.TextAttributesKey 9 | import com.intellij.openapi.fileTypes.SyntaxHighlighterBase 10 | import com.intellij.psi.tree.IElementType 11 | 12 | class GnSyntaxHighlighter : SyntaxHighlighterBase() { 13 | override fun getHighlightingLexer(): Lexer { 14 | return GnLexerAdapter() 15 | } 16 | 17 | override fun getTokenHighlights(tokenType: IElementType): Array { 18 | return arrayOf(when (tokenType) { 19 | Types.QUOTE, 20 | Types.STRING_LITERAL -> GnColors.STRING 21 | Types.COMMENT -> GnColors.COMMENT 22 | Types.AND, 23 | Types.OR, 24 | Types.EQUAL, 25 | Types.EQUAL_EQUAL, 26 | Types.NOT_EQUAL, 27 | Types.LESSER, 28 | Types.LESSER_EQUAL, 29 | Types.GREATER, 30 | Types.GREATER_EQUAL, 31 | Types.UNARY_NOT, 32 | Types.PLUS, 33 | Types.MINUS, 34 | Types.PLUS_EQUAL, 35 | Types.MINUS_EQUAL -> GnColors.OPERATOR 36 | Types.IF, 37 | Types.ELSE, 38 | Types.TRUE, 39 | Types.FALSE -> GnColors.KEYWORD 40 | Types.INTEGRAL_LITERAL -> GnColors.INT 41 | Types.OBRACE, Types.CBRACE -> GnColors.BRACES 42 | Types.OPAREN, Types.CPAREN -> GnColors.PARENS 43 | Types.OBRACKET, Types.CBRACKET -> GnColors.BRACKETS 44 | Types.DOT -> GnColors.DOT 45 | Types.DOLLAR -> GnColors.VARIABLE 46 | else -> return emptyArray() 47 | }.textAttributesKey) 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnSyntaxHighlighterFactory.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.intellij.openapi.fileTypes.SyntaxHighlighter 7 | import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory 8 | import com.intellij.openapi.project.Project 9 | import com.intellij.openapi.vfs.VirtualFile 10 | 11 | class GnSyntaxHighlighterFactory : SyntaxHighlighterFactory() { 12 | override fun getSyntaxHighlighter(project: Project?, 13 | virtualFile: VirtualFile?): SyntaxHighlighter { 14 | return GnSyntaxHighlighter() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/GnTypedHandler.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.intellij.codeInsight.AutoPopupController 7 | import com.intellij.codeInsight.CodeInsightSettings 8 | import com.intellij.codeInsight.editorActions.TypedHandlerDelegate 9 | import com.intellij.openapi.editor.Editor 10 | import com.intellij.openapi.fileTypes.FileType 11 | import com.intellij.openapi.project.Project 12 | import com.intellij.psi.PsiFile 13 | 14 | class GnTypedHandler : TypedHandlerDelegate() { 15 | override fun beforeCharTyped(c: Char, project: Project, editor: Editor, 16 | file: PsiFile, fileType: FileType): Result { 17 | if (GnLanguage != file.language) { 18 | return Result.CONTINUE 19 | } 20 | return if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) { 21 | Result.CONTINUE 22 | } else Result.CONTINUE 23 | } 24 | 25 | override fun checkAutoPopup(charTyped: Char, project: Project, editor: Editor, 26 | file: PsiFile): Result { 27 | if (GnLanguage != file.language) { 28 | return Result.CONTINUE 29 | } 30 | if (charTyped == '"' || charTyped == '/' || charTyped == ':') { 31 | AutoPopupController.getInstance(project).scheduleAutoPopup(editor) 32 | return Result.STOP 33 | } 34 | return Result.CONTINUE 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/actions/NewBuildFileAction.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.actions 5 | 6 | import com.google.idea.gn.psi.GnFile 7 | import com.intellij.ide.actions.CreateFileFromTemplateAction 8 | import com.intellij.ide.fileTemplates.FileTemplateManager 9 | import com.intellij.openapi.actionSystem.* 10 | import com.intellij.util.containers.getIfSingle 11 | import com.intellij.util.containers.stream 12 | 13 | 14 | class NewBuildFileAction : AnAction() { 15 | 16 | 17 | private fun isAvailable(dataContext: DataContext): Boolean { 18 | // Only show action if a BUILD.gn file does not exist. 19 | val view = LangDataKeys.IDE_VIEW.getData(dataContext) ?: return false 20 | val dir = view.directories.stream().getIfSingle() ?: return false 21 | return dir.virtualFile.findChild(GnFile.BUILD_FILE) == null 22 | } 23 | 24 | override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT 25 | 26 | override fun update(e: AnActionEvent) { 27 | val dataContext = e.dataContext 28 | val presentation = e.presentation 29 | presentation.isEnabledAndVisible = isAvailable(dataContext) 30 | } 31 | 32 | 33 | override fun actionPerformed(e: AnActionEvent) { 34 | val dataContext = e.dataContext 35 | val view = LangDataKeys.IDE_VIEW.getData(dataContext) ?: return 36 | val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return 37 | val dir = view.directories.stream().getIfSingle() ?: return 38 | val template = FileTemplateManager.getInstance(project) 39 | .getInternalTemplate(GnFile.TEMPLATE_NAME) 40 | 41 | CreateFileFromTemplateAction.createFileFromTemplate(GnFile.BUILD_FILE, template, dir, null, 42 | true) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/completion/CompletionIdentifier.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn.completion 6 | 7 | import com.google.idea.gn.GnKeys 8 | import com.intellij.codeInsight.AutoPopupController 9 | import com.intellij.codeInsight.completion.CompletionResultSet 10 | import com.intellij.codeInsight.lookup.LookupElementBuilder 11 | import com.intellij.icons.AllIcons 12 | import com.intellij.ui.LayeredIcon 13 | import javax.swing.Icon 14 | 15 | interface CompletionIdentifier { 16 | val identifierName: String 17 | val autoSuggestOnInsertion: Boolean get() = false 18 | val postInsertType: PostInsertType? get() = null 19 | 20 | 21 | enum class PostInsertType(val extra: String, val caretShift: Int) { 22 | CALL("()", 1), 23 | CALL_WITH_STRING("(\"\")", 2); 24 | } 25 | 26 | enum class IdentifierType { 27 | FUNCTION, 28 | TARGET_FUNCTION, 29 | VARIABLE, 30 | FUNCTION_VARIABLE, 31 | TEMPLATE; 32 | 33 | val icon: Icon 34 | get() = when (this) { 35 | FUNCTION -> AllIcons.Nodes.Function 36 | TARGET_FUNCTION -> LayeredIcon.create(AllIcons.Nodes.Target, AllIcons.Nodes.Shared) 37 | VARIABLE -> AllIcons.Nodes.Variable 38 | TEMPLATE -> AllIcons.Actions.Lightning 39 | FUNCTION_VARIABLE -> LayeredIcon.create(AllIcons.Nodes.Variable, AllIcons.Nodes.StaticMark) 40 | } 41 | } 42 | 43 | val identifierType: IdentifierType 44 | 45 | val typeString: String? get() = null 46 | 47 | fun gatherChildren(operator: (CompletionIdentifier) -> Unit) = Unit 48 | 49 | fun addToResult(resultSet: CompletionResultSet) { 50 | resultSet.startBatch() 51 | val element = LookupElementBuilder.create(identifierName) 52 | .withIcon(identifierType.icon) 53 | .withTypeText(typeString) 54 | .withInsertHandler { ctx, _ -> 55 | postInsertType?.let { 56 | ctx.document.insertString(ctx.tailOffset, it.extra) 57 | ctx.editor.caretModel.primaryCaret.moveCaretRelatively(it.caretShift, 0, false, false) 58 | } 59 | if (autoSuggestOnInsertion) { 60 | AutoPopupController.getInstance(ctx.project).scheduleAutoPopup(ctx.editor) 61 | } 62 | } 63 | element.putUserData(GnKeys.IDENTIFIER_COMPLETION_TYPE, identifierType) 64 | resultSet.addElement(element) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/completion/IdentifierCompletionProvider.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn.completion 6 | 7 | import com.google.idea.gn.GnKeys 8 | import com.google.idea.gn.psi.* 9 | import com.google.idea.gn.psi.Function 10 | import com.google.idea.gn.psi.builtin.Import 11 | import com.google.idea.gn.psi.builtin.Template 12 | import com.google.idea.gn.psi.scope.FileScope 13 | import com.google.idea.gn.psi.scope.Scope 14 | import com.intellij.codeInsight.completion.CompletionParameters 15 | import com.intellij.codeInsight.completion.CompletionProvider 16 | import com.intellij.codeInsight.completion.CompletionResultSet 17 | import com.intellij.openapi.progress.ProgressManager 18 | import com.intellij.psi.PsiElement 19 | import com.intellij.psi.util.parentOfTypes 20 | import com.intellij.psi.util.parents 21 | import com.intellij.util.ProcessingContext 22 | 23 | class IdentifierCompletionProvider : CompletionProvider() { 24 | override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, _result: CompletionResultSet) { 25 | val result = IdentifierCompletionResultSet(_result) 26 | 27 | val position = parameters.position 28 | val file = position.containingFile 29 | if (file !is GnFile) { 30 | return 31 | } 32 | 33 | val stopAt = position.parentOfTypes(GnStatement::class, 34 | GnBlock::class, GnFile::class) ?: file 35 | 36 | 37 | val capturingVisitor = object : Visitor.VisitorDelegate() { 38 | var finalScope: Scope? = null 39 | 40 | override fun resolveCall(call: GnCall, function: Function?): Visitor.CallAction = 41 | when { 42 | // Template and import calls must be executed so they show up in the scope. 43 | function is Template || function is Import -> Visitor.CallAction.EXECUTE 44 | position.parents(true).contains(call) -> Visitor.CallAction.VISIT_BLOCK 45 | else -> Visitor.CallAction.SKIP 46 | } 47 | 48 | 49 | override fun afterVisit(element: PsiElement, scope: Scope): Boolean { 50 | if (element == stopAt) { 51 | finalScope = scope 52 | return true 53 | } 54 | return false 55 | } 56 | } 57 | file.accept(Visitor(FileScope(), capturingVisitor)) 58 | 59 | val scope = capturingVisitor.finalScope ?: file.scope 60 | 61 | val inFunction = position.parentOfTypes(GnCall::class) 62 | ?.getUserData(GnKeys.CALL_RESOLVED_FUNCTION) 63 | 64 | 65 | scope.gatherCompletionIdentifiers { 66 | ProgressManager.checkCanceled() 67 | 68 | if (it == inFunction) { 69 | it.gatherChildren { child -> 70 | result.addIdentifier(child) 71 | } 72 | } 73 | 74 | // Don't suggest target functions within a target function. 75 | if (inFunction?.identifierType != CompletionIdentifier.IdentifierType.TARGET_FUNCTION 76 | || it.identifierType != CompletionIdentifier.IdentifierType.TARGET_FUNCTION) { 77 | result.addIdentifier(it) 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/completion/IdentifierCompletionResultSet.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn.completion 6 | 7 | import com.intellij.codeInsight.completion.CompletionResultSet 8 | 9 | class IdentifierCompletionResultSet(private val resultSet: CompletionResultSet) { 10 | private val added = mutableSetOf() 11 | 12 | fun addIdentifier(id: CompletionIdentifier) { 13 | if (!added.contains(id.identifierName)) { 14 | added.add(id.identifierName) 15 | id.addToResult(resultSet) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/config/GnConfigurableBase.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.config 5 | 6 | import com.intellij.openapi.options.Configurable 7 | import com.intellij.openapi.project.Project 8 | 9 | abstract class GnConfigurableBase(project: Project) : Configurable { 10 | 11 | val settings: GnSettingsService = project.gnSettings 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/config/GnProjectConfigurable.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.config 5 | 6 | import com.intellij.openapi.Disposable 7 | import com.intellij.openapi.options.Configurable 8 | import com.intellij.openapi.project.Project 9 | import javax.swing.JComponent 10 | 11 | class GnProjectConfigurable(project: Project) : GnConfigurableBase( 12 | project), Configurable.NoScroll, Disposable { 13 | 14 | private var modified: Boolean = false 15 | 16 | override fun isModified() = modified 17 | 18 | override fun getDisplayName(): String { 19 | return "Gn" 20 | } 21 | 22 | override fun apply() { 23 | settings.modify { 24 | it.projectRoot = projectPathField.text 25 | } 26 | } 27 | 28 | private val projectPathField = pathToDirectoryTextField( 29 | this, 30 | "Select Project's Gn root", 31 | onTextChanged = fun() { 32 | modified = true 33 | } 34 | ).apply { 35 | isEditable = false 36 | project.gnRoot?.let { 37 | text = it.path 38 | } 39 | } 40 | 41 | override fun createComponent(): JComponent? = layout { 42 | row("Gn root", projectPathField) 43 | } 44 | 45 | override fun dispose() { 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/config/GnSettingsService.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.config 5 | 6 | import com.intellij.openapi.components.ServiceManager 7 | import com.intellij.openapi.project.Project 8 | import com.intellij.openapi.project.guessProjectDir 9 | import com.intellij.openapi.vfs.LocalFileSystem 10 | import com.intellij.openapi.vfs.VirtualFile 11 | 12 | interface GnSettingsService { 13 | data class State( 14 | var version: Int? = null, 15 | var projectRoot: String? = null 16 | ) 17 | 18 | fun modify(action: (State) -> Unit) 19 | 20 | val projectRoot: String? 21 | } 22 | 23 | val Project.gnSettings: GnSettingsService 24 | get() = getService(GnSettingsService::class.java) 25 | ?: error("Failed to get GnSettingsService for $this") 26 | 27 | val Project.gnRoot: VirtualFile? 28 | get() = 29 | gnSettings.projectRoot?.let { 30 | LocalFileSystem.getInstance().findFileByPath(it)?.let { f -> 31 | if (f.isDirectory) { 32 | f 33 | } else { 34 | null 35 | } 36 | } 37 | } ?: guessProjectDir() 38 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/config/GnSettingsServiceImpl.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.config 5 | 6 | import com.intellij.configurationStore.serializeObjectInto 7 | import com.intellij.openapi.components.PersistentStateComponent 8 | import com.intellij.openapi.components.Storage 9 | import com.intellij.openapi.components.StoragePathMacros 10 | import com.intellij.openapi.project.Project 11 | import com.intellij.util.xmlb.XmlSerializer 12 | import org.jdom.Element 13 | 14 | 15 | private const val serviceName: String = "GnSettings" 16 | 17 | @com.intellij.openapi.components.State(name = serviceName, storages = [ 18 | Storage(StoragePathMacros.WORKSPACE_FILE), 19 | Storage("misc.xml", deprecated = true) 20 | ]) 21 | class GnSettingsServiceImpl( 22 | private val project: Project 23 | ) : PersistentStateComponent, GnSettingsService { 24 | @Volatile 25 | private var state: GnSettingsService.State = GnSettingsService.State() 26 | 27 | override fun getState(): Element { 28 | val element = Element(serviceName) 29 | serializeObjectInto(state, element) 30 | return element 31 | } 32 | 33 | override fun loadState(element: Element) { 34 | val rawState = element.clone() 35 | XmlSerializer.deserializeInto(state, rawState) 36 | } 37 | 38 | override val projectRoot: String? 39 | get() = state.projectRoot 40 | 41 | override fun modify(action: (GnSettingsService.State) -> Unit) { 42 | val newState = state.copy().also(action) 43 | if (state != newState) { 44 | state = newState.copy() 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/config/layout.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.config 5 | 6 | import com.intellij.openapi.Disposable 7 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory 8 | import com.intellij.openapi.ui.LabeledComponent 9 | import com.intellij.openapi.ui.TextComponentAccessor 10 | import com.intellij.openapi.ui.TextFieldWithBrowseButton 11 | import com.intellij.ui.DocumentAdapter 12 | import com.intellij.ui.IdeBorderFactory 13 | import com.intellij.util.ui.JBUI 14 | import com.intellij.util.ui.UIUtil 15 | import java.awt.BorderLayout 16 | import javax.swing.BoxLayout 17 | import javax.swing.JComponent 18 | import javax.swing.JPanel 19 | import javax.swing.event.DocumentEvent 20 | 21 | const val HGAP = 30 22 | const val VERTICAL_OFFSET = 2 23 | const val HORIZONTAL_OFFSET = 5 24 | 25 | fun layout(block: LayoutBuilder.() -> Unit): JPanel { 26 | val panel = JPanel(BorderLayout()) 27 | val innerPanel = JPanel().apply { 28 | layout = BoxLayout(this, BoxLayout.Y_AXIS) 29 | } 30 | panel.add(innerPanel, BorderLayout.NORTH) 31 | val builder = LayoutBuilderImpl(innerPanel).apply(block) 32 | UIUtil.mergeComponentsWithAnchor(builder.labeledComponents) 33 | return panel 34 | } 35 | 36 | interface LayoutBuilder { 37 | fun row(text: String = "", component: JComponent, toolTip: String = "") 38 | fun block(text: String, block: LayoutBuilder.() -> Unit) 39 | } 40 | 41 | private class LayoutBuilderImpl( 42 | val panel: JPanel, 43 | val labeledComponents: MutableList> = mutableListOf() 44 | ) : LayoutBuilder { 45 | override fun block(text: String, block: LayoutBuilder.() -> Unit) { 46 | val blockPanel = JPanel().apply { 47 | layout = BoxLayout(this, BoxLayout.Y_AXIS) 48 | border = IdeBorderFactory.createTitledBorder(text, false) 49 | } 50 | LayoutBuilderImpl(blockPanel, labeledComponents).apply(block) 51 | panel.add(blockPanel) 52 | } 53 | 54 | override fun row(text: String, component: JComponent, toolTip: String) { 55 | val labeledComponent = LabeledComponent.create(component, text, BorderLayout.WEST).apply { 56 | (layout as? BorderLayout)?.hgap = HGAP 57 | border = JBUI.Borders.empty(VERTICAL_OFFSET, HORIZONTAL_OFFSET) 58 | } 59 | labeledComponent.toolTipText = toolTip.trimIndent() 60 | labeledComponents += labeledComponent 61 | panel.add(labeledComponent) 62 | } 63 | } 64 | 65 | fun pathToDirectoryTextField( 66 | disposable: Disposable, 67 | title: String, 68 | onTextChanged: () -> Unit = {} 69 | ): TextFieldWithBrowseButton { 70 | 71 | val component = TextFieldWithBrowseButton(null, disposable) 72 | component.addBrowseFolderListener(title, null, null, 73 | FileChooserDescriptorFactory.createSingleFolderDescriptor(), 74 | TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT 75 | ) 76 | component.childComponent.document.addDocumentListener(object : DocumentAdapter() { 77 | override fun textChanged(e: DocumentEvent) { 78 | onTextChanged() 79 | } 80 | }) 81 | 82 | return component 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/manipulators/GnStringExprManipulator.kt: -------------------------------------------------------------------------------- 1 | package com.google.idea.gn.manipulators 2 | 3 | import com.google.idea.gn.psi.impl.GnStringExprImpl 4 | import com.intellij.openapi.diagnostic.logger 5 | import com.intellij.openapi.util.TextRange 6 | import com.intellij.psi.AbstractElementManipulator 7 | 8 | 9 | class GnStringExprManipulator : AbstractElementManipulator() { 10 | override fun handleContentChange( 11 | element: GnStringExprImpl, 12 | range: TextRange, 13 | newContent: String? 14 | ): GnStringExprImpl? { 15 | // TODO: Implement this 16 | LOG.warn("Renaming GN Expression is currently not supported") 17 | return null 18 | } 19 | 20 | companion object { 21 | private val LOG = logger() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/Builtin.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi 5 | 6 | import com.google.idea.gn.psi.builtin.* 7 | 8 | object Builtin { 9 | 10 | val FUNCTIONS: Map by lazy { 11 | sequenceOf( 12 | DeclareArgs(), 13 | Defined(), 14 | FilterExclude(), 15 | FilterInclude(), 16 | Foreach(), 17 | ForwardVariablesFrom(), 18 | GetLabelInfo(), 19 | GetPathInfo(), 20 | GetTargetOutputs(), 21 | GetEnv(), 22 | Import(), 23 | ProcessFileTemplate(), 24 | ReadFile(), 25 | RebasePath(), 26 | SetDefaultToolchain(), 27 | SplitList(), 28 | StringJoin(), 29 | StringReplace(), 30 | StringSplit(), 31 | Template(), 32 | com.google.idea.gn.psi.builtin.Target() 33 | ).plus(BuiltinTargetFunction.values().asSequence()) 34 | .plus(NoOpFunctions.values().asSequence()) 35 | .associateBy { it.identifierName } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/ElementType.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi 5 | 6 | import com.google.idea.gn.GnLanguage 7 | import com.intellij.psi.tree.IElementType 8 | import org.jetbrains.annotations.NonNls 9 | 10 | class ElementType(@NonNls debugName: String) : IElementType(debugName, GnLanguage) { 11 | override fun toString(): String = 12 | when (this) { 13 | Types.AND_EXPR -> "&& Expr" 14 | Types.ARRAY_ACCESS -> "array Access" 15 | Types.ASSIGNMENT -> "Assignment" 16 | Types.ASSIGN_OP -> "Assignment Operator" 17 | Types.BLOCK -> "Block" 18 | Types.CALL -> "Call" 19 | Types.COLLECTION -> "Collection" 20 | Types.CONDITION -> "Condition" 21 | Types.ELSE_CONDITION -> "Else Condition" 22 | Types.EQUAL_EXPR -> "EQUAL_EXPR" 23 | Types.EXPR -> "Expression" 24 | Types.EXPR_LIST -> "Expression-List" 25 | Types.GE_EXPR -> "> Expr" 26 | Types.GT_EXPR -> ">= Expr" 27 | Types.ID -> "Identifier" 28 | Types.LE_EXPR -> "<= Expr" 29 | Types.LITERAL_EXPR -> "Literal Expr" 30 | Types.LT_EXPR -> "< Expr" 31 | Types.LVALUE -> "LValue" 32 | Types.MINUS_EXPR -> "- Expr" 33 | Types.NOT_EQUAL_EXPR -> "!= Expr" 34 | Types.OR_EXPR -> "|| Expr" 35 | Types.PAREN_EXPR -> "( Expr )" 36 | Types.PLUS_EXPR -> "+ Expr" 37 | Types.PRIMARY_EXPR -> "Primary-Expr" 38 | Types.SCOPE_ACCESS -> ". Scope Access" 39 | Types.STATEMENT -> "Statement" 40 | Types.STATEMENT_LIST -> "Statement-List" 41 | Types.UNARY_EXPR -> "Unary-Expr" 42 | Types.STRING_EXPAND -> "String expansion" 43 | Types.STRING_HEX -> "String hex expansion" 44 | Types.STRING_EXPR -> "String expression" 45 | Types.STRING_LITERAL_EXPR -> "String literal expresion" 46 | Types.STRING_IDENT -> "String identifier access" 47 | Types.STRING_INNER -> "String contents" 48 | else -> super.toString() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/Function.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.scope.Scope 8 | 9 | interface Function : CompletionIdentifier { 10 | fun execute(call: GnCall, 11 | targetScope: Scope): GnValue? 12 | 13 | val isBuiltin: Boolean 14 | val variables: Map get() = emptyMap() 15 | 16 | override val postInsertType: CompletionIdentifier.PostInsertType? 17 | get() = CompletionIdentifier.PostInsertType.CALL 18 | 19 | override fun gatherChildren(operator: (CompletionIdentifier) -> Unit) = 20 | variables.forEach { operator(it.value) } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/FunctionVariable.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn.psi 6 | 7 | import com.google.idea.gn.completion.CompletionIdentifier 8 | 9 | interface FunctionVariable : CompletionIdentifier { 10 | val type: GnCompositeType? get() = null 11 | 12 | override val identifierType: CompletionIdentifier.IdentifierType 13 | get() = CompletionIdentifier.IdentifierType.FUNCTION_VARIABLE 14 | 15 | override val typeString: String? 16 | get() = type?.toString() 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/GnBinaryExpr.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn.psi 6 | 7 | import com.google.idea.gn.psi.scope.Scope 8 | 9 | interface GnBinaryExpr { 10 | 11 | fun evaluate(scope: Scope): GnValue? 12 | val exprList: List 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/GnCompositeType.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi 5 | 6 | enum class GnCompositeType(val primitive: GnValue.Type, val inner: GnValue.Type? = null) { 7 | BOOL(GnValue.Type.BOOL), 8 | STRING(GnValue.Type.STRING), 9 | INT(GnValue.Type.INT), 10 | SCOPE(GnValue.Type.SCOPE), 11 | LIST(GnValue.Type.LIST), 12 | LIST_OF_STRING(GnValue.Type.LIST, GnValue.Type.STRING); 13 | 14 | override fun toString(): String = if (inner != null) { 15 | "$primitive of $inner" 16 | } else { 17 | primitive.toString() 18 | } 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/GnFile.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi 5 | 6 | import com.google.idea.gn.GnFileType 7 | import com.google.idea.gn.GnLanguage 8 | import com.google.idea.gn.psi.scope.FileScope 9 | import com.google.idea.gn.psi.scope.Scope 10 | import com.intellij.extapi.psi.PsiFileBase 11 | import com.intellij.openapi.fileTypes.FileType 12 | import com.intellij.openapi.util.Key 13 | import com.intellij.psi.FileViewProvider 14 | import com.intellij.psi.util.CachedValue 15 | import com.intellij.psi.util.CachedValueProvider 16 | import com.intellij.psi.util.CachedValuesManager 17 | 18 | class GnFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, GnLanguage) { 19 | override fun getFileType(): FileType { 20 | return GnFileType 21 | } 22 | 23 | override fun toString(): String { 24 | val vf = virtualFile 25 | return if (vf != null) { 26 | "GN File " + vf.path 27 | } else "GN File" 28 | } 29 | 30 | val scope: Scope 31 | get() = CachedValuesManager.getCachedValue(this, CACHE_KEY) { 32 | val fileScope = buildScope() 33 | CachedValueProvider.Result(fileScope, this) 34 | } 35 | 36 | fun buildScope(injected: Map? = null): Scope { 37 | val fileScope = FileScope() 38 | injected?.let { 39 | for ((k, v) in it) { 40 | fileScope.addVariable(Variable(k, v)) 41 | } 42 | } 43 | accept(Visitor(fileScope)) 44 | return fileScope 45 | } 46 | 47 | companion object { 48 | const val BUILD_FILE = "BUILD.gn" 49 | const val GNI = "gni" 50 | const val TEMPLATE_NAME = "Gn File" 51 | val CACHE_KEY = Key>("gn-file-scope") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/GnFormatBlock.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi 5 | 6 | import com.intellij.formatting.* 7 | import com.intellij.lang.ASTNode 8 | import com.intellij.psi.formatter.common.AbstractBlock 9 | import java.util.* 10 | 11 | class GnFormatBlock @JvmOverloads constructor(node: ASTNode, alignment: Alignment?, 12 | private val mSpacingBuilder: SpacingBuilder, private val mIndent: Boolean = false) : AbstractBlock( 13 | node, Wrap.createWrap(WrapType.NONE, false), alignment) { 14 | private fun makeChild(node: ASTNode, indent: Boolean): GnFormatBlock { 15 | return GnFormatBlock(node, null, mSpacingBuilder, indent) 16 | } 17 | 18 | override fun buildChildren(): List { 19 | val blocks: MutableList = ArrayList() 20 | var child = myNode.firstChildNode 21 | while (child != null) { 22 | if (child.elementType === Types.STATEMENT_LIST && myNode.elementType === Types.BLOCK 23 | || (child.elementType === Types.EXPR_LIST 24 | && myNode.elementType === Types.COLLECTION)) { 25 | blocks.add(makeChild(child, child.firstChildNode != null)) 26 | } else if (child.elementType !== TokenType.WHITE_SPACE) { 27 | blocks.add(makeChild(child, false)) 28 | } 29 | child = child.treeNext 30 | } 31 | return blocks 32 | } 33 | 34 | override fun getIndent(): Indent? { 35 | return if (mIndent) { 36 | Indent.getNormalIndent() 37 | } else { 38 | Indent.getNoneIndent() 39 | } 40 | } 41 | 42 | override fun getChildIndent(): Indent? { 43 | return if (myNode.elementType === Types.BLOCK || myNode.elementType === Types.COLLECTION) { 44 | Indent.getNormalIndent() 45 | } else Indent.getNoneIndent() 46 | } 47 | 48 | override fun getSpacing(child1: Block?, child2: Block): Spacing? { 49 | return mSpacingBuilder.getSpacing(this, child1, child2) 50 | } 51 | 52 | override fun isLeaf(): Boolean { 53 | return myNode.firstChildNode == null 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/GnPsiUtil.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi 5 | 6 | import com.google.idea.gn.GnKeys 7 | import com.google.idea.gn.GnLabel 8 | import com.google.idea.gn.config.gnRoot 9 | import com.google.idea.gn.psi.scope.BlockScope 10 | import com.google.idea.gn.psi.scope.Scope 11 | import com.intellij.openapi.diagnostic.Logger 12 | import com.intellij.openapi.vfs.VfsUtil 13 | import com.intellij.openapi.vfs.VirtualFile 14 | import com.intellij.psi.PsiFile 15 | import com.intellij.psi.PsiManager 16 | import java.io.StringWriter 17 | 18 | object GnPsiUtil { 19 | 20 | private val LOGGER = Logger.getInstance(GnPsiUtil.javaClass) 21 | 22 | fun evaluate(expr: GnExpr, scope: Scope, visitorDelegate: Visitor.VisitorDelegate? = null): GnValue? { 23 | LOGGER.debug("evaluating $expr [${expr.text}]") 24 | val result = when (expr) { 25 | is GnStringExpr -> evaluateStringExpr(expr, scope) 26 | is GnLiteralExpr -> evaluateLiteral(expr, scope) 27 | is GnPrimaryExpr -> evaluatePrimary(expr, scope, visitorDelegate) 28 | is GnUnaryExpr -> evaluateUnaryNot(expr, scope) 29 | is GnBinaryExpr -> expr.evaluate(scope) 30 | else -> null 31 | } 32 | 33 | LOGGER.debug("evaluating $expr = $result [${expr.text}]") 34 | return result 35 | } 36 | 37 | private fun evaluatePrimary(expr: GnPrimaryExpr, 38 | scope: Scope, 39 | visitorDelegate: Visitor.VisitorDelegate?): GnValue? { 40 | expr.id?.let { id -> 41 | val v = scope.getVariable(id.text) ?: return null 42 | return v.value 43 | } 44 | expr.scopeAccess?.let { scopeAccess -> 45 | return evaluateScopeAccess(scopeAccess, scope) 46 | } 47 | expr.arrayAccess?.let { arrayAccess -> 48 | return evaluateArrayAccess(arrayAccess, scope) 49 | } 50 | expr.collection?.let { collection -> 51 | return evaluateCollection(collection, scope) 52 | } 53 | expr.block?.let { block -> 54 | val blockScope = BlockScope(scope) 55 | block.accept(Visitor(blockScope, visitorDelegate ?: Visitor.VisitorDelegate())) 56 | return blockScope.intoValue() 57 | } 58 | expr.call?.let { call -> 59 | return scope.getFunction(call.id.text)?.let { func -> 60 | call.putUserData(GnKeys.CALL_RESOLVED_FUNCTION, func) 61 | func.execute(call, scope) 62 | } 63 | } 64 | return null 65 | } 66 | 67 | private fun evaluateCollection(collection: GnCollection, scope: Scope): GnValue? { 68 | return GnValue(collection.exprList.exprList.map { evaluate(it, scope) ?: return null }) 69 | } 70 | 71 | private fun evaluateUnaryNot(expr: GnUnaryExpr, scope: Scope): GnValue? { 72 | return expr.expr?.let { inner -> evaluate(inner, scope)?.bool?.let { b -> GnValue(!b) } } 73 | } 74 | 75 | private fun evaluateArrayAccess(access: GnArrayAccess, scope: Scope): GnValue? { 76 | val id = access.id.text 77 | val index = evaluate(access.expr, scope)?.int ?: return null 78 | val list = scope.getVariable(id)?.value?.list ?: return null 79 | if (index < list.size) { 80 | return list[index] 81 | } 82 | return null 83 | } 84 | 85 | private fun evaluateScopeAccess(access: GnScopeAccess, scope: Scope): GnValue? { 86 | val left = access.idList[0] 87 | val right = access.idList[1] 88 | return scope.getVariable(left.text)?.value?.scope?.get(right.text) 89 | } 90 | 91 | private fun evaluateStringExpand(expand: GnStringExpand, scope: Scope): String? { 92 | expand.id?.let { ident -> 93 | return scope.getVariable(ident.text)?.value?.string 94 | } 95 | expand.scopeAccess?.let { scopeAccess -> 96 | return evaluateScopeAccess(scopeAccess, scope)?.string 97 | } 98 | return null 99 | } 100 | 101 | private fun evaluateStringInner(inner: GnStringInner, scope: Scope): String? { 102 | inner.stringLiteralExpr?.let { literal -> 103 | return literal.text 104 | } 105 | inner.stringIdent?.let { ident -> 106 | return scope.getVariable(ident.id.text)?.value?.string 107 | } 108 | inner.stringHex?.let { hex -> 109 | return hex.node.findChildByType(Types.HEX_BYTE)?.text?.substring(2)?.toInt(16)?.toChar() 110 | ?.toString() 111 | } 112 | inner.stringExpand?.let { expand -> 113 | return evaluateStringExpand(expand, scope) 114 | } 115 | return null 116 | } 117 | 118 | private fun evaluateStringExpr(expr: GnStringExpr, scope: Scope): GnValue? { 119 | val writer = StringWriter() 120 | for (inner in expr.stringInnerList) { 121 | val s = evaluateStringInner(inner, scope) ?: return null 122 | writer.append(s) 123 | } 124 | return GnValue(writer.toString()) 125 | } 126 | 127 | private fun evaluateLiteral(literal: GnLiteralExpr, scope: Scope): GnValue? { 128 | val def = literal.firstChild 129 | if (def is GnStringExpr) { 130 | return evaluateStringExpr(def, scope) 131 | } 132 | return when (def.node.elementType) { 133 | Types.TRUE -> GnValue(true) 134 | Types.FALSE -> GnValue(false) 135 | Types.INTEGRAL_LITERAL -> try { 136 | GnValue(def.text.toInt()) 137 | } catch (_e: NumberFormatException) { 138 | null 139 | } 140 | else -> null 141 | } 142 | } 143 | 144 | fun evaluateFirst(exprList: GnExprList, scope: Scope): GnValue? { 145 | val exprs = exprList.exprList 146 | return if (exprs.isEmpty()) { 147 | null 148 | } else evaluate(exprs[0], scope) 149 | } 150 | 151 | fun evaluateFirstToString(exprList: GnExprList, scope: Scope): String? { 152 | return resolveTo(evaluateFirst(exprList, scope), String::class.java) 153 | } 154 | 155 | fun resolveTo(eval: GnValue?, tClass: Class): T? { 156 | val o = eval?.value 157 | return if (!tClass.isInstance(eval?.value)) { 158 | null 159 | } else tClass.cast(o) 160 | } 161 | 162 | fun findPsiFile(labelLocation: PsiFile, label: GnLabel): PsiFile? { 163 | var virtualFile: VirtualFile = findVirtualFile(labelLocation, label) ?: return null 164 | if (virtualFile.isDirectory) { 165 | virtualFile = virtualFile.findChild(GnFile.BUILD_FILE) ?: return null 166 | } 167 | return PsiManager.getInstance(labelLocation.project).findFile(virtualFile) 168 | } 169 | 170 | fun findVirtualFile(labelLocation: PsiFile, 171 | label: GnLabel): VirtualFile? { 172 | return if (label.isAbsolute) { 173 | labelLocation.project.gnRoot 174 | } else { 175 | labelLocation.originalFile.virtualFile.parent 176 | }?.let { VfsUtil.findRelativeFile(it, *label.parts) } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/GnValue.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi 5 | 6 | class GnValue { 7 | val value: Any 8 | 9 | constructor(string: String) { 10 | value = string 11 | } 12 | 13 | constructor(bool: Boolean) { 14 | value = bool 15 | } 16 | 17 | constructor(int: Int) { 18 | value = int 19 | } 20 | 21 | constructor(scope: Map) { 22 | value = scope 23 | } 24 | 25 | constructor(list: List) { 26 | value = list 27 | } 28 | 29 | val string: String? 30 | get() = tryCast() 31 | 32 | val int: Int? 33 | get() = tryCast() 34 | 35 | val bool: Boolean? 36 | get() = tryCast() 37 | 38 | private inline fun tryCast(): T? { 39 | if (value is T) { 40 | return value 41 | } 42 | return null 43 | } 44 | 45 | val scope: Map? 46 | get() = tryCast>() 47 | 48 | val list: List? 49 | get() = tryCast>() 50 | 51 | 52 | fun and(other: GnValue): GnValue? = bool?.let { l -> other.bool?.let { r -> GnValue(r && l) } } 53 | fun or(other: GnValue): GnValue? = bool?.let { l -> other.bool?.let { r -> GnValue(r || l) } } 54 | fun greaterThan(other: GnValue): GnValue? = int?.let { l -> 55 | other.int?.let { r -> 56 | GnValue(l > r) 57 | } 58 | } 59 | 60 | fun greaterThanOrEqual(other: GnValue): GnValue? = int?.let { l -> 61 | other.int?.let { r -> 62 | GnValue(l >= r) 63 | } 64 | } 65 | 66 | fun lessThan(other: GnValue): GnValue? = int?.let { l -> other.int?.let { r -> GnValue(l < r) } } 67 | fun lessThanOrEqual(other: GnValue): GnValue? = int?.let { l -> 68 | other.int?.let { r -> 69 | GnValue(l <= r) 70 | } 71 | } 72 | 73 | fun plus(other: GnValue): GnValue? { 74 | string?.let { l -> return other.string?.let { r -> GnValue(l + r) } } 75 | int?.let { l -> return other.int?.let { r -> GnValue(l + r) } } 76 | list?.let { l -> return other.list?.let { r -> GnValue(l.plus(r)) } } 77 | return null 78 | } 79 | 80 | fun plusEqual(other: GnValue): GnValue { 81 | when (value) { 82 | is Int, 83 | is String -> return plus(other) ?: this 84 | } 85 | // List allows appending both a single element or an entire other list. 86 | list?.let { l -> return other.list?.let { r -> GnValue(l.plus(r)) } ?: GnValue(l.plus(other)) } 87 | return this 88 | } 89 | 90 | fun minusEqual(other: GnValue): GnValue { 91 | when (value) { 92 | is Int -> return minus(other) ?: this 93 | } 94 | // List allows removing both a single element or an entire other list. 95 | list?.let { l -> 96 | return other.list?.let { r -> GnValue(l.filter { !r.contains(it) }) } 97 | ?: GnValue(l.filter { it != other }) 98 | } 99 | return this 100 | } 101 | 102 | fun minus(other: GnValue): GnValue? { 103 | int?.let { l -> return other.int?.let { r -> GnValue(l - r) } } 104 | list?.let { l -> return other.list?.let { r -> GnValue(l.filter { !r.contains(it) }) } } 105 | return null 106 | } 107 | 108 | override fun equals(other: Any?): Boolean { 109 | if (other !is GnValue) { 110 | return false 111 | } 112 | return value == other.value 113 | } 114 | 115 | override fun toString(): String = "GnValue($value)" 116 | override fun hashCode(): Int { 117 | return value.hashCode() 118 | } 119 | 120 | val type: Type 121 | get() { 122 | when (value) { 123 | is String -> return Type.STRING 124 | is Boolean -> return Type.BOOL 125 | is Int -> return Type.INT 126 | } 127 | list?.let { return Type.LIST } 128 | // If none of the other types, must be SCOPE. 129 | scope!! 130 | return Type.SCOPE 131 | } 132 | 133 | enum class Type { 134 | BOOL, 135 | STRING, 136 | INT, 137 | SCOPE, 138 | LIST; 139 | 140 | override fun toString(): String = 141 | when (this) { 142 | BOOL -> "Boolean" 143 | STRING -> "String" 144 | INT -> "Integer" 145 | SCOPE -> "Scope" 146 | LIST -> "List" 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/Target.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi 5 | 6 | class Target(val name: String, val call: GnCall?) 7 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/TemplateFunction.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi 5 | 6 | import com.google.idea.gn.GnKeys 7 | import com.google.idea.gn.completion.CompletionIdentifier 8 | import com.google.idea.gn.psi.builtin.BuiltinVariable 9 | import com.google.idea.gn.psi.builtin.ForwardVariablesFrom 10 | import com.google.idea.gn.psi.builtin.Template 11 | import com.google.idea.gn.psi.scope.BlockScope 12 | import com.google.idea.gn.psi.scope.BuiltinScope 13 | import com.google.idea.gn.psi.scope.Scope 14 | import com.google.idea.gn.psi.scope.TemplateScope 15 | import com.intellij.psi.PsiElement 16 | import com.intellij.psi.PsiElementVisitor 17 | import com.intellij.psi.util.PsiTreeUtil 18 | import com.intellij.psi.util.parentsOfType 19 | 20 | class TemplateFunction(override val identifierName: String, val declaration: GnCall, val declarationScope: Scope) : Function { 21 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 22 | 23 | // Prevent recurring forever. 24 | if (PsiTreeUtil.isAncestor(declaration, call, false)) { 25 | return null 26 | } 27 | 28 | 29 | val declarationBlock = declaration.block ?: return null 30 | val executionScope: BlockScope = TemplateScope(declarationScope, targetScope.callSite ?: call) 31 | 32 | executionScope.addVariable( 33 | Variable(Template.TARGET_NAME, 34 | GnPsiUtil.evaluateFirst(call.exprList, targetScope))) 35 | 36 | call.block?.let { callBlock -> 37 | val innerScope: Scope = BlockScope(targetScope) 38 | Visitor(innerScope).visitBlock(callBlock) 39 | innerScope.consolidateVariables()?.let { 40 | executionScope 41 | .addVariable(Variable(BuiltinVariable.INVOKER.identifierName, GnValue(it))) 42 | } 43 | } 44 | 45 | Visitor(BlockScope(executionScope)).visitBlock(declarationBlock) 46 | return null 47 | } 48 | 49 | fun buildDummyInvokeScope(): Scope { 50 | val scope = BlockScope(declarationScope) 51 | scope.addVariable(Variable(Template.TARGET_NAME, GnValue("dummy"))) 52 | // TODO allow any variables to come from invoker. 53 | scope.addVariable(Variable(BuiltinVariable.INVOKER.identifierName)) 54 | return scope 55 | } 56 | 57 | override val variables: Map by lazy { 58 | gatherInvokerVariables() ?: emptyMap() 59 | } 60 | 61 | override val identifierType: CompletionIdentifier.IdentifierType 62 | get() = CompletionIdentifier.IdentifierType.TEMPLATE 63 | 64 | override val isBuiltin: Boolean 65 | get() = false 66 | 67 | private fun gatherInvokerVariables(): Map? { 68 | val block = declaration.block ?: return null 69 | val variables = mutableMapOf() 70 | 71 | block.accept(Visitor(BlockScope(declarationScope), object : Visitor.VisitorDelegate() { 72 | fun visitForwardVariables(call: GnCall) { 73 | val exprList = call.exprList.exprList 74 | if (exprList.size < 2) { 75 | return 76 | } 77 | if (!exprList[0].textMatches(BuiltinVariable.INVOKER.identifierName)) { 78 | return 79 | } 80 | // We're evaluating on built-in scope assuming that variables are usually set as literals. 81 | val forward = GnPsiUtil.evaluate(exprList[1], BuiltinScope) 82 | forward?.list?.let { vars -> 83 | val varMap = vars.mapNotNull { 84 | it.string?.let { s -> Pair(s, TemplateVariable(s)) } 85 | } 86 | variables.putAll(varMap) 87 | return 88 | } 89 | forward?.string?.let { star -> 90 | if (star == ForwardVariablesFrom.STAR) { 91 | val parent = call.parent.parentsOfType(GnCall::class.java).firstOrNull() ?: return 92 | if (parent != declaration) { 93 | // Forward all variables from the resolved function. 94 | parent.getUserData(GnKeys.CALL_RESOLVED_FUNCTION)?.let { f -> 95 | variables.putAll(f.variables) 96 | } 97 | } 98 | } 99 | return 100 | } 101 | 102 | } 103 | 104 | override fun resolveCall(call: GnCall, function: Function?): Visitor.CallAction = 105 | when (function) { 106 | is Template -> Visitor.CallAction.EXECUTE 107 | is ForwardVariablesFrom -> { 108 | visitForwardVariables(call) 109 | Visitor.CallAction.SKIP 110 | } 111 | else -> Visitor.CallAction.VISIT_BLOCK 112 | } 113 | 114 | 115 | override fun shouldExecuteExpr(expr: GnExpr): Boolean { 116 | expr.accept(object : PsiElementVisitor() { 117 | override fun visitElement(element: PsiElement) { 118 | if (element !is GnScopeAccess) { 119 | element.acceptChildren(this) 120 | return 121 | } 122 | if (element.idList.size < 2) { 123 | return 124 | } 125 | val first = element.idList[0] 126 | if (!first.textMatches(BuiltinVariable.INVOKER.identifierName)) { 127 | return 128 | } 129 | val second = element.idList[1].text 130 | variables[second] = TemplateVariable(second) 131 | } 132 | }) 133 | // We allow block expressions to be evaluated, since we can have variable 134 | // forwarding happening in there. 135 | return (expr is GnPrimaryExpr && expr.block != null) 136 | } 137 | 138 | override val observeConditions: Boolean 139 | get() = false 140 | })) 141 | 142 | return variables 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/TemplateVariable.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi 5 | 6 | class TemplateVariable(override val identifierName: String, override val type: GnCompositeType? = null) : FunctionVariable { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/TokenType.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi 5 | 6 | import com.google.idea.gn.GnLanguage 7 | import com.intellij.psi.TokenType 8 | import com.intellij.psi.tree.IElementType 9 | import org.jetbrains.annotations.NonNls 10 | 11 | class TokenType(@NonNls debugName: String) : IElementType(debugName, GnLanguage) { 12 | override fun toString(): String = 13 | when (this) { 14 | Types.COMMA -> "," 15 | Types.CBRACKET -> "]" 16 | Types.OBRACKET -> "[" 17 | Types.OPAREN -> "(" 18 | Types.CPAREN -> ")" 19 | Types.OBRACE -> "{" 20 | Types.CBRACE -> "" 21 | Types.COMMENT -> "Comment" 22 | Types.DOT -> "." 23 | Types.ELSE -> "else" 24 | Types.EQUAL -> "=" 25 | Types.EQUAL_EQUAL -> "==" 26 | Types.FALSE -> "false" 27 | Types.GREATER -> ">" 28 | Types.GREATER_EQUAL -> ">=" 29 | Types.IDENTIFIER -> "identifier" 30 | Types.IF -> "if" 31 | Types.INTEGRAL_LITERAL -> "Integral literal" 32 | Types.LESSER -> "<" 33 | Types.LESSER_EQUAL -> "<=" 34 | Types.MINUS -> "-" 35 | Types.MINUS_EQUAL -> "-=" 36 | Types.NOT_EQUAL -> "!=" 37 | Types.OR -> "||" 38 | Types.PLUS -> "+" 39 | Types.PLUS_EQUAL -> "+=" 40 | Types.STRING_LITERAL -> "String literal" 41 | Types.TRUE -> "true" 42 | Types.UNARY_NOT -> "!" 43 | Types.DOLLAR -> "$" 44 | Types.HEX_BYTE -> "Hex expression" 45 | Types.QUOTE -> "quote" 46 | else -> "TokenType." + super.toString() 47 | } 48 | 49 | companion object { 50 | val WHITE_SPACE: IElementType = TokenType.WHITE_SPACE 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/Variable.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | 8 | class Variable(override val identifierName: String) : CompletionIdentifier { 9 | constructor(name: String, value: GnValue?) : this(name) { 10 | this.value = value 11 | } 12 | 13 | override val identifierType: CompletionIdentifier.IdentifierType 14 | get() = CompletionIdentifier.IdentifierType.VARIABLE 15 | 16 | override val typeString: String? 17 | get() = value?.type?.toString() 18 | var value: GnValue? = null 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/BuiltinVariable.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.psi.FunctionVariable 7 | import com.google.idea.gn.psi.GnCompositeType 8 | 9 | enum class BuiltinVariable(override val identifierName: String, override val type: GnCompositeType) : FunctionVariable { 10 | DEPS("deps", GnCompositeType.LIST_OF_STRING), 11 | PUBLIC_DEPS("public_deps", GnCompositeType.LIST_OF_STRING), 12 | DATA_DEPS("data_deps", GnCompositeType.LIST_OF_STRING), 13 | INVOKER("invoker", GnCompositeType.SCOPE), 14 | SOURCES("sources", GnCompositeType.LIST_OF_STRING), 15 | ALL_DEPENDENT_CONFIGS("all_dependent_configs", GnCompositeType.LIST_OF_STRING), 16 | PUBLIC_CONFIGS("public_configs", GnCompositeType.LIST_OF_STRING), 17 | CFLAGS("cflags", GnCompositeType.LIST_OF_STRING), 18 | CFLAGS_C("cflags_c", GnCompositeType.LIST_OF_STRING), 19 | CFLAGS_CC("cflags_cc", GnCompositeType.LIST_OF_STRING), 20 | CFLAGS_OBJC("cflags_objc", GnCompositeType.LIST_OF_STRING), 21 | CFLAGS_OBJCC("cflags_objcc", GnCompositeType.LIST_OF_STRING), 22 | ASMFLAGS("asmflags", GnCompositeType.LIST_OF_STRING), 23 | DEFINES("defines", GnCompositeType.LIST_OF_STRING), 24 | INCLUDE_DIRS("include_dirs", GnCompositeType.LIST_OF_STRING), 25 | INPUTS("inputs", GnCompositeType.LIST_OF_STRING), 26 | LDFLAGS("ldflags", GnCompositeType.LIST_OF_STRING), 27 | LIB_DIRS("lib_dirs", GnCompositeType.LIST_OF_STRING), 28 | LIBS("libs", GnCompositeType.LIST_OF_STRING), 29 | PRECOMPILED_HEADER("precompiled_header", GnCompositeType.STRING), 30 | PRECOMPILED_SOURCE("precompiled_source", GnCompositeType.STRING), 31 | RUSTFLAGS("rustflags", GnCompositeType.LIST_OF_STRING), 32 | RUSTENV("rustenv", GnCompositeType.LIST_OF_STRING), 33 | CHECK_INCLUDES("check_includes", GnCompositeType.BOOL), 34 | CONFIGS("configs", GnCompositeType.LIST_OF_STRING), 35 | DATA("data", GnCompositeType.LIST_OF_STRING), 36 | FRIEND("friend", GnCompositeType.LIST_OF_STRING), 37 | METADATA("metadata", GnCompositeType.SCOPE), 38 | OUTPUT_NAME("output_name", GnCompositeType.STRING), 39 | OUTPUT_EXTENSION("output_extension", GnCompositeType.STRING), 40 | PUBLIC("public", GnCompositeType.LIST_OF_STRING), 41 | TESTONLY("testonly", GnCompositeType.BOOL), 42 | VISIBILITY("visibility", GnCompositeType.LIST_OF_STRING), 43 | ALIASED_DEPS("aliased_deps", GnCompositeType.SCOPE), 44 | CRATE_ROOT("crate_root", GnCompositeType.STRING), 45 | CRATE_NAME("crate_name", GnCompositeType.STRING), 46 | ARGS("args", GnCompositeType.LIST_OF_STRING), 47 | DEPFILE("depfile", GnCompositeType.STRING), 48 | RESPONSE_FILE_CONTENTS("response_file_contents", GnCompositeType.STRING), 49 | SCRIPT("script", GnCompositeType.STRING), 50 | OUTPUTS("outputs", GnCompositeType.LIST_OF_STRING), 51 | POOL("pool", GnCompositeType.STRING), 52 | BUNDLE_ROOT_DIR("bundle_root_dir", GnCompositeType.STRING), 53 | BUNDLE_CONTENTS_DIR("bundle_contents_dir", GnCompositeType.STRING), 54 | BUNDLE_RESOURCES_DIR("bundle_resources_dir", GnCompositeType.STRING), 55 | BUNDLE_EXECUTABLE_DIR("bundle_executable_dir", GnCompositeType.STRING), 56 | BUNDLE_DEPS_FILTER("bundle_deps_filter", GnCompositeType.LIST_OF_STRING), 57 | PRODUCT_TYPE("product_type", GnCompositeType.STRING), 58 | CODE_SIGNING_ARGS("code_signing_args", GnCompositeType.LIST_OF_STRING), 59 | CODE_SIGNING_SCRIPT("code_signing_script", GnCompositeType.STRING), 60 | CODE_SIGNING_SOURCES("code_signing_sources", GnCompositeType.LIST_OF_STRING), 61 | CODE_SIGNING_OUTPUTS("code_signing_outputs", GnCompositeType.LIST_OF_STRING), 62 | XCODE_EXTRA_ATTRIBUTES("xcode_extra_attributes", GnCompositeType.SCOPE), 63 | XCODE_TEST_APPLICATION_NAME("xcode_test_application_name", GnCompositeType.STRING), 64 | PARTIAL_INFO_PLIST("partial_info_plist", GnCompositeType.STRING), 65 | CONTENTS("contents", GnCompositeType.SCOPE), 66 | DATA_KEYS("data_keys", GnCompositeType.LIST_OF_STRING), 67 | REBASE("rebase", GnCompositeType.BOOL), 68 | WALK_KEYS("walk_keys", GnCompositeType.LIST_OF_STRING), 69 | OUTPUT_CONVERSION("output_conversion", GnCompositeType.STRING), 70 | CRATE_TYPE("crate_type", GnCompositeType.STRING), 71 | COMPLETE_STATIC_LIB("complete_static_lib", GnCompositeType.BOOL), 72 | 73 | ; 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/DeclareArgs.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.* 8 | import com.google.idea.gn.psi.Function 9 | import com.google.idea.gn.psi.scope.BlockScope 10 | import com.google.idea.gn.psi.scope.Scope 11 | 12 | class DeclareArgs : Function { 13 | 14 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 15 | if (call.exprList.exprList.size != 0) { 16 | return null 17 | } 18 | 19 | val scope = BlockScope(targetScope) 20 | call.block?.accept(Visitor(scope)) 21 | 22 | // NOTE we don't support overrides here, just install all variables with their default values 23 | 24 | scope.consolidateVariables()?.let { 25 | for (v in it) { 26 | targetScope.addVariable(Variable(v.key, v.value)) 27 | } 28 | } 29 | 30 | return null 31 | } 32 | 33 | override val isBuiltin: Boolean 34 | get() = true 35 | override val identifierName: String 36 | get() = NAME 37 | override val identifierType: CompletionIdentifier.IdentifierType 38 | get() = CompletionIdentifier.IdentifierType.FUNCTION 39 | 40 | companion object { 41 | const val NAME = "declare_args" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/Defined.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnPsiUtil 10 | import com.google.idea.gn.psi.GnValue 11 | import com.google.idea.gn.psi.scope.Scope 12 | 13 | class Defined : Function { 14 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 15 | val expr = call.exprList.exprList.getOrNull(0) ?: return null 16 | return GnValue(GnPsiUtil.evaluate(expr, targetScope) != null) 17 | } 18 | 19 | override val isBuiltin: Boolean 20 | get() = true 21 | override val identifierName: String 22 | get() = NAME 23 | override val identifierType: CompletionIdentifier.IdentifierType 24 | get() = CompletionIdentifier.IdentifierType.FUNCTION 25 | 26 | companion object { 27 | const val NAME = "defined" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/FilterExclude.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnValue 10 | import com.google.idea.gn.psi.scope.Scope 11 | 12 | class FilterExclude : Function { 13 | override fun execute(call: GnCall, targetScope: Scope): GnValue? = executeFilter(call.exprList, 14 | targetScope, exclude = true) 15 | 16 | override val isBuiltin: Boolean 17 | get() = true 18 | override val identifierName: String 19 | get() = NAME 20 | override val identifierType: CompletionIdentifier.IdentifierType 21 | get() = CompletionIdentifier.IdentifierType.FUNCTION 22 | 23 | companion object { 24 | const val NAME = "filter_exclude" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/FilterInclude.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnValue 10 | import com.google.idea.gn.psi.scope.Scope 11 | 12 | class FilterInclude : Function { 13 | override fun execute(call: GnCall, targetScope: Scope): GnValue? = 14 | executeFilter(call.exprList, targetScope, exclude = false) 15 | 16 | override val isBuiltin: Boolean 17 | get() = true 18 | override val identifierName: String 19 | get() = NAME 20 | override val identifierType: CompletionIdentifier.IdentifierType 21 | get() = CompletionIdentifier.IdentifierType.FUNCTION 22 | 23 | companion object { 24 | const val NAME = "filter_include" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/Foreach.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.* 8 | import com.google.idea.gn.psi.Function 9 | import com.google.idea.gn.psi.scope.Scope 10 | 11 | class Foreach : Function { 12 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 13 | val exprList = call.exprList.exprList 14 | val block = call.block 15 | if (exprList.size != 2 || block == null) { 16 | return null 17 | } 18 | val id = (exprList[0] as? GnPrimaryExpr)?.id?.text ?: return null 19 | val list = GnPsiUtil.evaluate(exprList[1], targetScope)?.list ?: return null 20 | val oldValue = targetScope.getVariable(id) 21 | for (v in list) { 22 | targetScope.addVariable(Variable(id, v)) 23 | // Visit the statementList directly, foreach does not define a new block scope. 24 | block.statementList.accept(Visitor(targetScope)) 25 | } 26 | targetScope.deleteVariable(id) 27 | oldValue?.let { 28 | targetScope.addVariable(it) 29 | } 30 | 31 | return null 32 | } 33 | 34 | override val isBuiltin: Boolean 35 | get() = true 36 | override val identifierName: String 37 | get() = NAME 38 | override val identifierType: CompletionIdentifier.IdentifierType 39 | get() = CompletionIdentifier.IdentifierType.FUNCTION 40 | 41 | companion object { 42 | const val NAME = "foreach" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/ForwardVariablesFrom.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.* 8 | import com.google.idea.gn.psi.Function 9 | import com.google.idea.gn.psi.scope.Scope 10 | 11 | class ForwardVariablesFrom : Function { 12 | 13 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 14 | val exprList = call.exprList.exprList 15 | if (exprList.size < 2) { 16 | return null 17 | } 18 | val src = GnPsiUtil.evaluate(exprList[0], targetScope)?.scope ?: return null 19 | val fwdValue = GnPsiUtil.evaluate(exprList[1], targetScope) ?: return null 20 | val doForward = when { 21 | fwdValue.string == "*" -> null 22 | fwdValue.list != null -> fwdValue.list!!.mapNotNull { it.string }.toSet() 23 | else -> return null 24 | } 25 | 26 | val dontForward = if (exprList.size >= 3) { 27 | GnPsiUtil.evaluate(exprList[2], targetScope)?.list?.mapNotNull { it.string }?.toSet() 28 | ?: return null 29 | } else { 30 | emptySet() 31 | } 32 | 33 | for (v in src) { 34 | if ((doForward == null || doForward.contains(v.key)) && !dontForward.contains(v.key)) { 35 | targetScope.addVariable(Variable(v.key, v.value)) 36 | } 37 | } 38 | 39 | return null 40 | } 41 | 42 | override val isBuiltin: Boolean 43 | get() = true 44 | override val identifierName: String 45 | get() = NAME 46 | override val identifierType: CompletionIdentifier.IdentifierType 47 | get() = CompletionIdentifier.IdentifierType.FUNCTION 48 | 49 | companion object { 50 | const val NAME = "forward_variables_from" 51 | const val STAR = "*" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/GetEnv.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnValue 10 | import com.google.idea.gn.psi.scope.Scope 11 | 12 | class GetEnv : Function { 13 | override fun execute(call: GnCall, targetScope: Scope): GnValue? = GnValue("") 14 | 15 | override val isBuiltin: Boolean 16 | get() = true 17 | override val identifierName: String 18 | get() = NAME 19 | override val identifierType: CompletionIdentifier.IdentifierType 20 | get() = CompletionIdentifier.IdentifierType.FUNCTION 21 | 22 | companion object { 23 | const val NAME = "getenv" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/GetLabelInfo.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.GnLabel 7 | import com.google.idea.gn.completion.CompletionIdentifier 8 | import com.google.idea.gn.psi.Function 9 | import com.google.idea.gn.psi.GnCall 10 | import com.google.idea.gn.psi.GnPsiUtil 11 | import com.google.idea.gn.psi.GnValue 12 | import com.google.idea.gn.psi.scope.Scope 13 | 14 | class GetLabelInfo : Function { 15 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 16 | val exprList = call.exprList.exprList 17 | if (exprList.size != 2) { 18 | return null 19 | } 20 | val label = GnPsiUtil.evaluate(exprList[0], targetScope)?.string?.let { 21 | GnLabel.parse(it)?.toAbsolute(call.containingFile) 22 | } 23 | ?: return null 24 | 25 | val what = GnPsiUtil.evaluate(exprList[1], targetScope)?.string ?: return null 26 | val str = when (what) { 27 | "name" -> { 28 | // The short name of the target. This will match the value of the 29 | // "target_name" variable inside that target's declaration. For the label 30 | // "//foo/bar:baz" this will return "baz". 31 | label.target 32 | } 33 | "dir" -> { 34 | // The directory containing the target's definition, with no slash at the 35 | // end. For the label "//foo/bar:baz" this will return "//foo/bar". 36 | label.dirString 37 | } 38 | "target_gen_dir" -> { 39 | // The generated file directory for the target. This will match the value of 40 | // the "target_gen_dir" variable when inside that target's declaration. 41 | null 42 | } 43 | "root_gen_dir" -> { 44 | // The root of the generated file tree for the target. This will match the 45 | // value of the "root_gen_dir" variable when inside that target's 46 | // declaration. 47 | null 48 | } 49 | "target_out_dir" -> { 50 | // The output directory for the target. This will match the value of the 51 | // "target_out_dir" variable when inside that target's declaration. 52 | null 53 | } 54 | "root_out_dir" -> { 55 | // The root of the output file tree for the target. This will match the 56 | // value of the "root_out_dir" variable when inside that target's 57 | // declaration. 58 | null 59 | } 60 | "label_no_toolchain" -> { 61 | // The fully qualified version of this label, not including the toolchain. 62 | // For the input ":bar" it might return "//foo:bar". 63 | label.toFullyQualified().dropToolChain().toString() 64 | } 65 | "label_with_toolchain" -> { 66 | // The fully qualified version of this label, including the toolchain. For 67 | // the input ":bar" it might return "//foo:bar(//toolchain:x64)". 68 | label.toFullyQualified().toString() 69 | } 70 | "toolchain" -> { 71 | // The label of the toolchain. This will match the value of the 72 | // "current_toolchain" variable when inside that target's declaration. 73 | // TODO this should fallback to the default_toolchain 74 | label.toolchain 75 | } 76 | else -> null 77 | } 78 | 79 | return str?.let { GnValue(it) } 80 | } 81 | 82 | override val isBuiltin: Boolean 83 | get() = true 84 | override val identifierName: String 85 | get() = NAME 86 | override val identifierType: CompletionIdentifier.IdentifierType 87 | get() = CompletionIdentifier.IdentifierType.FUNCTION 88 | 89 | companion object { 90 | const val NAME = "get_label_info" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/GetPathInfo.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnPsiUtil 10 | import com.google.idea.gn.psi.GnValue 11 | import com.google.idea.gn.psi.scope.Scope 12 | import com.intellij.util.PathUtil 13 | 14 | class GetPathInfo : Function { 15 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 16 | val exprList = call.exprList.exprList 17 | if (exprList.size != 2) { 18 | return null 19 | } 20 | val path = GnPsiUtil.evaluate(exprList[0], targetScope)?.string 21 | ?: return null 22 | 23 | val what = GnPsiUtil.evaluate(exprList[1], targetScope)?.string ?: return null 24 | val str: String? = when (what) { 25 | "file" -> { 26 | // The substring after the last slash in the path, including the name and 27 | // extension. If the input ends in a slash, the empty string will be 28 | // returned. 29 | // "foo/bar.txt" => "bar.txt" 30 | // "bar.txt" => "bar.txt" 31 | // "foo/" => "" 32 | // "" => "" 33 | PathUtil.getFileName(path) 34 | } 35 | "name" -> { 36 | // The substring of the file name not including the extension."foo/bar.txt" => "bar" 37 | // "foo/bar" => "bar" 38 | // "foo/" => "" 39 | 40 | // This is nasty. Do something better. 41 | val parts = PathUtil.getFileName(path).split(".") 42 | if (parts.size > 1) { 43 | parts.dropLast(1).joinToString(".") 44 | } else { 45 | parts.joinToString(".") 46 | } 47 | 48 | } 49 | "extension" -> { 50 | // The substring following the last period following the last slash, or the 51 | // empty string if not found . The period is not included . 52 | // "foo/bar.txt" => "txt" 53 | // "foo/bar" => "" 54 | PathUtil.getFileExtension(path) 55 | } 56 | "dir" -> { 57 | // The directory portion of the name, not including the slash. 58 | // "foo/bar.txt" => "foo" 59 | // "//foo/bar" => "//foo" 60 | // "foo" => "." 61 | // The result will never end in a slash, so if the resulting is empty, the 62 | // system ("/") or source ("//") roots, a "." will be appended such that it 63 | // is always legal to append a slash and a filename and get a valid path. 64 | PathUtil.getParentPath(path) 65 | } 66 | "out_dir" -> { 67 | // The output file directory corresponding to the path of the given file, 68 | // not including a trailing slash. 69 | // "//foo/bar/baz.txt" => "//out/Default/obj/foo/bar" 70 | null 71 | } 72 | "gen_dir" -> { 73 | // The generated file directory corresponding to the path of the given file, 74 | // not including a trailing slash. 75 | // "//foo/bar/baz.txt" => "//out/Default/gen/foo/bar" 76 | null 77 | } 78 | "abspath" -> { 79 | // The full absolute path name to the file or directory. It will be resolved 80 | // relative to the current directory, and then the source- absolute version 81 | // will be returned. If the input is system- absolute, the same input will 82 | // be returned. 83 | // "foo/bar.txt" => "//mydir/foo/bar.txt" 84 | // "foo/" => "//mydir/foo/" 85 | // "//foo/bar" => "//foo/bar" (already absolute) 86 | // "/usr/include" => "/usr/include" (already absolute) 87 | // If you want to make the path relative to another directory, or to be 88 | // system-absolute, see rebase_path(). 89 | null 90 | } 91 | else -> { 92 | null 93 | } 94 | } 95 | return str?.let { GnValue(it) } 96 | } 97 | 98 | override val isBuiltin: Boolean 99 | get() = true 100 | override val identifierName: String 101 | get() = NAME 102 | override val identifierType: CompletionIdentifier.IdentifierType 103 | get() = CompletionIdentifier.IdentifierType.FUNCTION 104 | 105 | companion object { 106 | const val NAME = "get_path_info" 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/GetTargetOutputs.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnValue 10 | import com.google.idea.gn.psi.scope.Scope 11 | 12 | class GetTargetOutputs : Function { 13 | override fun execute(call: GnCall, targetScope: Scope): GnValue? = GnValue(listOf()) 14 | 15 | override val isBuiltin: Boolean 16 | get() = true 17 | override val identifierName: String 18 | get() = NAME 19 | override val identifierType: CompletionIdentifier.IdentifierType 20 | get() = CompletionIdentifier.IdentifierType.FUNCTION 21 | 22 | companion object { 23 | const val NAME = "get_target_outputs" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/Import.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.GnLabel 7 | import com.google.idea.gn.GnLanguage 8 | import com.google.idea.gn.completion.CompletionIdentifier 9 | import com.google.idea.gn.psi.* 10 | import com.google.idea.gn.psi.Function 11 | import com.google.idea.gn.psi.scope.Scope 12 | 13 | class Import : Function { 14 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 15 | val name = GnPsiUtil.evaluateFirstToString(call.exprList, targetScope) ?: return null 16 | val label: GnLabel = GnLabel.parse(name) ?: return null 17 | val file = GnPsiUtil.findPsiFile(call.containingFile, label) 18 | if (file == null || GnLanguage != file.language) { 19 | return null 20 | } 21 | file.accept(Visitor(targetScope)) 22 | 23 | return null 24 | } 25 | 26 | override val identifierType: CompletionIdentifier.IdentifierType 27 | get() = CompletionIdentifier.IdentifierType.FUNCTION 28 | 29 | override val isBuiltin: Boolean 30 | get() = true 31 | 32 | override val identifierName: String 33 | get() = NAME 34 | 35 | override val autoSuggestOnInsertion: Boolean 36 | get() = true 37 | 38 | override val postInsertType: CompletionIdentifier.PostInsertType? 39 | get() = CompletionIdentifier.PostInsertType.CALL_WITH_STRING 40 | 41 | companion object { 42 | const val NAME = "import" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/NoOpFunctions.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnValue 10 | import com.google.idea.gn.psi.scope.Scope 11 | 12 | // Builtin functions that will be no-ops when executing. 13 | enum class NoOpFunctions(override val identifierName: String) : Function { 14 | ASSERT("assert"), 15 | EXEC_SCRIPT("exec_script"), 16 | NOT_NEEDED("not_needed"), 17 | POOL("pool"), 18 | PRINT("print"), 19 | SET_DEFAULTS("set_defaults"), 20 | SET_SOURCES_ASSIGNMENT_FILTER("set_sources_assignment_filter"), 21 | TOOL("tool"), 22 | TOOLCHAIN("toolchain"), 23 | WRITE_FILE("write_file"); 24 | 25 | override fun execute(call: GnCall, targetScope: Scope): GnValue? = null 26 | 27 | override val isBuiltin: Boolean 28 | get() = true 29 | override val identifierType: CompletionIdentifier.IdentifierType 30 | get() = CompletionIdentifier.IdentifierType.FUNCTION 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/ProcessFileTemplate.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnPsiUtil 10 | import com.google.idea.gn.psi.GnValue 11 | import com.google.idea.gn.psi.scope.Scope 12 | 13 | class ProcessFileTemplate : Function { 14 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 15 | // TODO do we need a complete implementation here? 16 | val exprList = call.exprList.exprList 17 | if (exprList.size < 2) { 18 | return null 19 | } 20 | // Just return the expected number of outputs for now. 21 | val inputs = GnPsiUtil.evaluate(exprList[0], targetScope)?.list?.mapNotNull { it.string } 22 | ?: return null 23 | val outputs = GnPsiUtil.evaluate(exprList[1], targetScope)?.list?.mapNotNull { it.string } 24 | ?: return null 25 | val result = mutableListOf() 26 | for (i in inputs) { 27 | for (o in outputs) { 28 | result.add(GnValue(o.replace(Regex("\\{.*}"), i))) 29 | } 30 | } 31 | return GnValue(result) 32 | } 33 | 34 | override val isBuiltin: Boolean 35 | get() = true 36 | override val identifierName: String 37 | get() = NAME 38 | override val identifierType: CompletionIdentifier.IdentifierType 39 | get() = CompletionIdentifier.IdentifierType.FUNCTION 40 | 41 | companion object { 42 | const val NAME = "process_file_template" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/ReadFile.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnValue 10 | import com.google.idea.gn.psi.scope.Scope 11 | 12 | class ReadFile : Function { 13 | // TODO implement ReadFile 14 | override fun execute(call: GnCall, targetScope: Scope): GnValue? = GnValue("") 15 | 16 | override val isBuiltin: Boolean 17 | get() = true 18 | override val identifierName: String 19 | get() = NAME 20 | override val identifierType: CompletionIdentifier.IdentifierType 21 | get() = CompletionIdentifier.IdentifierType.FUNCTION 22 | 23 | companion object { 24 | const val NAME = "read_file" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/RebasePath.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnPsiUtil 10 | import com.google.idea.gn.psi.GnValue 11 | import com.google.idea.gn.psi.scope.Scope 12 | 13 | class RebasePath : Function { 14 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 15 | // It should be enough to return the unrebased input 16 | val expr = call.exprList.exprList.firstOrNull() ?: return null 17 | return GnPsiUtil.evaluate(expr, targetScope) 18 | } 19 | 20 | override val isBuiltin: Boolean 21 | get() = true 22 | override val identifierName: String 23 | get() = NAME 24 | override val identifierType: CompletionIdentifier.IdentifierType 25 | get() = CompletionIdentifier.IdentifierType.FUNCTION 26 | 27 | companion object { 28 | const val NAME = "rebase_path" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/SetDefaultToolchain.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnValue 10 | import com.google.idea.gn.psi.scope.Scope 11 | 12 | class SetDefaultToolchain : Function { 13 | // TODO implement 14 | override fun execute(call: GnCall, targetScope: Scope): GnValue? = null 15 | 16 | override val isBuiltin: Boolean 17 | get() = true 18 | override val identifierName: String 19 | get() = NAME 20 | override val identifierType: CompletionIdentifier.IdentifierType 21 | get() = CompletionIdentifier.IdentifierType.FUNCTION 22 | 23 | companion object { 24 | const val NAME = "set_default_toolchain" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/SplitList.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnPsiUtil 10 | import com.google.idea.gn.psi.GnValue 11 | import com.google.idea.gn.psi.scope.Scope 12 | 13 | class SplitList : Function { 14 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 15 | val exprList = call.exprList.exprList 16 | if (exprList.size < 2) { 17 | return null 18 | } 19 | val list = GnPsiUtil.evaluate(exprList[0], targetScope)?.list ?: return null 20 | val n = GnPsiUtil.evaluate(exprList[1], targetScope)?.int ?: return null 21 | if (n <= 0) { 22 | return null 23 | } 24 | var per = list.size / n 25 | var mod = list.size - (per * n) 26 | if (mod != 0) { 27 | per++ 28 | } 29 | var take = 0 30 | val ret = mutableListOf() 31 | while (take + per <= list.size && per != 0) { 32 | ret.add(GnValue(list.subList(take, take + per))) 33 | take += per 34 | if (mod > 0) { 35 | mod-- 36 | if (mod == 0) { 37 | per-- 38 | } 39 | } 40 | } 41 | if (take < list.size) { 42 | ret.add(GnValue(list.subList(take, list.size))) 43 | } 44 | while (ret.size < n) { 45 | ret.add(GnValue(emptyList())) 46 | } 47 | return GnValue(ret) 48 | } 49 | 50 | override val isBuiltin: Boolean 51 | get() = true 52 | override val identifierName: String 53 | get() = NAME 54 | override val identifierType: CompletionIdentifier.IdentifierType 55 | get() = CompletionIdentifier.IdentifierType.FUNCTION 56 | 57 | companion object { 58 | const val NAME = "split_list" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/StringJoin.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnPsiUtil 10 | import com.google.idea.gn.psi.GnValue 11 | import com.google.idea.gn.psi.scope.Scope 12 | 13 | class StringJoin : Function { 14 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 15 | val exprList = call.exprList.exprList 16 | if (exprList.size != 2) { 17 | return null 18 | } 19 | val sep = GnPsiUtil.evaluate(exprList[0], targetScope)?.string ?: return null 20 | val list = GnPsiUtil.evaluate(exprList[1], targetScope)?.list?.mapNotNull { it.string } 21 | ?: return null 22 | return GnValue(list.joinToString(sep)) 23 | } 24 | 25 | override val isBuiltin: Boolean 26 | get() = true 27 | override val identifierName: String 28 | get() = NAME 29 | override val identifierType: CompletionIdentifier.IdentifierType 30 | get() = CompletionIdentifier.IdentifierType.FUNCTION 31 | 32 | companion object { 33 | const val NAME = "string_join" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/StringReplace.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnPsiUtil 10 | import com.google.idea.gn.psi.GnValue 11 | import com.google.idea.gn.psi.scope.Scope 12 | 13 | class StringReplace : Function { 14 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 15 | val exprList = call.exprList.exprList 16 | if (exprList.size < 3) { 17 | return null 18 | } 19 | val str = GnPsiUtil.evaluate(exprList[0], targetScope)?.string ?: return null 20 | val old = GnPsiUtil.evaluate(exprList[1], targetScope)?.string ?: return null 21 | val new = GnPsiUtil.evaluate(exprList[2], targetScope)?.string ?: return null 22 | return GnValue(str.replace(old, new)) 23 | } 24 | 25 | override val isBuiltin: Boolean 26 | get() = true 27 | override val identifierName: String 28 | get() = NAME 29 | override val identifierType: CompletionIdentifier.IdentifierType 30 | get() = CompletionIdentifier.IdentifierType.FUNCTION 31 | 32 | companion object { 33 | const val NAME = "string_replace" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/StringSplit.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.Function 8 | import com.google.idea.gn.psi.GnCall 9 | import com.google.idea.gn.psi.GnPsiUtil 10 | import com.google.idea.gn.psi.GnValue 11 | import com.google.idea.gn.psi.scope.Scope 12 | 13 | class StringSplit : Function { 14 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 15 | val exprList = call.exprList.exprList 16 | if (exprList.isEmpty()) { 17 | return null 18 | } 19 | val str = GnPsiUtil.evaluate(exprList[0], targetScope)?.string ?: return null 20 | val sep = exprList.getOrNull(1)?.let { GnPsiUtil.evaluate(it, targetScope)?.string } ?: " " 21 | return GnValue(str.split(sep).filter { it.isNotEmpty() }.map { GnValue(it) }) 22 | } 23 | 24 | override val isBuiltin: Boolean 25 | get() = true 26 | override val identifierName: String 27 | get() = NAME 28 | override val identifierType: CompletionIdentifier.IdentifierType 29 | get() = CompletionIdentifier.IdentifierType.FUNCTION 30 | 31 | companion object { 32 | const val NAME = "string_split" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/Target.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.* 8 | import com.google.idea.gn.psi.Function 9 | import com.google.idea.gn.psi.scope.Scope 10 | import com.intellij.extapi.psi.ASTWrapperPsiElement 11 | 12 | 13 | class SkipFirstArgumentCall(val call: GnCall) : ASTWrapperPsiElement( 14 | call.node), GnCall { 15 | override fun getId(): GnId = call.id 16 | 17 | override fun getExprList(): GnExprList = object : GnExprList, ASTWrapperPsiElement( 18 | call.exprList.node) { 19 | override fun getExprList(): List = call.exprList.exprList.drop(1) 20 | } 21 | 22 | override fun getBlock(): GnBlock? = call.block 23 | 24 | } 25 | 26 | class Target : Function { 27 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 28 | val args = call.exprList.exprList 29 | if (args.size != 2) { 30 | return null 31 | } 32 | val functionName = GnPsiUtil.evaluate(args[0], targetScope)?.string 33 | ?: return null 34 | val function = targetScope.getFunction(functionName) ?: return null 35 | return function.execute(SkipFirstArgumentCall(call), targetScope) 36 | } 37 | 38 | override val identifierType: CompletionIdentifier.IdentifierType 39 | get() = CompletionIdentifier.IdentifierType.FUNCTION 40 | 41 | override val isBuiltin: Boolean 42 | get() = true 43 | 44 | override val identifierName: String 45 | get() = NAME 46 | 47 | companion object { 48 | const val NAME = "target" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/Template.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.GnKeys 7 | import com.google.idea.gn.completion.CompletionIdentifier 8 | import com.google.idea.gn.psi.* 9 | import com.google.idea.gn.psi.Function 10 | import com.google.idea.gn.psi.scope.Scope 11 | 12 | class Template : Function { 13 | override fun execute(call: GnCall, targetScope: Scope): GnValue? { 14 | val name = GnPsiUtil.evaluateFirstToString(call.exprList, targetScope) ?: return null 15 | val func = TemplateFunction(name, call, targetScope) 16 | targetScope.installFunction(func) 17 | call.putUserData(GnKeys.TEMPLATE_INSTALLED_FUNCTION, func) 18 | return null 19 | } 20 | 21 | override val identifierType: CompletionIdentifier.IdentifierType 22 | get() = CompletionIdentifier.IdentifierType.FUNCTION 23 | 24 | override val isBuiltin: Boolean 25 | get() = true 26 | 27 | override val identifierName: String 28 | get() = NAME 29 | 30 | override val postInsertType: CompletionIdentifier.PostInsertType? 31 | get() = CompletionIdentifier.PostInsertType.CALL_WITH_STRING 32 | 33 | companion object { 34 | const val NAME = "template" 35 | const val TARGET_NAME = "target_name" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/builtin/util.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.builtin 5 | 6 | import com.google.idea.gn.GnFilePattern 7 | import com.google.idea.gn.psi.GnExprList 8 | import com.google.idea.gn.psi.GnPsiUtil 9 | import com.google.idea.gn.psi.GnValue 10 | import com.google.idea.gn.psi.scope.Scope 11 | 12 | 13 | fun executeFilter(_exprList: GnExprList, scope: Scope, exclude: Boolean): GnValue? { 14 | val exprList = _exprList.exprList 15 | if (exprList.size != 2) { 16 | return null 17 | } 18 | val list = GnPsiUtil.evaluate(exprList[0], scope)?.list ?: return null 19 | val patterns = GnPsiUtil.evaluate(exprList[1], scope)?.list?.mapNotNull { v -> 20 | v.string?.let { 21 | GnFilePattern(it) 22 | } 23 | } 24 | ?: return null 25 | 26 | return GnValue(list.filter { 27 | it.string?.let { s -> 28 | exclude.xor(patterns.any { patt -> 29 | patt.matches(s) 30 | }) 31 | } ?: false 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/impl/GnBinaryExprImpl.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn.psi.impl 6 | 7 | import com.google.idea.gn.psi.* 8 | import com.google.idea.gn.psi.scope.Scope 9 | import com.intellij.extapi.psi.ASTWrapperPsiElement 10 | import com.intellij.lang.ASTNode 11 | 12 | 13 | abstract class GnBinaryExprImpl(node: ASTNode) : ASTWrapperPsiElement(node), GnBinaryExpr { 14 | 15 | override fun evaluate(scope: Scope): GnValue? { 16 | val list = exprList 17 | val left = GnPsiUtil.evaluate(list[0], scope) ?: return null 18 | val right = GnPsiUtil.evaluate(list[1], scope) ?: return null 19 | return when (this) { 20 | is GnGtExpr -> left.greaterThan(right) 21 | is GnGeExpr -> left.greaterThanOrEqual(right) 22 | is GnLtExpr -> left.lessThan(right) 23 | is GnLeExpr -> left.lessThanOrEqual(right) 24 | is GnAndExpr -> left.and(right) 25 | is GnOrExpr -> left.or(right) 26 | is GnEqualExpr -> GnValue(left == right) 27 | is GnNotEqualExpr -> GnValue(left != right) 28 | is GnPlusExpr -> left.plus(right) 29 | is GnMinusExpr -> left.minus(right) 30 | else -> null 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/impl/GnIdentifierImpl.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn.psi.impl 6 | 7 | import com.google.idea.gn.psi.GnCall 8 | import com.google.idea.gn.psi.reference.GnCallIdentifierReference 9 | import com.intellij.extapi.psi.ASTWrapperPsiElement 10 | import com.intellij.lang.ASTNode 11 | import com.intellij.psi.PsiElement 12 | import com.intellij.psi.PsiReference 13 | 14 | abstract class GnIdentifierImpl(node: ASTNode) : ASTWrapperPsiElement(node), PsiElement { 15 | override fun getReference(): PsiReference? { 16 | return when (val parent = parent) { 17 | is GnCall -> GnCallIdentifierReference(parent) 18 | else -> null 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/impl/GnLiteralReferenceImpl.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.impl 5 | 6 | import com.google.idea.gn.GnLabel 7 | import com.google.idea.gn.psi.GnFile 8 | import com.google.idea.gn.psi.GnPsiUtil 9 | import com.google.idea.gn.psi.GnStringExpr 10 | import com.google.idea.gn.psi.reference.GnLabelReference 11 | import com.intellij.extapi.psi.ASTWrapperPsiElement 12 | import com.intellij.lang.ASTNode 13 | import com.intellij.psi.PsiReference 14 | 15 | abstract class GnLiteralReferenceImpl(node: ASTNode) : ASTWrapperPsiElement(node), GnStringExpr { 16 | override fun getReference(): PsiReference? { 17 | val scope = when (val file = containingFile) { 18 | is GnFile -> file.scope 19 | else -> return null 20 | } 21 | val value = GnPsiUtil.evaluate(this, scope)?.string ?: return null 22 | val label: GnLabel = GnLabel.parse(value) 23 | ?: return null 24 | return GnLabelReference(this, label) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/reference/GnCallIdentifierReference.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn.psi.reference 6 | 7 | import com.google.idea.gn.GnKeys 8 | import com.google.idea.gn.psi.Function 9 | import com.google.idea.gn.psi.GnCall 10 | import com.google.idea.gn.psi.TemplateFunction 11 | import com.google.idea.gn.psi.Visitor 12 | import com.google.idea.gn.psi.builtin.Import 13 | import com.google.idea.gn.psi.builtin.Template 14 | import com.google.idea.gn.psi.scope.FileScope 15 | import com.google.idea.gn.psi.scope.Scope 16 | import com.intellij.openapi.util.TextRange 17 | import com.intellij.psi.PsiElement 18 | import com.intellij.psi.PsiReferenceBase 19 | import com.intellij.psi.util.parents 20 | 21 | class GnCallIdentifierReference(val call: GnCall) : PsiReferenceBase( 22 | call.id, TextRange(0, call.id.textRange.length)) { 23 | 24 | private fun getResolvedFunction(): Function? { 25 | call.containingFile.accept(Visitor(FileScope(), object : Visitor.VisitorDelegate() { 26 | override fun afterVisit(element: PsiElement, scope: Scope): Boolean = element == call 27 | 28 | override fun resolveCall(call: GnCall, function: Function?): Visitor.CallAction = 29 | when { 30 | // Template and import calls must be executed so they show up in the scope. 31 | function is Template || 32 | function is Import -> Visitor.CallAction.EXECUTE 33 | this@GnCallIdentifierReference.call.parents(true) 34 | .contains(call) -> Visitor.CallAction.VISIT_BLOCK 35 | else -> Visitor.CallAction.SKIP 36 | } 37 | })) 38 | return call.getUserData(GnKeys.CALL_RESOLVED_FUNCTION) 39 | } 40 | 41 | fun isTemplate(): Boolean = getResolvedFunction() is TemplateFunction 42 | 43 | override fun resolve(): PsiElement? = getResolvedFunction()?.let { 44 | if (it !is TemplateFunction) { 45 | return@let null 46 | } 47 | it.declaration 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/reference/GnLabelReference.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.reference 5 | 6 | import com.google.idea.gn.GnLabel 7 | import com.google.idea.gn.psi.GnFile 8 | import com.google.idea.gn.psi.GnPsiUtil 9 | import com.intellij.openapi.util.TextRange 10 | import com.intellij.psi.PsiElement 11 | import com.intellij.psi.PsiReferenceBase 12 | 13 | class GnLabelReference(element: PsiElement, private val label: GnLabel) : PsiReferenceBase( 14 | element, TextRange(1, element.textRange.length - 1)) { 15 | override fun resolve(): PsiElement? { 16 | val file = GnPsiUtil.findPsiFile( 17 | element.containingFile, label) 18 | val labelTarget = label.target 19 | if (file !is GnFile || labelTarget == null) { 20 | return file 21 | } 22 | if (file.name != GnFile.BUILD_FILE) { 23 | return file 24 | } 25 | val scope = file.scope 26 | val t = label.target?.let { scope.getTarget(it) } 27 | return if (t?.call == null) 28 | file 29 | else t.call 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/scope/BlockScope.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.scope 5 | 6 | import com.google.idea.gn.psi.Function 7 | import com.google.idea.gn.psi.GnValue 8 | 9 | open class BlockScope(parent: Scope?) : Scope(parent) { 10 | override fun getFunction(name: String): Function? { 11 | return installedFunctions[name] ?: super.getFunction(name) 12 | } 13 | 14 | override fun installFunction(function: Function) { 15 | installedFunctions[function.identifierName] = function 16 | } 17 | 18 | fun intoValue(): GnValue? { 19 | return consolidateVariables()?.let { GnValue(it) } ?: GnValue(emptyMap()) 20 | } 21 | 22 | override val functions: Sequence 23 | get() = installedFunctions.asSequence().map { it.value } 24 | 25 | private val installedFunctions: MutableMap = HashMap() 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/scope/BuiltinScope.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.scope 5 | 6 | import com.google.idea.gn.psi.Builtin 7 | import com.google.idea.gn.psi.Function 8 | 9 | object BuiltinScope : Scope(null) { 10 | override fun getFunction(name: String): Function? { 11 | return Builtin.FUNCTIONS[name] 12 | } 13 | 14 | override fun installFunction(function: Function) = Unit // Can't install functions on BuiltinScope 15 | 16 | override val functions: Sequence 17 | get() = Builtin.FUNCTIONS.asSequence().map { it.value } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/scope/FileScope.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.scope 5 | 6 | import com.google.idea.gn.psi.Target 7 | import java.util.* 8 | 9 | class FileScope : BlockScope(BuiltinScope) { 10 | override val targets: Map? 11 | get() = _targets 12 | 13 | override fun addTarget(target: Target) { 14 | if (_targets == null) { 15 | _targets = HashMap() 16 | } 17 | _targets!![target.name] = target 18 | } 19 | 20 | private var _targets: MutableMap? = null 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/scope/Scope.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.scope 5 | 6 | import com.google.idea.gn.completion.CompletionIdentifier 7 | import com.google.idea.gn.psi.* 8 | import com.google.idea.gn.psi.Function 9 | import com.google.idea.gn.psi.Target 10 | import java.util.* 11 | 12 | abstract class Scope protected constructor(val parent: Scope?) { 13 | open fun getFunction(name: String): Function? { 14 | return parent?.getFunction(name) 15 | } 16 | 17 | abstract fun installFunction(function: Function) 18 | 19 | abstract val functions: Sequence 20 | 21 | open val targets: Map? 22 | get() = parent?.targets 23 | 24 | open fun getTarget(name: String): Target? { 25 | val targets = targets ?: return null 26 | return targets[name] 27 | } 28 | 29 | open fun addTarget(target: Target) { 30 | parent?.addTarget(target) 31 | } 32 | 33 | open fun addVariable(v: Variable) { 34 | if (_variables == null) { 35 | _variables = HashMap() 36 | } 37 | _variables!![v.identifierName] = v 38 | } 39 | 40 | open fun deleteVariable(id: String) { 41 | _variables?.remove(id) 42 | } 43 | 44 | open fun getVariable(name: String): Variable? { 45 | return _variables?.get(name) ?: parent?.getVariable(name) 46 | } 47 | 48 | val variables: Map? 49 | get() = _variables 50 | 51 | open val callSite: GnCall? 52 | get() = parent?.callSite 53 | 54 | fun consolidateVariables(): Map? { 55 | val map = variables?.filterValues { it.value != null }?.mapValues { it.value.value!! } 56 | if (map.isNullOrEmpty()) { 57 | return null 58 | } 59 | return map 60 | } 61 | 62 | fun gatherCompletionIdentifiers(operator: (CompletionIdentifier) -> Unit) { 63 | parent?.gatherCompletionIdentifiers(operator) 64 | functions.forEach { 65 | operator(it) 66 | } 67 | variables?.forEach { 68 | operator(it.value) 69 | } 70 | } 71 | 72 | override fun toString(): String { 73 | return "$parent{Scope(variables: ${variables?.values?.joinToString { it.identifierName }}, functions:${functions.joinToString { it.identifierName }})}" 74 | } 75 | 76 | private var _variables: MutableMap? = null 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/psi/scope/TemplateScope.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.psi.scope 5 | 6 | import com.google.idea.gn.psi.GnCall 7 | 8 | class TemplateScope(parent: Scope?, var call: GnCall) : BlockScope(parent) { 9 | override val callSite: GnCall? 10 | get() = call 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/google/idea/gn/util/PathUtils.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn.util 6 | 7 | import com.google.idea.gn.config.gnRoot 8 | import com.intellij.openapi.project.Project 9 | import com.intellij.openapi.vfs.VfsUtilCore 10 | import com.intellij.openapi.vfs.VirtualFile 11 | import com.intellij.psi.PsiFile 12 | import java.io.StringWriter 13 | 14 | 15 | fun getPathLabel(file: VirtualFile, base: VirtualFile, absolute: Boolean = true): String? = 16 | VfsUtilCore.getRelativePath(file, base)?.let { relative -> 17 | val writer = StringWriter() 18 | if (absolute) { 19 | writer.write("//") 20 | } 21 | writer.write(relative) 22 | writer.toString() 23 | } 24 | 25 | 26 | fun getPathLabel(file: VirtualFile, project: Project): String? = project.gnRoot 27 | ?.let { getPathLabel(file, it) } 28 | 29 | fun getPathLabel(file: PsiFile): String? = file.virtualFile?.parent 30 | ?.let { getPathLabel(it, file.project) } 31 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | com.google.idea.gn 7 | GN 8 | The Fuchsia Authors 10 | 11 | Languages 12 | 13 | GN Plugin for IntelliJ.
15 |

Features

16 |
    17 |
  • Basic syntax highlighting
  • 18 |
  • Very basic target parsing
  • 19 |
  • Click-through navigation for resolved label references
  • 20 |
  • Very basic formatting
  • 21 |
22 |

Note that this is a GN language plugin, it helps you author and edit .gn 23 | files; it does not support importing a project model from a GN-based 24 | project.

25 | ]]>
26 | 27 | com.intellij.modules.lang 28 | 29 | 30 | 31 | 33 | 34 | 35 | 37 | 39 | 40 | 42 | 43 | 44 | 46 | 47 | 49 | 51 | 53 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 67 | 68 | 72 | 73 | 74 | 75 | 76 | 77 | 80 | 82 | 83 | 84 | 85 |
86 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 30 | 32 | 57 | 59 | 64 | 65 | 68 | 74 | 79 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/main/resources/fileTemplates/internal/Gn File.gn.ft: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/intellij-gn-plugin/829d0cdee71dcc42a72e5d3c6f724526676bdf9b/src/main/resources/fileTemplates/internal/Gn File.gn.ft -------------------------------------------------------------------------------- /src/main/resources/fileTemplates/internal/Gn File.gn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Empty Gn file. 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/icons/gn.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 30 | 32 | 57 | 59 | 64 | 65 | 71 | 76 | 81 | 82 | -------------------------------------------------------------------------------- /src/test/java/com/google/idea/gn/AnnotatorTest.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn 6 | 7 | import com.google.idea.gn.psi.GnFile 8 | import com.google.idea.gn.util.GnCodeInsightTestCase 9 | import com.intellij.codeInsight.daemon.impl.HighlightInfo 10 | import com.intellij.openapi.editor.colors.TextAttributesKey 11 | import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl 12 | 13 | class AnnotatorTest : GnCodeInsightTestCase() { 14 | 15 | class HighlightChecker(val text: String, val highlightKey: TextAttributesKey) { 16 | 17 | constructor(text: String, color: GnColors) : this(text, color.textAttributesKey) 18 | 19 | constructor(info: HighlightInfo) : this(info.text, info.forcedTextAttributesKey) 20 | 21 | override fun equals(other: Any?): Boolean { 22 | if (this === other) return true 23 | if (other !is HighlightChecker) return false 24 | 25 | if (text != other.text) return false 26 | if (highlightKey != other.highlightKey) return false 27 | 28 | return true 29 | } 30 | 31 | override fun toString(): String { 32 | return "HighlightChecker(text='$text', highlightKey='$highlightKey')" 33 | } 34 | 35 | override fun hashCode(): Int { 36 | var result = text.hashCode() 37 | result = 31 * result + highlightKey.hashCode() 38 | return result 39 | } 40 | } 41 | 42 | fun testColorAnnotations() { 43 | configureFromFileText(GnFile.BUILD_FILE, """ 44 | import("//build.gni") 45 | 46 | template("my_template") { 47 | a = { b = true } 48 | a.b = false 49 | group(target_name) { 50 | forward_variables_from(invoker, "*") 51 | } 52 | } 53 | 54 | my_template("foo") { 55 | } 56 | """.trimIndent()) 57 | val highlight = CodeInsightTestFixtureImpl.instantiateAndRun(file, editor, IntArray(0), false) 58 | assertEquals(listOf( 59 | HighlightChecker("import", GnColors.BUILTIN_FUNCTION), 60 | HighlightChecker("template", GnColors.BUILTIN_FUNCTION), 61 | HighlightChecker("a", GnColors.VARIABLE), 62 | HighlightChecker("b", GnColors.VARIABLE), 63 | HighlightChecker("a", GnColors.VARIABLE), 64 | HighlightChecker("b", GnColors.VARIABLE), 65 | HighlightChecker("group", GnColors.TARGET_FUNCTION), 66 | HighlightChecker("target_name", GnColors.VARIABLE), 67 | HighlightChecker("forward_variables_from", GnColors.BUILTIN_FUNCTION), 68 | HighlightChecker("invoker", GnColors.VARIABLE), 69 | HighlightChecker("my_template", GnColors.TEMPLATE) 70 | ), highlight.map { HighlightChecker(it) }) 71 | } 72 | 73 | fun testAnnotateInsideTemplates() { 74 | configureFromFileText(GnFile.BUILD_FILE, """ 75 | template("foo") { 76 | } 77 | 78 | template("bar") { 79 | foo("baz") { 80 | 81 | } 82 | } 83 | """.trimIndent()) 84 | val highlight = CodeInsightTestFixtureImpl.instantiateAndRun(file, editor, IntArray(0), false) 85 | .find { it.text == "foo" } ?: error("Failed to get highlight target") 86 | assertEquals(GnColors.TEMPLATE.textAttributesKey, HighlightChecker(highlight).highlightKey) 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/google/idea/gn/AutoCompleteTest.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn 6 | 7 | import com.google.idea.gn.completion.CompletionIdentifier 8 | import com.google.idea.gn.completion.FileCompletionProvider 9 | import com.google.idea.gn.psi.Builtin 10 | import com.google.idea.gn.psi.GnFile 11 | import com.google.idea.gn.psi.builtin.BuiltinTargetFunction 12 | import com.google.idea.gn.util.GnCodeInsightTestCase 13 | import com.intellij.codeInsight.completion.* 14 | import com.intellij.codeInsight.lookup.Lookup 15 | import com.intellij.codeInsight.lookup.LookupElement 16 | import com.intellij.codeInsight.lookup.LookupManager 17 | import com.intellij.openapi.application.runWriteAction 18 | import com.intellij.openapi.util.Key 19 | 20 | class AutoCompleteTest : GnCodeInsightTestCase() { 21 | 22 | private fun performCompletion(): List { 23 | CodeCompletionHandlerBase(CompletionType.BASIC).invokeCompletion(project, editor) 24 | return LookupManager.getActiveLookup(editor)?.items ?: emptyList() 25 | } 26 | 27 | private fun performLabelCompletion() = performCompletionAndGetItemsWithKey( 28 | GnKeys.LABEL_COMPLETION_TYPE) 29 | 30 | private fun performIdentifierCompletion() = performCompletionAndGetItemsWithKey( 31 | GnKeys.IDENTIFIER_COMPLETION_TYPE) 32 | 33 | private fun performCompletionAndGetItemsWithKey(key: Key): List> = 34 | performCompletion().map { it.lookupString to it.getUserData(key) } 35 | 36 | fun testLabelCompletion() { 37 | copyTestFilesByPath { 38 | when (it) { 39 | "BUILD.gn", 40 | "src/BUILD.gn", 41 | "src/lib/BUILD.gn", 42 | "src/test/BUILD.gn" -> true 43 | else -> false 44 | } 45 | } 46 | configureFromFileText(GnFile.BUILD_FILE, """group("g"){deps=[""]}""") 47 | 48 | assertEquals(listOf( 49 | Pair(":g", FileCompletionProvider.CompleteType.TARGET), 50 | Pair("src", FileCompletionProvider.CompleteType.DIRECTORY), 51 | Pair("src/lib", FileCompletionProvider.CompleteType.DIRECTORY), 52 | Pair("src/lib:my_lib", FileCompletionProvider.CompleteType.TARGET), 53 | Pair("src/test", FileCompletionProvider.CompleteType.TARGET), 54 | Pair("src:my_src", FileCompletionProvider.CompleteType.TARGET)), 55 | performLabelCompletion()) 56 | } 57 | 58 | fun testAbsoluteLabelCompletion() { 59 | copyTestFilesByPath { 60 | when (it) { 61 | "BUILD.gn", 62 | "src/BUILD.gn", 63 | "src/lib/BUILD.gn", 64 | "src/test/BUILD.gn" -> true 65 | else -> false 66 | } 67 | } 68 | configureFromFileText(GnFile.BUILD_FILE, """group("g"){deps=["//"]}""") 69 | 70 | assertEquals(listOf( 71 | Pair("//:g", FileCompletionProvider.CompleteType.TARGET), 72 | Pair("//src", FileCompletionProvider.CompleteType.DIRECTORY), 73 | Pair("//src/lib", FileCompletionProvider.CompleteType.DIRECTORY), 74 | Pair("//src/lib:my_lib", FileCompletionProvider.CompleteType.TARGET), 75 | Pair("//src/test", FileCompletionProvider.CompleteType.TARGET), 76 | Pair("//src:my_src", FileCompletionProvider.CompleteType.TARGET)), 77 | performLabelCompletion()) 78 | } 79 | 80 | fun testImportCompletion() { 81 | copyTestFilesByPath { 82 | when (it) { 83 | "build/rules.gni" -> true 84 | else -> false 85 | } 86 | } 87 | configureFromFileText(GnFile.BUILD_FILE, """import("")""") 88 | 89 | assertEquals(listOf( 90 | Pair("build", FileCompletionProvider.CompleteType.DIRECTORY), 91 | Pair("build/rules.gni", FileCompletionProvider.CompleteType.FILE)), 92 | performLabelCompletion()) 93 | } 94 | 95 | fun testAbsoluteImportCompletion() { 96 | copyTestFilesByPath { 97 | when (it) { 98 | "build/rules.gni" -> true 99 | else -> false 100 | } 101 | } 102 | configureFromFileText(GnFile.BUILD_FILE, """import("//")""") 103 | 104 | assertEquals(listOf( 105 | Pair("//build", FileCompletionProvider.CompleteType.DIRECTORY), 106 | Pair("//build/rules.gni", FileCompletionProvider.CompleteType.FILE)), 107 | performLabelCompletion()) 108 | } 109 | 110 | fun testIdentifierCompletionWithinTargetCall() { 111 | configureFromFileText(GnFile.BUILD_FILE, """ 112 | global = "x" 113 | group("g") { 114 | local = "d" 115 | 116 | } 117 | """.trimIndent()) 118 | 119 | val expect = setOf( 120 | "global" to CompletionIdentifier.IdentifierType.VARIABLE, 121 | "local" to CompletionIdentifier.IdentifierType.VARIABLE) 122 | .plus(Builtin.FUNCTIONS.values.filter { it !is BuiltinTargetFunction } 123 | .map { it.identifierName to it.identifierType }) 124 | .plus( 125 | BuiltinTargetFunction.GROUP.variables.values.map { it.identifierName to it.identifierType }) 126 | 127 | assertEquals(expect, 128 | performIdentifierCompletion().toSet()) 129 | } 130 | 131 | fun testIdentifierCompletionFromFileRoot() { 132 | configureFromFileText(GnFile.BUILD_FILE, """ 133 | global = "x" 134 | 135 | template("my_template") { 136 | } 137 | 138 | 139 | """.trimIndent()) 140 | val expect = setOf( 141 | "global" to CompletionIdentifier.IdentifierType.VARIABLE, 142 | "my_template" to CompletionIdentifier.IdentifierType.TEMPLATE) 143 | .plus(Builtin.FUNCTIONS.values.map { it.identifierName to it.identifierType }) 144 | assertEquals(expect, performIdentifierCompletion().toSet()) 145 | } 146 | 147 | fun testIdentifierExtraInsertion() { 148 | configureFromFileText(GnFile.BUILD_FILE, "templ") 149 | val item = performCompletion()[0] 150 | runWriteAction { 151 | editor.document.replaceString(0, editor.caretModel.primaryCaret.offset, "") 152 | CompletionUtil.emulateInsertion(item, editor.caretModel.primaryCaret.offset, 153 | InsertionContext(OffsetMap(editor.document), Lookup.AUTO_INSERT_SELECT_CHAR, 154 | arrayOf(item), 155 | file, editor, false)) 156 | } 157 | assertEquals("""template("")""", editor.document.text) 158 | assertEquals("template(\"".length, editor.caretModel.primaryCaret.offset) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/test/java/com/google/idea/gn/BaseTest.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn 6 | 7 | import com.google.idea.gn.psi.GnFile 8 | import com.google.idea.gn.util.GnCodeInsightTestCase 9 | import com.intellij.openapi.vfs.VfsUtil 10 | import com.intellij.openapi.vfs.VirtualFile 11 | import com.intellij.openapi.vfs.VirtualFileVisitor 12 | 13 | // Verifies code in base test class 14 | class BaseTest : GnCodeInsightTestCase() { 15 | fun testPathIsClear() { 16 | copyTestFilesByPath { 17 | when (it) { 18 | "build/rules.gni", 19 | "src/BUILD.gn" -> true 20 | else -> false 21 | } 22 | } 23 | configureFromFileText(GnFile.BUILD_FILE, """# Nothing""") 24 | 25 | val collected = mutableSetOf() 26 | val dir = file.containingDirectory.virtualFile 27 | VfsUtil.visitChildrenRecursively(dir, object : VirtualFileVisitor() { 28 | override fun visitFile(file: VirtualFile): Boolean { 29 | if (!file.isDirectory) { 30 | collected.add(VfsUtil.getRelativePath(file, dir)!!) 31 | } 32 | return true 33 | } 34 | }) 35 | assertEquals(setOf(GnFile.BUILD_FILE, "build/rules.gni", "src/BUILD.gn"), collected) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/google/idea/gn/FilePatternTest.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import org.junit.Test 7 | import kotlin.test.assertEquals 8 | import kotlin.test.assertFalse 9 | import kotlin.test.assertTrue 10 | 11 | class FilePatternTest { 12 | 13 | @Test 14 | fun testAroundWildcards() { 15 | val pattern = GnFilePattern("*asdf*") 16 | assertEquals("^.*\\Qasdf\\E.*$", pattern.regex.pattern) 17 | assertTrue(pattern.matches("asdf")) 18 | assertTrue(pattern.matches("aaaaaaaasdfbbbbb")) 19 | assertTrue(pattern.matches("asdfbbbb")) 20 | assertTrue(pattern.matches("aaaaaasdf")) 21 | assertFalse(pattern.matches("aaaa")) 22 | assertFalse(pattern.matches("sdf")) 23 | } 24 | 25 | @Test 26 | fun testExactMatch() { 27 | val pattern = GnFilePattern("asdf") 28 | assertEquals("^\\Qasdf\\E$", pattern.regex.pattern) 29 | assertTrue(pattern.matches("asdf")) 30 | assertFalse(pattern.matches("aaaaaaaasdfbbbbb")) 31 | assertFalse(pattern.matches("asdfbbbb")) 32 | assertFalse(pattern.matches("aaaaaasdf")) 33 | assertFalse(pattern.matches("aaaa")) 34 | assertFalse(pattern.matches("sdf")) 35 | } 36 | 37 | @Test 38 | fun testEndingInLiteral() { 39 | val pattern = GnFilePattern("*.cc") 40 | assertEquals("^.*\\Q.cc\\E$", pattern.regex.pattern) 41 | assertTrue(pattern.matches("hello.cc")) 42 | assertTrue(pattern.matches(".cc")) 43 | assertFalse(pattern.matches("hello.ccx")) 44 | assertFalse(pattern.matches("hellocc")) 45 | } 46 | 47 | @Test 48 | fun testPathBoundary() { 49 | val pattern = GnFilePattern("\\bwin/*") 50 | assertEquals("^(.*\\/)?\\Qwin/\\E.*$", pattern.regex.pattern) 51 | assertTrue(pattern.matches("win/foo")) 52 | assertTrue(pattern.matches("foo/win/bar.cc")) 53 | assertFalse(pattern.matches("iwin/foo")) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/google/idea/gn/FoldingTest.kt: -------------------------------------------------------------------------------- 1 | package com.google.idea.gn 2 | 3 | import com.google.idea.gn.util.GnCodeInsightTestCase 4 | import org.junit.Test 5 | 6 | open class FoldingTest : GnCodeInsightTestCase() { 7 | @Test 8 | fun testBlockFolding() { 9 | var filePath = "$testDataPath/project/src/test/FoldingTest.gn" 10 | myFixture.testFolding(filePath) 11 | } 12 | override fun getTestDataPath() = "src/test/testData" 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/google/idea/gn/GnLabelTest.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.google.idea.gn.GnLabel.Companion.parse 7 | import org.junit.Test 8 | import kotlin.test.* 9 | 10 | class GnLabelTest { 11 | 12 | @Test 13 | fun testCompleteAbsolutePath() { 14 | val path = parse("//src/lib/my_lib:my_target(my_toolchain)") 15 | assertNotNull(path) 16 | assertTrue(path.isAbsolute) 17 | assertEquals(3, path.parts.size) 18 | assertEquals("src", path.parts[0]) 19 | assertEquals("lib", path.parts[1]) 20 | assertEquals("my_lib", path.parts[2]) 21 | assertEquals("my_target", path.target) 22 | assertEquals("my_toolchain", path.toolchain) 23 | assertEquals("//src/lib/my_lib:my_target(my_toolchain)", path.toString()) 24 | } 25 | 26 | 27 | @Test 28 | fun testBasicAbsolutePath() { 29 | val path = parse("//src/lib/my_lib") 30 | assertNotNull(path) 31 | assertTrue(path.isAbsolute) 32 | assertEquals(3, path.parts.size) 33 | assertEquals("src", path.parts[0]) 34 | assertEquals("lib", path.parts[1]) 35 | assertEquals("my_lib", path.parts[2]) 36 | assertEquals("my_lib", path.target) 37 | assertNull(path.toolchain) 38 | assertEquals("//src/lib/my_lib", path.toString()) 39 | } 40 | 41 | 42 | @Test 43 | fun testVariablesInPath() { 44 | val path = parse("//src/lib/my_lib:target(toolchain)") 45 | assertNotNull(path) 46 | assertTrue(path.isAbsolute) 47 | assertEquals(3, path.parts.size) 48 | assertEquals("src", path.parts[0]) 49 | assertEquals("lib", path.parts[1]) 50 | assertEquals("target", path.target) 51 | assertEquals("toolchain", path.toolchain) 52 | assertEquals("//src/lib/my_lib:target(toolchain)", path.toString()) 53 | } 54 | 55 | @Test 56 | fun testRelativePath() { 57 | val path = parse("lib/my_lib") 58 | assertNotNull(path) 59 | assertFalse(path.isAbsolute) 60 | assertEquals(2, path.parts.size) 61 | assertEquals("lib", path.parts[0]) 62 | assertEquals("my_lib", path.parts[1]) 63 | assertEquals("my_lib", path.target) 64 | assertNull(path.toolchain) 65 | assertEquals("lib/my_lib", path.toString()) 66 | } 67 | 68 | @Test 69 | fun testAllowIncompleteTarget() { 70 | val path = parse("lib:") 71 | assertNotNull(path) 72 | assertEquals("", path.target) 73 | } 74 | 75 | @Test 76 | fun testTargetInFilePath() { 77 | val path = parse(":target") 78 | assertNotNull(path) 79 | assertEquals(0, path.parts.size) 80 | assertEquals("target", path.target) 81 | assertNull(path.toolchain) 82 | assertEquals(":target", path.toString()) 83 | } 84 | 85 | @Test 86 | fun testInvalidPath() { 87 | assertNull(parse("")) 88 | assertNull(parse("//")) 89 | assertNull(parse(null)) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/com/google/idea/gn/ParseTest.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn 6 | 7 | import com.intellij.testFramework.ParsingTestCase 8 | 9 | class ParseTest : ParsingTestCase("", "gn", true, GnParserDefinition()) { 10 | 11 | override fun getTestDataPath(): String = "src/test/testData" 12 | 13 | override fun skipSpaces(): Boolean = true 14 | 15 | override fun includeRanges(): Boolean = true 16 | 17 | fun testSimpleParseCheck() { 18 | doTest(true) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/google/idea/gn/ReferencesTest.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.google.idea.gn.psi.GnFile 7 | import com.google.idea.gn.psi.Types 8 | import com.google.idea.gn.util.GnCodeInsightTestCase 9 | import com.google.idea.gn.util.PatternGatherer 10 | import com.google.idea.gn.util.findElementMatching 11 | import com.google.idea.gn.util.resolvedReference 12 | import com.intellij.patterns.PlatformPatterns.psiElement 13 | import com.intellij.patterns.StandardPatterns 14 | 15 | class ReferencesTest : GnCodeInsightTestCase() { 16 | 17 | fun testLabelReferences() { 18 | copyTestFiles( 19 | "build/rules.gni", 20 | "src/lib/BUILD.gn" 21 | ) 22 | configureFromFileText(GnFile.BUILD_FILE, """ 23 | import("//build/rules.gni") 24 | group("x") { 25 | deps = [ 26 | "//src/lib:my_lib", 27 | "src/lib:my_lib" 28 | ] 29 | } 30 | 31 | source_set("y") { 32 | sources = [ "lib/lib.cc" ] 33 | } 34 | """.trimIndent()) 35 | 36 | val elements = PatternGatherer( 37 | "import" to psiElement(Types.STRING_EXPR).withText("\"//build/rules.gni\""), 38 | "absTarget" to psiElement(Types.STRING_EXPR).withText("\"//src/lib:my_lib\""), 39 | "relTarget" to psiElement(Types.STRING_EXPR).withText("\"//src/lib:my_lib\""), 40 | "sourceFile" to psiElement(Types.STRING_EXPR).withText("\"lib/lib.cc\"") 41 | ).gather(file) 42 | 43 | 44 | assertEquals(getProjectPsiFile("build/rules.gni"), 45 | elements.resolvedReference("import")) 46 | val libGroup = getProjectPsiFile("src/lib/BUILD.gn")?.findElementMatching( 47 | psiElement(Types.CALL).withText(StandardPatterns.string().startsWith("group(\"my_lib\")"))) 48 | ?: error("Failed to find group declaration in src/lib/BUILD.gn") 49 | assertEquals(libGroup, elements.resolvedReference("absTarget")) 50 | assertEquals(libGroup, elements.resolvedReference("relTarget")) 51 | assertEquals(getProjectPsiFile("src/lib/lib.cc"), elements.resolvedReference("sourceFile")) 52 | } 53 | 54 | 55 | fun testTemplateReferences() { 56 | copyTestFiles("build/rules.gni") 57 | configureFromFileText(GnFile.BUILD_FILE, """ 58 | import("//build/rules.gni") 59 | 60 | template("foo") {} 61 | foo("bar") {} 62 | test_template("baz") {} 63 | """.trimIndent()) 64 | 65 | val elements = PatternGatherer( 66 | "foo" to psiElement(Types.ID).withText("foo"), 67 | "test_template" to psiElement(Types.ID).withText("test_template") 68 | ).gather(file) 69 | 70 | assertEquals(file.findElementMatching(psiElement(Types.CALL).withText( 71 | StandardPatterns.string().startsWith("""template("foo")"""))) ?: error( 72 | "Failed to match template declaration"), 73 | elements.resolvedReference("foo")) 74 | 75 | assertEquals(getProjectPsiFile("build/rules.gni")?.findElementMatching( 76 | psiElement(Types.CALL).withText( 77 | StandardPatterns.string().startsWith("""template("test_template")"""))) ?: error( 78 | "Failed to match template declaration"), 79 | elements.resolvedReference("test_template")) 80 | 81 | } 82 | 83 | fun testLabelResolvesToTemplateCall() { 84 | copyTestFiles("build/rules.gni") 85 | configureFromFileText(GnFile.BUILD_FILE, """ 86 | import("//build/rules.gni") 87 | 88 | test_template("foo") {} 89 | 90 | group("test") { 91 | deps = [":foo"] 92 | } 93 | """.trimIndent()) 94 | 95 | val reference = file.findElementMatching( 96 | psiElement(Types.STRING_EXPR).withText("\":foo\""))?.reference?.resolve() 97 | 98 | assertEquals(file.findElementMatching( 99 | psiElement(Types.CALL).withText(StandardPatterns.string().startsWith("test_template"))) 100 | ?: error("Failed to match template call site"), reference) 101 | } 102 | 103 | 104 | fun testNestedTemplateReferences() { 105 | copyTestFiles("build/rules.gni") 106 | configureFromFileText(GnFile.BUILD_FILE, """ 107 | template("bar") { 108 | group("bar" + target_name) {} 109 | } 110 | template("foo") { 111 | bar("foo" + target_name) {} 112 | } 113 | foo("baz") {} 114 | 115 | group("x") { 116 | deps = [":barfoobaz"] 117 | } 118 | """.trimIndent()) 119 | 120 | if (gnFile.scope.targets?.containsKey("barfoobaz") != true) { 121 | error("Failed to find target in ${gnFile.scope.targets}") 122 | } 123 | 124 | val label = file.findElementMatching(psiElement(Types.STRING_EXPR).withText("""":barfoobaz"""")) 125 | ?: error("failed to get label") 126 | 127 | val expect = file.findElementMatching( 128 | psiElement(Types.CALL).withText( 129 | StandardPatterns.string().startsWith("foo"))) ?: error( 130 | "failed to find call site") 131 | 132 | val ref = label.reference?.resolve() 133 | 134 | LOGGER.info("expect: ${expect.text}") 135 | LOGGER.info("got: ${ref?.text}") 136 | 137 | assertEquals(expect, ref) 138 | } 139 | 140 | fun testReferenceThroughTargetTemplate() { 141 | configureFromFileText(GnFile.BUILD_FILE, """ 142 | template("bar") { 143 | target("group", target_name) {} 144 | } 145 | 146 | bar("baz") {} 147 | 148 | group("x") { 149 | deps = [":baz"] 150 | } 151 | """.trimIndent()) 152 | 153 | if (gnFile.scope.targets?.containsKey("baz") != true) { 154 | error("Failed to find baz in ${gnFile.scope.targets}") 155 | } 156 | 157 | val label = file.findElementMatching( 158 | psiElement(Types.STRING_EXPR).withText("""":baz"""")) 159 | ?: error("failed to get label") 160 | 161 | val expect = file.findElementMatching( 162 | psiElement(Types.CALL).withText( 163 | StandardPatterns.string().startsWith("bar"))) ?: error( 164 | "failed to find call site") 165 | 166 | val ref = label.reference?.resolve() 167 | 168 | LOGGER.info("expect: ${expect.text}") 169 | LOGGER.info("got: ${ref?.text}") 170 | 171 | assertEquals(expect, ref) 172 | } 173 | 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/test/java/com/google/idea/gn/TemplateVariablesTest.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn 5 | 6 | import com.google.idea.gn.psi.Function 7 | import com.google.idea.gn.psi.GnFile 8 | import com.google.idea.gn.psi.Types 9 | import com.google.idea.gn.psi.builtin.BuiltinTargetFunction 10 | import com.google.idea.gn.util.GnCodeInsightTestCase 11 | import com.google.idea.gn.util.findElementMatching 12 | import com.intellij.patterns.PlatformPatterns.psiElement 13 | import com.intellij.patterns.StandardPatterns 14 | 15 | class TemplateVariablesTest : GnCodeInsightTestCase() { 16 | 17 | private fun setupAndGetFooCall(contents: String): Function { 18 | configureFromFileText(GnFile.BUILD_FILE, contents) 19 | gnFile.buildScope() 20 | val template = file.findElementMatching( 21 | psiElement(Types.CALL).withText(StandardPatterns.string().startsWith("foo"))) 22 | ?: error( 23 | "Failed to find \"foo\" template call") 24 | return template.getUserData(GnKeys.CALL_RESOLVED_FUNCTION) ?: error( 25 | "resolved function not present") 26 | } 27 | 28 | fun testScopeAccess() { 29 | val f = setupAndGetFooCall(""" 30 | template("foo") { 31 | a = invoker.apple 32 | b = invoker.banana 33 | if(invoker.orange) { 34 | c = invoker.pineapple 35 | } else if(invoker.coconut) { 36 | d = invoker.peach 37 | } else { 38 | f = invoker.cherry 39 | } 40 | k = invoker.plum + invoker.blueberry 41 | } 42 | 43 | foo("foo_target") {} 44 | """.trimIndent()) 45 | 46 | assertEquals( 47 | setOf("apple", 48 | "banana", 49 | "orange", 50 | "pineapple", 51 | "coconut", 52 | "peach", 53 | "cherry", 54 | "plum", 55 | "blueberry"), f.variables.keys) 56 | } 57 | 58 | fun testForwardSpecificVariables() { 59 | val f = setupAndGetFooCall(""" 60 | template("foo") { 61 | forward_variables_from(invoker, [ 62 | "apple", 63 | "banana", 64 | "orange", 65 | ]) 66 | } 67 | 68 | foo("bar") {} 69 | """.trimIndent()) 70 | 71 | assertEquals(setOf("apple", "banana", "orange"), f.variables.keys) 72 | } 73 | 74 | fun testForwardIntoBuiltin() { 75 | val f = setupAndGetFooCall(""" 76 | template("foo") { 77 | group(target_name) { 78 | forward_variables_from(invoker, "*") 79 | } 80 | } 81 | 82 | foo("bar") {} 83 | """.trimIndent()) 84 | 85 | assertEquals(BuiltinTargetFunction.GROUP.variables, f.variables) 86 | } 87 | 88 | fun testForwardFromTemplate() { 89 | val f = setupAndGetFooCall(""" 90 | template("foo") { 91 | template("baz") { 92 | a = invoker.apple 93 | } 94 | 95 | baz(target_name) { 96 | forward_variables_from(invoker, "*") 97 | } 98 | } 99 | 100 | foo("bar") {} 101 | """.trimIndent()) 102 | 103 | assertEquals(setOf("apple"), f.variables.keys) 104 | } 105 | 106 | fun testForwardIntoBlock() { 107 | val f = setupAndGetFooCall(""" 108 | template("foo") { 109 | foo = { 110 | forward_variables_from(invoker, ["apple", "banana"]) 111 | } 112 | forward_variables_from(invoker, ["coconut"]) 113 | } 114 | 115 | foo("bar") {} 116 | """.trimIndent()) 117 | 118 | assertEquals(setOf("apple", "banana", "coconut"), f.variables.keys) 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/test/java/com/google/idea/gn/util/Extensions.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.util 5 | 6 | import com.intellij.patterns.ElementPattern 7 | import com.intellij.psi.PsiElement 8 | import com.intellij.psi.PsiElementVisitor 9 | import com.intellij.psi.PsiFile 10 | 11 | fun Map.resolvedReference(tag: String): PsiElement? = 12 | this[tag]?.reference?.resolve() 13 | 14 | fun PsiFile.findElementMatching(pattern: ElementPattern): PsiElement? { 15 | val visitor = object : PsiElementVisitor() { 16 | var result: PsiElement? = null 17 | override fun visitElement(element: PsiElement) { 18 | when { 19 | result != null -> return 20 | pattern.accepts(element) -> { 21 | result = element 22 | } 23 | else -> { 24 | element.children.forEach { it.accept(this) } 25 | } 26 | } 27 | } 28 | } 29 | this.accept(visitor) 30 | return visitor.result 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/google/idea/gn/util/GnCodeInsightTestCase.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.google.idea.gn.util 6 | 7 | import com.google.idea.gn.config.gnRoot 8 | import com.google.idea.gn.psi.GnFile 9 | import com.google.idea.gn.psi.GnPsiUtil 10 | import com.intellij.openapi.application.runWriteAction 11 | import com.intellij.openapi.diagnostic.Logger 12 | import com.intellij.openapi.vfs.VfsUtil 13 | import com.intellij.openapi.vfs.VirtualFile 14 | import com.intellij.openapi.vfs.VirtualFileVisitor 15 | import com.intellij.psi.PsiFile 16 | import com.intellij.psi.PsiManager 17 | import com.intellij.testFramework.LightPlatformCodeInsightTestCase 18 | import com.intellij.testFramework.fixtures.IdeaTestExecutionPolicy 19 | import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory 20 | import com.intellij.testFramework.fixtures.TempDirTestFixture 21 | import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl 22 | import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl 23 | 24 | abstract class GnCodeInsightTestCase : LightPlatformCodeInsightTestCase() { 25 | 26 | override fun setUp() { 27 | val factory = IdeaTestFixtureFactory.getFixtureFactory() 28 | val fixtureBuilder = factory.createLightFixtureBuilder(projectDescriptor, "gnCodeInsightTest") 29 | myFixture = CodeInsightTestFixtureImpl(fixtureBuilder.fixture, getTempDirFixture()) 30 | myFixture.setUp(); 31 | } 32 | 33 | protected lateinit var myFixture :CodeInsightTestFixtureImpl; 34 | 35 | val LOGGER = Logger.getInstance(GnPsiUtil.javaClass) 36 | 37 | override fun getTestDataPath() = "src/test/testData/project/" 38 | 39 | val gnFile: GnFile get() = file as GnFile 40 | 41 | fun getProjectFile(path: String): VirtualFile? = 42 | project.gnRoot!!.findFileByRelativePath(path) 43 | 44 | fun getProjectPsiFile(path: String): PsiFile? = getProjectFile( 45 | path)?.let { PsiManager.getInstance(project).findFile(it) } 46 | 47 | fun copyTestFilesByVirtualFile(filter: (VirtualFile) -> Boolean) { 48 | runWriteAction { 49 | val projDir = project.gnRoot!! 50 | VfsUtil.copyDirectory(this, getVirtualFile(""), projDir, filter) 51 | // Delete any empty directories. 52 | VfsUtil.visitChildrenRecursively(projDir, object : VirtualFileVisitor() { 53 | override fun visitFile(file: VirtualFile): Boolean { 54 | return if (file.isDirectory && file.children.isEmpty()) { 55 | file.delete(this) 56 | false 57 | } else { 58 | true 59 | } 60 | } 61 | }) 62 | } 63 | } 64 | 65 | fun copyTestFilesByPath(filter: (String) -> Boolean) { 66 | copyTestFilesByVirtualFile { 67 | it.isDirectory || filter(VfsUtil.getRelativePath(it, getVirtualFile(""))!!) 68 | } 69 | } 70 | 71 | fun copyTestFiles(vararg files: String) { 72 | val set = files.toSet() 73 | copyTestFilesByPath { 74 | set.contains(it) 75 | } 76 | } 77 | 78 | protected fun getTempDirFixture(): TempDirTestFixture { 79 | val policy = IdeaTestExecutionPolicy.current() 80 | return if (policy != null) policy.createTempDirTestFixture() else LightTempDirTestFixtureImpl(true) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/com/google/idea/gn/util/PatternGatherer.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | package com.google.idea.gn.util 5 | 6 | import com.intellij.patterns.ElementPattern 7 | import com.intellij.psi.PsiElement 8 | import com.intellij.psi.PsiElementVisitor 9 | 10 | 11 | class PatternGatherer(val patterns: Map>) { 12 | 13 | constructor(vararg patterns: Pair>) : this( 14 | patterns.toMap()) 15 | 16 | fun gather(root: PsiElement): Map { 17 | val result = mutableMapOf() 18 | root.accept(object : PsiElementVisitor() { 19 | override fun visitElement(element: PsiElement) { 20 | for (p in patterns) { 21 | if (p.value.accepts(element)) { 22 | if (result.containsKey(p.key)) { 23 | throw RuntimeException( 24 | "Found duplicate element (${result[p.key]}/$element) for pattern ${p.value}") 25 | } 26 | result[p.key] = element 27 | } 28 | } 29 | element.children.forEach { 30 | if (result.size == patterns.size) { 31 | return 32 | } 33 | it.accept(this) 34 | } 35 | } 36 | }) 37 | return result 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/testData/project/build/rules.gni: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Google LLC All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | template("test_template"){ 6 | group(target_name){ 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/test/testData/project/src/BUILD.gn: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Google LLC All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | group("my_src") { 6 | deps = ["//src/lib:my_lib"] 7 | } 8 | -------------------------------------------------------------------------------- /src/test/testData/project/src/lib/BUILD.gn: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Google LLC All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | group("my_lib") { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/test/testData/project/src/lib/lib.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Google LLC All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | -------------------------------------------------------------------------------- /src/test/testData/project/src/test/BUILD.gn: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Google LLC All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | group("test") { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/test/testData/project/src/test/FoldingTest.gn: -------------------------------------------------------------------------------- 1 | items = [1, 2 | 2, 3 | 3, 4 | 4 5 | ] 6 | 7 | foreach(item, items) { 8 | print(item) 9 | } 10 | 11 | condition = true 12 | if(condition){ 13 | condition = false 14 | } 15 | 16 | template("my_template") { 17 | "a = false" 18 | } 19 | -------------------------------------------------------------------------------- /src/test/testData/simpleParseCheck.gn: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Google LLC All rights reserved. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | import("//thing") 6 | 7 | global = x 8 | 9 | # Test syntax rules for parser. 10 | group("my_group") { 11 | a = 0 12 | a += 1 13 | a -= 1 14 | b = ["a"] 15 | b += "b" 16 | b -= "b" 17 | c = a && b 18 | c = a || b 19 | c = a + b 20 | c = a - b 21 | c = !a 22 | c = a + b + d 23 | c = a + b + !d 24 | if(c) { 25 | do_thing() 26 | } 27 | if(a == 1) { 28 | v = 0 29 | } else if (a != 3 && (b != 2 && c == 0)) { 30 | v = 1 31 | } else { 32 | v = [ b + "i",] 33 | } 34 | c = true 35 | c = false 36 | c = [] 37 | c = {} 38 | c = { 39 | i = 0 40 | } 41 | 42 | identifier = "abc$identifier abc" 43 | brackets = "abc${identifier}abc" 44 | scope_access = "abc${identifier.field}abc" 45 | array_access = "abc${identifier[3]}aeaf" 46 | } 47 | --------------------------------------------------------------------------------