From 07ee0f1486df746a294fc4cc48882871894f789e Mon Sep 17 00:00:00 2001 From: Ashok Koyi Date: Mon, 21 Sep 2020 21:31:27 +0530 Subject: [PATCH] Refactored code to use proper build. Replaced code to ensure plugin works with newer APIs --- .github/workflows/build.yml | 225 +++++++ .github/workflows/release.yml | 70 +++ .gitignore | 20 + CHANGELOG.md | 14 + README.md | 29 +- build.gradle.kts | 137 +++++ gradle.properties | 12 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 ++++++ gradlew.bat | 89 +++ resources/1.html | 12 - resources/1.rest | 10 - resources/META-INF/plugin.xml | 61 -- settings.gradle.kts | 12 + src/icon/optimise.sh | 3 + src/icon/pluginIcon.svg | 258 ++++++++ src/icon/pluginIcon_dark.svg | 254 ++++++++ src/icon/restFileType.svg | 157 +++++ .../rest/plugin => main/grammars}/Rest.bnf | 8 +- .../rest/plugin => main/grammars}/Rest.flex | 6 +- .../ide/rest/plugin/RestLanguage.java | 11 + .../ide/rest/plugin/action/SendAction.java | 159 +++++ .../ide/rest/plugin/action/StopAction.java | 44 ++ .../codestyle}/RestCodeStyleSettings.java | 8 +- .../RestCodeStyleSettingsProvider.java | 55 ++ .../rest/plugin/editor/RestClientEditor.java | 200 ++++++ .../editor/RestClientEditorProvider.java | 61 ++ .../rest/plugin/editor}/RestCommenter.java | 2 +- .../ide/rest/plugin/file/RestFileType.java | 45 ++ .../ide/rest/plugin/file/RestPsiFile.java | 41 ++ .../plugin/grammar}/RestLexerAdapter.java | 11 +- .../plugin/grammar/RestParserDefinition.java | 73 +++ .../highlighter/RestColorSettingsPage.java | 75 +++ .../plugin/highlighter}/RestHighlighter.java | 8 +- .../highlighter}/RestHighlighterFactory.java | 18 +- .../ide/rest/plugin/http/HttpRequest.java | 60 ++ .../ide/rest/plugin/http/HttpResponse.java | 20 + .../ide/rest/plugin/http/RequestExecutor.java | 148 +++++ .../ide/rest/plugin/http/Requests.java | 102 +++ .../plugin/injector/RestHostInjector.java | 70 +++ .../basecode/ide/rest/plugin/misc/Icons.java | 13 + .../basecode/ide/rest/plugin/misc/Util.java | 38 ++ .../ide/rest/plugin/psi/RestElement.java | 3 - .../ide/rest/plugin/psi/RestElementType.java | 9 +- .../rest/plugin/psi/RestRequestParser.java | 140 +++++ .../ide/rest/plugin/psi/RestTokenType.java | 9 +- .../psi/impl/ResponseLiteralEscaper.java | 35 ++ .../psi/impl/RestWrapperPsiElement.java | 31 + src/main/resources/META-INF/plugin.xml | 70 +++ src/main/resources/META-INF/pluginIcon.svg | 14 + .../resources/META-INF/pluginIcon_dark.svg | 14 + src/main/resources/META-INF/restFileType.svg | 40 ++ .../main/resources}/META-INF/schema.xml | 0 .../ide/rest/plugin/HeaderPanelComponent.java | 135 ---- src/ru/basecode/ide/rest/plugin/Request.java | 159 ----- .../ide/rest/plugin/RequestExecutor.java | 123 ---- .../ide/rest/plugin/RequestParser.java | 118 ---- src/ru/basecode/ide/rest/plugin/Requests.java | 74 --- .../ide/rest/plugin/RestClientPlugin.java | 24 - .../plugin/RestCodeStyleSettingsProvider.java | 53 -- .../rest/plugin/RestColorSettingsPage.java | 85 --- src/ru/basecode/ide/rest/plugin/RestFile.java | 43 -- .../ide/rest/plugin/RestFileType.java | 44 -- .../ide/rest/plugin/RestFileTypeFactory.java | 15 - .../ide/rest/plugin/RestHostInjector.java | 69 --- .../ide/rest/plugin/RestLanguage.java | 15 - .../basecode/ide/rest/plugin/RestLexer.java | 579 ------------------ .../ide/rest/plugin/RestParserDefinition.java | 78 --- .../ide/rest/plugin/RestProjectComponent.java | 95 --- .../basecode/ide/rest/plugin/RunAction.java | 153 ----- .../basecode/ide/rest/plugin/StopAction.java | 42 -- .../ide/rest/plugin/http/Response.java | 37 -- .../psi/impl/ResponseLiteralEscaper.java | 33 - .../rest/plugin/psi/impl/RestElementImpl.java | 33 - .../plugin/psi/RestRequestParserTest.java | 45 ++ .../RequestParserTest_only_body.txt | 0 .../RequestParserTest_only_client_params.txt | 0 .../RequestParserTest_only_headers.txt | 0 .../RequestParserTest_only_method.txt | 0 .../resources}/RequestParserTest_only_url.txt | 0 .../RequestParserTest_url_with_params.txt | 0 .../ide/rest/plugin/RequestParserTest.java | 44 -- 83 files changed, 3102 insertions(+), 2188 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat delete mode 100644 resources/1.html delete mode 100644 resources/1.rest delete mode 100644 resources/META-INF/plugin.xml create mode 100644 settings.gradle.kts create mode 100755 src/icon/optimise.sh create mode 100644 src/icon/pluginIcon.svg create mode 100644 src/icon/pluginIcon_dark.svg create mode 100644 src/icon/restFileType.svg rename src/{ru/basecode/ide/rest/plugin => main/grammars}/Rest.bnf (85%) rename src/{ru/basecode/ide/rest/plugin => main/grammars}/Rest.flex (95%) create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/RestLanguage.java create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/action/SendAction.java create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/action/StopAction.java rename src/{ru/basecode/ide/rest/plugin => main/java/ru/basecode/ide/rest/plugin/codestyle}/RestCodeStyleSettings.java (56%) create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/codestyle/RestCodeStyleSettingsProvider.java create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/editor/RestClientEditor.java create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/editor/RestClientEditorProvider.java rename src/{ru/basecode/ide/rest/plugin => main/java/ru/basecode/ide/rest/plugin/editor}/RestCommenter.java (93%) create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/file/RestFileType.java create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/file/RestPsiFile.java rename src/{ru/basecode/ide/rest/plugin => main/java/ru/basecode/ide/rest/plugin/grammar}/RestLexerAdapter.java (56%) create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/grammar/RestParserDefinition.java create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/highlighter/RestColorSettingsPage.java rename src/{ru/basecode/ide/rest/plugin => main/java/ru/basecode/ide/rest/plugin/highlighter}/RestHighlighter.java (95%) rename src/{ru/basecode/ide/rest/plugin => main/java/ru/basecode/ide/rest/plugin/highlighter}/RestHighlighterFactory.java (53%) create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/http/HttpRequest.java create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/http/HttpResponse.java create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/http/RequestExecutor.java create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/http/Requests.java create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/injector/RestHostInjector.java create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/misc/Icons.java create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/misc/Util.java rename src/{ => main/java}/ru/basecode/ide/rest/plugin/psi/RestElement.java (93%) rename src/{ => main/java}/ru/basecode/ide/rest/plugin/psi/RestElementType.java (67%) create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/psi/RestRequestParser.java rename src/{ => main/java}/ru/basecode/ide/rest/plugin/psi/RestTokenType.java (67%) create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/psi/impl/ResponseLiteralEscaper.java create mode 100644 src/main/java/ru/basecode/ide/rest/plugin/psi/impl/RestWrapperPsiElement.java create mode 100644 src/main/resources/META-INF/plugin.xml create mode 100644 src/main/resources/META-INF/pluginIcon.svg create mode 100644 src/main/resources/META-INF/pluginIcon_dark.svg create mode 100644 src/main/resources/META-INF/restFileType.svg rename {resources => src/main/resources}/META-INF/schema.xml (100%) delete mode 100644 src/ru/basecode/ide/rest/plugin/HeaderPanelComponent.java delete mode 100644 src/ru/basecode/ide/rest/plugin/Request.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RequestExecutor.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RequestParser.java delete mode 100644 src/ru/basecode/ide/rest/plugin/Requests.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RestClientPlugin.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RestCodeStyleSettingsProvider.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RestColorSettingsPage.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RestFile.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RestFileType.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RestFileTypeFactory.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RestHostInjector.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RestLanguage.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RestLexer.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RestParserDefinition.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RestProjectComponent.java delete mode 100644 src/ru/basecode/ide/rest/plugin/RunAction.java delete mode 100644 src/ru/basecode/ide/rest/plugin/StopAction.java delete mode 100644 src/ru/basecode/ide/rest/plugin/http/Response.java delete mode 100644 src/ru/basecode/ide/rest/plugin/psi/impl/ResponseLiteralEscaper.java delete mode 100644 src/ru/basecode/ide/rest/plugin/psi/impl/RestElementImpl.java create mode 100644 src/test/java/ru/basecode/ide/rest/plugin/psi/RestRequestParserTest.java rename {test/ru/basecode/ide/rest/plugin => src/test/resources}/RequestParserTest_only_body.txt (100%) rename {test/ru/basecode/ide/rest/plugin => src/test/resources}/RequestParserTest_only_client_params.txt (100%) rename {test/ru/basecode/ide/rest/plugin => src/test/resources}/RequestParserTest_only_headers.txt (100%) rename {test/ru/basecode/ide/rest/plugin => src/test/resources}/RequestParserTest_only_method.txt (100%) rename {test/ru/basecode/ide/rest/plugin => src/test/resources}/RequestParserTest_only_url.txt (100%) rename {test/ru/basecode/ide/rest/plugin => src/test/resources}/RequestParserTest_url_with_params.txt (100%) delete mode 100644 test/ru/basecode/ide/rest/plugin/RequestParserTest.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1b76843 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,225 @@ +# GitHub Actions Workflow created for testing and preparing the plugin release in following steps: +# - validate Gradle Wrapper, +# - run test and verifyPlugin tasks, +# - run buildPlugin task and prepare artifact for the further tests, +# - run IntelliJ Plugin Verifier, +# - create a draft release. +# +# Workflow is triggered on push and pull_request events. +# +# Docs: +# - GitHub Actions: https://help.github.com/en/actions +# - IntelliJ Plugin Verifier GitHub Action: https://github.com/ChrisCarini/intellij-platform-plugin-verifier-action +# +## JBIJPPTPL + +name: Build +on: [push, pull_request] +jobs: + + # Run Gradle Wrapper Validation Action to verify the wrapper's checksum + gradleValidation: + name: Gradle Wrapper + runs-on: ubuntu-latest + steps: + + # Check out current repository + - name: Fetch Sources + uses: actions/checkout@v2 + + # Validate wrapper + - name: Gradle Wrapper Validation + uses: gradle/wrapper-validation-action@v1 + + # Run verifyPlugin and test Gradle tasks + test: + name: Test + needs: gradleValidation + runs-on: ubuntu-latest + steps: + + # Setup Java 1.8 environment for the next steps + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + # Check out current repository + - name: Fetch Sources + uses: actions/checkout@v2 + + # Cache Gradle dependencies + - name: Setup Gradle Dependencies Cache + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-caches-${{ hashFiles('**/*.gradle', '**/*.gradle.kts') }} + + # Cache Gradle Wrapper + - name: Setup Gradle Wrapper Cache + uses: actions/cache@v2 + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }} + + # Run detekt, ktlint and tests + - name: Run Linters and Test + run: ./gradlew check + + # Run verifyPlugin Gradle task + - name: Verify Plugin + run: ./gradlew verifyPlugin + + # Build plugin with buildPlugin Gradle task and provide the artifact for the next workflow jobs + # Requires test job to be passed + build: + name: Build + needs: test + runs-on: ubuntu-latest + outputs: + name: ${{ steps.properties.outputs.name }} + version: ${{ steps.properties.outputs.version }} + changelog: ${{ steps.properties.outputs.changelog }} + artifact: ${{ steps.properties.outputs.artifact }} + steps: + + # Setup Java 1.8 environment for the next steps + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + # Check out current repository + - name: Fetch Sources + uses: actions/checkout@v2 + + # Cache Gradle Dependencies + - name: Setup Gradle Dependencies Cache + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-caches-${{ hashFiles('**/*.gradle', '**/*.gradle.kts') }} + + # Cache Gradle Wrapper + - name: Setup Gradle Wrapper Cache + uses: actions/cache@v2 + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }} + + # Set environment variables + - name: Export Properties + id: properties + shell: bash + run: | + PROPERTIES="$(./gradlew properties --console=plain -q)" + VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')" + NAME="$(echo "$PROPERTIES" | grep "^pluginName_:" | cut -f2- -d ' ')" + CHANGELOG="$(./gradlew getChangelog --unreleased --no-header --console=plain -q)" + CHANGELOG="${CHANGELOG//'%'/'%25'}" + CHANGELOG="${CHANGELOG//$'\n'/'%0A'}" + CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" + ARTIFACT="${NAME}-${VERSION}.zip" + + echo "::set-output name=version::$VERSION" + echo "::set-output name=name::$NAME" + echo "::set-output name=changelog::$CHANGELOG" + echo "::set-output name=artifact::$ARTIFACT" + + # Build artifact using buildPlugin Gradle task + - name: Build Plugin + run: ./gradlew buildPlugin + + # Upload plugin artifact to make it available in the next jobs + - name: Upload artifact + uses: actions/upload-artifact@v1 + with: + name: plugin-artifact + path: ./build/distributions/${{ needs.build.outputs.artifact }} + + # Verify built plugin using IntelliJ Plugin Verifier tool + # Requires build job to be passed + verify: + name: Verify + needs: build + runs-on: ubuntu-latest + steps: + + # Download plugin artifact provided by the previous job + - name: Download Artifact + uses: actions/download-artifact@v1 + with: + name: plugin-artifact + + # Run IntelliJ Plugin Verifier action using GitHub Action + - name: Verify Plugin + id: verify + uses: ChrisCarini/intellij-platform-plugin-verifier-action@v0.0.2 + with: + plugin-location: plugin-artifact/*.zip + ide-versions: | + ideaIC:2019.3 + ideaIC:2020.1 + ideaIC:2020.2 + + # Print the output of the verify step + - name: Print Logs + if: ${{ always() }} + env: + OUTPUT_LOG: ${{ steps.verify.outputs.verification-output-log-filename }} + run: | + echo "The verifier log file [$OUTPUT_LOG] contents : " ; + cat $OUTPUT_LOG + + # Prepare a draft release for GitHub Releases page for the manual verification + # If accepted and published, release workflow would be triggered + releaseDraft: + name: Release Draft + needs: [build, verify] + runs-on: ubuntu-latest + steps: + + # Check out current repository + - name: Fetch Sources + uses: actions/checkout@v2 + + # Remove old release drafts by using the curl request for the available releases with draft flag + - name: Remove Old Release Drafts + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + curl -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/repos/$GITHUB_REPOSITORY/releases \ + | tr '\r\n' ' ' \ + | jq '.[] | select(.draft == true) | .id' \ + | xargs -I '{}' \ + curl -X DELETE -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/repos/$GITHUB_REPOSITORY/releases/{} + + # Create new release draft - which is not publicly visible and requires manual acceptance + - name: Create Release Draft + id: createDraft + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ needs.build.outputs.version }} + release_name: v${{ needs.build.outputs.version }} + body: ${{ needs.build.outputs.changelog }} + draft: true + + # Download plugin artifact provided by the previous job + - name: Download Artifact + uses: actions/download-artifact@v1 + with: + name: plugin-artifact + + # Upload artifact as a release asset + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.createDraft.outputs.upload_url }} + asset_path: ./plugin-artifact/${{ needs.build.outputs.artifact }} + asset_name: ${{ needs.build.outputs.artifact }} + asset_content_type: application/zip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..382fa91 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,70 @@ +# GitHub Actions Workflow created for handling the release process based on the draft release prepared +# with the Build workflow. Running the publishPlugin task requires the PUBLISH_TOKEN secret provided. + +name: Release +on: + release: + types: [prereleased, released] + +jobs: + + # Prepare and publish the plugin to the Marketplace repository + release: + name: Publish Plugin + runs-on: ubuntu-latest + steps: + + # Setup Java 1.8 environment for the next steps + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + # Check out current repository + - name: Fetch Sources + uses: actions/checkout@v2 + with: + ref: ${{ github.event.release.tag_name }} + + # Publish the plugin to the Marketplace + - name: Publish Plugin + env: + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + run: ./gradlew publishPlugin + + # Patch changelog, commit and push to the current repository + changelog: + name: Update Changelog + needs: release + runs-on: ubuntu-latest + steps: + + # Setup Java 1.8 environment for the next steps + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + # Check out current repository + - name: Fetch Sources + uses: actions/checkout@v2 + with: + ref: ${{ github.event.release.tag_name }} + + # Update Unreleased section with the current version + - name: Patch Changelog + run: ./gradlew patchChangelog + + # Commit patched Changelog + - name: Commit files + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git commit -m "Update changelog" -a + + # Push changes + - name: Push changes + uses: ad-m/github-push-action@master + with: + branch: main + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2404953 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +gen/ +build/ +.gradle/ +.idea/ +target/ +out/ +.project +.classpath +/bootstrap.xml +/font-awesome.xml +*.*~ +*.iml +**/*.iml +*.ipl +**/*.ipl +*.iwl +**/*.iwl +/logs +*.log +config.properties diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7c34880 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ + + +# Changelog + +## [Unreleased] +### Changed +- Migrated the plugin to gradle + +### Fixed +- Fixed issues with deprecated APIs + +## [0.1.1] +### Added +- Basic support diff --git a/README.md b/README.md index 5b2049c..393967b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,14 @@ -This plugin helps you to run http-requests from an IDEA's text editor +# Rest Client -#### Features -- Supports GET, POST, PUT, DELETE, PATCH requests +![Build](https://github.com/danblack/idea-rest-client/workflows/Build/badge.svg) +[![Version](https://img.shields.io/jetbrains/plugin/v/9232-http-editor-client.svg)](https://plugins.jetbrains.com/plugin/9232-http-editor-client) +[![Downloads](https://img.shields.io/jetbrains/plugin/d/9232-http-editor-client.svg)](https://plugins.jetbrains.com/plugin/9232-http-editor-client) + + +Allows making http requests using IDEA text editor + +## Features +- Supports GET, POST, PUT, DELETE, PATCH, HEAD & OPTIONS requests - Multiline request parameters - Response auto format (based on response Content-Type header) - Customized colors and fonts @@ -10,7 +17,7 @@ This plugin helps you to run http-requests from an IDEA's text editor ![demo](doc/demo.png) -#### YouTube demos +## YouTube demos * [Simple demo](https://www.youtube.com/watch?v=AliJaGmXlxc) @@ -90,3 +97,17 @@ http://localhost:8080/mirror-params?one=1 one: 1 five: 5 ``` + + + +## Installation + +- Using IDE built-in plugin system: + + Preferences > Plugins > Marketplace > Search for "%NAME%" > + Install Plugin + +- Manually: + + Download the [latest release](https://github.com/%REPOSITORY%/releases/latest) and install it manually using + Preferences > Plugins > ⚙️ > Install plugin from disk... diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..f3b3ed5 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,137 @@ +import org.jetbrains.changelog.closure +import org.jetbrains.changelog.markdownToHTML +import org.jetbrains.grammarkit.tasks.GenerateLexer +import org.jetbrains.grammarkit.tasks.GenerateParser + +plugins { + // plugin development + id("org.jetbrains.intellij") version "0.4.26" + // for parsing grammer files & generate lexer & grammer + id("org.jetbrains.grammarkit") version "2020.2.1" + // for patching plugin.xml with the content from CHANGELOG.md file + id("org.jetbrains.changelog") version "0.5.0" + + java + idea + + // Plugin which can check for Gradle dependencies, use the help/dependencyUpdates task. + id("com.github.ben-manes.versions") version "0.28.0" + // Plugin which can update Gradle dependencies, use the help/useLatestVersions task. + id("se.patrikerdes.use-latest-versions") version "0.2.14" + + // Used to debug in a different IDE + maven + id("de.undercouch.download") version "4.0.4" +} + +// Import variables from gradle.properties file +val pluginGroup: String by project +// `pluginName_` variable ends with `_` because of the collision with Kotlin magic getter in the `intellij` closure. +// Read more about the issue: https://github.com/JetBrains/intellij-platform-plugin-template/issues/29 +val pluginName_: String by project +val pluginVersion: String by project +val pluginSinceBuild: String by project +val pluginUntilBuild: String by project + +val platformType: String by project +val platformVersion: String by project +val platformDownloadSources: String by project + +group = pluginGroup +version = pluginVersion + +repositories { + mavenCentral() + mavenLocal() +} + +dependencies { + implementation("org.jetbrains:annotations:20.1.0") + + // lombok + compileOnly("org.projectlombok:lombok:1.18.12") + annotationProcessor("org.projectlombok:lombok:1.18.12") + testCompileOnly("org.projectlombok:lombok:1.18.12") + testAnnotationProcessor("org.projectlombok:lombok:1.18.12") + compileOnly("org.jetbrains:annotations:20.1.0") +} + +// Configure gradle-intellij-plugin plugin. +// Read more: https://github.com/JetBrains/gradle-intellij-plugin +intellij { + pluginName = pluginName_ + version = platformVersion + type = platformType + downloadSources = platformDownloadSources.toBoolean() + updateSinceUntilBuild = true +} + +val generateRestLexer = task("generateRestLexer") { + source = "src/main/grammars/Rest.flex" + targetDir = "gen/ru/basecode/ide/rest/plugin/grammar/" + targetClass = "_RestLexer" + purgeOldFiles = true +} + +val generateRestParser = task("generateRestParser") { + source = "src/main/grammars/Rest.bnf" + targetRoot = "gen" + pathToParser = "ru/basecode/ide/rest/plugin/parser/_RestParser.java" + pathToPsiRoot = "ru/basecode/ide/rest/plugin/psi" + purgeOldFiles = true +} + +tasks { + compileJava { + dependsOn(generateRestLexer, generateRestParser) + sourceCompatibility = JavaVersion.VERSION_1_8.toString() + targetCompatibility = JavaVersion.VERSION_1_8.toString() + options.compilerArgs = listOf("-Xlint:deprecation") + } + + patchPluginXml { + version(pluginVersion) + sinceBuild(pluginSinceBuild) + untilBuild(pluginUntilBuild) + + // Extract the section from README.md and provide for the plugin's manifest + pluginDescription( + closure { + File("./README.md").readText().lines().run { + val start = "" + val end = "" + + if (!containsAll(listOf(start, end))) { + throw GradleException("Plugin description section not found in README.md file:\n$start ... $end") + } + subList(indexOf(start) + 1, indexOf(end)) + }.joinToString("\n").run { markdownToHTML(this) } + } + ) + + // Get the latest available change notes from the changelog file + changeNotes( + closure { + changelog.getLatest().toHTML() + } + ) + } + + publishPlugin { + dependsOn("patchChangelog") + token(System.getenv("PUBLISH_TOKEN")) + channels(pluginVersion.split('-').getOrElse(1) { "default" }.split('.').first()) + } +} + +sourceSets { + main { + java.srcDirs("gen") + } +} + +idea { + module { + generatedSourceDirs.add(file("gen")) + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..a69aab4 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,12 @@ +# IntelliJ Platform Artifacts Repositories +# -> https://www.jetbrains.org/intellij/sdk/docs/reference_guide/intellij_artifacts.html + +pluginGroup = ru.basecode.ide.rest.plugin +pluginName_ = Rest Client +pluginVersion = 0.2.0 +pluginSinceBuild = 202.6397.94 +pluginUntilBuild = + +platformType = IC +platformVersion = 2020.2 +platformDownloadSources = true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 zcma&O1CT9Y(k9%tZQHhO+qUh#ZQHhO+qmuS+qP|E@9xZO?0h@l{(r>DQ>P;GjjD{w zH}lENr;dU&FbEU?00aa80D$0M0RRB{U*7-#kbjS|qAG&4l5%47zyJ#WrfA#1$1Ctx zf&Z_d{GW=lf^w2#qRJ|CvSJUi(^E3iv~=^Z(zH}F)3Z%V3`@+rNB7gTVU{Bb~90p|f+0(v;nz01EG7yDMX9@S~__vVgv%rS$+?IH+oZ03D5zYrv|^ zC1J)SruYHmCki$jLBlTaE5&dFG9-kq3!^i>^UQL`%gn6)jz54$WDmeYdsBE9;PqZ_ zoGd=P4+|(-u4U1dbAVQrFWoNgNd;0nrghPFbQrJctO>nwDdI`Q^i0XJDUYm|T|RWc zZ3^Qgo_Qk$%Fvjj-G}1NB#ZJqIkh;kX%V{THPqOyiq)d)0+(r9o(qKlSp*hmK#iIY zA^)Vr$-Hz<#SF=0@tL@;dCQsm`V9s1vYNq}K1B)!XSK?=I1)tX+bUV52$YQu*0%fnWEukW>mxkz+%3-S!oguE8u#MGzST8_Dy^#U?fA@S#K$S@9msUiX!gd_ow>08w5)nX{-KxqMOo7d?k2&?Vf z&diGDtZr(0cwPe9z9FAUSD9KC)7(n^lMWuayCfxzy8EZsns%OEblHFSzP=cL6}?J| z0U$H!4S_TVjj<`6dy^2j`V`)mC;cB%* z8{>_%E1^FH!*{>4a7*C1v>~1*@TMcLK{7nEQ!_igZC}ikJ$*<$yHy>7)oy79A~#xE zWavoJOIOC$5b6*q*F_qN1>2#MY)AXVyr$6x4b=$x^*aqF*L?vmj>Mgv+|ITnw_BoW zO?jwHvNy^prH{9$rrik1#fhyU^MpFqF2fYEt(;4`Q&XWOGDH8k6M=%@fics4ajI;st# zCU^r1CK&|jzUhRMv;+W~6N;u<;#DI6cCw-otsc@IsN3MoSD^O`eNflIoR~l4*&-%RBYk@gb^|-JXs&~KuSEmMxB}xSb z@K76cXD=Y|=I&SNC2E+>Zg?R6E%DGCH5J1nU!A|@eX9oS(WPaMm==k2s_ueCqdZw| z&hqHp)47`c{BgwgvY2{xz%OIkY1xDwkw!<0veB#yF4ZKJyabhyyVS`gZepcFIk%e2 zTcrmt2@-8`7i-@5Nz>oQWFuMC_KlroCl(PLSodswHqJ3fn<;gxg9=}~3x_L3P`9Sn zChIf}8vCHvTriz~T2~FamRi?rh?>3bX1j}%bLH+uFX+p&+^aXbOK7clZxdU~6Uxgy z8R=obwO4dL%pmVo*Ktf=lH6hnlz_5k3cG;m8lgaPp~?eD!Yn2kf)tU6PF{kLyn|oI@eQ`F z3IF7~Blqg8-uwUuWZScRKn%c2_}dXB6Dx_&xR*n9M9LXasJhtZdr$vBY!rP{c@=)& z#!?L$2UrkvClwQO>U*fSMs67oSj2mxiJ$t;E|>q%Kh_GzzWWO&3;ufU%2z%ucBU8H z3WIwr$n)cfCXR&>tyB7BcSInK>=ByZA%;cVEJhcg<#6N{aZC4>K41XF>ZgjG`z_u& zGY?;Ad?-sgiOnI`oppF1o1Gurqbi*;#x2>+SSV6|1^G@ooVy@fg?wyf@0Y!UZ4!}nGuLeC^l)6pwkh|oRY`s1Pm$>zZ3u-83T|9 zGaKJIV3_x+u1>cRibsaJpJqhcm%?0-L;2 zitBrdRxNmb0OO2J%Y&Ym(6*`_P3&&5Bw157{o7LFguvxC$4&zTy#U=W*l&(Q2MNO} zfaUwYm{XtILD$3864IA_nn34oVa_g^FRuHL5wdUd)+W-p-iWCKe8m_cMHk+=? zeKX)M?Dt(|{r5t7IenkAXo%&EXIb-i^w+0CX0D=xApC=|Xy(`xy+QG^UyFe z+#J6h_&T5i#sV)hj3D4WN%z;2+jJcZxcI3*CHXGmOF3^)JD5j&wfX)e?-|V0GPuA+ zQFot%aEqGNJJHn$!_}#PaAvQ^{3-Ye7b}rWwrUmX53(|~i0v{}G_sI9uDch_brX&6 zWl5Ndj-AYg(W9CGfQf<6!YmY>Ey)+uYd_JNXH=>|`OH-CDCmcH(0%iD_aLlNHKH z7bcW-^5+QV$jK?R*)wZ>r9t}loM@XN&M-Pw=F#xn(;u3!(3SXXY^@=aoj70;_=QE9 zGghsG3ekq#N||u{4We_25U=y#T*S{4I{++Ku)> zQ!DZW;pVcn>b;&g2;YE#+V`v*Bl&Y-i@X6D*OpNA{G@JAXho&aOk(_j^weW{#3X5Y z%$q_wpb07EYPdmyH(1^09i$ca{O<}7) zRWncXdSPgBE%BM#by!E>tdnc$8RwUJg1*x($6$}ae$e9Knj8gvVZe#bLi!<+&BkFj zg@nOpDneyc+hU9P-;jmOSMN|*H#>^Ez#?;%C3hg_65leSUm;iz)UkW)jX#p)e&S&M z1|a?wDzV5NVnlhRBCd_;F87wp>6c<&nkgvC+!@KGiIqWY4l}=&1w7|r6{oBN8xyzh zG$b#2=RJp_iq6)#t5%yLkKx(0@D=C3w+oiXtSuaQ%I1WIb-eiE$d~!)b@|4XLy!CZ z9p=t=%3ad@Ep+<9003D2KZ5VyP~_n$=;~r&YUg5UZ0KVD&tR1DHy9x)qWtKJp#Kq# zP*8p#W(8JJ_*h_3W}FlvRam?<4Z+-H77^$Lvi+#vmhL9J zJ<1SV45xi;SrO2f=-OB(7#iNA5)x1uNC-yNxUw|!00vcW2PufRm>e~toH;M0Q85MQLWd?3O{i8H+5VkR@l9Dg-ma ze2fZ%>G(u5(k9EHj2L6!;(KZ8%8|*-1V|B#EagbF(rc+5iL_5;Eu)L4Z-V;0HfK4d z*{utLse_rvHZeQ>V5H=f78M3Ntg1BPxFCVD{HbNA6?9*^YIq;B-DJd{Ca2L#)qWP? zvX^NhFmX?CTWw&Ns}lgs;r3i+Bq@y}Ul+U%pzOS0Fcv9~aB(0!>GT0)NO?p=25LjN z2bh>6RhgqD7bQj#k-KOm@JLgMa6>%-ok1WpOe)FS^XOU{c?d5shG(lIn3GiVBxmg`u%-j=)^v&pX1JecJics3&jvPI)mDut52? z3jEA)DM%}BYbxxKrizVYwq?(P&19EXlwD9^-6J+4!}9{ywR9Gk42jjAURAF&EO|~N z)?s>$Da@ikI4|^z0e{r`J8zIs>SpM~Vn^{3fArRu;?+43>lD+^XtUcY1HidJwnR6+ z!;oG2=B6Z_=M%*{z-RaHc(n|1RTKQdNjjV!Pn9lFt^4w|AeN06*j}ZyhqZ^!-=cyGP_ShV1rGxkx8t zB;8`h!S{LD%ot``700d0@Grql(DTt4Awgmi+Yr0@#jbe=2#UkK%rv=OLqF)9D7D1j z!~McAwMYkeaL$~kI~90)5vBhBzWYc3Cj1WI0RS`z000R8-@ET0dA~*r(gSiCJmQMN&4%1D zyVNf0?}sBH8zNbBLn>~(W{d3%@kL_eQ6jEcR{l>C|JK z(R-fA!z|TTRG40|zv}7E@PqCAXP3n`;%|SCQ|ZS%ym$I{`}t3KPL&^l5`3>yah4*6 zifO#{VNz3)?ZL$be;NEaAk9b#{tV?V7 zP|wf5YA*1;s<)9A4~l3BHzG&HH`1xNr#%){4xZ!jq%o=7nN*wMuXlFV{HaiQLJ`5G zBhDi#D(m`Q1pLh@Tq+L;OwuC52RdW7b8}~60WCOK5iYMUad9}7aWBuILb({5=z~YF zt?*Jr5NG+WadM{mDL>GyiByCuR)hd zA=HM?J6l1Xv0Dl+LW@w$OTcEoOda^nFCw*Sy^I@$sSuneMl{4ys)|RY#9&NxW4S)9 zq|%83IpslTLoz~&vTo!Ga@?rj_kw{|k{nv+w&Ku?fyk4Ki4I?);M|5Axm)t+BaE)D zm(`AQ#k^DWrjbuXoJf2{Aj^KT zFb1zMSqxq|vceV+Mf-)$oPflsO$@*A0n0Z!R{&(xh8s}=;t(lIy zv$S8x>m;vQNHuRzoaOo?eiWFe{0;$s`Bc+Osz~}Van${u;g(su`3lJ^TEfo~nERfP z)?aFzpDgnLYiERsKPu|0tq4l2wT)Atr6Qb%m-AUn6HnCue*yWICp7TjW$@sO zm5rm4aTcPQ(rfi7a`xP7cKCFrJD}*&_~xgLyr^-bmsL}y;A5P|al8J3WUoBSjqu%v zxC;mK!g(7r6RRJ852Z~feoC&sD3(6}^5-uLK8o)9{8L_%%rItZK9C){UxB|;G>JbP zsRRtS4-3B*5c+K2kvmgZK8472%l>3cntWUOVHxB|{Ay~aOg5RN;{PJgeVD*H%ac+y!h#wi%o2bF2Ca8IyMyH{>4#{E_8u^@+l-+n=V}Sq?$O z{091@v%Bd*3pk0^2UtiF9Z+(a@wy6 zUdw8J*ze$K#=$48IBi1U%;hmhO>lu!uU;+RS}p&6@rQila7WftH->*A4=5W|Fmtze z)7E}jh@cbmr9iup^i%*(uF%LG&!+Fyl@LFA-}Ca#bxRfDJAiR2dt6644TaYw1Ma79 zt8&DYj31j^5WPNf5P&{)J?WlCe@<3u^78wnd(Ja4^a>{^Tw}W>|Cjt^If|7l^l)^Q zbz|7~CF(k_9~n|h;ysZ+jHzkXf(*O*@5m zLzUmbHp=x!Q|!9NVXyipZ3)^GuIG$k;D)EK!a5=8MFLI_lpf`HPKl=-Ww%z8H_0$j ztJ||IfFG1lE9nmQ0+jPQy zCBdKkjArH@K7jVcMNz);Q(Q^R{d5G?-kk;Uu_IXSyWB)~KGIizZL(^&qF;|1PI7!E zTP`%l)gpX|OFn&)M%txpQ2F!hdA~hX1Cm5)IrdljqzRg!f{mN%G~H1&oqe`5eJCIF zHdD7O;AX-{XEV(a`gBFJ9ews#CVS2y!&>Cm_dm3C8*n3MA*e67(WC?uP@8TXuMroq z{#w$%z@CBIkRM7?}Xib+>hRjy?%G!fiw8! z8(gB+8J~KOU}yO7UGm&1g_MDJ$IXS!`+*b*QW2x)9>K~Y*E&bYMnjl6h!{17_8d!%&9D`a7r&LKZjC<&XOvTRaKJ1 zUY@hl5^R&kZl3lU3njk`3dPzxj$2foOL26r(9zsVF3n_F#v)s5vv3@dgs|lP#eylq62{<-vczqP!RpVBTgI>@O6&sU>W|do17+#OzQ7o5A$ICH z?GqwqnK^n2%LR;$^oZM;)+>$X3s2n}2jZ7CdWIW0lnGK-b#EG01)P@aU`pg}th&J-TrU`tIpb5t((0eu|!u zQz+3ZiOQ^?RxxK4;zs=l8q!-n7X{@jSwK(iqNFiRColuEOg}!7cyZi`iBX4g1pNBj zAPzL?P^Ljhn;1$r8?bc=#n|Ed7wB&oHcw()&*k#SS#h}jO?ZB246EGItsz*;^&tzp zu^YJ0=lwsi`eP_pU8}6JA7MS;9pfD;DsSsLo~ogzMNP70@@;Fm8f0^;>$Z>~}GWRw!W5J3tNX*^2+1f3hz{~rIzJo z6W%J(H!g-eI_J1>0juX$X4Cl6i+3wbc~k146UIX&G22}WE>0ga#WLsn9tY(&29zBvH1$`iWtTe zG2jYl@P!P)eb<5DsR72BdI7-zP&cZNI{7q3e@?N8IKc4DE#UVr->|-ryuJXk^u^>4 z$3wE~=q390;XuOQP~TNoDR?#|NSPJ%sTMInA6*rJ%go|=YjGe!B>z6u$IhgQSwoV* zjy3F2#I>uK{42{&IqP59)Y(1*Z>>#W8rCf4_eVsH)`v!P#^;BgzKDR`ARGEZzkNX+ zJUQu=*-ol=Xqqt5=`=pA@BIn@6a9G8C{c&`i^(i+BxQO9?YZ3iu%$$da&Kb?2kCCo zo7t$UpSFWqmydXf@l3bVJ=%K?SSw)|?srhJ-1ZdFu*5QhL$~-IQS!K1s@XzAtv6*Y zl8@(5BlWYLt1yAWy?rMD&bwze8bC3-GfNH=p zynNFCdxyX?K&G(ZZ)afguQ2|r;XoV^=^(;Cku#qYn4Lus`UeKt6rAlFo_rU`|Rq z&G?~iWMBio<78of-2X(ZYHx~=U0Vz4btyXkctMKdc9UM!vYr~B-(>)(Hc|D zMzkN4!PBg%tZoh+=Gba!0++d193gbMk2&krfDgcbx0jI92cq?FFESVg0D$>F+bil} zY~$)|>1HZsX=5sAZ2WgPB5P=8X#TI+NQ(M~GqyVB53c6IdX=k>Wu@A0Svf5#?uHaF zsYn|koIi3$(%GZ2+G+7Fv^lHTb#5b8sAHSTnL^qWZLM<(1|9|QFw9pnRU{svj}_Al zL)b9>fN{QiA($8peNEJyy`(a{&uh-T4_kdZFIVsKKVM(?05}76EEz?#W za^fiZOAd14IJ4zLX-n7Lq0qlQ^lW8Cvz4UKkV9~P}>sq0?xD3vg+$4vLm~C(+ zM{-3Z#qnZ09bJ>}j?6ry^h+@PfaD7*jZxBEY4)UG&daWb??6)TP+|3#Z&?GL?1i+280CFsE|vIXQbm| zM}Pk!U`U5NsNbyKzkrul-DzwB{X?n3E6?TUHr{M&+R*2%yOiXdW-_2Yd6?38M9Vy^ z*lE%gA{wwoSR~vN0=no}tP2Ul5Gk5M(Xq`$nw#ndFk`tcpd5A=Idue`XZ!FS>Q zG^0w#>P4pPG+*NC9gLP4x2m=cKP}YuS!l^?sHSFftZy{4CoQrb_ z^20(NnG`wAhMI=eq)SsIE~&Gp9Ne0nD4%Xiu|0Fj1UFk?6avDqjdXz{O1nKao*46y zT8~iA%Exu=G#{x=KD;_C&M+Zx4+n`sHT>^>=-1YM;H<72k>$py1?F3#T1*ef9mLZw z5naLQr?n7K;2l+{_uIw*_1nsTn~I|kkCgrn;|G~##hM;9l7Jy$yJfmk+&}W@JeKcF zx@@Woiz8qdi|D%aH3XTx5*wDlbs?dC1_nrFpm^QbG@wM=i2?Zg;$VK!c^Dp8<}BTI zyRhAq@#%2pGV49*Y5_mV4+OICP|%I(dQ7x=6Ob}>EjnB_-_18*xrY?b%-yEDT(wrO z9RY2QT0`_OpGfMObKHV;QLVnrK%mc?$WAdIT`kJQT^n%GuzE7|9@k3ci5fYOh(287 zuIbg!GB3xLg$YN=n)^pHGB0jH+_iIiC=nUcD;G6LuJsjn2VI1cyZx=a?ShCsF==QK z;q~*m&}L<-cb+mDDXzvvrRsybcgQ;Vg21P(uLv5I+eGc7o7tc6`;OA9{soHFOz zT~2?>Ts}gprIX$wRBb4yE>ot<8+*Bv`qbSDv*VtRi|cyWS>)Fjs>fkNOH-+PX&4(~ z&)T8Zam2L6puQl?;5zg9h<}k4#|yH9czHw;1jw-pwBM*O2hUR6yvHATrI%^mvs9q_ z&ccT0>f#eDG<^WG^q@oVqlJrhxH)dcq2cty@l3~|5#UDdExyXUmLQ}f4#;6fI{f^t zDCsgIJ~0`af%YR%Ma5VQq-p21k`vaBu6WE?66+5=XUd%Ay%D$irN>5LhluRWt7 zov-=f>QbMk*G##&DTQyou$s7UqjjW@k6=!I@!k+S{pP8R(2=e@io;N8E`EOB;OGoI zw6Q+{X1_I{OO0HPpBz!X!@`5YQ2)t{+!?M_iH25X(d~-Zx~cXnS9z>u?+If|iNJbx zyFU2d1!ITX64D|lE0Z{dLRqL1Ajj=CCMfC4lD3&mYR_R_VZ>_7_~|<^o*%_&jevU+ zQ4|qzci=0}Jydw|LXLCrOl1_P6Xf@c0$ieK2^7@A9UbF{@V_0p%lqW|L?5k>bVM8|p5v&2g;~r>B8uo<4N+`B zH{J)h;SYiIVx@#jI&p-v3dwL5QNV1oxPr8J%ooezTnLW>i*3Isb49%5i!&ac_dEXv zvXmVUck^QHmyrF8>CGXijC_R-y(Qr{3Zt~EmW)-nC!tiH`wlw5D*W7Pip;T?&j%kX z6DkZX4&}iw>hE(boLyjOoupf6JpvBG8}jIh!!VhnD0>}KSMMo{1#uU6kiFcA04~|7 zVO8eI&x1`g4CZ<2cYUI(n#wz2MtVFHx47yE5eL~8bot~>EHbevSt}LLMQX?odD{Ux zJMnam{d)W4da{l7&y-JrgiU~qY3$~}_F#G7|MxT)e;G{U`In&?`j<5D->}cb{}{T(4DF0BOk-=1195KB-E*o@c?`>y#4=dMtYtSY=&L{!TAjFVcq0y@AH`vH! z$41+u!Ld&}F^COPgL(EE{0X7LY&%D7-(?!kjFF7=qw<;`V{nwWBq<)1QiGJgUc^Vz ztMUlq1bZqKn17|6x6iAHbWc~l1HcmAxr%$Puv!znW)!JiukwIrqQ00|H$Z)OmGG@= zv%A8*4cq}(?qn4rN6o`$Y))(MyXr8R<2S^J+v(wmFmtac!%VOfN?&(8Nr!T@kV`N; z*Q33V3t`^rN&aBiHet)18wy{*wi1=W!B%B-Q6}SCrUl$~Hl{@!95ydml@FK8P=u4s z4e*7gV2s=YxEvskw2Ju!2%{8h01rx-3`NCPc(O zH&J0VH5etNB2KY6k4R@2Wvl^Ck$MoR3=)|SEclT2ccJ!RI9Nuter7u9@;sWf-%um;GfI!=eEIQ2l2p_YWUd{|6EG ze{yO6;lMc>;2tPrsNdi@&1K6(1;|$xe8vLgiouj%QD%gYk`4p{Ktv9|j+!OF-P?@p z;}SV|oIK)iwlBs+`ROXkhd&NK zzo__r!B>tOXpBJMDcv!Mq54P+n4(@dijL^EpO1wdg~q+!DT3lB<>9AANSe!T1XgC=J^)IP0XEZ()_vpu!!3HQyJhwh?r`Ae%Yr~b% zO*NY9t9#qWa@GCPYOF9aron7thfWT`eujS4`t2uG6)~JRTI;f(ZuoRQwjZjp5Pg34 z)rp$)Kr?R+KdJ;IO;pM{$6|2y=k_siqvp%)2||cHTe|b5Ht8&A{wazGNca zX$Ol?H)E_R@SDi~4{d-|8nGFhZPW;Cts1;08TwUvLLv&_2$O6Vt=M)X;g%HUr$&06 zISZb(6)Q3%?;3r~*3~USIg=HcJhFtHhIV(siOwV&QkQe#J%H9&E21!C*d@ln3E@J* zVqRO^<)V^ky-R|%{(9`l-(JXq9J)1r$`uQ8a}$vr9E^nNiI*thK8=&UZ0dsFN_eSl z(q~lnD?EymWLsNa3|1{CRPW60>DSkY9YQ;$4o3W7Ms&@&lv9eH!tk~N&dhqX&>K@} zi1g~GqglxkZ5pEFkllJ)Ta1I^c&Bt6#r(QLQ02yHTaJB~- zCcE=5tmi`UA>@P=1LBfBiqk)HB4t8D?02;9eXj~kVPwv?m{5&!&TFYhu>3=_ zsGmYZ^mo*-j69-42y&Jj0cBLLEulNRZ9vXE)8~mt9C#;tZs;=#M=1*hebkS;7(aGf zcs7zH(I8Eui9UU4L--))yy`&d&$In&VA2?DAEss4LAPCLd>-$i?lpXvn!gu^JJ$(DoUlc6wE98VLZ*z`QGQov5l4Fm_h?V-;mHLYDVOwKz7>e4+%AzeO>P6v}ndPW| zM>m#6Tnp7K?0mbK=>gV}=@k*0Mr_PVAgGMu$j+pWxzq4MAa&jpCDU&-5eH27Iz>m^ zax1?*HhG%pJ((tkR(V(O(L%7v7L%!_X->IjS3H5kuXQT2!ow(;%FDE>16&3r){!ex zhf==oJ!}YU89C9@mfDq!P3S4yx$aGB?rbtVH?sHpg?J5C->!_FHM%Hl3#D4eplxzQ zRA+<@LD%LKSkTk2NyWCg7u=$%F#;SIL44~S_OGR}JqX}X+=bc@swpiClB`Zbz|f!4 z7Ysah7OkR8liXfI`}IIwtEoL}(URrGe;IM8%{>b1SsqXh)~w}P>yiFRaE>}rEnNkT z!HXZUtxUp1NmFm)Dm@-{FI^aRQqpSkz}ZSyKR%Y}YHNzBk)ZIp} zMtS=aMvkgWKm9&oTcU0?S|L~CDqA+sHpOxwnswF-fEG)cXCzUR?ps@tZa$=O)=L+5 zf%m58cq8g_o}3?Bhh+c!w4(7AjxwQ3>WnVi<{{38g7yFboo>q|+7qs<$8CPXUFAN< zG&}BHbbyQ5n|qqSr?U~GY{@GJ{(Jny{bMaOG{|IkUj7tj^9pa9|FB_<+KHLxSxR;@ zHpS$4V)PP+tx}22fWx(Ku9y+}Ap;VZqD0AZW4gCDTPCG=zgJmF{|x;(rvdM|2|9a}cex6xrMkERnkE;}jvU-kmzd%_J50$M`lIPCKf+^*zL=@LW`1SaEc%=m zQ+lT06Gw+wVwvQ9fZ~#qd430v2HndFsBa9WjD0P}K(rZYdAt^5WQIvb%D^Q|pkVE^ zte$&#~zmULFACGfS#g=2OLOnIf2Of-k!(BIHjs77nr!5Q1*I9 z1%?=~#Oss!rV~?-6Gm~BWJiA4mJ5TY&iPm_$)H1_rTltuU1F3I(qTQ^U$S>%$l z)Wx1}R?ij0idp@8w-p!Oz{&*W;v*IA;JFHA9%nUvVDy7Q8woheC#|8QuDZb-L_5@R zOqHwrh|mVL9b=+$nJxM`3eE{O$sCt$UK^2@L$R(r^-_+z?lOo+me-VW=Zw z-Bn>$4ovfWd%SPY`ab-u9{INc*k2h+yH%toDHIyqQ zO68=u`N}RIIs7lsn1D){)~%>ByF<>i@qFb<-axvu(Z+6t7v<^z&gm9McRB~BIaDn$ z#xSGT!rzgad8o>~kyj#h1?7g96tOcCJniQ+*#=b7wPio>|6a1Z?_(TS{)KrPe}(8j z!#&A=k(&Pj^F;r)CI=Z{LVu>uj!_W1q4b`N1}E(i%;BWjbEcnD=mv$FL$l?zS6bW!{$7j1GR5ocn94P2u{ z70tAAcpqtQo<@cXw~@i-@6B23;317|l~S>CB?hR5qJ%J3EFgyBdJd^fHZu7AzHF(BQ!tyAz^L0`X z23S4Fe{2X$W0$zu9gm%rg~A>ijaE#GlYlrF9$ds^QtaszE#4M(OLVP2O-;XdT(XIC zatwzF*)1c+t~c{L=fMG8Z=k5lv>U0;C{caN1NItnuSMp)6G3mbahu>E#sj&oy94KC zpH}8oEw{G@N3pvHhp{^-YaZeH;K+T_1AUv;IKD<=mv^&Ueegrb!yf`4VlRl$M?wsl zZyFol(2|_QM`e_2lYSABpKR{{NlxlDSYQNkS;J66aT#MSiTx~;tUmvs-b*CrR4w=f z8+0;*th6kfZ3|5!Icx3RV11sp=?`0Jy3Fs0N4GZQMN=8HmT6%x9@{Dza)k}UwL6JT zHRDh;%!XwXr6yuuy`4;Xsn0zlR$k%r%9abS1;_v?`HX_hI|+EibVnlyE@3aL5vhQq zlIG?tN^w@0(v9M*&L+{_+RQZw=o|&BRPGB>e5=ys7H`nc8nx)|-g;s7mRc7hg{GJC zAe^vCIJhajmm7C6g! zL&!WAQ~5d_5)00?w_*|*H>3$loHrvFbitw#WvLB!JASO?#5Ig5$Ys10n>e4|3d;tS zELJ0|R4n3Az(Fl3-r^QiV_C;)lQ1_CW{5bKS15U|E9?ZgLec@%kXr84>5jV2a5v=w z?pB1GPdxD$IQL4)G||B_lI+A=08MUFFR4MxfGOu07vfIm+j=z9tp~5i_6jb`tR>qV z$#`=BQ*jpCjm$F0+F)L%xRlnS%#&gro6PiRfu^l!EVan|r3y}AHJQOORGx4~ z&<)3=K-tx518DZyp%|!EqpU!+X3Et7n2AaC5(AtrkW>_57i}$eqs$rupubg0a1+WO zGHZKLN2L0D;ab%{_S1Plm|hx8R?O14*w*f&2&bB050n!R2by zw!@XOQx$SqZ5I<(Qu$V6g>o#A!JVwErWv#(Pjx=KeS0@hxr4?13zj#oWwPS(7Ro|v z>Mp@Kmxo79q|}!5qtX2-O@U&&@6s~!I&)1WQIl?lTnh6UdKT_1R640S4~f=_xoN3- zI+O)$R@RjV$F=>Ti7BlnG1-cFKCC(t|Qjm{SalS~V-tX#+2ekRhwmN zZr`8{QF6y~Z!D|{=1*2D-JUa<(1Z=;!Ei!KiRNH?o{p5o3crFF=_pX9O-YyJchr$~ zRC`+G+8kx~fD2k*ZIiiIGR<8r&M@3H?%JVOfE>)})7ScOd&?OjgAGT@WVNSCZ8N(p zuQG~76GE3%(%h1*vUXg$vH{ua0b`sQ4f0*y=u~lgyb^!#CcPJa2mkSEHGLsnO^kb$ zru5_l#nu=Y{rSMWiYx?nO{8I!gH+?wEj~UM?IrG}E|bRIBUM>UlY<`T1EHpRr36vv zBi&dG8oxS|J$!zoaq{+JpJy+O^W(nt*|#g32bd&K^w-t>!Vu9N!k9eA8r!Xc{utY> zg9aZ(D2E0gL#W0MdjwES-7~Wa8iubPrd?8-$C4BP?*wok&O8+ykOx{P=Izx+G~hM8 z*9?BYz!T8~dzcZr#ux8kS7u7r@A#DogBH8km8Ry4slyie^n|GrTbO|cLhpqgMdsjX zJ_LdmM#I&4LqqsOUIXK8gW;V0B(7^$y#h3h>J0k^WJfAMeYek%Y-Dcb_+0zPJez!GM zAmJ1u;*rK=FNM0Nf}Y!!P9c4)HIkMnq^b;JFd!S3?_Qi2G#LIQ)TF|iHl~WKK6JmK zbv7rPE6VkYr_%_BT}CK8h=?%pk@3cz(UrZ{@h40%XgThP*-Oeo`T0eq9 zA8BnWZKzCy5e&&_GEsU4*;_k}(8l_&al5K-V*BFM=O~;MgRkYsOs%9eOY6s6AtE*<7GQAR2ulC3RAJrG_P1iQK5Z~&B z&f8X<>yJV6)oDGIlS$Y*D^Rj(cszTy5c81a5IwBr`BtnC6_e`ArI8CaTX_%rx7;cn zR-0?J_LFg*?(#n~G8cXut(1nVF0Oka$A$1FGcERU<^ggx;p@CZc?3UB41RY+wLS`LWFNSs~YP zuw1@DNN3lTd|jDL7gjBsd9}wIw}4xT2+8dBQzI00m<@?c2L%>}QLfK5%r!a-iII`p zX@`VEUH)uj^$;7jVUYdADQ2k*!1O3WdfgF?OMtUXNpQ1}QINamBTKDuv19^{$`8A1 zeq%q*O0mi@(%sZU>Xdb0Ru96CFqk9-L3pzLVsMQ`Xpa~N6CR{9Rm2)A|CI21L(%GW zh&)Y$BNHa=FD+=mBw3{qTgw)j0b!Eahs!rZnpu)z!!E$*eXE~##yaXz`KE5(nQM`s zD!$vW9XH)iMxu9R>r$VlLk9oIR%HxpUiW=BK@4U)|1WNQ=mz9a z^!KkO=>GaJ!GBXm{KJj^;kh-MkUlEQ%lza`-G&}C5y1>La1sR6hT=d*NeCnuK%_LV zOXt$}iP6(YJKc9j-Fxq~*ItVUqljQ8?oaysB-EYtFQp9oxZ|5m0^Hq(qV!S+hq#g( z?|i*H2MIr^Kxgz+3vIljQ*Feejy6S4v~jKEPTF~Qhq!(ms5>NGtRgO5vfPPc4Z^AM zTj!`5xEreIN)vaNxa|q6qWdg>+T`Ol0Uz)ckXBXEGvPNEL3R8hB3=C5`@=SYgAju1 z!)UBr{2~=~xa{b8>x2@C7weRAEuatC)3pkRhT#pMPTpSbA|tan%U7NGMvzmF?c!V8 z=pEWxbdXbTAGtWTyI?Fml%lEr-^AE}w#l(<7OIw;ctw}imYax&vR4UYNJZK6P7ZOd zP87XfhnUHxCUHhM@b*NbTi#(-8|wcv%3BGNs#zRCVV(W?1Qj6^PPQa<{yaBwZ`+<`w|;rqUY_C z&AeyKwwf*q#OW-F()lir=T^<^wjK65Lif$puuU5+tk$;e_EJ;Lu+pH>=-8=PDhkBg z8cWt%@$Sc#C6F$Vd+0507;{OOyT7Hs%nKS88q-W!$f~9*WGBpHGgNp}=C*7!RiZ5s zn1L_DbKF@B8kwhDiLKRB@lsXVVLK|ph=w%_`#owlf@s@V(pa`GY$8h%;-#h@TsO|Y8V=n@*!Rog7<7Cid%apR|x zOjhHCyfbIt%+*PCveTEcuiDi%Wx;O;+K=W?OFUV%)%~6;gl?<0%)?snDDqIvkHF{ zyI02)+lI9ov42^hL>ZRrh*HhjF9B$A@=H94iaBESBF=eC_KT$8A@uB^6$~o?3Wm5t1OIaqF^~><2?4e3c&)@wKn9bD? zoeCs;H>b8DL^F&>Xw-xjZEUFFTv>JD^O#1E#)CMBaG4DX9bD(Wtc8Rzq}9soQ8`jf zeSnHOL}<+WVSKp4kkq&?SbETjq6yr@4%SAqOG=9E(3YeLG9dtV+8vmzq+6PFPk{L; z(&d++iu=^F%b+ea$i2UeTC{R*0Isk;vFK!no<;L+(`y`3&H-~VTdKROkdyowo1iqR zbVW(3`+(PQ2>TKY>N!jGmGo7oeoB8O|P_!Ic@ zZ^;3dnuXo;WJ?S+)%P>{Hcg!Jz#2SI(s&dY4QAy_vRlmOh)QHvs_7c&zkJCmJGVvV zX;Mtb>QE+xp`KyciG$Cn*0?AK%-a|=o!+7x&&yzHQOS>8=B*R=niSnta^Pxp1`=md z#;$pS$4WCT?mbiCYU?FcHGZ#)kHVJTTBt^%XE(Q};aaO=Zik0UgLcc0I(tUpt(>|& zcxB_|fxCF7>&~5eJ=Dpn&5Aj{A^cV^^}(7w#p;HG&Q)EaN~~EqrE1qKrMAc&WXIE;>@<&)5;gD2?={Xf@Mvn@OJKw=8Mgn z!JUFMwD+s==JpjhroT&d{$kQAy%+d`a*XxDEVxy3`NHzmITrE`o!;5ClXNPb4t*8P zzAivdr{j_v!=9!^?T3y?gzmqDWX6mkzhIzJ-3S{T5bcCFMr&RPDryMcdwbBuZbsgN zGrp@^i?rcfN7v0NKGzDPGE#4yszxu=I_`MI%Z|10nFjU-UjQXXA?k8Pk|OE<(?ae) zE%vG#eZAlj*E7_3dx#Zz4kMLj>H^;}33UAankJiDy5ZvEhrjr`!9eMD8COp}U*hP+ zF}KIYx@pkccIgyxFm#LNw~G&`;o&5)2`5aogs`1~7cMZQ7zj!%L4E`2yzlQN6REX20&O<9 zKV6fyr)TScJPPzNTC2gL+0x#=u>(({{D7j)c-%tvqls3#Y?Z1m zV5WUE)zdJ{$p>yX;^P!UcXP?UD~YM;IRa#Rs5~l+*$&nO(;Ers`G=0D!twR(0GF@c zHl9E5DQI}Oz74n zfKP>&$q0($T4y$6w(p=ERAFh+>n%iaeRA%!T%<^+pg?M)@ucY<&59$x9M#n+V&>}=nO9wCV{O~lg&v#+jcUj(tQ z`0u1YH)-`U$15a{pBkGyPL0THv1P|4e@pf@3IBZS4dVJPo#H>pWq%Lr0YS-SeWash z8R7=jb28KPMI|_lo#GEO|5B?N_e``H*23{~a!AmUJ+fb4HX-%QI@lSEUxKlGV7z7Q zSKw@-TR>@1RL%w{x}dW#k1NgW+q4yt2Xf1J62Bx*O^WG8OJ|FqI4&@d3_o8Id@*)4 zYrk=>@!wv~mh7YWv*bZhxqSmFh2Xq)o=m;%n$I?GSz49l1$xRpPu_^N(vZ>*>Z<04 z2+rP70oM=NDysd!@fQdM2OcyT?3T^Eb@lIC-UG=Bw{BjQ&P`KCv$AcJ;?`vdZ4){d z&gkoUK{$!$$K`3*O-jyM1~p-7T*qb)Ys>Myt^;#1&a%O@x8A+E>! zY8=eD`ZG)LVagDLBeHg>=atOG?Kr%h4B%E6m@J^C+U|y)XX@f z8oyJDW|9g=<#f<{JRr{y#~euMnv)`7j=%cHWLc}ngjq~7k**6%4u>Px&W%4D94(r* z+akunK}O0DC2A%Xo9jyF;DobX?!1I(7%}@7F>i%&nk*LMO)bMGg2N+1iqtg+r(70q zF5{Msgsm5GS7DT`kBsjMvOrkx&|EU!{{~gL4d2MWrAT=KBQ-^zQCUq{5PD1orxlIL zq;CvlWx#f1NWvh`hg011I%?T_s!e38l*lWVt|~z-PO4~~1g)SrJ|>*tXh=QfXT)%( z+ex+inPvD&O4Ur;JGz>$sUOnWdpSLcm1X%aQDw4{dB!cnj`^muI$CJ2%p&-kULVCE z>$eMR36kN$wCPR+OFDM3-U(VOrp9k3)lI&YVFqd;Kpz~K)@Fa&FRw}L(SoD z9B4a+hQzZT-BnVltst&=kq6Y(f^S4hIGNKYBgMxGJ^;2yrO}P3;r)(-I-CZ)26Y6? z&rzHI_1GCvGkgy-t1E;r^3Le30|%$ebDRu2+gdLG)r=A~Qz`}~&L@aGJ{}vVs_GE* zVUjFnzHiXfKQbpv&bR&}l2bzIjAooB)=-XNcYmrGmBh(&iu@o!^hn0^#}m2yZZUK8 zufVm7Gq0y`Mj;9b>`c?&PZkU0j4>IL=UL&-Lp3j&47B5pAW4JceG{!XCA)kT<%2nqCxj<)uy6XR_uws~>_MEKPOpAQ!H zkn>FKh)<9DwwS*|Y(q?$^N!6(51O0 z^JM~Ax{AI1Oj$fs-S5d4T7Z_i1?{%0SsIuQ&r8#(JA=2iLcTN+?>wOL532%&dMYkT z*T5xepC+V6zxhS@vNbMoi|i)=rpli@R9~P!39tWbSSb904ekv7D#quKbgFEMTb48P zuq(VJ+&L8aWU(_FCD$3^uD!YM%O^K(dvy~Wm2hUuh6bD|#(I39Xt>N1Y{ZqXL`Fg6 zKQ?T2htHN!(Bx;tV2bfTtIj7e)liN-29s1kew>v(D^@)#v;}C4-G=7x#;-dM4yRWm zyY`cS21ulzMK{PoaQ6xChEZ}o_#}X-o}<&0)$1#3we?+QeLt;aVCjeA)hn!}UaKt< zat1fHEx13y-rXNMvpUUmCVzocPmN~-Y4(YJvQ#db)4|%B!rBsgAe+*yor~}FrNH08 z3V!97S}D7d$zbSD{$z;@IYMxM6aHdypIuS*pr_U6;#Y!_?0i|&yU*@16l z*dcMqDQgfNBf}?quiu4e>H)yTVfsp#f+Du0@=Kc41QockXkCkvu>FBd6Q+@FL!(Yx z2`YuX#eMEiLEDhp+9uFqME_E^faV&~9qjBHJkIp~%$x^bN=N)K@kvSVEMdDuzA0sn z88CBG?`RX1@#hQNd`o^V{37)!w|nA)QfiYBE^m=yQKv-fQF+UCMcuEe1d4BH7$?>b zJl-r9@0^Ie=)guO1vOd=i$_4sz>y3x^R7n4ED!5oXL3@5**h(xr%Hv)_gILarO46q+MaDOF%ChaymKoI6JU5Pg;7#2n9-18|S1;AK+ zgsn6;k6-%!QD>D?cFy}8F;r@z8H9xN1jsOBw2vQONVqBVEbkiNUqgw~*!^##ht>w0 zUOykwH=$LwX2j&nLy=@{hr)2O&-wm-NyjW7n~Zs9UlH;P7iP3 zI}S(r0YFVYacnKH(+{*)Tbw)@;6>%=&Th=+Z6NHo_tR|JCI8TJiXv2N7ei7M^Q+RM z?9o`meH$5Yi;@9XaNR#jIK^&{N|DYNNbtdb)XW1Lv2k{E>;?F`#Pq|&_;gm~&~Zc9 zf+6ZE%{x4|{YdtE?a^gKyzr}dA>OxQv+pq|@IXL%WS0CiX!V zm$fCePA%lU{%pTKD7|5NJHeXg=I0jL@$tOF@K*MI$)f?om)D63K*M|r`gb9edD1~Y zc|w7N)Y%do7=0{RC|AziW7#am$)9jciRJ?IWl9PE{G3U+$%FcyKs_0Cgq`=K3@ttV z9g;M!3z~f_?P%y3-ph%vBMeS@p7P&Ea8M@97+%XEj*(1E6vHj==d zjsoviB>j^$_^OI_DEPvFkVo(BGRo%cJeD){6Uckei=~1}>sp299|IRjhXe)%?uP0I zF5+>?0#Ye}T^Y$u_rc4=lPcq4K^D(TZG-w30-YiEM=dcK+4#o*>lJ8&JLi+3UcpZk z!^?95S^C0ja^jwP`|{<+3cBVog$(mRdQmadS+Vh~z zS@|P}=|z3P6uS+&@QsMp0no9Od&27O&14zHXGAOEy zh~OKpymK5C%;LLb467@KgIiVwYbYd6wFxI{0-~MOGfTq$nBTB!{SrWmL9Hs}C&l&l#m?s*{tA?BHS4mVKHAVMqm63H<|c5n0~k)-kbg zXidai&9ZUy0~WFYYKT;oe~rytRk?)r8bptITsWj(@HLI;@=v5|XUnSls7$uaxFRL+ zRVMGuL3w}NbV1`^=Pw*0?>bm8+xfeY(1PikW*PB>>Tq(FR`91N0c2&>lL2sZo5=VD zQY{>7dh_TX98L2)n{2OV=T10~*YzX27i2Q7W86M4$?gZIXZaBq#sA*{PH8){|GUi;oM>e?ua7eF4WFuFYZSG| zze?srg|5Ti8Og{O zeFxuw9!U+zhyk?@w zjsA6(oKD=Ka;A>Ca)oPORxK+kxH#O@zhC!!XS4@=swnuMk>t+JmLmFiE^1aX3f<)D@`%K0FGK^gg1a1j>zi z2KhV>sjU7AX3F$SEqrXSC}fRx64GDoc%!u2Yag68Lw@w9v;xOONf@o)Lc|Uh3<21ctTYu-mFZuHk*+R{GjXHIGq3p)tFtQp%TYqD=j1&y)>@zxoxUJ!G@ zgI0XKmP6MNzw>nRxK$-Gbzs}dyfFzt>#5;f6oR27ql!%+{tr+(`(>%51|k`ML} zY4eE)Lxq|JMas(;JibNQds1bUB&r}ydMQXBY4x(^&fY_&LlQC)3hylc$~8&~|06-D z#T+%66rYbHX%^KuqJED_wuGB+=h`nWA!>1n0)3wZrBG3%`b^Ozv6__dNa@%V14|!D zQ?o$z5u0^8`giv%qE!BzZ!3j;BlDlJDk)h@9{nSQeEk!z9RGW) z${RSF3phEM*ce*>Xdp}585vj$|40=&S{S-GTiE?Op*vY&Lvr9}BO$XWy80IF+6@%n z5*2ueT_g@ofP#u5pxb7n*fv^Xtt7&?SRc{*2Ka-*!BuOpf}neHGCiHy$@Ka1^Dint z;DkmIL$-e)rj4o2WQV%Gy;Xg(_Bh#qeOsTM2f@KEe~4kJ8kNLQ+;(!j^bgJMcNhvklP5Z6I+9Fq@c&D~8Fb-4rmDT!MB5QC{Dsb;BharP*O;SF4& zc$wj-7Oep7#$WZN!1nznc@Vb<_Dn%ga-O#J(l=OGB`dy=Sy&$(5-n3zzu%d7E#^8`T@}V+5B;PP8J14#4cCPw-SQTdGa2gWL0*zKM z#DfSXs_iWOMt)0*+Y>Lkd=LlyoHjublNLefhKBv@JoC>P7N1_#> zv=mLWe96%EY;!ZGSQDbZWb#;tzqAGgx~uk+-$+2_8U`!ypbwXl z^2E-FkM1?lY@yt8=J3%QK+xaZ6ok=-y%=KXCD^0r!5vUneW>95PzCkOPO*t}p$;-> ze5j-BLT_;)cZQzR2CEsm@rU7GZfFtdp*a|g4wDr%8?2QkIGasRfDWT-Dvy*U{?IHT z*}wGnzdlSptl#ZF^sf)KT|BJs&kLG91^A6ls{CzFprZ6-Y!V0Xysh%9p%iMd7HLsS zN+^Un$tDV)T@i!v?3o0Fsx2qI(AX_$dDkBzQ@fRM%n zRXk6hb9Py#JXUs+7)w@eo;g%QQ95Yq!K_d=z{0dGS+pToEI6=Bo8+{k$7&Z zo4>PH(`ce8E-Ps&uv`NQ;U$%t;w~|@E3WVOCi~R4oj5wP?%<*1C%}Jq%a^q~T7u>K zML5AKfQDv6>PuT`{SrKHRAF+^&edg6+5R_#H?Lz3iGoWo#PCEd0DS;)2U({{X#zU^ zw_xv{4x7|t!S)>44J;KfA|DC?;uQ($l+5Vp7oeqf7{GBF9356nx|&B~gs+@N^gSdd zvb*>&W)|u#F{Z_b`f#GVtQ`pYv3#||N{xj1NgB<#=Odt6{eB%#9RLt5v zIi|0u70`#ai}9fJjKv7dE!9ZrOIX!3{$z_K5FBd-Kp-&e4(J$LD-)NMTp^_pB`RT; zftVVlK2g@+1Ahv2$D){@Y#cL#dUj9*&%#6 zd2m9{1NYp>)6=oAvqdCn5#cx{AJ%S8skUgMglu2*IAtd+z1>B&`MuEAS(D(<6X#Lj z?f4CFx$)M&$=7*>9v1ER4b6!SIz-m0e{o0BfkySREchp?WdVPpQCh!q$t>?rL!&Jg zd#heM;&~A}VEm8Dvy&P|J*eAV&w!&Nx6HFV&B8jJFVTmgLaswn!cx$&%JbTsloz!3 zMEz1d`k==`Ueub_JAy_&`!ogbwx27^ZXgFNAbx=g_I~5nO^r)}&myw~+yY*cJl4$I znNJ32M&K=0(2Dj_>@39`3=FX!v3nZHno_@q^!y}%(yw0PqOo=);6Y@&ylVe>nMOZ~ zd>j#QQSBn3oaWd;qy$&5(5H$Ayi)0haAYO6TH>FR?rhqHmNOO+(})NB zLI@B@v0)eq!ug`>G<@htRlp3n!EpU|n+G+AvXFrWSUsLMBfL*ZB`CRsIVHNTR&b?K zxBgsN0BjfB>UVcJ|x%=-zb%OV7lmZc& zxiupadZVF7)6QuhoY;;FK2b*qL0J-Rn-8!X4ZY$-ZSUXV5DFd7`T41c(#lAeLMoeT z4%g655v@7AqT!i@)Edt5JMbN(=Q-6{=L4iG8RA%}w;&pKmtWvI4?G9pVRp|RTw`g0 zD5c12B&A2&P6Ng~8WM2eIW=wxd?r7A*N+&!Be7PX3s|7~z=APxm=A?5 zt>xB4WG|*Td@VX{Rs)PV0|yK`oI3^xn(4c_j&vgxk_Y3o(-`_5o`V zRTghg6%l@(qodXN;dB#+OKJEEvhfcnc#BeO2|E(5df-!fKDZ!%9!^BJ_4)9P+9Dq5 zK1=(v?KmIp34r?z{NEWnLB3Px{XYwy-akun4F7xTRr2^zeYW{gcK9)>aJDdU5;w5@ zak=<+-PLH-|04pelTb%ULpuuuJC7DgyT@D|p{!V!0v3KpDnRjANN12q6SUR3mb9<- z>2r~IApQGhstZ!3*?5V z8#)hJ0TdZg0M-BK#nGFP>$i=qk82DO z7h;Ft!D5E15OgW)&%lej*?^1~2=*Z5$2VX>V{x8SC+{i10BbtUk9@I#Vi&hX)q
Q!LwySI{Bnv%Sm)yh{^sSVJ8&h_D-BJ_YZe5eCaAWU9b$O2c z$T|{vWVRtOL!xC0DTc(Qbe`ItNtt5hr<)VijD0{U;T#bUEp381_y`%ZIav?kuYG{iyYdEBPW=*xNSc;Rlt6~F4M`5G+VtOjc z*0qGzCb@gME5udTjJA-9O<&TWd~}ysBd(eVT1-H82-doyH9RST)|+Pb{o*;$j9Tjs zhU!IlsPsj8=(x3bAKJTopW3^6AKROHR^7wZ185wJGVhA~hEc|LP;k7NEz-@4p5o}F z`AD6naG3(n=NF9HTH81=F+Q|JOz$7wm9I<+#BSmB@o_cLt2GkW9|?7mM;r!JZp89l zbo!Hp8=n!XH1{GwaDU+k)pGp`C|cXkCU5%vcH)+v@0eK>%7gWxmuMu9YLlChA|_D@ zi#5zovN_!a-0?~pUV-Rj*1P)KwdU-LguR>YM&*Nen+ln8Q$?WFCJg%DY%K}2!!1FE zDv-A%Cbwo^p(lzac&_TZ-l#9kq`mhLcY3h9ZTUVCM(Ad&=EriQY5{jJv<5K&g|*Lk zgV%ILnf1%8V2B0E&;Sp4sYbYOvvMebLwYwzkRQ#F8GpTQq#uv=J`uaSJ34OWITeSGo6+-8Xw znCk*n{kdDEi)Hi&u^)~cs@iyCkFWB2SWZU|Uc%^43ZIZQ-vWNExCCtDWjqHs;;tWf$v{}0{p0Rvxkq``)*>+Akq%|Na zA`@~-Vfe|+(AIlqru+7Ceh4nsVmO9p9jc8}HX^W&ViBDXT+uXbT#R#idPn&L>+#b6 zflC-4C5-X;kUnR~L>PSLh*gvL68}RBsu#2l`s_9KjUWRhiqF`j)`y`2`YU(>3bdBj z?>iyjEhe-~$^I5!nn%B6Wh+I`FvLNvauve~eX<+Ipl&04 zT}};W&1a3%W?dJ2=N#0t?e+aK+%t}5q%jSLvp3jZ%?&F}nOOWr>+{GFIa%wO_2`et z=JzoRR~}iKuuR+azPI8;Gf9)z3kyA4EIOSl!sRR$DlW}0>&?GbgPojmjmnln;cTqCt=ADbE zZ8GAnoM+S1(5$i8^O4t`ue;vO4i}z0wz-QEIVe5_u03;}-!G1NyY8;h^}y;tzY}i5 zqQr#Ur3Fy8sSa$Q0ys+f`!`+>9WbvU_I`Sj;$4{S>O3?#inLHCrtLy~!s#WXV=oVP zeE93*Nc`PBi4q@%Ao$x4lw9vLHM!6mn3-b_cebF|n-2vt-zYVF_&sDE--J-P;2WHo z+@n2areE0o$LjvjlV2X7ZU@j+`{*8zq`JR3gKF#EW|#+{nMyo-a>nFFTg&vhyT=b} zDa8+v0(Dgx0yRL@ZXOYIlVSZ0|MFizy0VPW8;AfA5|pe!#j zX}Py^8fl5SyS4g1WSKKtnyP+_PoOwMMwu`(i@Z)diJp~U54*-miOchy7Z35eL>^M z4p<-aIxH4VUZgS783@H%M7P9hX>t{|RU7$n4T(brCG#h9e9p! z+o`i;EGGq3&pF;~5V~eBD}lC)>if$w%Vf}AFxGqO88|ApfHf&Bvu+xdG)@vuF}Yvk z)o;~k-%+0K0g+L`Wala!$=ZV|z$e%>f0%XoLib%)!R^RoS+{!#X?h-6uu zF&&KxORdZU&EwQFITIRLo(7TA3W}y6X{?Y%y2j0It!ekU#<)$qghZtpcS>L3uh`Uj z7GY;6f$9qKynP#oS3$$a{p^{D+0oJQ71`1?OAn_m8)UGZmj3l*ZI)`V-a>MKGGFG< z&^jg#Ok%(hhm>hSrZ5;Qga4u(?^i>GiW_j9%_7M>j(^|Om$#{k+^*ULnEgzW_1gCICtAD^WpC`A z{9&DXkG#01Xo)U$OC(L5Y$DQ|Q4C6CjUKk1UkPj$nXH##J{c8e#K|&{mA*;b$r0E4 zUNo0jthwA(c&N1l=PEe8Rw_8cEl|-eya9z&H3#n`B$t#+aJ03RFMzrV@gowbe8v(c zIFM60^0&lCFO10NU4w@|61xiZ4CVXeaKjd;d?sv52XM*lS8XiVjgWpRB;&U_C0g+`6B5V&w|O6B*_q zsATxL!M}+$He)1eOWECce#eS@2n^xhlB4<_Nn?yCVEQWDs(r`|@2GqLe<#(|&P0U? z$7V5IgpWf09uIf_RazRwC?qEqRaHyL?iiS05UiGesJy%^>-C{{ypTBI&B0-iUYhk> zIk<5xpsuV@g|z(AZD+C-;A!fTG=df1=<%nxy(a(IS+U{ME4ZbDEBtcD_3V=icT6*_ z)>|J?>&6%nvHhZERBtjK+s4xnut*@>GAmA5m*OTp$!^CHTr}vM4n(X1Q*;{e-Rd2BCF-u@1ZGm z!S8hJ6L=Gl4T_SDa7Xx|-{4mxveJg=ctf`BJ*fy!yF6Dz&?w(Q_6B}WQVtNI!BVBC zKfX<>7vd6C96}XAQmF-Jd?1Q4eTfRB3q7hCh0f!(JkdWT5<{iAE#dKy*Jxq&3a1@~ z8C||Dn2mFNyrUV|<-)C^_y7@8c2Fz+2jrae9deBDu;U}tJ{^xAdxCD248(k;dCJ%o z`y3sADe>U%suxwwv~8A1+R$VB=Q?%U?4joI$um;aH+eCrBqpn- z%79D_7rb;R-;-9RTrwi9dPlg8&@tfWhhZ(Vx&1PQ+6(huX`;M9x~LrW~~#3{j0Bh2kDU$}@!fFQej4VGkJv?M4rU^x!RU zEwhu$!CA_iDjFjrJa`aocySDX16?~;+wgav;}Zut6Mg%C4>}8FL?8)Kgwc(Qlj{@#2Pt0?G`$h7P#M+qoXtlV@d}%c&OzO+QYKK`kyXaK{U(O^2DyIXCZlNQjt0^8~8JzNGrIxhj}}M z&~QZlbx%t;MJ(Vux;2tgNKGlAqphLq%pd}JG9uoVHUo?|hN{pLQ6Em%r*+7t^<);X zm~6=qChlNAVXNN*Sow->*4;}T;l;D1I-5T{Bif@4_}=>l`tK;qqDdt5zvisCKhMAH z#r}`)7VW?LZqfdmXQ%zo5bJ00{Xb9^YKrk0Nf|oIW*K@(=`o2Vndz}ZDyk{!u}PVx zzd--+_WC*U{~DH3{?GI64IB+@On&@9X>EUAo&L+G{L^dozaI4C3G#2wr~hseW@K&g zKWs{uHu-9Je!3;4pE>eBltKUXb^*hG8I&413)$J&{D4N%7PcloU6bn%jPxJyQL?g* z9g+YFFEDiE`8rW^laCNzQmi7CTnPfwyg3VDHRAl>h=In6jeaVOP@!-CP60j3+#vpL zEYmh_oP0{-gTe7Or`L6x)6w?77QVi~jD8lWN@3RHcm80iV%M1A!+Y6iHM)05iC64tb$X2lV_%Txk@0l^hZqi^%Z?#- zE;LE0uFx)R08_S-#(wC=dS&}vj6P4>5ZWjhthP=*Hht&TdLtKDR;rXEX4*z0h74FA zMCINqrh3Vq;s%3MC1YL`{WjIAPkVL#3rj^9Pj9Ss7>7duy!9H0vYF%>1jh)EPqvlr6h%R%CxDsk| z!BACz7E%j?bm=pH6Eaw{+suniuY7C9Ut~1cWfOX9KW9=H><&kQlinPV3h9R>3nJvK z4L9(DRM=x;R&d#a@oFY7mB|m8h4692U5eYfcw|QKwqRsshN(q^v$4$)HgPpAJDJ`I zkqjq(8Cd!K!+wCd=d@w%~e$=gdUgD&wj$LQ1r>-E=O@c ze+Z$x{>6(JA-fNVr)X;*)40Eym1TtUZI1Pwwx1hUi+G1Jlk~vCYeXMNYtr)1?qwyg zsX_e*$h?380O00ou?0R@7-Fc59o$UvyVs4cUbujHUA>sH!}L54>`e` zHUx#Q+Hn&Og#YVOuo*niy*GU3rH;%f``nk#NN5-xrZ34NeH$l`4@t);4(+0|Z#I>Y z)~Kzs#exIAaf--65L0UHT_SvV8O2WYeD>Mq^Y6L!Xu8%vnpofG@w!}R7M28?i1*T&zp3X4^OMCY6(Dg<-! zXmcGQrRgHXGYre7GfTJ)rhl|rs%abKT_Nt24_Q``XH{88NVPW+`x4ZdrMuO0iZ0g` z%p}y};~T5gbb9SeL8BSc`SO#ixC$@QhXxZ=B}L`tP}&k?1oSPS=4%{UOHe0<_XWln zwbl5cn(j-qK`)vGHY5B5C|QZd5)W7c@{bNVXqJ!!n$^ufc?N9C-BF2QK1(kv++h!>$QbAjq)_b$$PcJdV+F7hz0Hu@ zqj+}m0qn{t^tD3DfBb~0B36|Q`bs*xs|$i^G4uNUEBl4g;op-;Wl~iThgga?+dL7s zUP(8lMO?g{GcYpDS{NM!UA8Hco?#}eNEioRBHy4`mq!Pd-9@-97|k$hpEX>xoX+dY zDr$wfm^P&}Wu{!%?)U_(%Mn79$(ywvu*kJ9r4u|MyYLI_67U7%6Gd_vb##Nerf@>& z8W11z$$~xEZt$dPG}+*IZky+os5Ju2eRi;1=rUEeIn>t-AzC_IGM-IXWK3^6QNU+2pe=MBn4I*R@A%-iLDCOHTE-O^wo$sL_h{dcPl=^muAQb`_BRm};=cy{qSkui;`WSsj9%c^+bIDQ z0`_?KX0<-=o!t{u(Ln)v>%VGL z0pC=GB7*AQ?N7N{ut*a%MH-tdtNmNC+Yf$|KS)BW(gQJ*z$d{+{j?(e&hgTy^2|AR9vx1Xre2fagGv0YXWqtNkg*v%40v?BJBt|f9wX5 z{QTlCM}b-0{mV?IG>TW_BdviUKhtosrBqdfq&Frdz>cF~yK{P@(w{Vr7z2qKFwLhc zQuogKO@~YwyS9%+d-zD7mJG~@?EFJLSn!a&mhE5$_4xBl&6QHMzL?CdzEnC~C3$X@ zvY!{_GR06ep5;<#cKCSJ%srxX=+pn?ywDwtJ2{TV;0DKBO2t++B(tIO4)Wh`rD13P z4fE$#%zkd=UzOB74gi=-*CuID&Z3zI^-`4U^S?dHxK8fP*;fE|a(KYMgMUo`THIS1f!*6dOI2 zFjC3O=-AL`6=9pp;`CYPTdVX z8(*?V&%QoipuH0>WKlL8A*zTKckD!paN@~hh zmXzm~qZhMGVdQGd=AG8&20HW0RGV8X{$9LldFZYm zE?}`Q3i?xJRz43S?VFMmqRyvWaS#(~Lempg9nTM$EFDP(Gzx#$r)W&lpFKqcAoJh-AxEw$-bjW>`_+gEi z2w`99#UbFZGiQjS8kj~@PGqpsPX`T{YOj`CaEqTFag;$jY z8_{Wzz>HXx&G*Dx<5skhpETxIdhKH?DtY@b9l8$l?UkM#J-Snmts7bd7xayKTFJ(u zyAT&@6cAYcs{PBfpqZa%sxhJ5nSZBPji?Zlf&}#L?t)vC4X5VLp%~fz2Sx<*oN<7` z?ge=k<=X7r<~F7Tvp9#HB{!mA!QWBOf%EiSJ6KIF8QZNjg&x~-%e*tflL(ji_S^sO ztmib1rp09uon}RcsFi#k)oLs@$?vs(i>5k3YN%$T(5Or(TZ5JW9mA6mIMD08=749$ z!d+l*iu{Il7^Yu}H;lgw=En1sJpCKPSqTCHy4(f&NPelr31^*l%KHq^QE>z>Ks_bH zjbD?({~8Din7IvZeJ>8Ey=e;I?thpzD=zE5UHeO|neioJwG;IyLk?xOz(yO&0DTU~ z^#)xcs|s>Flgmp;SmYJ4g(|HMu3v7#;c*Aa8iF#UZo7CvDq4>8#qLJ|YdZ!AsH%^_7N1IQjCro

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..12d38de --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/resources/1.html b/resources/1.html deleted file mode 100644 index af723ef..0000000 --- a/resources/1.html +++ /dev/null @@ -1,12 +0,0 @@ - - - -

-
-
-
- - \ No newline at end of file diff --git a/resources/1.rest b/resources/1.rest deleted file mode 100644 index 77f7110..0000000 --- a/resources/1.rest +++ /dev/null @@ -1,10 +0,0 @@ - ->>> RESPONSE - - -
-
-
-
- - \ No newline at end of file diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml deleted file mode 100644 index c6ceb1a..0000000 --- a/resources/META-INF/plugin.xml +++ /dev/null @@ -1,61 +0,0 @@ - - ru.basecode.ide.rest.plugin - Simple Rest Client - 1.0 - Denis Chernyshov - - - - - - - - - - - - - - ru.basecode.ide.rest.plugin.RestClientPlugin - - - - - - ru.basecode.ide.rest.plugin.RestProjectComponent - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..477afc6 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,12 @@ +rootProject.name = "idea-rest-client" +pluginManagement { + repositories { + maven{ + url = uri("https://jetbrains.bintray.com/intellij-plugin-service") + } + maven { + url = uri("https://plugins.gradle.org/m2/") + } + mavenCentral() + } +} diff --git a/src/icon/optimise.sh b/src/icon/optimise.sh new file mode 100755 index 0000000..95e28b0 --- /dev/null +++ b/src/icon/optimise.sh @@ -0,0 +1,3 @@ +svgo --folder=. --output=../main/resources/META-INF +# Remove custom style attribute `-inkscape-font-specification:'Londrina Sketch, Normal';` manually +# TODO: do a simple sed? diff --git a/src/icon/pluginIcon.svg b/src/icon/pluginIcon.svg new file mode 100644 index 0000000..b973ed5 --- /dev/null +++ b/src/icon/pluginIcon.svg @@ -0,0 +1,258 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icon/pluginIcon_dark.svg b/src/icon/pluginIcon_dark.svg new file mode 100644 index 0000000..0add319 --- /dev/null +++ b/src/icon/pluginIcon_dark.svg @@ -0,0 +1,254 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icon/restFileType.svg b/src/icon/restFileType.svg new file mode 100644 index 0000000..0402e00 --- /dev/null +++ b/src/icon/restFileType.svg @@ -0,0 +1,157 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + R + + + diff --git a/src/ru/basecode/ide/rest/plugin/Rest.bnf b/src/main/grammars/Rest.bnf similarity index 85% rename from src/ru/basecode/ide/rest/plugin/Rest.bnf rename to src/main/grammars/Rest.bnf index 44d9220..d8d0ab7 100644 --- a/src/ru/basecode/ide/rest/plugin/Rest.bnf +++ b/src/main/grammars/Rest.bnf @@ -1,5 +1,5 @@ { - parserClass="ru.basecode.ide.rest.plugin.parser.RestParser" + parserClass="ru.basecode.ide.rest.plugin.parser._RestParser" extends="com.intellij.extapi.psi.ASTWrapperPsiElement" @@ -13,7 +13,7 @@ tokenTypeClass="ru.basecode.ide.rest.plugin.psi.RestTokenType" } -restFile ::= item_* +restPsiFile ::= item_* //private item_ ::= (WHITE_SPACE|COMMENT|OPTION|PARAM|HEADER|URL|METHOD|SEPARATOR|REQUEST_BODY_LINE|RESPONSE_BODY_LINE|CRLF|BAD_CHARACTER)+ @@ -44,12 +44,12 @@ e_header ::= HEADER headers ::= e_header {e_header|COMMENT|ws}* request_body ::= REQUEST_BODY_LINE {REQUEST_BODY_LINE|COMMENT|ws}* { - mixin="ru.basecode.ide.rest.plugin.psi.impl.RestElementImpl" + mixin="ru.basecode.ide.rest.plugin.psi.impl.RestWrapperPsiElement" } response ::= comments? headers? comments? response_body? response_body ::= RESPONSE_BODY_LINE {RESPONSE_BODY_LINE|COMMENT|ws}* { - mixin="ru.basecode.ide.rest.plugin.psi.impl.RestElementImpl" + mixin="ru.basecode.ide.rest.plugin.psi.impl.RestWrapperPsiElement" } diff --git a/src/ru/basecode/ide/rest/plugin/Rest.flex b/src/main/grammars/Rest.flex similarity index 95% rename from src/ru/basecode/ide/rest/plugin/Rest.flex rename to src/main/grammars/Rest.flex index 32fcfa1..b09c9e0 100644 --- a/src/ru/basecode/ide/rest/plugin/Rest.flex +++ b/src/main/grammars/Rest.flex @@ -1,4 +1,4 @@ -package ru.basecode.ide.rest.plugin; +package ru.basecode.ide.rest.plugin.grammar; import com.intellij.lexer.FlexLexer; import com.intellij.psi.tree.IElementType; @@ -7,7 +7,7 @@ import com.intellij.psi.TokenType; %% -%class RestLexer +%class _RestLexer %implements FlexLexer %unicode %function advance @@ -15,7 +15,7 @@ import com.intellij.psi.TokenType; %eof{ return; %eof} -HTTP_METHOD = ("GET" | "POST" | "PUT" | "PATCH" | "DELETE") +HTTP_METHOD = ("GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD") OPTION = "--" {LINE}? HEADER = "@" {LINE}? PARAM = "&" {LINE}? diff --git a/src/main/java/ru/basecode/ide/rest/plugin/RestLanguage.java b/src/main/java/ru/basecode/ide/rest/plugin/RestLanguage.java new file mode 100644 index 0000000..7f068a9 --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/RestLanguage.java @@ -0,0 +1,11 @@ +package ru.basecode.ide.rest.plugin; + +import com.intellij.lang.Language; + +public class RestLanguage extends Language { + public static final Language INSTANCE = new RestLanguage(); + + public RestLanguage() { + super("Rest"); + } +} diff --git a/src/main/java/ru/basecode/ide/rest/plugin/action/SendAction.java b/src/main/java/ru/basecode/ide/rest/plugin/action/SendAction.java new file mode 100644 index 0000000..756e370 --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/action/SendAction.java @@ -0,0 +1,159 @@ +package ru.basecode.ide.rest.plugin.action; + +import com.intellij.icons.AllIcons; +import com.intellij.ide.ActivityTracker; +import com.intellij.lang.Language; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.CustomShortcutSet; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.keymap.Keymap; +import com.intellij.openapi.keymap.KeymapManagerListener; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.basecode.ide.rest.plugin.file.RestPsiFile; +import ru.basecode.ide.rest.plugin.http.HttpRequest; +import ru.basecode.ide.rest.plugin.http.HttpResponse; +import ru.basecode.ide.rest.plugin.http.RequestExecutor; +import ru.basecode.ide.rest.plugin.psi.RestESeparator; +import ru.basecode.ide.rest.plugin.psi.RestRequest; +import ru.basecode.ide.rest.plugin.psi.RestRequestParser; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.function.Supplier; + +import static com.intellij.openapi.keymap.KeymapManagerListener.TOPIC; +import static ru.basecode.ide.rest.plugin.misc.Util.format; +import static ru.basecode.ide.rest.plugin.misc.Util.isSuitable; + +public class SendAction extends AnAction { + private static final Logger log = Logger.getInstance(SendAction.class); + + public SendAction() { + super(AllIcons.Actions.Execute); + } + + @Override + public void update(@NotNull AnActionEvent e) { + final RequestExecutor executor = RequestExecutor.getOrDefault(e); + if (executor == null) { + e.getPresentation().setEnabled(false); + } else { + super.update(e); + e.getPresentation().setEnabled(executor.isWaiting()); + } + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + final RequestExecutor executor = RequestExecutor.getOrDefault(e); + if (executor == null || executor.isRunning()) { + return; + } + final Project project = e.getRequiredData(CommonDataKeys.PROJECT); + final VirtualFile file = e.getRequiredData(CommonDataKeys.VIRTUAL_FILE); + if (!isSuitable(project, file)) { + return; + } + final Document document = e.getRequiredData(CommonDataKeys.EDITOR).getDocument(); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + RestPsiFile restPsiFile = getRestFile(project, document); + HttpRequest request = getRequest(restPsiFile); + if (restPsiFile == null || request == null) { + return; + } + try { + long startTime = System.currentTimeMillis(); + String start = "# Executing request...\n# URL: " + request.getUrl() + "\n# Start time: " + + LocalDateTime.now(); + writeResponse(project, document, () -> restPsiFile, start); + HttpResponse response = executor.execute(request); + writeResponse(project, document, () -> restPsiFile, "# Reading response..."); + String headers = "\n# " + response.getStatus() + "\n" + getHeaders(response); + long duration = System.currentTimeMillis() - startTime; + final StringBuilder psiFileContent = new StringBuilder(); + psiFileContent.append("\n# Duration: ").append(duration).append(" ms\n# URL: ").append(request.getUrl()).append("\n").append(headers); + if(response.getBody() != null) { + String formattedResponseBody = getFormattedResponse(project, response.getContentType(), response.getBody()); + psiFileContent.append(formattedResponseBody); + } + writeResponse(project, document, () -> restPsiFile, psiFileContent.toString()); + } catch (Exception ex) { + writeResponse(project, document, () -> restPsiFile, "# Error: " + ex.getMessage()); + } finally { + // Ask activity tracker to update all action statuses + ActivityTracker.getInstance().inc(); + } + }); + } + + private String getHeaders(HttpResponse response) { + StringBuilder sb = new StringBuilder("\n"); + for (String header : response.getHeaders()) { + sb.append("@").append(header).append("\n"); + } + return sb.append("\n").toString(); + } + + private String getFormattedResponse(Project project, @NotNull String contentType, @NotNull String responseBody) { + Collection langList = Language.findInstancesByMimeType(contentType); + if (!langList.isEmpty()) { + return format(project, langList.iterator().next(), responseBody); + } + return responseBody; + } + + @Nullable + private HttpRequest getRequest(RestPsiFile restPsiFile) { + return ApplicationManager.getApplication().runReadAction((Computable) () -> { + if (restPsiFile.getRequest() == null) { + return null; + } + return RestRequestParser.parse(restPsiFile.getRequest()); + }); + } + + private void writeResponse(Project project, Document doc, Supplier file, + String text) { + WriteCommandAction.runWriteCommandAction(project, () -> { + int responsePosition; + String separatorString; + RestPsiFile restPsiFile = file.get(); + RestESeparator separator = restPsiFile.getSeparator(); + if (separator != null && separator.isValid()) { + responsePosition = separator.getTextOffset(); + separatorString = ""; + } else { + RestRequest request = restPsiFile.getRequest(); + responsePosition = request.getTextOffset() + request.getTextLength(); + separatorString = "\n"; + } + String sb = separatorString + "%%%\n" + text; + doc.replaceString(responsePosition, doc.getTextLength(), sb.replace("\r", "")); + }); + } + + private RestPsiFile getRestFile(Project project, Document document) { + return ApplicationManager.getApplication().runReadAction((Computable) () -> { + if (project.isOpen()) { + PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); + if (psiFile != null && psiFile.isValid() && psiFile instanceof RestPsiFile) { + return (RestPsiFile) psiFile; + } + } + return null; + }); + } + +} diff --git a/src/main/java/ru/basecode/ide/rest/plugin/action/StopAction.java b/src/main/java/ru/basecode/ide/rest/plugin/action/StopAction.java new file mode 100644 index 0000000..e2875e1 --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/action/StopAction.java @@ -0,0 +1,44 @@ +package ru.basecode.ide.rest.plugin.action; + +import com.intellij.icons.AllIcons; +import com.intellij.ide.ActivityTracker; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.diagnostic.Logger; +import org.jetbrains.annotations.NotNull; +import ru.basecode.ide.rest.plugin.http.RequestExecutor; + +/** + * @author danblack + */ +public class StopAction extends AnAction { + private static final Logger log = Logger.getInstance(StopAction.class); + + public StopAction() { + super(AllIcons.Actions.Suspend); + } + + @Override + public void update(@NotNull AnActionEvent e) { +// log.error(e); + final RequestExecutor executor = RequestExecutor.getOrDefault(e); + if (executor == null) { + e.getPresentation().setEnabled(false); + } else { + super.update(e); + e.getPresentation().setEnabled(executor.isRunning()); + } + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { +// log.error(e); + final RequestExecutor executor = RequestExecutor.getOrDefault(e); + if (executor != null && executor.isRunning()) { + executor.stop(); + // Ask activity tracker to update all action statuses + ActivityTracker.getInstance().inc(); + } + } + +} diff --git a/src/ru/basecode/ide/rest/plugin/RestCodeStyleSettings.java b/src/main/java/ru/basecode/ide/rest/plugin/codestyle/RestCodeStyleSettings.java similarity index 56% rename from src/ru/basecode/ide/rest/plugin/RestCodeStyleSettings.java rename to src/main/java/ru/basecode/ide/rest/plugin/codestyle/RestCodeStyleSettings.java index dcbd449..199911e 100644 --- a/src/ru/basecode/ide/rest/plugin/RestCodeStyleSettings.java +++ b/src/main/java/ru/basecode/ide/rest/plugin/codestyle/RestCodeStyleSettings.java @@ -1,4 +1,4 @@ -package ru.basecode.ide.rest.plugin; +package ru.basecode.ide.rest.plugin.codestyle; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CustomCodeStyleSettings; @@ -7,7 +7,7 @@ * @author danblack */ public class RestCodeStyleSettings extends CustomCodeStyleSettings { - protected RestCodeStyleSettings(CodeStyleSettings container) { - super("RestCodeStyleSettings", container); - } + protected RestCodeStyleSettings(CodeStyleSettings container) { + super("RestCodeStyleSettings", container); + } } diff --git a/src/main/java/ru/basecode/ide/rest/plugin/codestyle/RestCodeStyleSettingsProvider.java b/src/main/java/ru/basecode/ide/rest/plugin/codestyle/RestCodeStyleSettingsProvider.java new file mode 100644 index 0000000..c6c8fa7 --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/codestyle/RestCodeStyleSettingsProvider.java @@ -0,0 +1,55 @@ +package ru.basecode.ide.rest.plugin.codestyle; + +import com.intellij.application.options.CodeStyleAbstractConfigurable; +import com.intellij.application.options.CodeStyleAbstractPanel; +import com.intellij.application.options.TabbedLanguageCodeStylePanel; +import com.intellij.psi.codeStyle.CodeStyleConfigurable; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsProvider; +import com.intellij.psi.codeStyle.CustomCodeStyleSettings; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.basecode.ide.rest.plugin.RestLanguage; + +/** + * @author danblack + */ +public class RestCodeStyleSettingsProvider extends CodeStyleSettingsProvider { + @Override + public @NotNull CodeStyleConfigurable createConfigurable(@NotNull CodeStyleSettings settings, + @NotNull CodeStyleSettings modelSettings) { + return new CodeStyleAbstractConfigurable(settings, modelSettings, + RestLanguage.INSTANCE.getDisplayName()) { + + @Nullable + @Override + public String getHelpTopic() { + return null; + } + + @Override + protected CodeStyleAbstractPanel createPanel(CodeStyleSettings settings) { + return new RestTabbedLanguageCodeStylePanel(getCurrentSettings(), settings); + } + }; + } + + @Nullable + @Override + public String getConfigurableDisplayName() { + return RestLanguage.INSTANCE.getDisplayName(); + } + + @Nullable + @Override + public CustomCodeStyleSettings createCustomSettings(CodeStyleSettings settings) { + return new RestCodeStyleSettings(settings); + } + + private static class RestTabbedLanguageCodeStylePanel extends TabbedLanguageCodeStylePanel { + protected RestTabbedLanguageCodeStylePanel(CodeStyleSettings currentSettings, + CodeStyleSettings settings) { + super(RestLanguage.INSTANCE, currentSettings, settings); + } + } +} diff --git a/src/main/java/ru/basecode/ide/rest/plugin/editor/RestClientEditor.java b/src/main/java/ru/basecode/ide/rest/plugin/editor/RestClientEditor.java new file mode 100644 index 0000000..a12219f --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/editor/RestClientEditor.java @@ -0,0 +1,200 @@ +package ru.basecode.ide.rest.plugin.editor; + +import com.intellij.codeHighlighting.BackgroundEditorHighlighter; +import com.intellij.ide.ActivityTracker; +import com.intellij.ide.structureView.StructureViewBuilder; +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionPlaces; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.CustomShortcutSet; +import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.FileEditorLocation; +import com.intellij.openapi.fileEditor.FileEditorState; +import com.intellij.openapi.fileEditor.FileEditorStateLevel; +import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl; +import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; +import com.intellij.openapi.keymap.Keymap; +import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.keymap.KeymapManagerListener; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ui.JBEmptyBorder; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.basecode.ide.rest.plugin.http.RequestExecutor; + +import javax.swing.*; +import java.awt.*; +import java.beans.PropertyChangeListener; + +import static com.intellij.openapi.keymap.KeymapManagerListener.TOPIC; + +public class RestClientEditor implements FileEditor { + + private final PsiAwareTextEditorImpl editor; + private final ActionToolbarImpl toolbar; + private final JPanel panel; + + public RestClientEditor(@NotNull Project project, @NotNull VirtualFile file, @NotNull TextEditorProvider provider) { + editor = new PsiAwareTextEditorImpl(project, file, provider); + toolbar = createToolbarFromGroupId("RestClient.Toolbar.Left"); + panel = new JPanel(new BorderLayout()); + panel.add(toolbar, BorderLayout.NORTH); + panel.add(editor.getComponent(), BorderLayout.CENTER); + } + + @Override + public @NotNull JComponent getComponent() { + return panel; + } + + @NotNull + private static ActionToolbarImpl createToolbarFromGroupId(@SuppressWarnings("SameParameterValue") @NotNull String groupId) { + final ActionManager actionManager = ActionManager.getInstance(); + + if (!actionManager.isGroup(groupId)) { + throw new IllegalStateException(groupId + " should have been a group"); + } + final ActionGroup group = ((ActionGroup)actionManager.getAction(groupId)); + final ActionToolbarImpl editorToolbar = + ((ActionToolbarImpl)actionManager.createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, group, true)); + editorToolbar.setOpaque(false); + editorToolbar.setBorder(new JBEmptyBorder(0, 2, 0, 2)); +// editorToolbar.setForceMinimumSize(true); + + return editorToolbar; + } + + @Override + public @Nullable JComponent getPreferredFocusedComponent() { + return editor.getPreferredFocusedComponent(); + } + + @Override + public @NotNull FileEditorState getState(@NotNull FileEditorStateLevel level) { + return editor.getState(level); + } + + @Override + public void setState(@NotNull FileEditorState state) { + editor.setState(state); + } + + @Override + public boolean isModified() { + return editor.isModified(); + } + + @Override + public boolean isValid() { + return editor.isValid(); + } + + @Override + public @NonNls @NotNull String getName() { + return editor.getName(); + } + + @Override + public void selectNotify() { + editor.selectNotify(); + toolbar.getActions().forEach(this::subscribeToKeyboardShortcuts); + } + + @Override + public void deselectNotify() { + editor.deselectNotify(); + toolbar.getActions().forEach(this::unsubscribeFromKeyboardShortcuts); + } + + @Override + public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) { + editor.addPropertyChangeListener(listener); + } + + @Override + public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) { + editor.removePropertyChangeListener(listener); + } + + @Override + public @Nullable BackgroundEditorHighlighter getBackgroundHighlighter() { + return editor.getBackgroundHighlighter(); + } + + @Override + public @Nullable FileEditorLocation getCurrentLocation() { + return editor.getCurrentLocation(); + } + + @Override + public @Nullable StructureViewBuilder getStructureViewBuilder() { + return editor.getStructureViewBuilder(); + } + + @Override + public void dispose() { + toolbar.getActions().forEach(this::unsubscribeFromKeyboardShortcuts); + Disposer.dispose(editor); + } + + @Override + public @Nullable T getUserData(@NotNull Key key) { + return editor.getUserData(key); + } + + @Override + public void putUserData(@NotNull Key key, @Nullable T value) { + editor.putUserData(key, value); + } + + private void subscribeToKeyboardShortcuts(AnAction action) { + ApplicationManager.getApplication().getMessageBus().connect(this) + .subscribe(TOPIC, new KeymapManagerListener() { + @Override + public void shortcutChanged(@NotNull Keymap keymap, @NotNull String actionId) { + unsubscribeFromKeyboardShortcuts(action); + registerShortCuts(action, keymap); + } + + @Override + public void keymapAdded(@NotNull Keymap keymap) { + unsubscribeFromKeyboardShortcuts(action); + registerShortCuts(action, keymap); + } + + @Override + public void keymapRemoved(@NotNull Keymap keymap) { + unsubscribeFromKeyboardShortcuts(action); + registerShortCuts(action, keymap); + } + + @Override + public void activeKeymapChanged(@Nullable Keymap keymap) { + unsubscribeFromKeyboardShortcuts(action); + if (keymap != null) { + registerShortCuts(action, keymap); + } + } + }); + registerShortCuts(action, KeymapManager.getInstance().getActiveKeymap()); + } + + private void registerShortCuts(@NotNull AnAction action, @NotNull Keymap keymap) { + action.registerCustomShortcutSet(editor.getComponent(), null); + } + + private void unsubscribeFromKeyboardShortcuts(@NotNull AnAction action) { + final RequestExecutor executor = RequestExecutor.getOrDefault(editor.getEditor()); + executor.stop(); + // Ask activity tracker to update all action statuses + ActivityTracker.getInstance().inc(); + action.unregisterCustomShortcutSet(editor.getComponent()); + } +} diff --git a/src/main/java/ru/basecode/ide/rest/plugin/editor/RestClientEditorProvider.java b/src/main/java/ru/basecode/ide/rest/plugin/editor/RestClientEditorProvider.java new file mode 100644 index 0000000..20bf82e --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/editor/RestClientEditorProvider.java @@ -0,0 +1,61 @@ +package ru.basecode.ide.rest.plugin.editor; + +import com.intellij.ide.scratch.ScratchUtil; +import com.intellij.lang.LanguageUtil; +import com.intellij.openapi.fileEditor.AsyncFileEditorProvider; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.FileEditorPolicy; +import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorProvider; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import ru.basecode.ide.rest.plugin.RestLanguage; +import ru.basecode.ide.rest.plugin.file.RestFileType; + +public class RestClientEditorProvider implements AsyncFileEditorProvider, DumbAware { + + private final PsiAwareTextEditorProvider editorProvider; + + public RestClientEditorProvider() { + editorProvider = new PsiAwareTextEditorProvider(); + } + + @Override + public boolean accept(@NotNull Project project, @NotNull VirtualFile file) { + final FileType fileType = file.getFileType(); + + final boolean isRestType = fileType == RestFileType.INSTANCE || ScratchUtil.isScratch(file) + && LanguageUtil.getLanguageForPsi(project, file) == RestLanguage.INSTANCE; + + return isRestType && editorProvider.accept(project, file); + } + + @Override + public @NotNull FileEditor createEditor(@NotNull Project project, + @NotNull VirtualFile file) { + return createEditorAsync(project, file).build(); + } + + @Override + public @NotNull @NonNls String getEditorTypeId() { + return RestClientEditor.class.getCanonicalName(); + } + + @Override + public @NotNull FileEditorPolicy getPolicy() { + return FileEditorPolicy.HIDE_DEFAULT_EDITOR; + } + + @Override + public @NotNull Builder createEditorAsync(@NotNull Project project, @NotNull VirtualFile file) { + return new Builder() { + @Override + public FileEditor build() { + return new RestClientEditor(project, file, RestClientEditorProvider.this.editorProvider); + } + }; + } +} diff --git a/src/ru/basecode/ide/rest/plugin/RestCommenter.java b/src/main/java/ru/basecode/ide/rest/plugin/editor/RestCommenter.java similarity index 93% rename from src/ru/basecode/ide/rest/plugin/RestCommenter.java rename to src/main/java/ru/basecode/ide/rest/plugin/editor/RestCommenter.java index 137691b..b47019e 100644 --- a/src/ru/basecode/ide/rest/plugin/RestCommenter.java +++ b/src/main/java/ru/basecode/ide/rest/plugin/editor/RestCommenter.java @@ -1,4 +1,4 @@ -package ru.basecode.ide.rest.plugin; +package ru.basecode.ide.rest.plugin.editor; import com.intellij.lang.Commenter; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/ru/basecode/ide/rest/plugin/file/RestFileType.java b/src/main/java/ru/basecode/ide/rest/plugin/file/RestFileType.java new file mode 100644 index 0000000..b6c338e --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/file/RestFileType.java @@ -0,0 +1,45 @@ +package ru.basecode.ide.rest.plugin.file; + +import com.intellij.icons.AllIcons; +import com.intellij.openapi.fileTypes.LanguageFileType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.basecode.ide.rest.plugin.RestLanguage; + +import javax.swing.*; + +import static ru.basecode.ide.rest.plugin.misc.Icons.REST_FILE_TYPE; + +public class RestFileType extends LanguageFileType { + public static final String REST_FILE_TYPE_TITLE = "Rest file"; + + public final static LanguageFileType INSTANCE = new RestFileType(); + + protected RestFileType() { + super(RestLanguage.INSTANCE); + } + + @NotNull + @Override + public String getName() { + return REST_FILE_TYPE_TITLE; + } + + @NotNull + @Override + public String getDescription() { + return "Rest file"; + } + + @NotNull + @Override + public String getDefaultExtension() { + return "rest"; + } + + @Nullable + @Override + public Icon getIcon() { + return REST_FILE_TYPE; + } +} diff --git a/src/main/java/ru/basecode/ide/rest/plugin/file/RestPsiFile.java b/src/main/java/ru/basecode/ide/rest/plugin/file/RestPsiFile.java new file mode 100644 index 0000000..9907720 --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/file/RestPsiFile.java @@ -0,0 +1,41 @@ +package ru.basecode.ide.rest.plugin.file; + +import com.intellij.extapi.psi.PsiFileBase; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.psi.FileViewProvider; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import ru.basecode.ide.rest.plugin.RestLanguage; +import ru.basecode.ide.rest.plugin.psi.RestESeparator; +import ru.basecode.ide.rest.plugin.psi.RestRequest; + +public class RestPsiFile extends PsiFileBase { + + public RestPsiFile(@NotNull FileViewProvider viewProvider) { + super(viewProvider, RestLanguage.INSTANCE); + } + + public RestRequest getRequest() { + for (PsiElement element : getChildren()) { + if (element instanceof RestRequest) { + return (RestRequest) element; + } + } + return null; + } + + public RestESeparator getSeparator() { + for (PsiElement element : getChildren()) { + if (element instanceof RestESeparator) { + return (RestESeparator) element; + } + } + return null; + } + + @NotNull + @Override + public FileType getFileType() { + return RestFileType.INSTANCE; + } +} diff --git a/src/ru/basecode/ide/rest/plugin/RestLexerAdapter.java b/src/main/java/ru/basecode/ide/rest/plugin/grammar/RestLexerAdapter.java similarity index 56% rename from src/ru/basecode/ide/rest/plugin/RestLexerAdapter.java rename to src/main/java/ru/basecode/ide/rest/plugin/grammar/RestLexerAdapter.java index 1ac1842..3829d3b 100644 --- a/src/ru/basecode/ide/rest/plugin/RestLexerAdapter.java +++ b/src/main/java/ru/basecode/ide/rest/plugin/grammar/RestLexerAdapter.java @@ -1,15 +1,12 @@ -package ru.basecode.ide.rest.plugin; +package ru.basecode.ide.rest.plugin.grammar; import com.intellij.lexer.FlexAdapter; import com.intellij.lexer.MergingLexerAdapter; import com.intellij.psi.tree.TokenSet; import ru.basecode.ide.rest.plugin.psi.RestTypes; -/** - * - */ public class RestLexerAdapter extends MergingLexerAdapter { - public RestLexerAdapter() { - super(new FlexAdapter(new RestLexer(null)), TokenSet.create(RestTypes.BAD_CHARACTER)); - } + public RestLexerAdapter() { + super(new FlexAdapter(new _RestLexer(null)), TokenSet.create(RestTypes.BAD_CHARACTER)); + } } diff --git a/src/main/java/ru/basecode/ide/rest/plugin/grammar/RestParserDefinition.java b/src/main/java/ru/basecode/ide/rest/plugin/grammar/RestParserDefinition.java new file mode 100644 index 0000000..d144cac --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/grammar/RestParserDefinition.java @@ -0,0 +1,73 @@ +package ru.basecode.ide.rest.plugin.grammar; + +import com.intellij.lang.ASTNode; +import com.intellij.lang.Language; +import com.intellij.lang.ParserDefinition; +import com.intellij.lang.PsiParser; +import com.intellij.lexer.Lexer; +import com.intellij.openapi.project.Project; +import com.intellij.psi.FileViewProvider; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.TokenType; +import com.intellij.psi.tree.IFileElementType; +import com.intellij.psi.tree.TokenSet; +import org.jetbrains.annotations.NotNull; +import ru.basecode.ide.rest.plugin.RestLanguage; +import ru.basecode.ide.rest.plugin.file.RestPsiFile; +import ru.basecode.ide.rest.plugin.parser._RestParser; +import ru.basecode.ide.rest.plugin.psi.RestTypes; + +public class RestParserDefinition implements ParserDefinition { + + public static final TokenSet WHITE_SPACES = TokenSet.create(TokenType.WHITE_SPACE); + + public static final IFileElementType FILE = + new IFileElementType(Language.findInstance(RestLanguage.class)); + + @NotNull + @Override + public Lexer createLexer(Project project) { + return new RestLexerAdapter(); + } + + @Override + public PsiParser createParser(Project project) { + return new _RestParser(); + } + + @Override + public IFileElementType getFileNodeType() { + return FILE; + } + + @NotNull + @Override + public TokenSet getWhitespaceTokens() { + return WHITE_SPACES; + } + + @NotNull + @Override + public TokenSet getCommentTokens() { + return TokenSet.EMPTY; + } + + @NotNull + @Override + public TokenSet getStringLiteralElements() { + return TokenSet.EMPTY; + } + + @NotNull + @Override + public PsiElement createElement(ASTNode node) { + return RestTypes.Factory.createElement(node); + } + + @Override + public PsiFile createFile(FileViewProvider viewProvider) { + return new RestPsiFile(viewProvider); + } + +} diff --git a/src/main/java/ru/basecode/ide/rest/plugin/highlighter/RestColorSettingsPage.java b/src/main/java/ru/basecode/ide/rest/plugin/highlighter/RestColorSettingsPage.java new file mode 100644 index 0000000..a823b95 --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/highlighter/RestColorSettingsPage.java @@ -0,0 +1,75 @@ +package ru.basecode.ide.rest.plugin.highlighter; + +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.fileTypes.SyntaxHighlighter; +import com.intellij.openapi.options.colors.AttributesDescriptor; +import com.intellij.openapi.options.colors.ColorDescriptor; +import com.intellij.openapi.options.colors.ColorSettingsPage; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import ru.basecode.ide.rest.plugin.RestLanguage; + +import javax.swing.*; +import java.util.Map; + +/** + * @author danblack + */ +public class RestColorSettingsPage implements ColorSettingsPage { + + private static final AttributesDescriptor[] DESCRIPTORS = + new AttributesDescriptor[] {new AttributesDescriptor("Comment", RestHighlighter.COMMENT), + new AttributesDescriptor("Method", RestHighlighter.METHOD), + new AttributesDescriptor("Url", RestHighlighter.URL), + new AttributesDescriptor("Header", RestHighlighter.HEADER), + new AttributesDescriptor("Option", RestHighlighter.OPTION), + new AttributesDescriptor("Param", RestHighlighter.PARAM), + new AttributesDescriptor("Error", RestHighlighter.BAD_CHARACTER), + new AttributesDescriptor("Separator", RestHighlighter.SEPARATOR), + new AttributesDescriptor("Body", RestHighlighter.BODY),}; + + @NotNull + @Override + public AttributesDescriptor[] getAttributeDescriptors() { + return DESCRIPTORS; + } + + @NotNull + @Override + public ColorDescriptor[] getColorDescriptors() { + return ColorDescriptor.EMPTY_ARRAY; + } + + @NotNull + @Override + public String getDisplayName() { + return RestLanguage.INSTANCE.getDisplayName(); + } + + @Nullable + @Override + public Icon getIcon() { + return null; + } + + @NotNull + @Override + public SyntaxHighlighter getHighlighter() { + return new RestHighlighter(); + } + + @NotNull + @Override + public String getDemoText() { + return "# This is a comment\n" + "-- option = value\n" + "POST\n" + "http://site.com\n" + + "¶m=value\n" + "@Content-type: application/xml\n" + "\n" + + "This is a request body\n" + "\n" + "%%%\n" + "@Content-length: 100\n" + + "\n" + "This is a response body\n" + ""; + } + + @Nullable + @Override + public Map getAdditionalHighlightingTagToDescriptorMap() { + return null; + } +} diff --git a/src/ru/basecode/ide/rest/plugin/RestHighlighter.java b/src/main/java/ru/basecode/ide/rest/plugin/highlighter/RestHighlighter.java similarity index 95% rename from src/ru/basecode/ide/rest/plugin/RestHighlighter.java rename to src/main/java/ru/basecode/ide/rest/plugin/highlighter/RestHighlighter.java index cd4ffe4..e04ae96 100644 --- a/src/ru/basecode/ide/rest/plugin/RestHighlighter.java +++ b/src/main/java/ru/basecode/ide/rest/plugin/highlighter/RestHighlighter.java @@ -1,4 +1,4 @@ -package ru.basecode.ide.rest.plugin; +package ru.basecode.ide.rest.plugin.highlighter; import com.intellij.lexer.Lexer; import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; @@ -7,13 +7,11 @@ import com.intellij.psi.TokenType; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; +import ru.basecode.ide.rest.plugin.grammar.RestLexerAdapter; import ru.basecode.ide.rest.plugin.psi.RestTypes; import static com.intellij.openapi.editor.colors.TextAttributesKey.createTextAttributesKey; -/** - * - */ public class RestHighlighter extends SyntaxHighlighterBase { @@ -65,6 +63,6 @@ public TextAttributesKey[] getTokenHighlights(IElementType tokenType) { } else if (tokenType.equals(TokenType.BAD_CHARACTER)) { return BAD_CHAR_KEYS; } - return EMPTY; + return TextAttributesKey.EMPTY_ARRAY; } } diff --git a/src/ru/basecode/ide/rest/plugin/RestHighlighterFactory.java b/src/main/java/ru/basecode/ide/rest/plugin/highlighter/RestHighlighterFactory.java similarity index 53% rename from src/ru/basecode/ide/rest/plugin/RestHighlighterFactory.java rename to src/main/java/ru/basecode/ide/rest/plugin/highlighter/RestHighlighterFactory.java index e8686a5..63c3647 100644 --- a/src/ru/basecode/ide/rest/plugin/RestHighlighterFactory.java +++ b/src/main/java/ru/basecode/ide/rest/plugin/highlighter/RestHighlighterFactory.java @@ -1,4 +1,4 @@ -package ru.basecode.ide.rest.plugin; +package ru.basecode.ide.rest.plugin.highlighter; import com.intellij.openapi.fileTypes.SyntaxHighlighter; import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory; @@ -6,16 +6,12 @@ import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; -/** - * - */ public class RestHighlighterFactory extends SyntaxHighlighterFactory { + private final RestHighlighter restHighlighter = new RestHighlighter(); - private final RestHighlighter restHighlighter = new RestHighlighter(); - - @NotNull - @Override - public SyntaxHighlighter getSyntaxHighlighter(Project project, VirtualFile virtualFile) { - return restHighlighter; - } + @NotNull + @Override + public SyntaxHighlighter getSyntaxHighlighter(Project project, VirtualFile virtualFile) { + return restHighlighter; + } } diff --git a/src/main/java/ru/basecode/ide/rest/plugin/http/HttpRequest.java b/src/main/java/ru/basecode/ide/rest/plugin/http/HttpRequest.java new file mode 100644 index 0000000..a9b3fa3 --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/http/HttpRequest.java @@ -0,0 +1,60 @@ +package ru.basecode.ide.rest.plugin.http; + +import lombok.Builder; +import lombok.Value; + +import java.util.List; + +/** + * @author danblack + */ +@Value +@Builder +public class HttpRequest { + + Method method; + String url; + List
headers; + String body; + + public HttpRequest(Method method, String url, List
headers, String body) { + this.method = method; + this.url = url; + this.headers = headers; + this.body = body; + } + + @Override + public String toString() { + return "Request{" + "method=" + method + ", url='" + url + '\'' + ", headers=" + headers + + ", body='" + body + '\'' + '}'; + } + + public static enum Method { + GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD + } + + + @Value + @Builder + public static class Header { + String name; + String value; + + public Header(String name, String value) { + this.name = name; + this.value = value; + } + } + + + @Value + @Builder + public static class Params { + int timeout; + + public Params(int timeout) { + this.timeout = timeout; + } + } +} diff --git a/src/main/java/ru/basecode/ide/rest/plugin/http/HttpResponse.java b/src/main/java/ru/basecode/ide/rest/plugin/http/HttpResponse.java new file mode 100644 index 0000000..24005f2 --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/http/HttpResponse.java @@ -0,0 +1,20 @@ +package ru.basecode.ide.rest.plugin.http; + +import lombok.Builder; +import lombok.Value; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @author danblack + */ +@Value +@Builder +public class HttpResponse { + String status; + List headers; + String contentType; + @Nullable + String body; +} diff --git a/src/main/java/ru/basecode/ide/rest/plugin/http/RequestExecutor.java b/src/main/java/ru/basecode/ide/rest/plugin/http/RequestExecutor.java new file mode 100644 index 0000000..00f2608 --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/http/RequestExecutor.java @@ -0,0 +1,148 @@ +package ru.basecode.ide.rest.plugin.http; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.UserDataHolder; +import org.apache.http.Header; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.util.EntityUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * @author danblack + */ +public class RequestExecutor { + + private static final Key EXECUTOR_KEY = new Key<>("ExecutorKey"); + + private static final Charset DEFAULT_CHARSET = UTF_8; + private final HttpClient httpClient = createHttpClient(); + private State state = State.WAITING; + private HttpRequestBase httpRequest; + + @Nullable + public static RequestExecutor getOrDefault(AnActionEvent event) { + // Its possible for the event that triggered this might not have an editor associated + final Editor editor = event.getData(CommonDataKeys.EDITOR); + if (editor != null) { + return getOrDefault(editor); + } + return null; + } + + @NotNull + public static RequestExecutor getOrDefault(UserDataHolder dataHolder) { + RequestExecutor executor = dataHolder.getUserData(EXECUTOR_KEY); + if (executor == null) { + executor = new RequestExecutor(); + dataHolder.putUserData(EXECUTOR_KEY, executor); + } + return executor; + } + + public void stop() { + if (httpRequest != null) { + httpRequest.abort(); + } + } + + private CloseableHttpClient createHttpClient() { + try { + SSLContextBuilder contextBuilder = SSLContextBuilder.create(); + contextBuilder.loadTrustMaterial(null, (x509Certificates, s) -> true); + SSLConnectionSocketFactory sslSocketFactory = + new SSLConnectionSocketFactory(contextBuilder.build(), NoopHostnameVerifier.INSTANCE); + return HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build(); + } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { + throw new RuntimeException(e); + } + } + + public HttpResponse execute(HttpRequest request) throws IOException { + //@formatter:off + state = State.RUNNING; + try { + httpRequest = Requests.createHttpRequest(request); + org.apache.http.HttpResponse response = httpClient.execute(httpRequest); + if (response.getEntity() != null) { + return HttpResponse.builder() + .status(response.getStatusLine().toString()) + .headers(getHeaders(response)) + .contentType(getContentType(response)) + .body(EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET)) + .build(); + } else { + return HttpResponse.builder() + .status(response.getStatusLine().toString()) + .headers(getHeaders(response)) + .contentType(getContentType(response)) + .build(); + } + } finally { + state = State.WAITING; + } + //@formatter:on + } + + private String getContentType(org.apache.http.HttpResponse response) { + Header header = response.getFirstHeader("Content-Type"); + if (header != null) { + String contentType = header.getValue(); + int semicolonIndex = contentType.indexOf(";"); + if (semicolonIndex >= 0) { + return contentType.substring(0, semicolonIndex); + } else { + return contentType; + } + } + return null; + } + + private List getHeaders(org.apache.http.HttpResponse response) { + Header[] allHeaders = response.getAllHeaders(); + if (allHeaders != null) { + ArrayList result = new ArrayList<>(); + for (Header header : allHeaders) { + result.add(header.getName() + ": " + header.getValue()); + } + return result; + } + return Collections.emptyList(); + } + + public boolean isRunning() { + return state == State.RUNNING; + } + + public boolean isWaiting() { + return state == State.WAITING; + } + + public State getState() { + return state; + } + + public enum State { + WAITING, RUNNING, STOPPING + } +} diff --git a/src/main/java/ru/basecode/ide/rest/plugin/http/Requests.java b/src/main/java/ru/basecode/ide/rest/plugin/http/Requests.java new file mode 100644 index 0000000..4888509 --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/http/Requests.java @@ -0,0 +1,102 @@ +package ru.basecode.ide.rest.plugin.http; + +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpOptions; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.entity.StringEntity; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.function.Function; + +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.DELETE; +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.GET; +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.HEAD; +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.OPTIONS; +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.PATCH; +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.POST; +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.PUT; + +/** + * @author danblack + */ +public class Requests { + + private static final Charset DEFAULT_CHARSET = Charset.defaultCharset(); + + public static HttpRequestBase createHttpRequest(HttpRequest request) + throws UnsupportedEncodingException { + if (request.getMethod() == GET) { + HttpGet httpGet = new HttpGet(request.getUrl()); + fillHeaders(request, httpGet); + return httpGet; + } + + if (request.getMethod() == POST) { + return create(HttpPost::new, request); + } + + if (request.getMethod() == PUT) { + return create(HttpPut::new, request); + } + + if (request.getMethod() == PATCH) { + return create(HttpPatch::new, request); + } + + if (request.getMethod() == OPTIONS) { + final HttpOptions httpOptiopns = new HttpOptions(request.getUrl()); + fillHeaders(request, httpOptiopns); + return httpOptiopns; + } + + if (request.getMethod() == HEAD) { + final HttpHead httpHead = new HttpHead(request.getUrl()); + fillHeaders(request, httpHead); + return httpHead; + } + + if (request.getMethod() == DELETE) { + HttpDelete httpDelete = new HttpDelete(request.getUrl()); + fillHeaders(request, httpDelete); + return httpDelete; + } + + throw new IllegalStateException(); + } + + public static HttpEntityEnclosingRequestBase create( + Function constructor, HttpRequest request) + throws UnsupportedEncodingException { + HttpEntityEnclosingRequestBase httpRequest = constructor.apply(request.getUrl()); + fillHeaders(request, httpRequest); + fillBody(request, httpRequest); + return httpRequest; + } + + private static void fillBody(HttpRequest request, HttpEntityEnclosingRequestBase httpRequest) { + String body = request.getBody(); + if (body != null) { + httpRequest.setEntity(new StringEntity(body, DEFAULT_CHARSET)); + for (HttpRequest.Header header : request.getHeaders()) { + if ("Content-Type".equals(header.getName())) { + if (!header.getValue().contains("charset")) { + // TODO: Not sure what the author wanted to do + } + } + } + } + } + + private static void fillHeaders(HttpRequest request, HttpRequestBase httpRequest) { + for (HttpRequest.Header header : request.getHeaders()) { + httpRequest.addHeader(header.getName(), header.getValue()); + } + } +} diff --git a/src/main/java/ru/basecode/ide/rest/plugin/injector/RestHostInjector.java b/src/main/java/ru/basecode/ide/rest/plugin/injector/RestHostInjector.java new file mode 100644 index 0000000..973e8ec --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/injector/RestHostInjector.java @@ -0,0 +1,70 @@ +package ru.basecode.ide.rest.plugin.injector; + +import com.intellij.lang.Language; +import com.intellij.lang.injection.MultiHostInjector; +import com.intellij.lang.injection.MultiHostRegistrar; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiLanguageInjectionHost; +import org.jetbrains.annotations.NotNull; +import ru.basecode.ide.rest.plugin.psi.RestEHeader; +import ru.basecode.ide.rest.plugin.psi.RestHeaders; +import ru.basecode.ide.rest.plugin.psi.RestRequest; +import ru.basecode.ide.rest.plugin.psi.RestRequestBody; +import ru.basecode.ide.rest.plugin.psi.RestResponse; +import ru.basecode.ide.rest.plugin.psi.RestResponseBody; +import ru.basecode.ide.rest.plugin.psi.impl.RestWrapperPsiElement; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class RestHostInjector implements MultiHostInjector { + @Override + public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, + @NotNull final PsiElement context) { + RestHeaders headers = null; + if (context instanceof RestResponseBody) { + headers = ((RestResponse) context.getParent()).getHeaders(); + } else if (context instanceof RestRequestBody) { + headers = ((RestRequest) context.getParent()).getHeaders(); + } + if (headers != null) { + String contentType = getContentType(headers); + if (contentType != null) { + Collection langList = Language.findInstancesByMimeType(contentType); + if (!langList.isEmpty()) { + registrar.startInjecting(langList.iterator().next()) + .addPlace(null, null, (PsiLanguageInjectionHost) context, + TextRange.create(0, context.getTextLength())).doneInjecting(); + } + } + } + } + + private String getContentType(@NotNull RestHeaders headers) { + if (headers.isValid()) { + for (RestEHeader eHeader : headers.getEHeaderList()) { + String header = eHeader.getText(); + if (header.startsWith("@Content-Type")) { + int colonIndex = header.indexOf(":"); + if (colonIndex >= 0) { + String contentType = header.substring(colonIndex + 1); + int semicolonIndex = contentType.indexOf(";"); + if (semicolonIndex >= 0) { + return contentType.substring(0, semicolonIndex).trim(); + } + return contentType.trim(); + } + } + } + } + return null; + } + + @NotNull + @Override + public List> elementsToInjectIn() { + return Collections.singletonList(RestWrapperPsiElement.class); + } +} diff --git a/src/main/java/ru/basecode/ide/rest/plugin/misc/Icons.java b/src/main/java/ru/basecode/ide/rest/plugin/misc/Icons.java new file mode 100644 index 0000000..5d6e56f --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/misc/Icons.java @@ -0,0 +1,13 @@ +package ru.basecode.ide.rest.plugin.misc; + +import com.intellij.openapi.util.IconLoader; +import lombok.NoArgsConstructor; + +import javax.swing.*; + +import static lombok.AccessLevel.PRIVATE; + +@NoArgsConstructor(access = PRIVATE) +public class Icons { + public static Icon REST_FILE_TYPE = IconLoader.getIcon("/META-INF/restFileType.svg"); +} diff --git a/src/main/java/ru/basecode/ide/rest/plugin/misc/Util.java b/src/main/java/ru/basecode/ide/rest/plugin/misc/Util.java new file mode 100644 index 0000000..f88b895 --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/misc/Util.java @@ -0,0 +1,38 @@ +package ru.basecode.ide.rest.plugin.misc; + +import com.intellij.lang.Language; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.impl.http.HttpVirtualFile; +import com.intellij.psi.FileViewProvider; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.impl.PsiFileFactoryImpl; +import lombok.NoArgsConstructor; +import org.jetbrains.annotations.NotNull; +import ru.basecode.ide.rest.plugin.RestLanguage; + +import static lombok.AccessLevel.PRIVATE; + +@NoArgsConstructor(access = PRIVATE) +public class Util { + public static boolean isSuitable(@NotNull Project project, @NotNull VirtualFile file) { + if (file instanceof HttpVirtualFile) { + return false; + } + final FileViewProvider provider = PsiManager.getInstance(project).findViewProvider(file); + return provider != null && RestLanguage.INSTANCE == provider.getBaseLanguage(); + } + + public static String format(final Project project, @NotNull Language language, + @NotNull String responseBody) { + return WriteCommandAction.runWriteCommandAction(project, (Computable) () -> { + final PsiFile psiFile = PsiFileFactoryImpl.getInstance(project).createFileFromText("virtual", language, responseBody); + CodeStyleManager.getInstance(project).reformatText(psiFile, 0, psiFile.getTextLength()); + return psiFile.getText(); + }); + } +} diff --git a/src/ru/basecode/ide/rest/plugin/psi/RestElement.java b/src/main/java/ru/basecode/ide/rest/plugin/psi/RestElement.java similarity index 93% rename from src/ru/basecode/ide/rest/plugin/psi/RestElement.java rename to src/main/java/ru/basecode/ide/rest/plugin/psi/RestElement.java index 6f5f1a1..2d095a3 100644 --- a/src/ru/basecode/ide/rest/plugin/psi/RestElement.java +++ b/src/main/java/ru/basecode/ide/rest/plugin/psi/RestElement.java @@ -2,8 +2,5 @@ import com.intellij.psi.NavigatablePsiElement; -/** - * - */ public interface RestElement extends NavigatablePsiElement { } diff --git a/src/ru/basecode/ide/rest/plugin/psi/RestElementType.java b/src/main/java/ru/basecode/ide/rest/plugin/psi/RestElementType.java similarity index 67% rename from src/ru/basecode/ide/rest/plugin/psi/RestElementType.java rename to src/main/java/ru/basecode/ide/rest/plugin/psi/RestElementType.java index 04e3089..788613b 100644 --- a/src/ru/basecode/ide/rest/plugin/psi/RestElementType.java +++ b/src/main/java/ru/basecode/ide/rest/plugin/psi/RestElementType.java @@ -5,11 +5,8 @@ import org.jetbrains.annotations.NotNull; import ru.basecode.ide.rest.plugin.RestLanguage; -/** - * - */ public class RestElementType extends IElementType { - public RestElementType(@NotNull @NonNls String debugName) { - super(debugName, RestLanguage.INSTANCE); - } + public RestElementType(@NotNull @NonNls String debugName) { + super(debugName, RestLanguage.INSTANCE); + } } diff --git a/src/main/java/ru/basecode/ide/rest/plugin/psi/RestRequestParser.java b/src/main/java/ru/basecode/ide/rest/plugin/psi/RestRequestParser.java new file mode 100644 index 0000000..fc43817 --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/psi/RestRequestParser.java @@ -0,0 +1,140 @@ +package ru.basecode.ide.rest.plugin.psi; + +import com.google.common.net.UrlEscapers; +import org.jetbrains.annotations.NotNull; +import ru.basecode.ide.rest.plugin.http.HttpRequest; +import ru.basecode.ide.rest.plugin.http.HttpRequest.Method; +import ru.basecode.ide.rest.plugin.psi.RestEHeader; +import ru.basecode.ide.rest.plugin.psi.RestEMethod; +import ru.basecode.ide.rest.plugin.psi.RestEParam; +import ru.basecode.ide.rest.plugin.psi.RestEUrl; +import ru.basecode.ide.rest.plugin.psi.RestHeaders; +import ru.basecode.ide.rest.plugin.psi.RestParams; +import ru.basecode.ide.rest.plugin.psi.RestRequest; +import ru.basecode.ide.rest.plugin.psi.RestRequestBody; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.DELETE; +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.GET; +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.HEAD; +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.OPTIONS; +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.PATCH; +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.POST; +import static ru.basecode.ide.rest.plugin.http.HttpRequest.Method.PUT; + +/** + * @author danblack + */ +public class RestRequestParser { + + private static final Function encoder = + v -> UrlEscapers.urlFormParameterEscaper().escape(v); + + private static Method getMethod(RestRequest request) { + RestEMethod method = request.getEMethod(); + if (method != null && method.isValid()) { + switch (method.getText()) { + case "GET": + return GET; + case "PUT": + return PUT; + case "POST": + return POST; + case "DELETE": + return DELETE; + case "PATCH": + return PATCH; + case "OPTIONS": + return OPTIONS; + case "HEAD": + return HEAD; + } + throw new IllegalStateException(); + } + return GET; + } + + public static HttpRequest parse(RestRequest request) { + Method method = getMethod(request); + String url = getUrl(request); + String body = getBody(request); + List headers = getHeaders(request); + return HttpRequest.builder().method(method).url(url).headers(headers).body(body).build(); + } + + private static List getHeaders(RestRequest request) { + ArrayList result = new ArrayList<>(); + RestHeaders headers = request.getHeaders(); + if (headers != null) { + for (RestEHeader header : headers.getEHeaderList()) { + String text = header.getText(); + int i = text.indexOf("@"); + if (i >= 0) { + i = text.indexOf(":"); + if (i >= 0) { + String name = text.substring(1, i).trim(); + String value = text.substring(i + 1).trim(); + result.add(new HttpRequest.Header(name, value)); + } + } + } + } + return result; + } + + private static String getBody(RestRequest request) { + RestRequestBody body = request.getRequestBody(); + if (body != null && body.isValid()) { + return body.getText(); + } + return null; + } + + @NotNull + private static String getUrl(RestRequest request) { + RestEUrl url = request.getEUrl(); + if (url.isValid()) { + String urlText = url.getText(); + boolean markExists = urlText.contains("?"); + StringBuilder sb = new StringBuilder(urlText); + RestParams params = request.getParams(); + if (params != null && params.isValid()) { + if (!markExists) { + sb.append('?'); + } + for (RestEParam param : params.getEParamList()) { + sb.append(param.getText()); + } + } + return encode(sb.toString()); + } + throw new IllegalStateException(""); + } + + static String encode(String url) { + StringBuilder sb = new StringBuilder(url.length()); + int qpos = url.indexOf("?"); + if (qpos == -1) { + return url; + } + sb.append(url.substring(0, qpos + 1)); + url = url.substring(qpos + 1); + String delimiter = ""; + for (String paramEntry : url.split("&")) { + sb.append(delimiter); + int epos = paramEntry.indexOf("="); + if (epos == -1) { + sb.append(UrlEscapers.urlFormParameterEscaper().escape(paramEntry.trim())); + } else { + String paramName = paramEntry.substring(0, epos).trim(); + String paramValue = paramEntry.substring(epos + 1).trim(); + sb.append(encoder.apply(paramName)).append("=").append(encoder.apply(paramValue)); + } + delimiter = "&"; + } + return sb.toString(); + } +} diff --git a/src/ru/basecode/ide/rest/plugin/psi/RestTokenType.java b/src/main/java/ru/basecode/ide/rest/plugin/psi/RestTokenType.java similarity index 67% rename from src/ru/basecode/ide/rest/plugin/psi/RestTokenType.java rename to src/main/java/ru/basecode/ide/rest/plugin/psi/RestTokenType.java index bb8254c..21c729a 100644 --- a/src/ru/basecode/ide/rest/plugin/psi/RestTokenType.java +++ b/src/main/java/ru/basecode/ide/rest/plugin/psi/RestTokenType.java @@ -5,11 +5,8 @@ import org.jetbrains.annotations.NotNull; import ru.basecode.ide.rest.plugin.RestLanguage; -/** - * - */ public class RestTokenType extends IElementType { - public RestTokenType(@NotNull @NonNls String debugName) { - super(debugName, RestLanguage.INSTANCE); - } + public RestTokenType(@NotNull @NonNls String debugName) { + super(debugName, RestLanguage.INSTANCE); + } } diff --git a/src/main/java/ru/basecode/ide/rest/plugin/psi/impl/ResponseLiteralEscaper.java b/src/main/java/ru/basecode/ide/rest/plugin/psi/impl/ResponseLiteralEscaper.java new file mode 100644 index 0000000..bf21a12 --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/psi/impl/ResponseLiteralEscaper.java @@ -0,0 +1,35 @@ +package ru.basecode.ide.rest.plugin.psi.impl; + +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.LiteralTextEscaper; +import org.jetbrains.annotations.NotNull; + +public class ResponseLiteralEscaper extends LiteralTextEscaper { + public ResponseLiteralEscaper(RestWrapperPsiElement host) { + super(host); + } + + @Override + public boolean decode(@NotNull final TextRange rangeInsideHost, @NotNull StringBuilder outChars) { + outChars + .append(myHost.getText(), rangeInsideHost.getStartOffset(), rangeInsideHost.getEndOffset()); + return true; + } + + @Override + public int getOffsetInHost(int offsetInDecoded, @NotNull final TextRange rangeInsideHost) { + int offset = offsetInDecoded + rangeInsideHost.getStartOffset(); + if (offset < rangeInsideHost.getStartOffset()) { + offset = rangeInsideHost.getStartOffset(); + } + if (offset > rangeInsideHost.getEndOffset()) { + offset = rangeInsideHost.getEndOffset(); + } + return offset; + } + + @Override + public boolean isOneLine() { + return true; + } +} diff --git a/src/main/java/ru/basecode/ide/rest/plugin/psi/impl/RestWrapperPsiElement.java b/src/main/java/ru/basecode/ide/rest/plugin/psi/impl/RestWrapperPsiElement.java new file mode 100644 index 0000000..de45c0e --- /dev/null +++ b/src/main/java/ru/basecode/ide/rest/plugin/psi/impl/RestWrapperPsiElement.java @@ -0,0 +1,31 @@ +package ru.basecode.ide.rest.plugin.psi.impl; + +import com.intellij.extapi.psi.ASTWrapperPsiElement; +import com.intellij.lang.ASTNode; +import com.intellij.psi.LiteralTextEscaper; +import com.intellij.psi.PsiLanguageInjectionHost; +import org.jetbrains.annotations.NotNull; + +public class RestWrapperPsiElement extends ASTWrapperPsiElement + implements PsiLanguageInjectionHost { + + public RestWrapperPsiElement(@NotNull ASTNode node) { + super(node); + } + + @Override + public boolean isValidHost() { + return true; + } + + @Override + public PsiLanguageInjectionHost updateText(@NotNull String text) { + return this; + } + + @NotNull + @Override + public LiteralTextEscaper createLiteralTextEscaper() { + return new ResponseLiteralEscaper(this); + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml new file mode 100644 index 0000000..6b36e36 --- /dev/null +++ b/src/main/resources/META-INF/plugin.xml @@ -0,0 +1,70 @@ + + ru.basecode.ide.rest.plugin + Rest Client + Denis Chernyshov + + + + + + + + + com.intellij.modules.platform + + com.intellij.modules.lang + com.intellij.modules.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/META-INF/pluginIcon.svg b/src/main/resources/META-INF/pluginIcon.svg new file mode 100644 index 0000000..dcd617e --- /dev/null +++ b/src/main/resources/META-INF/pluginIcon.svg @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/main/resources/META-INF/pluginIcon_dark.svg b/src/main/resources/META-INF/pluginIcon_dark.svg new file mode 100644 index 0000000..acbae6a --- /dev/null +++ b/src/main/resources/META-INF/pluginIcon_dark.svg @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/main/resources/META-INF/restFileType.svg b/src/main/resources/META-INF/restFileType.svg new file mode 100644 index 0000000..683c7a8 --- /dev/null +++ b/src/main/resources/META-INF/restFileType.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + R + + + + diff --git a/resources/META-INF/schema.xml b/src/main/resources/META-INF/schema.xml similarity index 100% rename from resources/META-INF/schema.xml rename to src/main/resources/META-INF/schema.xml diff --git a/src/ru/basecode/ide/rest/plugin/HeaderPanelComponent.java b/src/ru/basecode/ide/rest/plugin/HeaderPanelComponent.java deleted file mode 100644 index 4132d3e..0000000 --- a/src/ru/basecode/ide/rest/plugin/HeaderPanelComponent.java +++ /dev/null @@ -1,135 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.ActionPlaces; -import com.intellij.openapi.actionSystem.CustomShortcutSet; -import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.keymap.Keymap; -import com.intellij.openapi.keymap.KeymapManager; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Key; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -/** - * @author danblack - */ -public class HeaderPanelComponent implements Disposable { - - private static final Key HEADER_PANEL_KEY = new Key<>("HEADER_PANEL_KEY"); - - public static void dispose(@NotNull Editor editor) { - HeaderPanelComponent component = editor.getUserData(HEADER_PANEL_KEY); - if (component != null) { - Disposer.dispose(component); - } - } - - public static boolean attached(Editor editor) { - return editor.getUserData(HEADER_PANEL_KEY) != null; - } - - private final ActionToolbarImpl panel; - private final RequestExecutor requestExecutor; - private final RunAction runAction; - private final StopAction stopAction; - private final Editor editor; - private TextEditor textEditor; - private Keymap keymap; - - private final Keymap.Listener shortcutChangeListener = actionId -> { - if (actionId.equals(RunAction.ID) || actionId.equals(StopAction.ID)) { - unregisterShortCuts(); - registerShortCuts(); - } - }; - - public HeaderPanelComponent(TextEditor textEditor) { - this.textEditor = textEditor; - editor = textEditor.getEditor(); - editor.putUserData(HEADER_PANEL_KEY, this); - - requestExecutor = new RequestExecutor(); - - runAction = new RunAction(requestExecutor); - stopAction = new StopAction(requestExecutor); - - panel = createPanel(createActionGroup()); - getFileEditorManager().addTopComponent(textEditor, panel); - - registerKeymapManagerListener(); - registerShortCuts(); - } - - @Override - public void dispose() { - if (textEditor != null) { - getFileEditorManager().removeTopComponent(textEditor, panel); - unregisterShortCuts(); - editor.putUserData(HEADER_PANEL_KEY, null); - textEditor = null; - } - } - - private FileEditorManager getFileEditorManager() { - return FileEditorManager.getInstance(editor.getProject()); - } - - @NotNull - private DefaultActionGroup createActionGroup() { - DefaultActionGroup group = new DefaultActionGroup("rest-request", false); - group.add(runAction); - group.add(stopAction); - return group; - } - - private ActionToolbarImpl createPanel(DefaultActionGroup group) { - ActionToolbarImpl result = (ActionToolbarImpl) ActionManager.getInstance().createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, group, true); - result.setForceMinimumSize(true); - return result; - } - - private void registerShortCuts() { - Objects.requireNonNull(editor, "Editor required"); - - KeymapManager keymapManager = KeymapManager.getInstance(); - Keymap newKeymap = keymapManager.getActiveKeymap(); - - if (!Objects.equals(keymap, newKeymap)) { - if (keymap != null) { - unregisterShortCuts(); - keymap.removeShortcutChangeListener(shortcutChangeListener); - } - keymap = newKeymap; - keymap.addShortcutChangeListener(shortcutChangeListener); // does not work - } - - runAction.registerCustomShortcutSet( - new CustomShortcutSet(keymap.getShortcuts(RunAction.ID)), - editor.getComponent()); - - stopAction.registerCustomShortcutSet( - new CustomShortcutSet(keymap.getShortcuts(StopAction.ID)), - editor.getComponent()); - } - - private void registerKeymapManagerListener() { - KeymapManager keymapManager = KeymapManager.getInstance(); - keymapManager.addKeymapManagerListener(keymap -> { - unregisterShortCuts(); - registerShortCuts(); - }, this); - } - - private void unregisterShortCuts() { - Objects.requireNonNull(editor, "Editor required"); - runAction.unregisterCustomShortcutSet(editor.getComponent()); - stopAction.unregisterCustomShortcutSet(editor.getComponent()); - } -} diff --git a/src/ru/basecode/ide/rest/plugin/Request.java b/src/ru/basecode/ide/rest/plugin/Request.java deleted file mode 100644 index 5ee16b1..0000000 --- a/src/ru/basecode/ide/rest/plugin/Request.java +++ /dev/null @@ -1,159 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import java.util.List; - -/** - * @author danblack - */ -public class Request { - - public static class Header { - private final String name; - private final String value; - - public Header(String name, String value) { - this.name = name; - this.value = value; - } - - public String getName() { - return name; - } - - public String getValue() { - return value; - } - - @Override - public String toString() { - return "Header{" + - "name='" + name + '\'' + - ", value='" + value + '\'' + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Header header = (Header) o; - - if (name != null ? !name.equals(header.name) : header.name != null) return false; - return value != null ? value.equals(header.value) : header.value == null; - - } - - @Override - public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (value != null ? value.hashCode() : 0); - return result; - } - } - - enum Method { - GET, - POST, - PUT, - PATCH, - DELETE - } - - public static class Params { - private final int timeout; - - public Params(int timeout) { - this.timeout = timeout; - } - - public int getTimeout() { - return timeout; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Params params = (Params) o; - - return timeout == params.timeout; - - } - - @Override - public int hashCode() { - return timeout; - } - - @Override - - public String toString() { - return "Params{" + - "timeout=" + timeout + - '}'; - } - } - - private final Method method; - private final String url; - private final List
headers; - private final String body; - - public Request(Method method, String url, List
headers, String body) { - this.method = method; - this.url = url; - this.headers = headers; - this.body = body; - } - - public Method getMethod() { - return method; - } - - public String getUrl() { - return url; - } - - public List
getHeaders() { - return headers; - } - - public String getBody() { - return body; - } - - @Override - public String toString() { - return "Request{" + - "method=" + method + - ", url='" + url + '\'' + - ", headers=" + headers + - ", body='" + body + '\'' + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Request request = (Request) o; - - if (method != request.method) return false; - if (url != null ? !url.equals(request.url) : request.url != null) return false; - if (headers != null ? !headers.equals(request.headers) : request.headers != null) return false; - return body != null ? body.equals(request.body) : request.body == null; - - } - - @Override - public int hashCode() { - int result = method != null ? method.hashCode() : 0; - result = 31 * result + (url != null ? url.hashCode() : 0); - result = 31 * result + (headers != null ? headers.hashCode() : 0); - result = 31 * result + (body != null ? body.hashCode() : 0); - return result; - } -} diff --git a/src/ru/basecode/ide/rest/plugin/RequestExecutor.java b/src/ru/basecode/ide/rest/plugin/RequestExecutor.java deleted file mode 100644 index 904d862..0000000 --- a/src/ru/basecode/ide/rest/plugin/RequestExecutor.java +++ /dev/null @@ -1,123 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.UserDataHolder; -import org.apache.http.Header; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.util.EntityUtils; -import ru.basecode.ide.rest.plugin.http.Response; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * @author danblack - */ -public class RequestExecutor { - - private static final Key EXECUTOR_KEY = new Key<>("ExecutorKey"); - private State state = State.WAITING; - - enum State { - WAITING, - RUNNING, - STOPPING - } - - private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); - - private final HttpClient httpClient = createHttpClient(); - - public static RequestExecutor getInstance(UserDataHolder dataHolder) { - RequestExecutor executor = dataHolder.getUserData(EXECUTOR_KEY); - if (executor == null) { - executor = new RequestExecutor(); - dataHolder.putUserData(EXECUTOR_KEY, executor); - } - return executor; - } - - private HttpRequestBase httpRequest; - - public void stop() { - httpRequest.abort(); - } - - private CloseableHttpClient createHttpClient() { - try { - SSLContextBuilder contextBuilder = SSLContextBuilder.create(); - contextBuilder.loadTrustMaterial(null, (x509Certificates, s) -> true); - SSLConnectionSocketFactory sslSocketFactory = - new SSLConnectionSocketFactory(contextBuilder.build(), NoopHostnameVerifier.INSTANCE); - return HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build(); - } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { - throw new RuntimeException(e); - } - } - - public Response execute(Request request) throws IOException { - state = State.RUNNING; - try { - httpRequest = Requests.createHttpRequest(request); - HttpResponse response = httpClient.execute(httpRequest); - return new Response( - response.getStatusLine().toString(), - getHeaders(response), - getContentType(response), - EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET)); - } finally { - state = State.WAITING; - } - } - - private String getContentType(HttpResponse response) { - Header header = response.getFirstHeader("Content-Type"); - if (header != null) { - String contentType = header.getValue(); - int semicolonIndex = contentType.indexOf(";"); - if (semicolonIndex >= 0) { - return contentType.substring(0, semicolonIndex); - } else { - return contentType; - } - } - return null; - } - - private List getHeaders(HttpResponse response) { - Header[] allHeaders = response.getAllHeaders(); - if (allHeaders != null) { - ArrayList result = new ArrayList<>(); - for (Header header : allHeaders) { - result.add(header.getName() + ": " + header.getValue()); - } - return result; - } - return Collections.emptyList(); - } - - public boolean isRunning() { - return state == State.RUNNING; - } - - public boolean isWaiting() { - return state == State.WAITING; - } - - public State getState() { - return state; - } -} diff --git a/src/ru/basecode/ide/rest/plugin/RequestParser.java b/src/ru/basecode/ide/rest/plugin/RequestParser.java deleted file mode 100644 index 69e06e3..0000000 --- a/src/ru/basecode/ide/rest/plugin/RequestParser.java +++ /dev/null @@ -1,118 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.google.common.net.UrlEscapers; -import org.jetbrains.annotations.NotNull; -import ru.basecode.ide.rest.plugin.psi.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; - -/** - * @author danblack - */ -public class RequestParser { - - private static final Function encoder = v -> UrlEscapers.urlFormParameterEscaper().escape(v); - - private static Request.Method getMethod(RestRequest request) { - RestEMethod method = request.getEMethod(); - if (method != null && method.isValid()) { - switch (method.getText()) { - case "GET": - return Request.Method.GET; - case "PUT": - return Request.Method.PUT; - case "POST": - return Request.Method.POST; - case "DELETE": - return Request.Method.DELETE; - case "PATCH": - return Request.Method.PATCH; - } - throw new IllegalStateException(); - } - return Request.Method.GET; - } - - public static Request parse(RestRequest request) { - Request.Method method = getMethod(request); - String url = getUrl(request); - String body = getBody(request); - List headers = getHeaders(request); - return new Request(method, url, headers, body); - } - - private static List getHeaders(RestRequest request) { - ArrayList result = new ArrayList<>(); - RestHeaders headers = request.getHeaders(); - if (headers != null) { - for (RestEHeader header : headers.getEHeaderList()) { - String text = header.getText(); - int i = text.indexOf("@"); - if (i >= 0) { - i = text.indexOf(":"); - if (i >= 0) { - String name = text.substring(1, i).trim(); - String value = text.substring(i + 1).trim(); - result.add(new Request.Header(name, value)); - } - } - } - } - return result; - } - - private static String getBody(RestRequest request) { - RestRequestBody body = request.getRequestBody(); - if (body != null && body.isValid()) { - return body.getText(); - } - return null; - } - - @NotNull - private static String getUrl(RestRequest request) { - RestEUrl url = request.getEUrl(); - if (url.isValid()) { - String urlText = url.getText(); - boolean markExists = urlText.contains("?"); - StringBuilder sb = new StringBuilder(urlText); - RestParams params = request.getParams(); - if (params != null && params.isValid()) { - if (!markExists) { - sb.append('?'); - } - for (RestEParam param : params.getEParamList()) { - sb.append(param.getText()); - } - } - return encode(sb.toString()); - } - throw new IllegalStateException(""); - } - - static String encode(String url) { - StringBuilder sb = new StringBuilder(url.length()); - int qpos = url.indexOf("?"); - if (qpos == -1) { - return url; - } - sb.append(url.substring(0, qpos + 1)); - url = url.substring(qpos + 1); - String delimiter = ""; - for (String paramEntry : url.split("&")) { - sb.append(delimiter); - int epos = paramEntry.indexOf("="); - if (epos == -1) { - sb.append(UrlEscapers.urlFormParameterEscaper().escape(paramEntry.trim())); - } else { - String paramName = paramEntry.substring(0, epos).trim(); - String paramValue = paramEntry.substring(epos + 1).trim(); - sb.append(encoder.apply(paramName)).append("=").append(encoder.apply(paramValue)); - } - delimiter = "&"; - } - return sb.toString(); - } -} diff --git a/src/ru/basecode/ide/rest/plugin/Requests.java b/src/ru/basecode/ide/rest/plugin/Requests.java deleted file mode 100644 index 644a7d8..0000000 --- a/src/ru/basecode/ide/rest/plugin/Requests.java +++ /dev/null @@ -1,74 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import org.apache.http.client.methods.*; -import org.apache.http.entity.StringEntity; - -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.util.function.Function; - -/** - * @author danblack - */ -public class Requests { - - private static final Charset DEFAULT_CHARSET = Charset.defaultCharset(); - - public static HttpRequestBase createHttpRequest(Request request) throws UnsupportedEncodingException { - if (request.getMethod() == Request.Method.GET) { - HttpGet httpGet = new HttpGet(request.getUrl()); - fillHeaders(request, httpGet); - return httpGet; - } - - if (request.getMethod() == Request.Method.POST) { - return create(HttpPost::new, request); - } - - if (request.getMethod() == Request.Method.PUT) { - return create(HttpPut::new, request); - } - - if (request.getMethod() == Request.Method.PATCH) { - return create(HttpPatch::new, request); - } - - if (request.getMethod() == Request.Method.DELETE) { - HttpDelete httpDelete = new HttpDelete(request.getUrl()); - fillHeaders(request, httpDelete); - return httpDelete; - } - - throw new IllegalStateException(); - } - - public static HttpEntityEnclosingRequestBase create(Function constructor, - Request request) - throws UnsupportedEncodingException { - HttpEntityEnclosingRequestBase httpRequest = constructor.apply(request.getUrl()); - fillHeaders(request, httpRequest); - fillBody(request, httpRequest); - return httpRequest; - } - - private static void fillBody(Request request, HttpEntityEnclosingRequestBase httpRequest) - throws UnsupportedEncodingException { - String body = request.getBody(); - if (body != null) { - httpRequest.setEntity(new StringEntity(body, DEFAULT_CHARSET)); - for (Request.Header header : request.getHeaders()) { - if ("Content-Type".equals(header.getName())) { - if (!header.getValue().contains("charset")) { - - } - } - } - } - } - - private static void fillHeaders(Request request, HttpRequestBase httpRequest) { - for (Request.Header header : request.getHeaders()) { - httpRequest.addHeader(header.getName(), header.getValue()); - } - } -} diff --git a/src/ru/basecode/ide/rest/plugin/RestClientPlugin.java b/src/ru/basecode/ide/rest/plugin/RestClientPlugin.java deleted file mode 100644 index 762d538..0000000 --- a/src/ru/basecode/ide/rest/plugin/RestClientPlugin.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.openapi.components.ApplicationComponent; -import org.jetbrains.annotations.NotNull; - -/** - * @author danblack - */ -public class RestClientPlugin implements ApplicationComponent { - - @NotNull - @Override - public String getComponentName() { - return "Rest client plugin"; - } - - @Override - public void initComponent() { - } - - @Override - public void disposeComponent() { - } -} diff --git a/src/ru/basecode/ide/rest/plugin/RestCodeStyleSettingsProvider.java b/src/ru/basecode/ide/rest/plugin/RestCodeStyleSettingsProvider.java deleted file mode 100644 index c5fbd14..0000000 --- a/src/ru/basecode/ide/rest/plugin/RestCodeStyleSettingsProvider.java +++ /dev/null @@ -1,53 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.application.options.CodeStyleAbstractConfigurable; -import com.intellij.application.options.CodeStyleAbstractPanel; -import com.intellij.application.options.TabbedLanguageCodeStylePanel; -import com.intellij.openapi.options.Configurable; -import com.intellij.psi.codeStyle.CodeStyleSettings; -import com.intellij.psi.codeStyle.CodeStyleSettingsProvider; -import com.intellij.psi.codeStyle.CustomCodeStyleSettings; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author danblack - */ -public class RestCodeStyleSettingsProvider extends CodeStyleSettingsProvider { - @NotNull - @Override - public Configurable createSettingsPage(CodeStyleSettings settings, CodeStyleSettings originalSettings) { - return new CodeStyleAbstractConfigurable(settings, originalSettings, "Rest") { - - @Nullable - @Override - public String getHelpTopic() { - return null; - } - - @Override - protected CodeStyleAbstractPanel createPanel(CodeStyleSettings settings) { - return new RestTabbedLanguageCodeStylePanel(getCurrentSettings(), settings); - } - }; - } - - @Nullable - @Override - public String getConfigurableDisplayName() { - return "Rest"; - } - - @Nullable - @Override - public CustomCodeStyleSettings createCustomSettings(CodeStyleSettings settings) { - return new RestCodeStyleSettings(settings); - } - - private static class RestTabbedLanguageCodeStylePanel extends TabbedLanguageCodeStylePanel { - - protected RestTabbedLanguageCodeStylePanel(CodeStyleSettings currentSettings, CodeStyleSettings settings) { - super(RestLanguage.INSTANCE, currentSettings, settings); - } - } -} diff --git a/src/ru/basecode/ide/rest/plugin/RestColorSettingsPage.java b/src/ru/basecode/ide/rest/plugin/RestColorSettingsPage.java deleted file mode 100644 index e93e0a0..0000000 --- a/src/ru/basecode/ide/rest/plugin/RestColorSettingsPage.java +++ /dev/null @@ -1,85 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.openapi.editor.colors.TextAttributesKey; -import com.intellij.openapi.fileTypes.SyntaxHighlighter; -import com.intellij.openapi.options.colors.AttributesDescriptor; -import com.intellij.openapi.options.colors.ColorDescriptor; -import com.intellij.openapi.options.colors.ColorSettingsPage; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.Map; - -/** - * @author danblack - */ -public class RestColorSettingsPage implements ColorSettingsPage { - - private static final AttributesDescriptor[] DESCRIPTORS = new AttributesDescriptor[]{ - new AttributesDescriptor("Comment", RestHighlighter.COMMENT), - new AttributesDescriptor("Method", RestHighlighter.METHOD), - new AttributesDescriptor("Url", RestHighlighter.URL), - new AttributesDescriptor("Header", RestHighlighter.HEADER), - new AttributesDescriptor("Option", RestHighlighter.OPTION), - new AttributesDescriptor("Param", RestHighlighter.PARAM), - new AttributesDescriptor("Error", RestHighlighter.BAD_CHARACTER), - new AttributesDescriptor("Separator", RestHighlighter.SEPARATOR), - new AttributesDescriptor("Body", RestHighlighter.BODY), - }; - - @NotNull - @Override - public AttributesDescriptor[] getAttributeDescriptors() { - return DESCRIPTORS; - } - - @NotNull - @Override - public ColorDescriptor[] getColorDescriptors() { - return ColorDescriptor.EMPTY_ARRAY; - } - - @NotNull - @Override - public String getDisplayName() { - return "Rest"; - } - - @Nullable - @Override - public Icon getIcon() { - return null; - } - - @NotNull - @Override - public SyntaxHighlighter getHighlighter() { - return new RestHighlighter(); - } - - @NotNull - @Override - public String getDemoText() { - return "# This is a comment\n" + - "-- option = value\n" + - "POST\n" + - "http://site.com\n" + - "¶m=value\n" + - "@Content-type: application/xml\n" + - "\n" + - "This is a request body\n" + - "\n" + - "%%%\n" + - "@Content-length: 100\n" + - "\n" + - "This is a response body\n" + - ""; - } - - @Nullable - @Override - public Map getAdditionalHighlightingTagToDescriptorMap() { - return null; - } -} diff --git a/src/ru/basecode/ide/rest/plugin/RestFile.java b/src/ru/basecode/ide/rest/plugin/RestFile.java deleted file mode 100644 index 6924faa..0000000 --- a/src/ru/basecode/ide/rest/plugin/RestFile.java +++ /dev/null @@ -1,43 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.extapi.psi.PsiFileBase; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.psi.FileViewProvider; -import com.intellij.psi.PsiElement; -import org.jetbrains.annotations.NotNull; -import ru.basecode.ide.rest.plugin.psi.RestESeparator; -import ru.basecode.ide.rest.plugin.psi.RestRequest; - -/** - * - */ -public class RestFile extends PsiFileBase { - - protected RestFile(@NotNull FileViewProvider viewProvider) { - super(viewProvider, RestLanguage.INSTANCE); - } - - public RestRequest getRequest() { - for (PsiElement element : getChildren()) { - if (element instanceof RestRequest) { - return (RestRequest) element; - } - } - return null; - } - - public RestESeparator getSeparator() { - for (PsiElement element : getChildren()) { - if (element instanceof RestESeparator) { - return (RestESeparator) element; - } - } - return null; - } - - @NotNull - @Override - public FileType getFileType() { - return RestFileType.INSTANCE; - } -} diff --git a/src/ru/basecode/ide/rest/plugin/RestFileType.java b/src/ru/basecode/ide/rest/plugin/RestFileType.java deleted file mode 100644 index 6b146ff..0000000 --- a/src/ru/basecode/ide/rest/plugin/RestFileType.java +++ /dev/null @@ -1,44 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.icons.AllIcons; -import com.intellij.openapi.fileTypes.LanguageFileType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -/** - * - */ -public class RestFileType extends LanguageFileType { - - public final static LanguageFileType INSTANCE = new RestFileType(); - - protected RestFileType() { - super(RestLanguage.INSTANCE); - } - - @NotNull - @Override - public String getName() { - return "Rest file"; - } - - @NotNull - @Override - public String getDescription() { - return "Rest language file"; - } - - @NotNull - @Override - public String getDefaultExtension() { - return "rest"; - } - - @Nullable - @Override - public Icon getIcon() { - return AllIcons.General.Web; - } -} diff --git a/src/ru/basecode/ide/rest/plugin/RestFileTypeFactory.java b/src/ru/basecode/ide/rest/plugin/RestFileTypeFactory.java deleted file mode 100644 index eb90609..0000000 --- a/src/ru/basecode/ide/rest/plugin/RestFileTypeFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.openapi.fileTypes.FileTypeConsumer; -import com.intellij.openapi.fileTypes.FileTypeFactory; -import org.jetbrains.annotations.NotNull; - -/** - * - */ -public class RestFileTypeFactory extends FileTypeFactory { - @Override - public void createFileTypes(@NotNull FileTypeConsumer consumer) { - consumer.consume(RestFileType.INSTANCE, "rest"); - } -} diff --git a/src/ru/basecode/ide/rest/plugin/RestHostInjector.java b/src/ru/basecode/ide/rest/plugin/RestHostInjector.java deleted file mode 100644 index 3f6a5e7..0000000 --- a/src/ru/basecode/ide/rest/plugin/RestHostInjector.java +++ /dev/null @@ -1,69 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.lang.Language; -import com.intellij.lang.injection.MultiHostInjector; -import com.intellij.lang.injection.MultiHostRegistrar; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiLanguageInjectionHost; -import com.intellij.psi.impl.source.tree.injected.MultiHostRegistrarImpl; -import org.jetbrains.annotations.NotNull; -import ru.basecode.ide.rest.plugin.psi.*; -import ru.basecode.ide.rest.plugin.psi.impl.RestElementImpl; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * - */ -public class RestHostInjector implements MultiHostInjector { - @Override - public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull final PsiElement context) { - final MultiHostRegistrarImpl r = (MultiHostRegistrarImpl) registrar; - RestHeaders headers = null; - if (context instanceof RestResponseBody) { - headers = ((RestResponse) context.getParent()).getHeaders(); - } else if (context instanceof RestRequestBody) { - headers = ((RestRequest) context.getParent()).getHeaders(); - } - if (headers != null) { - String contentType = getContentType(headers); - if (contentType != null) { - Collection langList = Language.findInstancesByMimeType(contentType); - if (!langList.isEmpty()) { - r.startInjecting(langList.iterator().next()) - .addPlace(null, null, (PsiLanguageInjectionHost) context, TextRange.create(0, context.getTextLength())) - .doneInjecting(); - } - } - } - } - - private String getContentType(@NotNull RestHeaders headers) { - if (headers.isValid()) { - for (RestEHeader eHeader : headers.getEHeaderList()) { - String header = eHeader.getText(); - if (header.startsWith("@Content-Type")) { - int colonIndex = header.indexOf(":"); - if (colonIndex >= 0) { - String contentType = header.substring(colonIndex + 1); - int semicolonIndex = contentType.indexOf(";"); - if (semicolonIndex >= 0) { - return contentType.substring(0, semicolonIndex).trim(); - } - return contentType.trim(); - } - } - } - } - return null; - } - - @NotNull - @Override - public List> elementsToInjectIn() { - return Collections.singletonList(RestElementImpl.class); - } -} diff --git a/src/ru/basecode/ide/rest/plugin/RestLanguage.java b/src/ru/basecode/ide/rest/plugin/RestLanguage.java deleted file mode 100644 index b489cec..0000000 --- a/src/ru/basecode/ide/rest/plugin/RestLanguage.java +++ /dev/null @@ -1,15 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.lang.Language; - -/** - * - */ -public class RestLanguage extends Language { - - public static final Language INSTANCE = new RestLanguage(); - - public RestLanguage() { - super("Rest"); - } -} diff --git a/src/ru/basecode/ide/rest/plugin/RestLexer.java b/src/ru/basecode/ide/rest/plugin/RestLexer.java deleted file mode 100644 index 0f06ce6..0000000 --- a/src/ru/basecode/ide/rest/plugin/RestLexer.java +++ /dev/null @@ -1,579 +0,0 @@ -/* The following code was generated by JFlex 1.4.3 on 04.04.16 22:28 */ - -package ru.basecode.ide.rest.plugin; - -import com.intellij.lexer.FlexLexer; -import com.intellij.psi.tree.IElementType; -import ru.basecode.ide.rest.plugin.psi.RestTypes; -import com.intellij.psi.TokenType; - - -/** - * This class is a scanner generated by - * JFlex 1.4.3 - * on 04.04.16 22:28 from the specification file - * /Users/zoom/Development/ide/projects/idea-rest-client/src/ru/basecode/ide/rest/plugin/Rest.flex - */ -class RestLexer implements FlexLexer { - /** initial size of the lookahead buffer */ - private static final int ZZ_BUFFERSIZE = 16384; - - /** lexical states */ - public static final int S_RESP_HEADER = 12; - public static final int S_PARAM = 8; - public static final int S_RESP_BODY = 14; - public static final int S_REQ_HEADER = 10; - public static final int S_METHOD = 2; - public static final int S_URL = 6; - public static final int YYINITIAL = 0; - public static final int S_REQ_BODY = 16; - public static final int S_OPTION = 4; - - /** - * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l - * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l - * at the beginning of a line - * l is of the form l = 2*k, k a non negative integer - */ - private static final int ZZ_LEXSTATE[] = { - 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, - 8, 8 - }; - - /** - * Translates characters to character classes - */ - private static final String ZZ_CMAP_PACKED = - "\11\0\1\31\1\27\1\0\1\31\1\30\22\0\1\31\2\0\1\20"+ - "\1\0\1\32\1\17\6\0\1\15\1\0\1\26\12\0\1\25\5\0"+ - "\1\16\1\10\1\0\1\11\1\13\1\2\1\0\1\1\1\12\3\0"+ - "\1\14\2\0\1\5\1\4\2\0\1\6\1\3\1\7\22\0\1\21"+ - "\7\0\1\23\2\0\1\24\1\22\uff8b\0"; - - /** - * Translates characters to character classes - */ - private static final char [] ZZ_CMAP = zzUnpackCMap(ZZ_CMAP_PACKED); - - /** - * Translates DFA states to action switch labels. - */ - private static final int [] ZZ_ACTION = zzUnpackAction(); - - private static final String ZZ_ACTION_PACKED_0 = - "\11\0\5\1\1\2\1\1\2\3\1\4\1\1\1\5"+ - "\1\6\1\7\1\4\1\5\1\10\1\11\1\12\1\13"+ - "\4\0\1\14\1\0\1\15\1\0\1\5\1\16\4\0"+ - "\1\17\3\0\1\17\4\0\1\20"; - - private static int [] zzUnpackAction() { - int [] result = new int[53]; - int offset = 0; - offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); - return result; - } - - private static int zzUnpackAction(String packed, int offset, int [] result) { - int i = 0; /* index in packed string */ - int j = offset; /* index in unpacked array */ - int l = packed.length(); - while (i < l) { - int count = packed.charAt(i++); - int value = packed.charAt(i++); - do result[j++] = value; while (--count > 0); - } - return j; - } - - - /** - * Translates a state to a row index in the transition table - */ - private static final int [] ZZ_ROWMAP = zzUnpackRowMap(); - - private static final String ZZ_ROWMAP_PACKED_0 = - "\0\0\0\33\0\66\0\121\0\154\0\207\0\242\0\275"+ - "\0\330\0\363\0\u010e\0\u0129\0\u0144\0\u015f\0\u017a\0\u0195"+ - "\0\363\0\u01b0\0\u01cb\0\u01e6\0\u0201\0\u021c\0\u0237\0\u0252"+ - "\0\u026d\0\u0288\0\u02a3\0\u02be\0\u02d9\0\u02f4\0\u030f\0\u032a"+ - "\0\u0345\0\u0360\0\u037b\0\u0396\0\u03b1\0\u03cc\0\363\0\u03e7"+ - "\0\u0402\0\u041d\0\u0438\0\u0453\0\u046e\0\u0489\0\u04a4\0\u04bf"+ - "\0\u04da\0\u04f5\0\u0510\0\u052b\0\u0546"; - - private static int [] zzUnpackRowMap() { - int [] result = new int[53]; - int offset = 0; - offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); - return result; - } - - private static int zzUnpackRowMap(String packed, int offset, int [] result) { - int i = 0; /* index in packed string */ - int j = offset; /* index in unpacked array */ - int l = packed.length(); - while (i < l) { - int high = packed.charAt(i++) << 16; - result[j++] = high | packed.charAt(i++); - } - return j; - } - - /** - * The transition table of the DFA - */ - private static final int [] ZZ_TRANS = zzUnpackTrans(); - - private static final String ZZ_TRANS_PACKED_0 = - "\1\12\1\13\2\12\1\14\6\12\1\15\1\12\1\16"+ - "\2\12\1\17\1\20\5\12\1\21\1\22\1\23\21\12"+ - "\1\17\1\20\5\12\1\21\1\22\1\23\2\12\1\13"+ - "\2\12\1\14\6\12\1\15\1\12\1\24\2\12\1\17"+ - "\1\20\5\12\1\21\1\22\1\23\1\12\16\25\1\26"+ - "\1\27\1\17\6\25\1\21\1\22\1\30\1\31\16\25"+ - "\1\26\1\32\1\17\6\25\1\21\1\22\1\30\1\31"+ - "\16\25\1\33\1\25\1\17\6\25\1\21\1\22\1\30"+ - "\1\31\16\34\1\33\1\34\1\17\6\34\1\21\1\22"+ - "\1\23\1\34\27\35\1\21\1\22\1\23\1\35\27\25"+ - "\1\21\1\22\1\30\1\31\35\0\1\36\35\0\1\37"+ - "\1\0\1\36\1\40\24\0\1\41\45\0\1\42\15\0"+ - "\27\17\2\0\2\17\22\0\1\43\37\0\1\21\34\0"+ - "\1\23\16\0\1\44\15\0\27\25\2\0\2\25\27\26"+ - "\2\0\2\26\27\27\2\0\2\27\31\0\1\30\1\45"+ - "\27\25\2\0\1\25\1\46\27\32\2\0\2\32\27\33"+ - "\2\0\2\33\27\34\2\0\2\34\27\35\2\0\2\35"+ - "\3\0\1\47\35\0\1\36\27\0\1\50\43\0\1\51"+ - "\16\0\27\42\2\0\2\42\22\0\1\52\10\0\27\44"+ - "\2\0\2\44\32\0\1\53\27\25\2\0\1\25\1\54"+ - "\11\0\1\55\23\0\1\56\53\0\1\57\41\0\1\60"+ - "\27\25\2\0\1\54\1\25\12\0\1\47\23\0\1\61"+ - "\53\0\1\62\1\63\36\0\1\60\3\0\1\47\55\0"+ - "\1\63\33\0\1\64\32\0\1\65\4\0\27\65\2\0"+ - "\2\65"; - - private static int [] zzUnpackTrans() { - int [] result = new int[1377]; - int offset = 0; - offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result); - return result; - } - - private static int zzUnpackTrans(String packed, int offset, int [] result) { - int i = 0; /* index in packed string */ - int j = offset; /* index in unpacked array */ - int l = packed.length(); - while (i < l) { - int count = packed.charAt(i++); - int value = packed.charAt(i++); - value--; - do result[j++] = value; while (--count > 0); - } - return j; - } - - - /* error codes */ - private static final int ZZ_UNKNOWN_ERROR = 0; - private static final int ZZ_NO_MATCH = 1; - private static final int ZZ_PUSHBACK_2BIG = 2; - private static final char[] EMPTY_BUFFER = new char[0]; - private static final int YYEOF = -1; - private static java.io.Reader zzReader = null; // Fake - - /* error messages for the codes above */ - private static final String ZZ_ERROR_MSG[] = { - "Unkown internal scanner error", - "Error: could not match input", - "Error: pushback value was too large" - }; - - /** - * ZZ_ATTRIBUTE[aState] contains the attributes of state aState - */ - private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute(); - - private static final String ZZ_ATTRIBUTE_PACKED_0 = - "\11\0\1\11\6\1\1\11\14\1\4\0\1\1\1\0"+ - "\1\1\1\0\1\1\1\11\4\0\1\1\3\0\1\1"+ - "\4\0\1\1"; - - private static int [] zzUnpackAttribute() { - int [] result = new int[53]; - int offset = 0; - offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); - return result; - } - - private static int zzUnpackAttribute(String packed, int offset, int [] result) { - int i = 0; /* index in packed string */ - int j = offset; /* index in unpacked array */ - int l = packed.length(); - while (i < l) { - int count = packed.charAt(i++); - int value = packed.charAt(i++); - do result[j++] = value; while (--count > 0); - } - return j; - } - - /** the current state of the DFA */ - private int zzState; - - /** the current lexical state */ - private int zzLexicalState = YYINITIAL; - - /** this buffer contains the current text to be matched and is - the source of the yytext() string */ - private CharSequence zzBuffer = ""; - - /** this buffer may contains the current text array to be matched when it is cheap to acquire it */ - private char[] zzBufferArray; - - /** the textposition at the last accepting state */ - private int zzMarkedPos; - - /** the textposition at the last state to be included in yytext */ - private int zzPushbackPos; - - /** the current text position in the buffer */ - private int zzCurrentPos; - - /** startRead marks the beginning of the yytext() string in the buffer */ - private int zzStartRead; - - /** endRead marks the last character in the buffer, that has been read - from input */ - private int zzEndRead; - - /** - * zzAtBOL == true <=> the scanner is currently at the beginning of a line - */ - private boolean zzAtBOL = true; - - /** zzAtEOF == true <=> the scanner is at the EOF */ - private boolean zzAtEOF; - - /** denotes if the user-EOF-code has already been executed */ - private boolean zzEOFDone; - - - /** - * Creates a new scanner - * - * @param in the java.io.Reader to read input from. - */ - RestLexer(java.io.Reader in) { - this.zzReader = in; - } - - - /** - * Unpacks the compressed character translation table. - * - * @param packed the packed character translation table - * @return the unpacked character translation table - */ - private static char [] zzUnpackCMap(String packed) { - char [] map = new char[0x10000]; - int i = 0; /* index in packed string */ - int j = 0; /* index in unpacked array */ - while (i < 92) { - int count = packed.charAt(i++); - char value = packed.charAt(i++); - do map[j++] = value; while (--count > 0); - } - return map; - } - - public final int getTokenStart(){ - return zzStartRead; - } - - public final int getTokenEnd(){ - return getTokenStart() + yylength(); - } - - public void reset(CharSequence buffer, int start, int end,int initialState){ - zzBuffer = buffer; - zzBufferArray = com.intellij.util.text.CharArrayUtil.fromSequenceWithoutCopying(buffer); - zzCurrentPos = zzMarkedPos = zzStartRead = start; - zzPushbackPos = 0; - zzAtEOF = false; - zzAtBOL = true; - zzEndRead = end; - yybegin(initialState); - } - - /** - * Refills the input buffer. - * - * @return false, iff there was new input. - * - * @exception java.io.IOException if any I/O-Error occurs - */ - private boolean zzRefill() throws java.io.IOException { - return true; - } - - - /** - * Returns the current lexical state. - */ - public final int yystate() { - return zzLexicalState; - } - - - /** - * Enters a new lexical state - * - * @param newState the new lexical state - */ - public final void yybegin(int newState) { - zzLexicalState = newState; - } - - - /** - * Returns the text matched by the current regular expression. - */ - public final CharSequence yytext() { - return zzBuffer.subSequence(zzStartRead, zzMarkedPos); - } - - - /** - * Returns the character at position pos from the - * matched text. - * - * It is equivalent to yytext().charAt(pos), but faster - * - * @param pos the position of the character to fetch. - * A value from 0 to yylength()-1. - * - * @return the character at position pos - */ - public final char yycharat(int pos) { - return zzBufferArray != null ? zzBufferArray[zzStartRead+pos]:zzBuffer.charAt(zzStartRead+pos); - } - - - /** - * Returns the length of the matched text region. - */ - public final int yylength() { - return zzMarkedPos-zzStartRead; - } - - - /** - * Reports an error that occured while scanning. - * - * In a wellformed scanner (no or only correct usage of - * yypushback(int) and a match-all fallback rule) this method - * will only be called with things that "Can't Possibly Happen". - * If this method is called, something is seriously wrong - * (e.g. a JFlex bug producing a faulty scanner etc.). - * - * Usual syntax/scanner level error handling should be done - * in error fallback rules. - * - * @param errorCode the code of the errormessage to display - */ - private void zzScanError(int errorCode) { - String message; - try { - message = ZZ_ERROR_MSG[errorCode]; - } - catch (ArrayIndexOutOfBoundsException e) { - message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR]; - } - - throw new Error(message); - } - - - /** - * Pushes the specified amount of characters back into the input stream. - * - * They will be read again by then next call of the scanning method - * - * @param number the number of characters to be read again. - * This number must not be greater than yylength()! - */ - public void yypushback(int number) { - if ( number > yylength() ) - zzScanError(ZZ_PUSHBACK_2BIG); - - zzMarkedPos -= number; - } - - - /** - * Contains user EOF-code, which will be executed exactly once, - * when the end of file is reached - */ - private void zzDoEOF() { - if (!zzEOFDone) { - zzEOFDone = true; - - } - } - - - /** - * Resumes scanning until the next regular expression is matched, - * the end of input is encountered or an I/O-Error occurs. - * - * @return the next token - * @exception java.io.IOException if any I/O-Error occurs - */ - public IElementType advance() throws java.io.IOException { - int zzInput; - int zzAction; - - // cached fields: - int zzCurrentPosL; - int zzMarkedPosL; - int zzEndReadL = zzEndRead; - CharSequence zzBufferL = zzBuffer; - char[] zzBufferArrayL = zzBufferArray; - char [] zzCMapL = ZZ_CMAP; - - int [] zzTransL = ZZ_TRANS; - int [] zzRowMapL = ZZ_ROWMAP; - int [] zzAttrL = ZZ_ATTRIBUTE; - - while (true) { - zzMarkedPosL = zzMarkedPos; - - zzAction = -1; - - zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; - - zzState = ZZ_LEXSTATE[zzLexicalState]; - - - zzForAction: { - while (true) { - - if (zzCurrentPosL < zzEndReadL) - zzInput = (zzBufferArrayL != null ? zzBufferArrayL[zzCurrentPosL++] : zzBufferL.charAt(zzCurrentPosL++)); - else if (zzAtEOF) { - zzInput = YYEOF; - break zzForAction; - } - else { - // store back cached positions - zzCurrentPos = zzCurrentPosL; - zzMarkedPos = zzMarkedPosL; - boolean eof = zzRefill(); - // get translated positions and possibly new buffer - zzCurrentPosL = zzCurrentPos; - zzMarkedPosL = zzMarkedPos; - zzBufferL = zzBuffer; - zzEndReadL = zzEndRead; - if (eof) { - zzInput = YYEOF; - break zzForAction; - } - else { - zzInput = (zzBufferArrayL != null ? zzBufferArrayL[zzCurrentPosL++] : zzBufferL.charAt(zzCurrentPosL++)); - } - } - int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ]; - if (zzNext == -1) break zzForAction; - zzState = zzNext; - - int zzAttributes = zzAttrL[zzState]; - if ( (zzAttributes & 1) == 1 ) { - zzAction = zzState; - zzMarkedPosL = zzCurrentPosL; - if ( (zzAttributes & 8) == 8 ) break zzForAction; - } - - } - } - - // store back cached position - zzMarkedPos = zzMarkedPosL; - - switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { - case 7: - { yybegin(S_PARAM); return RestTypes.PARAM; - } - case 17: break; - case 4: - { return RestTypes.WHITE_SPACE; - } - case 18: break; - case 11: - { return RestTypes.RESPONSE_BODY_LINE; - } - case 19: break; - case 9: - { return RestTypes.HEADER; - } - case 20: break; - case 14: - { yybegin(S_METHOD); return RestTypes.METHOD; - } - case 21: break; - case 2: - { return RestTypes.COMMENT; - } - case 22: break; - case 1: - { return RestTypes.BAD_CHARACTER; - } - case 23: break; - case 15: - { yybegin(S_RESP_HEADER); return RestTypes.SEPARATOR; - } - case 24: break; - case 16: - { yybegin(S_URL); return RestTypes.URL; - } - case 25: break; - case 3: - { return RestTypes.CRLF; - } - case 26: break; - case 10: - { yybegin(S_RESP_BODY); return RestTypes.RESPONSE_BODY_LINE; - } - case 27: break; - case 6: - { yybegin(S_REQ_HEADER); return RestTypes.HEADER; - } - case 28: break; - case 12: - { yybegin(S_OPTION); return RestTypes.OPTION; - } - case 29: break; - case 8: - { return RestTypes.PARAM; - } - case 30: break; - case 13: - { return RestTypes.OPTION; - } - case 31: break; - case 5: - { yybegin(S_REQ_BODY); return RestTypes.REQUEST_BODY_LINE; - } - case 32: break; - default: - if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { - zzAtEOF = true; - zzDoEOF(); - return null; - } - else { - zzScanError(ZZ_NO_MATCH); - } - } - } - } - - -} diff --git a/src/ru/basecode/ide/rest/plugin/RestParserDefinition.java b/src/ru/basecode/ide/rest/plugin/RestParserDefinition.java deleted file mode 100644 index a069674..0000000 --- a/src/ru/basecode/ide/rest/plugin/RestParserDefinition.java +++ /dev/null @@ -1,78 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.lang.ASTNode; -import com.intellij.lang.Language; -import com.intellij.lang.ParserDefinition; -import com.intellij.lang.PsiParser; -import com.intellij.lexer.Lexer; -import com.intellij.openapi.project.Project; -import com.intellij.psi.FileViewProvider; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.TokenType; -import com.intellij.psi.tree.IFileElementType; -import com.intellij.psi.tree.TokenSet; -import org.jetbrains.annotations.NotNull; -import ru.basecode.ide.rest.plugin.parser.RestParser; -import ru.basecode.ide.rest.plugin.psi.RestTypes; - -/** - * - */ -public class RestParserDefinition implements ParserDefinition { - - public static final TokenSet WHITE_SPACES = TokenSet.create(TokenType.WHITE_SPACE); - - public static final IFileElementType FILE = - new IFileElementType(Language.findInstance(RestLanguage.class)); - - @NotNull - @Override - public Lexer createLexer(Project project) { - return new RestLexerAdapter(); - } - - @Override - public PsiParser createParser(Project project) { - return new RestParser(); - } - - @Override - public IFileElementType getFileNodeType() { - return FILE; - } - - @NotNull - @Override - public TokenSet getWhitespaceTokens() { - return WHITE_SPACES; - } - - @NotNull - @Override - public TokenSet getCommentTokens() { - return TokenSet.EMPTY; - } - - @NotNull - @Override - public TokenSet getStringLiteralElements() { - return TokenSet.EMPTY; - } - - @NotNull - @Override - public PsiElement createElement(ASTNode node) { - return RestTypes.Factory.createElement(node); - } - - @Override - public PsiFile createFile(FileViewProvider viewProvider) { - return new RestFile(viewProvider); - } - - @Override - public SpaceRequirements spaceExistanceTypeBetweenTokens(ASTNode left, ASTNode right) { - return null; - } -} diff --git a/src/ru/basecode/ide/rest/plugin/RestProjectComponent.java b/src/ru/basecode/ide/rest/plugin/RestProjectComponent.java deleted file mode 100644 index 949b047..0000000 --- a/src/ru/basecode/ide/rest/plugin/RestProjectComponent.java +++ /dev/null @@ -1,95 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.openapi.components.AbstractProjectComponent; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.*; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileAdapter; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.openapi.vfs.VirtualFilePropertyEvent; -import com.intellij.openapi.vfs.impl.http.HttpVirtualFile; -import com.intellij.psi.FileViewProvider; -import com.intellij.psi.PsiManager; -import com.intellij.util.messages.MessageBusConnection; -import org.jetbrains.annotations.NotNull; - -/** - * @author danblack - */ -public class RestProjectComponent extends AbstractProjectComponent { - - protected RestProjectComponent(Project project) { - super(project); - } - - @Override - public void initComponent() { - super.initComponent(); - registerOpenFileHandler(myProject.getMessageBus().connect(myProject)); - registerFileLanguageHandler(); - } - - private void registerFileLanguageHandler() { - VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileAdapter() { - @Override - public void propertyChanged(@NotNull VirtualFilePropertyEvent event) { - if (VirtualFile.PROP_NAME.equals(event.getPropertyName())) { - FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject); - VirtualFile file = event.getFile(); - if (fileEditorManager.isFileOpen(file)) { - refreshComponent(fileEditorManager, file); - } - } - } - }, myProject); - } - - private void registerOpenFileHandler(MessageBusConnection connection) { - connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() { - @Override - public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) { - refreshComponent(source, file); - } - }); - } - - public static boolean isSuitable(@NotNull Project project, @NotNull VirtualFile file) { - if (file instanceof HttpVirtualFile) { - return false; - } - final FileViewProvider provider = PsiManager.getInstance(project).findViewProvider(file); - return provider != null && RestLanguage.INSTANCE == provider.getBaseLanguage(); - } - - - private void detachComponent(@NotNull FileEditorManager fileEditorManager, @NotNull VirtualFile file) { - for (FileEditor fileEditor : fileEditorManager.getAllEditors(file)) { - if (fileEditor instanceof TextEditor) { - HeaderPanelComponent.dispose(((TextEditor) fileEditor).getEditor()); - } - } - } - - private void refreshComponent(@NotNull final FileEditorManager fileEditorManager, @NotNull VirtualFile file) { - if (isSuitable(fileEditorManager.getProject(), file)) { - attachComponent(fileEditorManager, file); - } else { - detachComponent(fileEditorManager, file); - } - } - - private void attachComponent(@NotNull FileEditorManager fileEditorManager, @NotNull VirtualFile file) { - for (FileEditor fileEditor : fileEditorManager.getAllEditors(file)) { - if (fileEditor instanceof TextEditor) { - TextEditor textEditor = (TextEditor) fileEditor; - Editor editor = textEditor.getEditor(); - if (!HeaderPanelComponent.attached(editor)) { - HeaderPanelComponent component = new HeaderPanelComponent(textEditor); - Disposer.register(fileEditor, () -> Disposer.dispose(component)); - } - } - } - } -} diff --git a/src/ru/basecode/ide/rest/plugin/RunAction.java b/src/ru/basecode/ide/rest/plugin/RunAction.java deleted file mode 100644 index 328da31..0000000 --- a/src/ru/basecode/ide/rest/plugin/RunAction.java +++ /dev/null @@ -1,153 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.icons.AllIcons; -import com.intellij.lang.Language; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiFile; -import com.intellij.psi.codeStyle.CodeStyleManager; -import com.intellij.psi.impl.PsiFileFactoryImpl; -import ru.basecode.ide.rest.plugin.http.Response; -import ru.basecode.ide.rest.plugin.psi.RestESeparator; -import ru.basecode.ide.rest.plugin.psi.RestRequest; - -import java.time.LocalDateTime; -import java.util.Collection; -import java.util.function.Supplier; - -/** - * - */ -public class RunAction extends AnAction { - - public static final String ID = "rest.action.run"; - - private final RequestExecutor executor; - - @Override - public void update(AnActionEvent e) { - if (executor == null) { - e.getPresentation().setEnabled(false); - } else { - super.update(e); - e.getPresentation().setEnabled(executor.isWaiting()); - } - } - - public RunAction() { - this(null); - } - - public RunAction(RequestExecutor executor) { - super("Execute http request", "", AllIcons.Actions.Execute); - this.executor = executor; - } - - private static void writeResponse(Project project, Document doc, Supplier file, String text) { - WriteCommandAction.runWriteCommandAction(project, () -> { - int responsePosition; - String separatorString; - RestFile restFile = file.get(); - RestESeparator separator = restFile.getSeparator(); - if (separator != null && separator.isValid()) { - responsePosition = separator.getTextOffset(); - separatorString = ""; - } else { - RestRequest request = restFile.getRequest(); - responsePosition = request.getTextOffset() + request.getTextLength(); - separatorString = "\n"; - } - String sb = separatorString + "%%%\n" + text; - doc.replaceString(responsePosition, doc.getTextLength(), sb.replace("\r", "")); - }); - } - - @Override - public void actionPerformed(AnActionEvent e) { - if (executor == null || executor.isRunning()) { - return; - } - final Project project = e.getRequiredData(CommonDataKeys.PROJECT); - final VirtualFile file = e.getRequiredData(CommonDataKeys.VIRTUAL_FILE); - if (!RestProjectComponent.isSuitable(project, file)) { - return; - } - final Document document = e.getRequiredData(CommonDataKeys.EDITOR).getDocument(); - ApplicationManager.getApplication().executeOnPooledThread((Runnable) () -> { - RestFile restFile = getRestFile(project, document); - if (restFile == null) { - return; - } - Request request = getRequest(restFile); - try { - long startTime = System.currentTimeMillis(); - String start = "# Executing request...\n# URL: " + request.getUrl() + "\n# Start time: " + LocalDateTime.now(); - writeResponse(project, document, () -> restFile, start); - Response response = executor.execute(request); - writeResponse(project, document, () -> restFile, "# Reading response..."); - String headers = "\n# " + response.getStatus() + "\n" + getHeaders(response); - String text = getFormattedResponse(project, response.getContentType(), response.getBody()); - long duration = System.currentTimeMillis() - startTime; - writeResponse(project, document, () -> restFile, "\n# Duration: " + duration + " ms\n# URL: " + request.getUrl() + "\n" + headers + text); - } catch (Exception e1) { - writeResponse(project, document, () -> restFile, "# Error: " + e1.getMessage()); - } - }); - } - - private String getHeaders(Response response) { - StringBuilder sb = new StringBuilder("\n"); - for (String header : response.getHeaders()) { - sb.append("@").append(header).append("\n"); - } - return sb.append("\n").toString(); - } - - private String getFormattedResponse(Project project, String contentType, String text) { - Collection langList = Language.findInstancesByMimeType(contentType); - if (!langList.isEmpty()) { - return format(project, langList.iterator().next(), text); - } - return text; - } - - private Request getRequest(RestFile restFile) { - return ApplicationManager.getApplication().runReadAction( - (Computable) () -> RequestParser.parse(restFile.getRequest())); - } - - private static RestFile getRestFile(Project project, Document document) { - return ApplicationManager.getApplication().runReadAction((Computable) () -> { - if (project.isOpen()) { - PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); - if (psiFile != null && psiFile.isValid() && psiFile instanceof RestFile) { - return (RestFile) psiFile; - } - } - return null; - }); - } - - public static String format(final Project project, Language language, String text) { - return WriteCommandAction.runWriteCommandAction(project, (Computable) () -> { - long startTime = System.currentTimeMillis(); - final PsiFile psiFile; - try { - psiFile = PsiFileFactoryImpl.getInstance(project).createFileFromText("virtual", language, text); - CodeStyleManager.getInstance(project).reformatText(psiFile, 0, psiFile.getTextLength()); - } finally { -// System.out.println(System.currentTimeMillis() - startTime); - } - - return psiFile.getText(); - }); - } -} diff --git a/src/ru/basecode/ide/rest/plugin/StopAction.java b/src/ru/basecode/ide/rest/plugin/StopAction.java deleted file mode 100644 index a97fd9d..0000000 --- a/src/ru/basecode/ide/rest/plugin/StopAction.java +++ /dev/null @@ -1,42 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import com.intellij.icons.AllIcons; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; - -/** - * @author danblack - */ -public class StopAction extends AnAction { - - public static final String ID = "rest.action.stop"; - - private final RequestExecutor executor; - - @Override - public void update(AnActionEvent e) { - if (executor == null) { - e.getPresentation().setEnabled(false); - } else { - super.update(e); - e.getPresentation().setEnabled(executor.isRunning()); - } - } - - public StopAction() { - this(null); - } - - public StopAction(RequestExecutor executor) { - super(AllIcons.Actions.Suspend); - this.executor = executor; - } - - @Override - public void actionPerformed(AnActionEvent e) { - if (executor != null && executor.isRunning()) { - executor.stop(); - } - } - -} diff --git a/src/ru/basecode/ide/rest/plugin/http/Response.java b/src/ru/basecode/ide/rest/plugin/http/Response.java deleted file mode 100644 index ade10f8..0000000 --- a/src/ru/basecode/ide/rest/plugin/http/Response.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.basecode.ide.rest.plugin.http; - -import java.util.List; - -/** - * @author danblack - */ -public class Response { - - private final String status; - private final List headers; - private final String contentType; - private final String body; - - public Response(String status, List headers, String contentType, String body) { - this.status = status; - this.headers = headers; - this.contentType = contentType; - this.body = body; - } - - public String getBody() { - return body; - } - - public List getHeaders() { - return headers; - } - - public String getContentType() { - return contentType; - } - - public String getStatus() { - return status; - } -} diff --git a/src/ru/basecode/ide/rest/plugin/psi/impl/ResponseLiteralEscaper.java b/src/ru/basecode/ide/rest/plugin/psi/impl/ResponseLiteralEscaper.java deleted file mode 100644 index 38f2570..0000000 --- a/src/ru/basecode/ide/rest/plugin/psi/impl/ResponseLiteralEscaper.java +++ /dev/null @@ -1,33 +0,0 @@ -package ru.basecode.ide.rest.plugin.psi.impl; - -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.LiteralTextEscaper; -import org.jetbrains.annotations.NotNull; - -/** - * - */ -public class ResponseLiteralEscaper extends LiteralTextEscaper { - public ResponseLiteralEscaper(RestElementImpl host) { - super(host); - } - - @Override - public boolean decode(@NotNull final TextRange rangeInsideHost, @NotNull StringBuilder outChars) { - outChars.append(myHost.getText(), rangeInsideHost.getStartOffset(), rangeInsideHost.getEndOffset()); - return true; - } - - @Override - public int getOffsetInHost(int offsetInDecoded, @NotNull final TextRange rangeInsideHost) { - int offset = offsetInDecoded + rangeInsideHost.getStartOffset(); - if (offset < rangeInsideHost.getStartOffset()) offset = rangeInsideHost.getStartOffset(); - if (offset > rangeInsideHost.getEndOffset()) offset = rangeInsideHost.getEndOffset(); - return offset; - } - - @Override - public boolean isOneLine() { - return true; - } -} diff --git a/src/ru/basecode/ide/rest/plugin/psi/impl/RestElementImpl.java b/src/ru/basecode/ide/rest/plugin/psi/impl/RestElementImpl.java deleted file mode 100644 index e765cd6..0000000 --- a/src/ru/basecode/ide/rest/plugin/psi/impl/RestElementImpl.java +++ /dev/null @@ -1,33 +0,0 @@ -package ru.basecode.ide.rest.plugin.psi.impl; - -import com.intellij.extapi.psi.ASTWrapperPsiElement; -import com.intellij.lang.ASTNode; -import com.intellij.psi.LiteralTextEscaper; -import com.intellij.psi.PsiLanguageInjectionHost; -import org.jetbrains.annotations.NotNull; - -/** - * - */ -public class RestElementImpl extends ASTWrapperPsiElement implements PsiLanguageInjectionHost { - - public RestElementImpl(@NotNull ASTNode node) { - super(node); - } - - @Override - public boolean isValidHost() { - return true; - } - - @Override - public PsiLanguageInjectionHost updateText(@NotNull String text) { - return this; - } - - @NotNull - @Override - public LiteralTextEscaper createLiteralTextEscaper() { - return new ResponseLiteralEscaper(this); - } -} diff --git a/src/test/java/ru/basecode/ide/rest/plugin/psi/RestRequestParserTest.java b/src/test/java/ru/basecode/ide/rest/plugin/psi/RestRequestParserTest.java new file mode 100644 index 0000000..ae32fe7 --- /dev/null +++ b/src/test/java/ru/basecode/ide/rest/plugin/psi/RestRequestParserTest.java @@ -0,0 +1,45 @@ +package ru.basecode.ide.rest.plugin.psi; + +import org.junit.Assert; +import org.junit.Test; +import ru.basecode.ide.rest.plugin.psi.RestRequestParser; + +/** + * @author danblack + */ +public class RestRequestParserTest extends Assert{ + + @Test + public void shouldNotChangeTheUrl() { + assertEquals("param", RestRequestParser.encode("param")); + assertEquals("?param", RestRequestParser.encode("?param")); + assertEquals("?param=", RestRequestParser.encode("?param=")); + assertEquals("http://www.site.com?param", RestRequestParser.encode("http://www.site.com?param")); + assertEquals("http://www.site.com?param=", RestRequestParser.encode("http://www.site.com?param=")); + assertEquals("http://www.site.com?param=value", RestRequestParser.encode("http://www.site.com?param=value")); + assertEquals("http://www.site.com?param=value", RestRequestParser.encode("http://www.site.com?param=value")); + assertEquals("http://www.site.com?param=value¶m2=value2", RestRequestParser.encode("http://www.site.com?param=value¶m2=value2")); + } + + @Test + public void shouldTrimSpacesInParamName() { + assertEquals("?param", RestRequestParser.encode("?param ")); + assertEquals("?param", RestRequestParser.encode("? param ")); + assertEquals("?param", RestRequestParser.encode("? param")); + assertEquals("?param=", RestRequestParser.encode("?param =")); + assertEquals("?param=", RestRequestParser.encode("? param =")); + assertEquals("?param=", RestRequestParser.encode("? param=")); + } + + @Test + public void shouldEncodeSpaces() { + assertEquals("?param=1+2", RestRequestParser.encode("?param=1 2")); + assertEquals("?p+aram=1+2", RestRequestParser.encode("?p aram=1 2")); + } + + @Test + public void shouldEncodeSymbols() { + assertEquals("?param=%D1%8B%D1%91", RestRequestParser.encode("?param=ыё")); + } + +} diff --git a/test/ru/basecode/ide/rest/plugin/RequestParserTest_only_body.txt b/src/test/resources/RequestParserTest_only_body.txt similarity index 100% rename from test/ru/basecode/ide/rest/plugin/RequestParserTest_only_body.txt rename to src/test/resources/RequestParserTest_only_body.txt diff --git a/test/ru/basecode/ide/rest/plugin/RequestParserTest_only_client_params.txt b/src/test/resources/RequestParserTest_only_client_params.txt similarity index 100% rename from test/ru/basecode/ide/rest/plugin/RequestParserTest_only_client_params.txt rename to src/test/resources/RequestParserTest_only_client_params.txt diff --git a/test/ru/basecode/ide/rest/plugin/RequestParserTest_only_headers.txt b/src/test/resources/RequestParserTest_only_headers.txt similarity index 100% rename from test/ru/basecode/ide/rest/plugin/RequestParserTest_only_headers.txt rename to src/test/resources/RequestParserTest_only_headers.txt diff --git a/test/ru/basecode/ide/rest/plugin/RequestParserTest_only_method.txt b/src/test/resources/RequestParserTest_only_method.txt similarity index 100% rename from test/ru/basecode/ide/rest/plugin/RequestParserTest_only_method.txt rename to src/test/resources/RequestParserTest_only_method.txt diff --git a/test/ru/basecode/ide/rest/plugin/RequestParserTest_only_url.txt b/src/test/resources/RequestParserTest_only_url.txt similarity index 100% rename from test/ru/basecode/ide/rest/plugin/RequestParserTest_only_url.txt rename to src/test/resources/RequestParserTest_only_url.txt diff --git a/test/ru/basecode/ide/rest/plugin/RequestParserTest_url_with_params.txt b/src/test/resources/RequestParserTest_url_with_params.txt similarity index 100% rename from test/ru/basecode/ide/rest/plugin/RequestParserTest_url_with_params.txt rename to src/test/resources/RequestParserTest_url_with_params.txt diff --git a/test/ru/basecode/ide/rest/plugin/RequestParserTest.java b/test/ru/basecode/ide/rest/plugin/RequestParserTest.java deleted file mode 100644 index e4c700b..0000000 --- a/test/ru/basecode/ide/rest/plugin/RequestParserTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package ru.basecode.ide.rest.plugin; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author danblack - */ -public class RequestParserTest extends Assert{ - - @Test - public void shouldNotChangeTheUrl() { - assertEquals("param", RequestParser.encode("param")); - assertEquals("?param", RequestParser.encode("?param")); - assertEquals("?param=", RequestParser.encode("?param=")); - assertEquals("http://www.site.com?param", RequestParser.encode("http://www.site.com?param")); - assertEquals("http://www.site.com?param=", RequestParser.encode("http://www.site.com?param=")); - assertEquals("http://www.site.com?param=value", RequestParser.encode("http://www.site.com?param=value")); - assertEquals("http://www.site.com?param=value", RequestParser.encode("http://www.site.com?param=value")); - assertEquals("http://www.site.com?param=value¶m2=value2", RequestParser.encode("http://www.site.com?param=value¶m2=value2")); - } - - @Test - public void shouldTrimSpacesInParamName() { - assertEquals("?param", RequestParser.encode("?param ")); - assertEquals("?param", RequestParser.encode("? param ")); - assertEquals("?param", RequestParser.encode("? param")); - assertEquals("?param=", RequestParser.encode("?param =")); - assertEquals("?param=", RequestParser.encode("? param =")); - assertEquals("?param=", RequestParser.encode("? param=")); - } - - @Test - public void shouldEncodeSpaces() { - assertEquals("?param=1+2", RequestParser.encode("?param=1 2")); - assertEquals("?p+aram=1+2", RequestParser.encode("?p aram=1 2")); - } - - @Test - public void shouldEncodeSymbols() { - assertEquals("?param=%D1%8B%D1%91", RequestParser.encode("?param=ыё")); - } - -} \ No newline at end of file