├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── build.yml │ ├── publish-release.yml │ ├── release.yml │ ├── sonarcloud.yml │ ├── tests.yml │ └── verify.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── assets └── sentry-glyph-dark-100x100.png ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ignoredProblems.txt ├── settings.gradle.kts └── src ├── main ├── java │ └── de │ │ └── php_perfect │ │ └── intellij │ │ └── ddev │ │ ├── DatabaseInfoChangedListener.java │ │ ├── DdevConfigArgumentProvider.java │ │ ├── DdevIntegrationBundle.java │ │ ├── DescriptionChangedListener.java │ │ ├── InitPluginActivity.java │ │ ├── StateChangedListener.java │ │ ├── StateInitializedListener.java │ │ ├── actions │ │ ├── ChangeSettingsAction.java │ │ ├── CheckVersionAction.java │ │ ├── DdevAwareAction.java │ │ ├── DdevConfigAction.java │ │ ├── DdevDeleteAction.java │ │ ├── DdevPowerOffAction.java │ │ ├── DdevPredefinedTerminalAction.java │ │ ├── DdevRestartAction.java │ │ ├── DdevRunAction.java │ │ ├── DdevShareAction.java │ │ ├── DdevStartAction.java │ │ ├── DdevStopAction.java │ │ ├── DisableCheckForUpdatesAction.java │ │ ├── InstallationInstructionsAction.java │ │ ├── ManagePluginsAction.java │ │ ├── OpenServiceAction.java │ │ ├── ReloadPluginAction.java │ │ ├── ReportIssueAction.java │ │ ├── ServicesActionGroup.java │ │ └── SyncStateAction.java │ │ ├── cmd │ │ ├── BinaryLocator.java │ │ ├── BinaryLocatorImpl.java │ │ ├── CommandFailedException.java │ │ ├── DatabaseInfo.java │ │ ├── Ddev.java │ │ ├── DdevImpl.java │ │ ├── DdevRunner.java │ │ ├── DdevRunnerImpl.java │ │ ├── DdevShellCommandHandlerImpl.java │ │ ├── Description.java │ │ ├── Docker.java │ │ ├── DockerImpl.java │ │ ├── ProcessExecutor.java │ │ ├── ProcessExecutorImpl.java │ │ ├── Runner.java │ │ ├── RunnerImpl.java │ │ ├── Service.java │ │ ├── Versions.java │ │ ├── WhichProvider.java │ │ ├── parser │ │ │ ├── JsonParser.java │ │ │ ├── JsonParserException.java │ │ │ ├── JsonParserImpl.java │ │ │ └── LogLine.java │ │ └── wsl │ │ │ └── WslAware.java │ │ ├── database │ │ ├── AutoConfigureDataSourceListener.java │ │ ├── DataSourceConfig.java │ │ ├── DataSourceProvider.java │ │ ├── DataSourceProviderImpl.java │ │ ├── DdevDataSourceManager.java │ │ └── DdevDataSourceManagerImpl.java │ │ ├── docker_compose │ │ ├── DdevComposeFileLoader.java │ │ ├── DdevComposeFileLoaderImpl.java │ │ ├── DockerComposeConfig.java │ │ ├── DockerComposeCredentialProvider.java │ │ └── DockerComposeCredentialProviderImpl.java │ │ ├── error_reporting │ │ ├── SentryErrorReporter.java │ │ └── SentrySdkInitializer.java │ │ ├── icons │ │ └── DdevIntegrationIcons.java │ │ ├── index │ │ ├── IndexEntry.java │ │ ├── IndexableConfiguration.java │ │ ├── ManagedConfigurationIndex.java │ │ └── ManagedConfigurationIndexImpl.java │ │ ├── node │ │ ├── AutoConfigureNodeInterpreterListener.java │ │ ├── NodeInterpreterConfig.java │ │ ├── NodeInterpreterProvider.java │ │ └── NodeInterpreterProviderImpl.java │ │ ├── notification │ │ ├── DdevNotifier.java │ │ └── DdevNotifierImpl.java │ │ ├── php │ │ ├── AutoConfigurePhpInterpreterListener.java │ │ ├── ConfigurationProvider.java │ │ ├── ConfigurationProviderImpl.java │ │ ├── DdevInterpreterConfig.java │ │ ├── PhpInterpreterProvider.java │ │ ├── PhpInterpreterProviderImpl.java │ │ ├── PhpVersionArgumentProvider.java │ │ ├── composer │ │ │ ├── DdevComposerExecution.java │ │ │ ├── DdevComposerExecutionProvider.java │ │ │ ├── DdevComposerForm.java │ │ │ └── DdevComposerProcessHandler.java │ │ └── server │ │ │ ├── ConfigureServerListener.java │ │ │ ├── ServerConfig.java │ │ │ ├── ServerConfigManager.java │ │ │ └── ServerConfigManagerImpl.java │ │ ├── service_actions │ │ ├── ServiceActionChangedListener.java │ │ ├── ServiceActionManager.java │ │ └── ServiceActionManagerImpl.java │ │ ├── settings │ │ ├── DdevSettingsComponent.java │ │ ├── DdevSettingsConfigurable.java │ │ └── DdevSettingsState.java │ │ ├── state │ │ ├── DdevConfigLoader.java │ │ ├── DdevConfigLoaderImpl.java │ │ ├── DdevStateManager.java │ │ ├── DdevStateManagerImpl.java │ │ ├── StartWatcherListener.java │ │ ├── State.java │ │ ├── StateImpl.java │ │ ├── StateWatcher.java │ │ ├── StateWatcherImpl.java │ │ └── UnknownStateListener.java │ │ ├── status_bar │ │ ├── DdevStatusBarWidgetFactoryImpl.java │ │ └── DdevStatusBarWidgetImpl.java │ │ ├── terminal │ │ ├── DdevPredefinedTerminalActionProvider.java │ │ ├── DdevTerminalRunner.java │ │ └── TutorialListener.java │ │ ├── tutorial │ │ ├── GotItTutorial.java │ │ └── GotItTutorialImpl.java │ │ ├── util │ │ ├── FeatureRequiredPlugins.java │ │ ├── PluginChecker.java │ │ └── PluginDisplayNameMapper.java │ │ └── version │ │ ├── CheckVersionListener.java │ │ ├── GithubClient.java │ │ ├── LatestRelease.java │ │ ├── ReleaseClient.java │ │ ├── Version.java │ │ ├── VersionChecker.java │ │ ├── VersionCheckerImpl.java │ │ └── util │ │ └── VersionCompare.java └── resources │ ├── META-INF │ ├── DdevIntegration-withDatabase.xml │ ├── DdevIntegration-withDocker.xml │ ├── DdevIntegration-withNodeRemoteInterpreter.xml │ ├── DdevIntegration-withPhp.xml │ ├── DdevIntegration-withTerminal.xml │ ├── plugin.xml │ └── pluginIcon.svg │ ├── icons │ ├── ddevLogoColor.svg │ ├── ddevLogoGrey.svg │ └── ddevLogoGrey_dark.svg │ └── messages │ └── DdevIntegrationBundle.properties └── test ├── java └── de │ └── php_perfect │ └── intellij │ └── ddev │ ├── cmd │ ├── BinaryLocatorTest.java │ ├── DdevImplTest.java │ ├── DdevShellCommandHandlerTest.java │ ├── DockerTest.java │ ├── MockProcessExecutor.java │ └── parser │ │ ├── JsonParserTest.java │ │ └── TestObject.java │ ├── database │ └── DataSourceProviderTest.java │ ├── index │ └── ManagedConfigurationIndexTest.java │ ├── notification │ └── DdevNotifierTest.java │ ├── php │ └── server │ │ └── ServerConfigManagerImplTest.java │ ├── serviceActions │ └── ServiceActionManagerImplTest.java │ ├── state │ ├── DdevConfigLoaderTest.java │ ├── DdevStateManagerTest.java │ ├── MockDdevConfigLoader.java │ └── StateWatcherTest.java │ ├── terminal │ ├── DdevPredefinedTerminalActionProviderTest.java │ └── DdevTerminalRunnerTest.java │ └── version │ ├── VersionCheckerImplTest.java │ ├── VersionTest.java │ └── util │ └── VersionCompareTest.java └── resources ├── ddev_describe.json ├── ddev_describe2.json ├── ddev_describe_w_debug.json └── ddev_version.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [ nico-loeber ] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🪲 Bug Report 2 | description: Create a report to help us improve. 3 | labels: [ bug ] 4 | body: 5 | - type: checkboxes 6 | id: has_searched 7 | attributes: 8 | label: Is there an existing issue for this? 9 | description: Please search to see if an issue already exists for the bug you encountered. 10 | options: 11 | - label: I have searched the existing issues 12 | required: true 13 | - type: checkboxes 14 | id: is_related 15 | attributes: 16 | label: Are you sure that this bug is related to this DDEV Integration Plugin? 17 | description: | 18 | Please make sure that your problem is not related to [DDEV](https://github.com/ddev/ddev). 19 | An error message in your IDE is a clear indication that there is a bug in this DDEV integration plugin. 20 | options: 21 | - label: I am sure 22 | - type: input 23 | id: report_id 24 | attributes: 25 | label: Enter your error report ID (If available) 26 | description: You will get a report ID after submitting a error report in your IDE, related to this plugin. 27 | - type: textarea 28 | id: description 29 | attributes: 30 | label: Describe the bug 31 | description: A clear and concise description of what the bug is. 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: reproduce 36 | attributes: 37 | label: Steps to reproduce 38 | description: Steps to reproduce the behavior. 39 | placeholder: | 40 | 1. Go to '...' 41 | 2. Click on '....' 42 | 3. Scroll down to '....' 43 | 4. See error 44 | validations: 45 | required: false 46 | - type: textarea 47 | id: additional 48 | attributes: 49 | label: Additional context 50 | description: | 51 | An error stack trace? Screenshots? References? Anything that will give us more context about the issue you are encountering! 52 | 53 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 54 | validations: 55 | required: false 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 💡 Feature Suggestions 4 | url: https://github.com/ddev/ddev-intellij-plugin/discussions/new?category=ideas 5 | about: Suggest and discuss new features for the DDEV Integration Plugin. 6 | - name: 📘 Documentation 7 | url: https://github.com/ddev/ddev-intellij-plugin/wiki 8 | about: Read the documentation to find out more about the features and settings this plugin has to offer. 9 | - name: 💬 DDEV Community Discord 10 | url: https://discord.gg/kDvSFBSZfs 11 | about: Join the DDEV Community to talk, exchange experiences or ask and answer questions. 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## The Problem/Issue/Bug: 2 | 3 | ## How this PR Solves the Problem: 4 | 5 | ## Manual Testing Instructions: 6 | 7 | ## Related Issue Link(s): 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | commit-message: 8 | prefix: "build" 9 | - package-ecosystem: "gradle" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | commit-message: 14 | prefix: "build" 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | types: [ opened, synchronize, reopened ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | # Check out current repository 15 | - name: Fetch Sources 16 | uses: actions/checkout@v5 17 | 18 | # Validate wrapper 19 | - name: Gradle Wrapper Validation 20 | uses: gradle/actions/wrapper-validation@v4 21 | 22 | # Set up Java environment for the next steps 23 | - name: Setup Java 24 | uses: actions/setup-java@v5 25 | with: 26 | distribution: zulu 27 | java-version: 21 28 | 29 | # Setup Gradle 30 | - name: Setup Gradle 31 | uses: gradle/actions/setup-gradle@v4 32 | with: 33 | gradle-home-cache-cleanup: true 34 | cache-read-only: ${{ github.ref != 'refs/heads/main' }} 35 | 36 | - name: Build project 37 | run: ./gradlew buildPlugin --exclude-task test 38 | 39 | - name: Upload Assets 40 | uses: actions/upload-artifact@v4 41 | with: 42 | name: ddev-intellij-plugin 43 | path: build/distributions/*.zip 44 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | 3 | on: 4 | release: 5 | types: [prereleased, released] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | # Check out current repository 12 | - name: Fetch Sources 13 | uses: actions/checkout@v5 14 | 15 | - name: Extract Tag Name 16 | uses: olegtarasov/get-tag@v2.1 17 | 18 | # Validate wrapper 19 | - name: Gradle Wrapper Validation 20 | uses: gradle/actions/wrapper-validation@v4 21 | 22 | # Set up Java environment for the next steps 23 | - name: Setup Java 24 | uses: actions/setup-java@v5 25 | with: 26 | distribution: zulu 27 | java-version: 21 28 | 29 | - name: Load secrets from 1Password 30 | uses: 1password/load-secrets-action@v3 31 | with: 32 | export-env: true 33 | env: 34 | OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} 35 | JETBRAINS_MARKETPLACE_PUBLISHING_TOKEN: op://ddev-intellij-plugin-secrets/JetBrains Marketplace Publishing Token/password 36 | JETBRAINS_MARKETPLACE_SIGNING_KEY: op://ddev-intellij-plugin-secrets/JetBrains Marketplace Signing Key/private key 37 | JETBRAINS_MARKETPLACE_SIGNING_KEY_CHAIN: op://ddev-intellij-plugin-secrets/JetBrains Marketplace Signing Key/add more/private key chain 38 | JETBRAINS_MARKETPLACE_SIGNING_KEY_PASSWORD: op://ddev-intellij-plugin-secrets/JetBrains Marketplace Signing Key/add more/private key password 39 | 40 | - name: Extract Channel from Version 41 | uses: actions-ecosystem/action-regex-match@v2 42 | id: match-channel 43 | with: 44 | text: ${{ github.ref }} 45 | regex: '(eap|beta|alpha).*$' 46 | 47 | - name: Setup Gradle 48 | uses: gradle/actions/setup-gradle@v4 49 | with: 50 | cache-read-only: ${{ github.ref != 'refs/heads/main' }} 51 | 52 | - name: Publish to JetBrains Marketplace 53 | run: ./gradlew publishPlugin 54 | env: 55 | PUBLISH_CHANNEL: ${{ steps.match-channel.outputs.group1 }} 56 | 57 | - name: Update GitHub Release 58 | uses: softprops/action-gh-release@v2 59 | with: 60 | fail_on_unmatched_files: true 61 | files: build/distributions/*.zip 62 | 63 | sentry_release: 64 | runs-on: ubuntu-latest 65 | steps: 66 | - name: Checkout 67 | uses: actions/checkout@v5 68 | with: 69 | fetch-depth: 0 70 | 71 | - name: Extract Tag Name 72 | uses: olegtarasov/get-tag@v2.1 73 | id: tagName 74 | 75 | - name: Load secrets from 1Password 76 | uses: 1password/load-secrets-action@v3 77 | with: 78 | export-env: true 79 | env: 80 | OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} 81 | SENTRY_AUTH_TOKEN: op://ddev-intellij-plugin-secrets/Sentry Token/credential 82 | SENTRY_ORG: op://ddev-intellij-plugin-secrets/Sentry Token/organization 83 | SENTRY_PROJECT: op://ddev-intellij-plugin-secrets/Sentry Token/project 84 | 85 | - name: Create Sentry release 86 | uses: getsentry/action-release@v3 87 | with: 88 | release: ${{ steps.tagName.outputs.tag }} 89 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '[0-9]+.[0-9]+.[0-9]+' 7 | - '[0-9]+.[0-9]+.[0-9]+-eap.*' 8 | - '[0-9]+.[0-9]+.[0-9]+-beta.*' 9 | - '[0-9]+.[0-9]+.[0-9]+-alpha.*' 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | # Check out current repository 16 | - name: Fetch Sources 17 | uses: actions/checkout@v5 18 | 19 | - name: Extract Tag Name 20 | uses: olegtarasov/get-tag@v2.1 21 | id: tagName 22 | 23 | # Validate wrapper 24 | - name: Gradle Wrapper Validation 25 | uses: gradle/actions/wrapper-validation@v4 26 | 27 | # Set up Java environment for the next steps 28 | - name: Setup Java 29 | uses: actions/setup-java@v5 30 | with: 31 | distribution: zulu 32 | java-version: 21 33 | 34 | - name: Extract Changelog 35 | id: gradle-changelog 36 | run: ./gradlew getChangelog --quiet --no-header --console=plain > build/extracted_release_changelog.md 37 | 38 | - name: Create Release 39 | uses: softprops/action-gh-release@v2 40 | with: 41 | prerelease: ${{ contains(github.ref, 'eap') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }} 42 | body_path: build/extracted_release_changelog.md 43 | -------------------------------------------------------------------------------- /.github/workflows/sonarcloud.yml: -------------------------------------------------------------------------------- 1 | name: SonarCloud 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | types: [ opened, synchronize, reopened ] 8 | 9 | jobs: 10 | sonarcloud: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | # Load secrets from 1Password 15 | - name: Load secrets from 1Password 16 | uses: 1password/load-secrets-action@v3 17 | if: ${{ env.OP_SERVICE_ACCOUNT_TOKEN != null }} 18 | with: 19 | export-env: true 20 | env: 21 | OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} 22 | SONAR_TOKEN: op://ddev-intellij-plugin-secrets/Sonar Token/credential 23 | 24 | # Check out current repository 25 | - name: Fetch Sources 26 | uses: actions/checkout@v5 27 | if: ${{ env.SONAR_TOKEN != null }} 28 | with: 29 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 30 | 31 | # Validate wrapper 32 | - name: Gradle Wrapper Validation 33 | if: ${{ env.SONAR_TOKEN != null }} 34 | uses: gradle/actions/wrapper-validation@v4 35 | 36 | # Set up Java environment for the next steps 37 | - name: Setup Java 38 | if: ${{ env.SONAR_TOKEN != null }} 39 | uses: actions/setup-java@v5 40 | with: 41 | distribution: zulu 42 | java-version: 21 43 | 44 | # SonarCloud Cache 45 | - name: 'Cache: SonarCloud' 46 | if: ${{ env.SONAR_TOKEN != null }} 47 | uses: actions/cache@v4 48 | with: 49 | path: ~/.sonar/cache 50 | key: ${{ runner.os }}-sonar 51 | 52 | - name: Setup Gradle 53 | if: ${{ env.SONAR_TOKEN != null }} 54 | uses: gradle/actions/setup-gradle@v4 55 | with: 56 | cache-read-only: ${{ github.ref != 'refs/heads/main' }} 57 | 58 | - name: Run sonarqube 59 | if: ${{ env.SONAR_TOKEN != null }} 60 | run: ./gradlew -Dtest.ignoreFailures=true sonarqube --info --stacktrace 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | types: [ opened, synchronize, reopened ] 8 | 9 | jobs: 10 | tests: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ ubuntu-latest, macos-latest, windows-latest ] 16 | 17 | steps: 18 | # Check out current repository 19 | - name: Fetch Sources 20 | uses: actions/checkout@v5 21 | 22 | # Validate wrapper 23 | - name: Gradle Wrapper Validation 24 | uses: gradle/actions/wrapper-validation@v4 25 | 26 | # Set up Java environment for the next steps 27 | - name: Setup Java 28 | uses: actions/setup-java@v5 29 | with: 30 | distribution: zulu 31 | java-version: 21 32 | 33 | - name: Setup Gradle 34 | uses: gradle/actions/setup-gradle@v4 35 | with: 36 | cache-read-only: ${{ github.ref != 'refs/heads/main' }} 37 | 38 | - name: Run tests 39 | run: ./gradlew test --stacktrace --info jacocoTestReport 40 | 41 | - name: Upload Test Report 42 | if: ${{ always() }} 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: test-report-${{ matrix.os }} 46 | path: build/reports/tests/test 47 | 48 | - name: Upload HTML test coverage report 49 | if: ${{ matrix.os == 'ubuntu-latest' && always() }} 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: coverage-report 53 | path: build/reports/jacoco/test/html 54 | -------------------------------------------------------------------------------- /.github/workflows/verify.yml: -------------------------------------------------------------------------------- 1 | name: Verify Plugin 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | types: [ opened, synchronize, reopened ] 8 | 9 | jobs: 10 | verify: 11 | runs-on: ubuntu-latest 12 | steps: 13 | # Free GitHub Actions Environment Disk Space 14 | - name: Maximize Build Space 15 | uses: jlumbroso/free-disk-space@main 16 | with: 17 | tool-cache: false 18 | large-packages: false 19 | 20 | # Check out current repository 21 | - name: Fetch Sources 22 | uses: actions/checkout@v5 23 | 24 | # Set up Java environment for the next steps 25 | - name: Setup Java 26 | uses: actions/setup-java@v5 27 | with: 28 | distribution: zulu 29 | java-version: 21 30 | 31 | - name: Setup Gradle 32 | uses: gradle/actions/setup-gradle@v4 33 | with: 34 | gradle-home-cache-cleanup: true 35 | cache-read-only: true 36 | 37 | - name: Verify Plugin 38 | run: ./gradlew verifyPlugin --stacktrace --info 39 | 40 | - name: Upload Report 41 | uses: actions/upload-artifact@v4 42 | with: 43 | name: test-report-${{ matrix.os }} 44 | path: build/reports/pluginVerifier 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /.intellijPlatform/ 3 | /.gradle/ 4 | /bin/ 5 | /build/ 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | For feature ideas, [open a discussion](https://github.com/ddev/ddev-intellij-plugin/discussions/new?category=ideas). 4 | For bug fixes, please open an issue first, then submit a pull request. For optimizations or test cases that don't 5 | change behavior, you can submit a [pull request](https://github.com/ddev/ddev-intellij-plugin/pulls) directly. 6 | 7 | ## Issues 8 | 9 | Ensure issues are related to this plugin, not [DDEV](https://github.com/ddev/ddev) itself. DDEV issues should be 10 | reported [here](https://github.com/ddev/ddev/issues). 11 | 12 | When [opening an issue](https://github.com/ddev/ddev-intellij-plugin/issues/new), include: 13 | - Your IDE (PHPStorm, WebStorm, etc.) 14 | - Operating system (including WSL for Windows users) 15 | - Steps to reproduce 16 | - Error report ID (if applicable) 17 | 18 | ## Error Reports 19 | 20 | When a critical error occurs, you'll see a flashing red exclamation mark in your IDE. Click it to file a bug report 21 | and receive an error report ID. These reports include your OS, DDEV, Docker, and IDE versions, helping us diagnose 22 | and fix issues efficiently. 23 | 24 | ## Development Setup 25 | 26 | 1. Open IntelliJ IDEA, select `File > New > Project from Version Control` and check out this repository 27 | 2. Select JDK 21 as project JDK 28 | 3. Run `runIde` to build and start a development version with the plugin 29 | 4. Consult the [IntelliJ Platform SDK documentation](https://plugins.jetbrains.com/docs/intellij/welcome.html) 30 | 31 | Any IntelliJ-based IDE can be used for development. To use a local installation as the development target, add to 32 | [build.gradle.kts](build.gradle.kts): 33 | 34 | ```kotlin 35 | tasks { 36 | runIde { 37 | ideDir.set(File("C:\\Users\\\\AppData\\Local\\Programs\\PhpStorm")) 38 | } 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Nico Löber 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | The DDEV Integration Logo 3 |

4 | 5 | [![Build](https://img.shields.io/github/actions/workflow/status/ddev/ddev-intellij-plugin/build.yml?branch=main "Build")](https://github.com/ddev/ddev-intellij-plugin/actions/workflows/build.yml) 6 | [![Version](https://img.shields.io/jetbrains/plugin/v/18813)](https://plugins.jetbrains.com/plugin/18813-ddev-integration) 7 | [![Downloads](https://img.shields.io/jetbrains/plugin/d/18813)](https://plugins.jetbrains.com/plugin/18813-ddev-integration) 8 | [![Rating](https://img.shields.io/jetbrains/plugin/r/rating/18813)](https://plugins.jetbrains.com/plugin/18813-ddev-integration/reviews) 9 | [![GitHub Sponsors](https://img.shields.io/github/sponsors/nico-loeber?label=Sponsors&logo=Github "Sponsors")](https://github.com/sponsors/nico-loeber?frequency=recurring&sponsor=nico-loeber) 10 | [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg "BSD 3")](https://opensource.org/licenses/BSD-3-Clause) 11 | 12 | # DDEV Integration for IntelliJ 13 | 14 | This plugin integrates [DDEV](https://github.com/ddev/ddev) with IntelliJ based IDEs such as PHPStorm or WebStorm. 15 | 16 | [DDEV](https://github.com/ddev/ddev) is an open source tool that makes it simple to get local PHP development 17 | environments up and running in minutes. 18 | 19 | ## Features 20 | 21 | The plugin allows you to operate all common [DDEV](https://github.com/ddev/ddev) features from your IDE's graphical user 22 | interface. 23 | 24 | - Run DDEV Actions 25 | - Quick access DDEV Services 26 | - Check for DDEV Updates 27 | - Assistance and Completion for the DDEV Configuration 28 | - Auto Configure PHP Remote Interpreter1 29 | - Auto Configure NodeJS Remote Interpreter1 30 | - Auto Configure Data Source1 31 | - Integrated DDEV Terminal1 32 | - And much more coming soon! 33 | 34 | 1 These features are only available in supporting IDEs, such as PHPStorm or IntelliJ Ultimate. 35 | 36 | ## Installation 37 | 38 | You can install the Plugin from the [Jetbrains Marketplace](https://plugins.jetbrains.com/plugin/18813-ddev-integration) 39 | or from your IDE. 40 | 41 | In your IDE, navigate to `Settings > Plugins > Marketplace` and search for "DDEV Integration". Click Install and restart 42 | your IDE. 43 | 44 | Please refer to the [DDEV Installation Guide](https://ddev.readthedocs.io/en/stable/) on how to install DDEV on your 45 | system. 46 | 47 | ## Contributions and Issues 48 | 49 | Please refer to the [Contributing Guidelines](./CONTRIBUTING.md). 50 | 51 | ## Supported by 52 | 53 | [DDEV](https://github.com/ddev) 54 | [JetBrains](https://jb.gg/OpenSourceSupport) 55 | [sentry.io](https://sentry.io/welcome/) 56 | 57 | ## Credits 58 | 59 | **Contributed by [@nico-loeber](https://github.com/php-perfect)** 60 | 61 | **Maintained by the [DDEV team](https://ddev.com/support-ddev/)** 62 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only the latest version of this plugin available on JetBrains Marketplace is supported with security updates. 6 | Please make sure you update your IDE regularly, otherwise you may not receive updates for this plugin. 7 | 8 | ## Reporting a Vulnerability 9 | 10 | We appreciate your efforts to responsibly disclose your findings and will make every effort to acknowledge your 11 | contributions. 12 | 13 | ### Reporting Process 14 | 15 | To report a security issue, please use GitHub's Private Vulnerability Reporting feature by clicking on the "Security" 16 | tab of this repository, then "Report a vulnerability", or by visiting 17 | https://github.com/ddev/ddev-intellij-plugin/security/advisories/new. 18 | 19 | ### What to Expect 20 | 21 | - We will confirm the vulnerability and determine its impact. 22 | - We will release a fix as soon as possible depending on complexity. 23 | -------------------------------------------------------------------------------- /assets/sentry-glyph-dark-100x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddev/ddev-intellij-plugin/3cf13a069ad26bed5f4709455d60d28f1af6e188/assets/sentry-glyph-dark-100x100.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | pluginGroup = de.php_perfect.intellij.ddev 2 | pluginName = DDEV Integration 3 | pluginRepositoryUrl = https://github.com/ddev/ddev-intellij-plugin 4 | 5 | # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html 6 | pluginSinceBuild = 224 7 | pluginUntilBuild = 224.* 8 | 9 | # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension 10 | platformVersion = 2025.1 11 | 12 | # Gradle Releases -> https://github.com/gradle/gradle/releases 13 | gradleVersion = 8.13 14 | 15 | # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib 16 | kotlin.stdlib.default.dependency = false 17 | 18 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html 19 | org.gradle.configuration-cache = true 20 | 21 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html 22 | org.gradle.caching = true 23 | 24 | org.gradle.jvmargs=-Xmx3G -XX:MaxMetaspaceSize=1G 25 | systemProp.sonar.gradle.skipCompile=true 26 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddev/ddev-intellij-plugin/3cf13a069ad26bed5f4709455d60d28f1af6e188/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /ignoredProblems.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddev/ddev-intellij-plugin/3cf13a069ad26bed5f4709455d60d28f1af6e188/ignoredProblems.txt -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "ddev-intellij-plugin" 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/DatabaseInfoChangedListener.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev; 2 | 3 | import com.intellij.util.messages.Topic; 4 | import de.php_perfect.intellij.ddev.cmd.DatabaseInfo; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | public interface DatabaseInfoChangedListener { 8 | Topic DATABASE_INFO_CHANGED_TOPIC = Topic.create("DDEV Database Info Changed", DatabaseInfoChangedListener.class); 9 | 10 | void onDatabaseInfoChanged(@Nullable DatabaseInfo databaseInfo); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/DdevConfigArgumentProvider.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.List; 7 | 8 | public interface DdevConfigArgumentProvider { 9 | @NotNull List<@NotNull String> getAdditionalArguments(@NotNull Project project); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/DdevIntegrationBundle.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev; 2 | 3 | import com.intellij.DynamicBundle; 4 | import org.jetbrains.annotations.Nls; 5 | import org.jetbrains.annotations.NonNls; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.PropertyKey; 8 | 9 | import java.util.function.Supplier; 10 | 11 | public final class DdevIntegrationBundle extends DynamicBundle { 12 | 13 | @NonNls 14 | private static final @NotNull String BUNDLE = "messages.DdevIntegrationBundle"; 15 | private static final @NotNull DdevIntegrationBundle INSTANCE = new DdevIntegrationBundle(); 16 | 17 | private DdevIntegrationBundle() { 18 | super(BUNDLE); 19 | } 20 | 21 | @NotNull 22 | public static @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) { 23 | return INSTANCE.getMessage(key, params); 24 | } 25 | 26 | @NotNull 27 | public static Supplier<@Nls String> messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) { 28 | return INSTANCE.getLazyMessage(key, params); 29 | } 30 | 31 | public static String getName() { 32 | return BUNDLE; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/DescriptionChangedListener.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev; 2 | 3 | import com.intellij.util.messages.Topic; 4 | import de.php_perfect.intellij.ddev.cmd.Description; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | public interface DescriptionChangedListener { 8 | Topic DESCRIPTION_CHANGED = Topic.create("DDEV description changed", DescriptionChangedListener.class); 9 | 10 | void onDescriptionChanged(@Nullable Description description); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/InitPluginActivity.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.openapi.project.DumbAware; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.startup.ProjectActivity; 7 | import de.php_perfect.intellij.ddev.error_reporting.SentrySdkInitializer; 8 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 9 | import kotlin.Unit; 10 | import kotlin.coroutines.Continuation; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | public final class InitPluginActivity implements ProjectActivity, DumbAware { 15 | @Nullable 16 | @Override 17 | public Object execute(@NotNull Project project, @NotNull Continuation continuation) { 18 | SentrySdkInitializer.init(); 19 | ApplicationManager.getApplication().executeOnPooledThread(() -> DdevStateManager.getInstance(project).initialize()); 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/StateChangedListener.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev; 2 | 3 | import com.intellij.util.messages.Topic; 4 | import de.php_perfect.intellij.ddev.state.State; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public interface StateChangedListener { 8 | Topic DDEV_CHANGED = Topic.create("DDEV state changed", StateChangedListener.class); 9 | 10 | void onDdevChanged(@NotNull State state); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/StateInitializedListener.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev; 2 | 3 | import com.intellij.util.messages.Topic; 4 | import de.php_perfect.intellij.ddev.state.State; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public interface StateInitializedListener { 8 | Topic STATE_INITIALIZED = Topic.create("DDEV state initialized", StateInitializedListener.class); 9 | 10 | void onStateInitialized(@NotNull State state); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/ChangeSettingsAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.options.ShowSettingsUtil; 5 | import com.intellij.openapi.project.DumbAwareAction; 6 | import de.php_perfect.intellij.ddev.DdevIntegrationBundle; 7 | import de.php_perfect.intellij.ddev.settings.DdevSettingsConfigurable; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import javax.swing.*; 11 | 12 | public final class ChangeSettingsAction extends DumbAwareAction { 13 | public ChangeSettingsAction() { 14 | super(DdevIntegrationBundle.messagePointer("action.DdevIntegration.ChangeSettings.text"), DdevIntegrationBundle.messagePointer("action.DdevIntegration.ChangeSettings.description"), (Icon) null); 15 | } 16 | 17 | @Override 18 | public void actionPerformed(@NotNull AnActionEvent e) { 19 | ShowSettingsUtil.getInstance().showSettingsDialog(e.getProject(), DdevSettingsConfigurable.getName()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/CheckVersionAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.project.DumbAware; 6 | import de.php_perfect.intellij.ddev.version.VersionChecker; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public final class CheckVersionAction extends AnAction implements DumbAware { 10 | @Override 11 | public void actionPerformed(@NotNull AnActionEvent e) { 12 | if (e.getProject() == null) { 13 | return; 14 | } 15 | 16 | VersionChecker.getInstance(e.getProject()).checkDdevVersion(true); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/DdevAwareAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.project.DumbAwareAction; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.util.NlsActions; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import javax.swing.*; 11 | 12 | abstract class DdevAwareAction extends DumbAwareAction { 13 | DdevAwareAction() { 14 | super(); 15 | } 16 | 17 | DdevAwareAction(@Nullable @NlsActions.ActionText String text, @Nullable @NlsActions.ActionDescription String description, @Nullable Icon icon) { 18 | super(text, description, icon); 19 | } 20 | 21 | @Override 22 | public void update(@NotNull AnActionEvent e) { 23 | final Project project = e.getProject(); 24 | 25 | if (project == null) { 26 | e.getPresentation().setEnabled(false); 27 | return; 28 | } 29 | 30 | try { 31 | e.getPresentation().setEnabled(this.isActive(project)); 32 | } catch (Exception ex) { 33 | // if state access fails, disable the action 34 | e.getPresentation().setEnabled(false); 35 | } 36 | } 37 | 38 | protected abstract boolean isActive(@NotNull Project project); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/DdevConfigAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.cmd.DdevRunner; 5 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 6 | import de.php_perfect.intellij.ddev.state.State; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public final class DdevConfigAction extends DdevRunAction { 10 | @Override 11 | protected void run(@NotNull Project project) { 12 | DdevRunner.getInstance().config(project); 13 | } 14 | 15 | @Override 16 | protected boolean isActive(@NotNull Project project) { 17 | final State state = DdevStateManager.getInstance(project).getState(); 18 | 19 | return state.isAvailable() && !state.isConfigured(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/DdevDeleteAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.cmd.DdevRunner; 5 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 6 | import de.php_perfect.intellij.ddev.state.State; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public final class DdevDeleteAction extends DdevRunAction { 10 | @Override 11 | protected void run(@NotNull Project project) { 12 | DdevRunner.getInstance().delete(project); 13 | } 14 | 15 | @Override 16 | protected boolean isActive(@NotNull Project project) { 17 | final State state = DdevStateManager.getInstance(project).getState(); 18 | 19 | return state.isAvailable() && state.isConfigured(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/DdevPowerOffAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.cmd.DdevRunner; 5 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 6 | import de.php_perfect.intellij.ddev.state.State; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public final class DdevPowerOffAction extends DdevRunAction { 10 | @Override 11 | protected void run(@NotNull Project project) { 12 | DdevRunner.getInstance().powerOff(project); 13 | } 14 | 15 | @Override 16 | protected boolean isActive(@NotNull Project project) { 17 | final State state = DdevStateManager.getInstance(project).getState(); 18 | 19 | return state.isAvailable() && state.isConfigured(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/DdevPredefinedTerminalAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.project.Project; 6 | import de.php_perfect.intellij.ddev.cmd.Description; 7 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 8 | import de.php_perfect.intellij.ddev.state.State; 9 | import de.php_perfect.intellij.ddev.terminal.DdevTerminalRunner; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.plugins.terminal.TerminalTabState; 12 | import org.jetbrains.plugins.terminal.TerminalToolWindowManager; 13 | 14 | public final class DdevPredefinedTerminalAction extends DdevAwareAction { 15 | @Override 16 | public void actionPerformed(@NotNull AnActionEvent e) { 17 | Project project = e.getProject(); 18 | 19 | if (project == null) { 20 | return; 21 | } 22 | 23 | DdevTerminalRunner runner = new DdevTerminalRunner(project); 24 | TerminalTabState tabState = new TerminalTabState(); 25 | tabState.myTabName = "DDEV Web Container"; 26 | 27 | TerminalToolWindowManager.getInstance(project).createNewSession(runner, tabState); 28 | } 29 | 30 | @Override 31 | protected boolean isActive(@NotNull Project project) { 32 | final State state = DdevStateManager.getInstance(project).getState(); 33 | 34 | if (!state.isAvailable() || !state.isConfigured()) { 35 | return false; 36 | } 37 | 38 | Description description = state.getDescription(); 39 | 40 | if (description == null) { 41 | return false; 42 | } 43 | 44 | return description.getStatus() == Description.Status.RUNNING; 45 | } 46 | 47 | @Override 48 | public @NotNull ActionUpdateThread getActionUpdateThread() { 49 | return ActionUpdateThread.BGT; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/DdevRestartAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.cmd.DdevRunner; 5 | import de.php_perfect.intellij.ddev.cmd.Description; 6 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 7 | import de.php_perfect.intellij.ddev.state.State; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public final class DdevRestartAction extends DdevRunAction { 11 | @Override 12 | protected void run(@NotNull Project project) { 13 | DdevRunner.getInstance().restart(project); 14 | } 15 | 16 | @Override 17 | protected boolean isActive(@NotNull Project project) { 18 | final State state = DdevStateManager.getInstance(project).getState(); 19 | 20 | if (!state.isAvailable() || !state.isConfigured()) { 21 | return false; 22 | } 23 | 24 | Description description = state.getDescription(); 25 | 26 | if (description == null) { 27 | return true; 28 | } 29 | 30 | return description.getStatus() == Description.Status.RUNNING; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/DdevRunAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.project.Project; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | abstract class DdevRunAction extends DdevAwareAction { 9 | @Override 10 | public void actionPerformed(@NotNull AnActionEvent e) { 11 | final Project project = e.getProject(); 12 | 13 | if (project == null) { 14 | return; 15 | } 16 | 17 | this.run(project); 18 | } 19 | 20 | protected abstract void run(@NotNull Project project); 21 | 22 | @Override 23 | public @NotNull ActionUpdateThread getActionUpdateThread() { 24 | return ActionUpdateThread.BGT; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/DdevShareAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.cmd.DdevRunner; 5 | import de.php_perfect.intellij.ddev.cmd.Description; 6 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 7 | import de.php_perfect.intellij.ddev.state.State; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public final class DdevShareAction extends DdevRunAction { 11 | @Override 12 | protected void run(@NotNull Project project) { 13 | DdevRunner.getInstance().share(project); 14 | } 15 | 16 | @Override 17 | protected boolean isActive(@NotNull Project project) { 18 | final State state = DdevStateManager.getInstance(project).getState(); 19 | 20 | if (!state.isAvailable()) { 21 | return false; 22 | } 23 | 24 | Description description = state.getDescription(); 25 | 26 | if (description == null) { 27 | return false; 28 | } 29 | 30 | return description.getStatus() == Description.Status.RUNNING; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/DdevStartAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.cmd.DdevRunner; 5 | import de.php_perfect.intellij.ddev.cmd.Description; 6 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 7 | import de.php_perfect.intellij.ddev.state.State; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public final class DdevStartAction extends DdevRunAction { 11 | @Override 12 | protected void run(@NotNull Project project) { 13 | DdevRunner.getInstance().start(project); 14 | } 15 | 16 | @Override 17 | protected boolean isActive(@NotNull Project project) { 18 | final State state = DdevStateManager.getInstance(project).getState(); 19 | 20 | if (!state.isAvailable() || !state.isConfigured()) { 21 | return false; 22 | } 23 | 24 | Description description = state.getDescription(); 25 | 26 | if (description == null) { 27 | return true; 28 | } 29 | 30 | return description.getStatus() != Description.Status.RUNNING && description.getStatus() != Description.Status.STARTING; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/DdevStopAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.cmd.DdevRunner; 5 | import de.php_perfect.intellij.ddev.cmd.Description; 6 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 7 | import de.php_perfect.intellij.ddev.state.State; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public final class DdevStopAction extends DdevRunAction { 11 | @Override 12 | protected void run(@NotNull Project project) { 13 | DdevRunner.getInstance().stop(project); 14 | } 15 | 16 | @Override 17 | protected boolean isActive(@NotNull Project project) { 18 | final State state = DdevStateManager.getInstance(project).getState(); 19 | 20 | if (!state.isAvailable() || !state.isConfigured()) { 21 | return false; 22 | } 23 | 24 | Description description = state.getDescription(); 25 | 26 | if (description == null) { 27 | return true; 28 | } 29 | 30 | return description.getStatus() == Description.Status.RUNNING; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/DisableCheckForUpdatesAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.project.DumbAwareAction; 5 | import com.intellij.openapi.project.Project; 6 | import de.php_perfect.intellij.ddev.DdevIntegrationBundle; 7 | import de.php_perfect.intellij.ddev.settings.DdevSettingsState; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public final class DisableCheckForUpdatesAction extends DumbAwareAction { 11 | public DisableCheckForUpdatesAction() { 12 | super(DdevIntegrationBundle.messagePointer("actions.disableCheckForUpdates")); 13 | } 14 | 15 | @Override 16 | public void actionPerformed(@NotNull AnActionEvent e) { 17 | final Project project = e.getProject(); 18 | 19 | if (project == null) { 20 | return; 21 | } 22 | 23 | DdevSettingsState.getInstance(project).checkForUpdates = false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/InstallationInstructionsAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.icons.AllIcons; 4 | import com.intellij.ide.BrowserUtil; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | import com.intellij.openapi.project.DumbAwareAction; 7 | import de.php_perfect.intellij.ddev.DdevIntegrationBundle; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public final class InstallationInstructionsAction extends DumbAwareAction { 11 | public InstallationInstructionsAction() { 12 | super(DdevIntegrationBundle.messagePointer("action.DdevIntegration.InstallationInstructions.text"), DdevIntegrationBundle.messagePointer("action.DdevIntegration.InstallationInstructions.description"), AllIcons.Ide.External_link_arrow); 13 | } 14 | 15 | @Override 16 | public void actionPerformed(@NotNull AnActionEvent e) { 17 | BrowserUtil.browse("https://ddev.readthedocs.io/en/stable/"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/ManagePluginsAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.options.ShowSettingsUtil; 5 | import com.intellij.openapi.project.DumbAwareAction; 6 | import de.php_perfect.intellij.ddev.DdevIntegrationBundle; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import javax.swing.*; 10 | 11 | public final class ManagePluginsAction extends DumbAwareAction { 12 | public ManagePluginsAction() { 13 | super(DdevIntegrationBundle.messagePointer("action.DdevIntegration.ManagePlugins.text"), DdevIntegrationBundle.messagePointer("action.DdevIntegration.ManagePlugins.description"), (Icon) null); 14 | } 15 | 16 | @Override 17 | public void actionPerformed(@NotNull AnActionEvent e) { 18 | var project = e.getProject(); 19 | 20 | if (project == null) { 21 | return; 22 | } 23 | 24 | ShowSettingsUtil.getInstance().showSettingsDialog(project, "Plugins"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/OpenServiceAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.ide.BrowserUtil; 4 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.util.NlsActions; 8 | import de.php_perfect.intellij.ddev.cmd.Description; 9 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 10 | import de.php_perfect.intellij.ddev.state.State; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import javax.swing.*; 15 | import java.net.URI; 16 | import java.util.Objects; 17 | 18 | public final class OpenServiceAction extends DdevAwareAction { 19 | 20 | private final @NotNull URI uri; 21 | 22 | public OpenServiceAction(@NotNull URI uri, @NotNull @NlsActions.ActionText String text, 23 | @Nullable @NlsActions.ActionDescription String description, @Nullable Icon icon) { 24 | super(text, description, icon); 25 | this.uri = uri; 26 | } 27 | 28 | @Override 29 | public void actionPerformed(@NotNull AnActionEvent e) { 30 | BrowserUtil.browse(this.uri); 31 | } 32 | 33 | @Override 34 | protected boolean isActive(@NotNull Project project) { 35 | final State state = DdevStateManager.getInstance(project).getState(); 36 | 37 | if (!state.isAvailable() || !state.isConfigured()) { 38 | return false; 39 | } 40 | 41 | Description description = state.getDescription(); 42 | 43 | if (description == null) { 44 | return false; 45 | } 46 | 47 | return description.getStatus() == Description.Status.RUNNING; 48 | } 49 | 50 | @Override 51 | public boolean equals(final Object o) { 52 | if (this == o) { 53 | return true; 54 | } 55 | if (o == null || getClass() != o.getClass()) { 56 | return false; 57 | } 58 | final OpenServiceAction that = (OpenServiceAction) o; 59 | return Objects.equals(uri, that.uri); 60 | } 61 | 62 | @Override 63 | public int hashCode() { 64 | return Objects.hash(uri); 65 | } 66 | 67 | @Override 68 | public @NotNull ActionUpdateThread getActionUpdateThread() { 69 | return ActionUpdateThread.BGT; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/ReloadPluginAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.icons.AllIcons; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.application.ApplicationManager; 6 | import com.intellij.openapi.project.DumbAwareAction; 7 | import com.intellij.openapi.project.Project; 8 | import de.php_perfect.intellij.ddev.DdevIntegrationBundle; 9 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public final class ReloadPluginAction extends DumbAwareAction { 13 | public ReloadPluginAction() { 14 | super(DdevIntegrationBundle.messagePointer("action.DdevIntegration.ReloadPlugin.text"), DdevIntegrationBundle.messagePointer("action.DdevIntegration.ReloadPlugin.description"), AllIcons.Actions.Refresh); 15 | } 16 | 17 | @Override 18 | public void actionPerformed(@NotNull AnActionEvent e) { 19 | Project project = e.getProject(); 20 | 21 | if (project == null) { 22 | return; 23 | } 24 | 25 | ApplicationManager.getApplication().executeOnPooledThread(() -> DdevStateManager.getInstance(project).initialize()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/ReportIssueAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.icons.AllIcons; 4 | import com.intellij.ide.BrowserUtil; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | import com.intellij.openapi.project.DumbAwareAction; 7 | import de.php_perfect.intellij.ddev.DdevIntegrationBundle; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public final class ReportIssueAction extends DumbAwareAction { 11 | private static final String NEW_ISSUE_URL = "https://github.com/ddev/ddev-intellij-plugin/issues/new?assignees=&labels=bug&template=bug_report.yml"; 12 | 13 | public ReportIssueAction() { 14 | super(DdevIntegrationBundle.messagePointer("action.DdevIntegration.ReportIssue.text"), DdevIntegrationBundle.messagePointer("action.DdevIntegration.ReportIssue.description"), AllIcons.Vcs.Vendors.Github); 15 | } 16 | 17 | @Override 18 | public void actionPerformed(@NotNull AnActionEvent e) { 19 | BrowserUtil.browse(NEW_ISSUE_URL); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/ServicesActionGroup.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.actionSystem.ActionGroup; 4 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 5 | import com.intellij.openapi.actionSystem.AnAction; 6 | import com.intellij.openapi.actionSystem.AnActionEvent; 7 | import com.intellij.openapi.project.DumbAware; 8 | import com.intellij.openapi.project.Project; 9 | import de.php_perfect.intellij.ddev.cmd.Description; 10 | import de.php_perfect.intellij.ddev.service_actions.ServiceActionManager; 11 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 12 | import de.php_perfect.intellij.ddev.state.State; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | public final class ServicesActionGroup extends ActionGroup implements DumbAware { 17 | @Override 18 | public AnAction @NotNull [] getChildren(@Nullable AnActionEvent e) { 19 | 20 | if (e == null || e.getProject() == null) { 21 | return new AnAction[0]; 22 | } 23 | 24 | return ServiceActionManager.getInstance(e.getProject()).getServiceActions(); 25 | } 26 | 27 | @Override 28 | public void update(@NotNull AnActionEvent e) { 29 | final Project project = e.getProject(); 30 | 31 | if (project == null) { 32 | e.getPresentation().setEnabled(false); 33 | return; 34 | } 35 | 36 | e.getPresentation().setEnabled(this.isActive(project)); 37 | } 38 | 39 | private boolean isActive(@NotNull Project project) { 40 | final State state = DdevStateManager.getInstance(project).getState(); 41 | final Description description = state.getDescription(); 42 | 43 | if (description == null) { 44 | return false; 45 | } 46 | 47 | return description.getStatus() == Description.Status.RUNNING; 48 | } 49 | 50 | @Override 51 | public @NotNull ActionUpdateThread getActionUpdateThread() { 52 | return ActionUpdateThread.BGT; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/actions/SyncStateAction.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.application.ApplicationManager; 5 | import com.intellij.openapi.project.DumbAwareAction; 6 | import com.intellij.openapi.project.Project; 7 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public final class SyncStateAction extends DumbAwareAction { 11 | @Override 12 | public void actionPerformed(@NotNull AnActionEvent e) { 13 | Project project = e.getProject(); 14 | 15 | if (project == null) { 16 | return; 17 | } 18 | 19 | ApplicationManager.getApplication().executeOnPooledThread(() -> DdevStateManager.getInstance(project).updateDescription()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/BinaryLocator.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.openapi.project.Project; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | public interface BinaryLocator { 9 | @Nullable String findInPath(@NotNull Project project); 10 | 11 | static BinaryLocator getInstance() { 12 | return ApplicationManager.getApplication().getService(BinaryLocator.class); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/BinaryLocatorImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.execution.ExecutionException; 4 | import com.intellij.execution.configurations.GeneralCommandLine; 5 | import com.intellij.execution.process.ProcessOutput; 6 | import com.intellij.openapi.diagnostic.Logger; 7 | import com.intellij.openapi.project.Project; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | public class BinaryLocatorImpl implements BinaryLocator { 12 | private static final @NotNull Logger LOG = Logger.getInstance(BinaryLocatorImpl.class); 13 | 14 | private static final @NotNull String DDEV_COMMAND = "ddev"; 15 | 16 | private static final int BINARY_LOOKUP_TIMEOUT = 15_000; 17 | 18 | @Override 19 | public @Nullable String findInPath(@NotNull Project project) { 20 | final String projectDir = project.getBasePath(); 21 | final GeneralCommandLine commandLine = new GeneralCommandLine(WhichProvider.getWhichCommand(projectDir), DDEV_COMMAND).withWorkDirectory(projectDir); 22 | 23 | try { 24 | final ProcessOutput processOutput = ProcessExecutor.getInstance().executeCommandLine(commandLine, BINARY_LOOKUP_TIMEOUT, true); 25 | 26 | if (processOutput.getExitCode() != 0) { 27 | return null; 28 | } 29 | 30 | return processOutput.getStdout().strip(); 31 | } catch (ExecutionException exception) { 32 | LOG.error(exception); 33 | return null; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/CommandFailedException.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | public class CommandFailedException extends Exception { 4 | public CommandFailedException(String message) { 5 | super(message); 6 | } 7 | 8 | public CommandFailedException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/DatabaseInfo.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.Objects; 7 | 8 | public record DatabaseInfo( 9 | @SerializedName("database_type") de.php_perfect.intellij.ddev.cmd.DatabaseInfo.@Nullable Type type, 10 | @SerializedName("database_version") @Nullable String version, @SerializedName("dbPort") int port, 11 | @SerializedName("dbname") @Nullable String name, @SerializedName("host") @Nullable String host, 12 | @SerializedName("username") @Nullable String username, @SerializedName("password") @Nullable String password, 13 | @SerializedName("published_port") int publishedPort) { 14 | public enum Type { 15 | @SerializedName("mysql") MYSQL, 16 | @SerializedName("mariadb") MARIADB, 17 | @SerializedName("postgres") POSTGRESQL, 18 | } 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | if (this == o) return true; 23 | if (!(o instanceof DatabaseInfo( 24 | Type type1, String version1, int port1, String name1, String host1, String username1, String password1, 25 | int publishedPort1 26 | ))) return false; 27 | return port == port1 && publishedPort == publishedPort1 && Objects.equals(name, name1) && Objects.equals(host, host1) && Objects.equals(version, version1) && Objects.equals(username, username1) && Objects.equals(password, password1) && type == type1; 28 | } 29 | 30 | @Override 31 | public int hashCode() { 32 | return Objects.hash(type, version, port, name, host, username, password, publishedPort); 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "DatabaseInfo{" + 38 | "type=" + type + 39 | ", version='" + version + '\'' + 40 | ", port=" + port + 41 | ", name='" + name + '\'' + 42 | ", host='" + host + '\'' + 43 | ", username='" + username + '\'' + 44 | ", password='" + password + '\'' + 45 | ", publishedPort=" + publishedPort + 46 | '}'; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/Ddev.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.openapi.project.Project; 5 | import de.php_perfect.intellij.ddev.version.Version; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public interface Ddev { 9 | @NotNull Version version(@NotNull String binary, @NotNull Project project) throws CommandFailedException; 10 | 11 | @NotNull Versions detailedVersions(@NotNull String binary, @NotNull Project project) throws CommandFailedException; 12 | 13 | @NotNull Description describe(@NotNull String binary, @NotNull Project project) throws CommandFailedException; 14 | 15 | static Ddev getInstance() { 16 | return ApplicationManager.getApplication().getService(Ddev.class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/DdevRunner.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.openapi.project.Project; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public interface DdevRunner { 8 | 9 | void start(@NotNull Project project); 10 | 11 | void restart(@NotNull Project project); 12 | 13 | void stop(@NotNull Project project); 14 | 15 | void powerOff(@NotNull Project project); 16 | 17 | void delete(@NotNull Project project); 18 | 19 | void share(@NotNull Project project); 20 | 21 | void config(@NotNull Project project); 22 | 23 | static DdevRunner getInstance() { 24 | return ApplicationManager.getApplication().getService(DdevRunner.class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/DdevShellCommandHandlerImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.execution.Executor; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.terminal.TerminalShellCommandHandler; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | public final class DdevShellCommandHandlerImpl implements TerminalShellCommandHandler { 10 | static final @NotNull String PREFIX = "ddev "; 11 | 12 | enum Action { 13 | START, 14 | STOP, 15 | RESTART, 16 | POWER_OFF, 17 | DELETE, 18 | SHARE, 19 | CONFIG, 20 | } 21 | 22 | @Override 23 | public boolean execute(@NotNull Project project, @Nullable String workingDirectory, boolean localSession, @NotNull String command, @NotNull Executor executor) { 24 | final Action action = parseAction(command); 25 | 26 | if (action == null) { 27 | return false; 28 | } 29 | 30 | this.executeAction(action, project); 31 | 32 | return true; 33 | } 34 | 35 | @Override 36 | public boolean matches(@NotNull Project project, @Nullable String workingDirectory, boolean localSession, @NotNull String command) { 37 | return parseAction(command) != null; 38 | } 39 | 40 | private Action parseAction(@NotNull String command) { 41 | if (!command.startsWith(PREFIX)) { 42 | return null; 43 | } 44 | 45 | final String actionString = command.substring(PREFIX.length()).trim(); 46 | 47 | return this.matchAction(actionString); 48 | } 49 | 50 | private Action matchAction(@NotNull String action) { 51 | return switch (action) { 52 | case "start" -> Action.START; 53 | case "stop" -> Action.STOP; 54 | case "restart" -> Action.RESTART; 55 | case "poweroff" -> Action.POWER_OFF; 56 | case "delete" -> Action.DELETE; 57 | case "share" -> Action.SHARE; 58 | case "config" -> Action.CONFIG; 59 | default -> null; 60 | }; 61 | } 62 | 63 | private void executeAction(@NotNull Action action, @NotNull Project project) { 64 | final DdevRunner ddevRunner = DdevRunner.getInstance(); 65 | 66 | switch (action) { 67 | case START -> ddevRunner.start(project); 68 | case STOP -> ddevRunner.stop(project); 69 | case RESTART -> ddevRunner.restart(project); 70 | case POWER_OFF -> ddevRunner.powerOff(project); 71 | case DELETE -> ddevRunner.delete(project); 72 | case SHARE -> ddevRunner.share(project); 73 | case CONFIG -> ddevRunner.config(project); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/Docker.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public interface Docker { 7 | boolean isRunning(String workDirectory); 8 | 9 | @NotNull String getContext(String workDirectory); 10 | 11 | static Docker getInstance() { 12 | return ApplicationManager.getApplication().getService(Docker.class); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/DockerImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.execution.ExecutionException; 4 | import com.intellij.execution.configurations.GeneralCommandLine; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public final class DockerImpl implements Docker { 8 | @Override 9 | public boolean isRunning(String workDirectory) { 10 | try { 11 | return ProcessExecutor.getInstance().executeCommandLine( 12 | new GeneralCommandLine("docker", "info") 13 | .withWorkDirectory(workDirectory), 14 | 5_000, 15 | false 16 | ).getExitCode() == 0; 17 | } catch (ExecutionException e) { 18 | return false; 19 | } 20 | } 21 | 22 | @Override 23 | public @NotNull String getContext(String workDirectory) { 24 | try { 25 | return ProcessExecutor.getInstance().executeCommandLine( 26 | new GeneralCommandLine("docker", "context", "show") 27 | .withWorkDirectory(workDirectory), 28 | 5_000, 29 | false 30 | ).getStdout(); 31 | } catch (ExecutionException e) { 32 | return ""; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/ProcessExecutor.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.execution.ExecutionException; 4 | import com.intellij.execution.configurations.GeneralCommandLine; 5 | import com.intellij.execution.process.ProcessOutput; 6 | import com.intellij.openapi.application.ApplicationManager; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public interface ProcessExecutor { 10 | @NotNull ProcessOutput executeCommandLine(GeneralCommandLine commandLine, int timeout, boolean loginShell) throws ExecutionException; 11 | 12 | static ProcessExecutor getInstance() { 13 | return ApplicationManager.getApplication().getService(ProcessExecutor.class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/ProcessExecutorImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.google.common.util.concurrent.UncheckedExecutionException; 4 | import com.intellij.execution.ExecutionException; 5 | import com.intellij.execution.configurations.GeneralCommandLine; 6 | import com.intellij.execution.process.CapturingProcessHandler; 7 | import com.intellij.execution.process.ProcessOutput; 8 | import com.intellij.openapi.diagnostic.Logger; 9 | import com.intellij.openapi.progress.EmptyProgressIndicator; 10 | import com.intellij.openapi.progress.ProgressManager; 11 | import de.php_perfect.intellij.ddev.cmd.wsl.WslAware; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | import java.util.concurrent.atomic.AtomicReference; 15 | 16 | public class ProcessExecutorImpl implements ProcessExecutor { 17 | public static final Logger LOG = Logger.getInstance(ProcessExecutorImpl.class); 18 | 19 | public @NotNull ProcessOutput executeCommandLine(GeneralCommandLine commandLine, int timeout, boolean loginShell) throws ExecutionException { 20 | final GeneralCommandLine patchedCommandLine = WslAware.patchCommandLine(commandLine, loginShell); 21 | final AtomicReference outputReference = new AtomicReference<>(); 22 | 23 | ProgressManager.getInstance().runProcess(() -> { 24 | try { 25 | CapturingProcessHandler processHandler = new CapturingProcessHandler(patchedCommandLine); 26 | ProcessOutput output = processHandler.runProcess(timeout); 27 | outputReference.set(output); 28 | 29 | LOG.debug("command: " + processHandler.getCommandLine() + " returned: " + output); 30 | } catch (ExecutionException e) { 31 | throw new UncheckedExecutionException(e); 32 | } 33 | }, new EmptyProgressIndicator()); 34 | 35 | return outputReference.get(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/Runner.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.execution.configurations.GeneralCommandLine; 4 | import com.intellij.openapi.project.Project; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | public interface Runner { 9 | void run(@NotNull GeneralCommandLine commandLine, @NotNull String title); 10 | 11 | void run(@NotNull GeneralCommandLine commandLine, @NotNull String title, @Nullable Runnable afterCompletion); 12 | 13 | static Runner getInstance(@NotNull Project project) { 14 | return project.getService(Runner.class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/RunnerImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.execution.ExecutionException; 4 | import com.intellij.execution.RunContentExecutor; 5 | import com.intellij.execution.configurations.GeneralCommandLine; 6 | import com.intellij.execution.process.ColoredProcessHandler; 7 | import com.intellij.execution.process.ProcessHandler; 8 | import com.intellij.execution.process.ProcessTerminatedListener; 9 | import com.intellij.openapi.Disposable; 10 | import com.intellij.openapi.application.ApplicationManager; 11 | import com.intellij.openapi.application.ModalityState; 12 | import com.intellij.openapi.diagnostic.Logger; 13 | import com.intellij.openapi.project.Project; 14 | import com.intellij.openapi.util.Disposer; 15 | import de.php_perfect.intellij.ddev.cmd.wsl.WslAware; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | public final class RunnerImpl implements Runner, Disposable { 20 | private static final Logger LOG = Logger.getInstance(RunnerImpl.class); 21 | 22 | private final @NotNull Project project; 23 | 24 | public RunnerImpl(@NotNull Project project) { 25 | this.project = project; 26 | } 27 | 28 | @Override 29 | public void run(@NotNull GeneralCommandLine commandLine, @NotNull String title) { 30 | this.run(commandLine, title, null); 31 | } 32 | 33 | @Override 34 | public void run(@NotNull GeneralCommandLine commandLine, @NotNull String title, @Nullable Runnable afterCompletion) { 35 | // Create process handler on background thread to avoid EDT violations 36 | ApplicationManager.getApplication().executeOnPooledThread(() -> { 37 | try { 38 | final ProcessHandler processHandler = this.createProcessHandler(commandLine); 39 | 40 | // Switch back to EDT for UI operations 41 | ApplicationManager.getApplication().invokeLater(() -> { 42 | final RunContentExecutor runContentExecutor = new RunContentExecutor(this.project, processHandler) 43 | .withTitle(title) 44 | .withActivateToolWindow(true) 45 | .withAfterCompletion(afterCompletion) 46 | .withStop(processHandler::destroyProcess, () -> !processHandler.isProcessTerminated()); 47 | Disposer.register(this, runContentExecutor); 48 | runContentExecutor.run(); 49 | }, ModalityState.nonModal()); 50 | } catch (ExecutionException exception) { 51 | LOG.warn("An error occurred running " + commandLine.getCommandLineString(), exception); 52 | } 53 | }); 54 | } 55 | 56 | private @NotNull ProcessHandler createProcessHandler(GeneralCommandLine commandLine) throws ExecutionException { 57 | final ProcessHandler handler = new ColoredProcessHandler(WslAware.patchCommandLine(commandLine)); 58 | ProcessTerminatedListener.attach(handler); 59 | 60 | return handler; 61 | } 62 | 63 | @Override 64 | public void dispose() { 65 | // Use service as parent disposable for running processes 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/Service.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | import java.util.Objects; 6 | 7 | public class Service { 8 | private final @Nullable String fullName; 9 | 10 | private final @Nullable String httpsUrl; 11 | 12 | private final @Nullable String httpUrl; 13 | 14 | public Service(@Nullable String fullName, @Nullable String httpsUrl, @Nullable String httpUrl) { 15 | this.fullName = fullName; 16 | this.httpsUrl = httpsUrl; 17 | this.httpUrl = httpUrl; 18 | } 19 | 20 | public @Nullable String getFullName() { 21 | return fullName; 22 | } 23 | 24 | public @Nullable String getHttpUrl() { 25 | return httpUrl; 26 | } 27 | 28 | public @Nullable String getHttpsUrl() { 29 | return httpsUrl; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) return true; 35 | if (!(o instanceof Service service)) return false; 36 | return Objects.equals(getFullName(), service.getFullName()) && Objects.equals(getHttpUrl(), service.getHttpUrl()) && Objects.equals(getHttpsUrl(), service.getHttpsUrl()); 37 | } 38 | 39 | @Override 40 | public int hashCode() { 41 | return Objects.hash(getFullName(), getHttpUrl(), getHttpsUrl()); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "Service{" + 47 | "fullName='" + fullName + '\'' + 48 | ", httpUrl='" + httpUrl + '\'' + 49 | ", httpsUrl='" + httpsUrl + '\'' + 50 | '}'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/Versions.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.Objects; 7 | 8 | public class Versions { 9 | @SerializedName("DDEV version") 10 | private final @Nullable String ddevVersion; 11 | 12 | @SerializedName("docker") 13 | private final @Nullable String dockerVersion; 14 | 15 | @SerializedName("docker-compose") 16 | private final @Nullable String dockerComposeVersion; 17 | 18 | @SerializedName("docker-platform") 19 | private final @Nullable String dockerPlatform; 20 | 21 | public Versions(@Nullable String ddevVersion, @Nullable String dockerVersion, @Nullable String dockerComposeVersion, @Nullable String dockerPlatform) { 22 | this.ddevVersion = ddevVersion; 23 | this.dockerVersion = dockerVersion; 24 | this.dockerComposeVersion = dockerComposeVersion; 25 | this.dockerPlatform = dockerPlatform; 26 | } 27 | 28 | public @Nullable String getDdevVersion() { 29 | return ddevVersion; 30 | } 31 | 32 | public @Nullable String getDockerVersion() { 33 | return dockerVersion; 34 | } 35 | 36 | public @Nullable String getDockerComposeVersion() { 37 | return dockerComposeVersion; 38 | } 39 | 40 | public @Nullable String getDockerPlatform() { 41 | return dockerPlatform; 42 | } 43 | 44 | @Override 45 | public boolean equals(Object o) { 46 | if (this == o) return true; 47 | if (o == null || getClass() != o.getClass()) return false; 48 | Versions versions = (Versions) o; 49 | return Objects.equals(getDdevVersion(), versions.getDdevVersion()) && Objects.equals(getDockerVersion(), versions.getDockerVersion()) && Objects.equals(getDockerComposeVersion(), versions.getDockerComposeVersion()) && Objects.equals(getDockerPlatform(), versions.getDockerPlatform()); 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | return Objects.hash(getDdevVersion(), getDockerVersion(), getDockerComposeVersion(), getDockerPlatform()); 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return "Versions{" + 60 | "ddevVersion='" + ddevVersion + '\'' + 61 | ", dockerVersion='" + dockerVersion + '\'' + 62 | ", dockerComposeVersion='" + dockerComposeVersion + '\'' + 63 | ", dockerPlatform='" + dockerPlatform + '\'' + 64 | '}'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/WhichProvider.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.execution.wsl.WSLDistribution; 4 | import com.intellij.execution.wsl.WslPath; 5 | import com.intellij.openapi.util.SystemInfo; 6 | 7 | final class WhichProvider { 8 | public static String getWhichCommand(String workingDirectory) { 9 | if (!SystemInfo.isWindows) { 10 | return "which"; 11 | } 12 | 13 | WSLDistribution distribution = WslPath.getDistributionByWindowsUncPath(workingDirectory); 14 | 15 | if (distribution != null) { 16 | return "which"; 17 | } 18 | 19 | return "where"; 20 | } 21 | 22 | private WhichProvider() { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/parser/JsonParser.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd.parser; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.lang.reflect.Type; 7 | 8 | public interface JsonParser { 9 | @NotNull T parse(final String json, final Type typeOfT) throws JsonParserException; 10 | 11 | static @NotNull JsonParser getInstance() { 12 | return ApplicationManager.getApplication().getService(JsonParser.class); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/parser/JsonParserException.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd.parser; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | public class JsonParserException extends Exception { 6 | public JsonParserException(String message) { 7 | super(message); 8 | } 9 | 10 | public JsonParserException(String message, @NotNull Throwable cause) { 11 | super(message, cause); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/parser/JsonParserImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd.parser; 2 | 3 | import com.google.gson.FieldNamingPolicy; 4 | import com.google.gson.Gson; 5 | import com.google.gson.GsonBuilder; 6 | import com.google.gson.JsonSyntaxException; 7 | import com.google.gson.reflect.TypeToken; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.lang.reflect.Type; 12 | import java.util.Scanner; 13 | 14 | public final class JsonParserImpl implements JsonParser { 15 | 16 | public @NotNull T parse(final String json, final Type typeOfT) throws JsonParserException { 17 | try (Scanner scanner = new Scanner(json)) { 18 | while (scanner.hasNextLine()) { 19 | final String line = scanner.nextLine(); 20 | final LogLine logLine = parseJson(line, typeOfT); 21 | 22 | if (logLine != null && logLine.raw() != null) { 23 | return logLine.raw(); 24 | } 25 | } 26 | } 27 | 28 | throw new JsonParserException("Could not parse the ddev describe output"); 29 | } 30 | 31 | private @Nullable LogLine parseJson(final String json, final Type typeOfT) throws JsonParserException { 32 | final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); 33 | final Type typeToken = TypeToken.getParameterized(LogLine.class, typeOfT).getType(); 34 | 35 | try { 36 | return gson.fromJson(json, typeToken); 37 | } catch (JsonSyntaxException exception) { 38 | throw new JsonParserException(String.format("Encountered invalid JSON '%s'", json), exception); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/parser/LogLine.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd.parser; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | public record LogLine(@Nullable T raw) { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/cmd/wsl/WslAware.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd.wsl; 2 | 3 | import com.intellij.execution.ExecutionException; 4 | import com.intellij.execution.configurations.GeneralCommandLine; 5 | import com.intellij.execution.wsl.WSLCommandLineOptions; 6 | import com.intellij.execution.wsl.WSLDistribution; 7 | import com.intellij.execution.wsl.WslPath; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public class WslAware { 11 | private WslAware() { 12 | } 13 | 14 | public static T patchCommandLine(T commandLine) { 15 | return patchCommandLine(commandLine, false); 16 | } 17 | 18 | public static T patchCommandLine(T commandLine, boolean loginShell) { 19 | WSLDistribution distribution = WslPath.getDistributionByWindowsUncPath(commandLine.getWorkDirectory().getPath()); 20 | 21 | if (distribution == null) { 22 | return commandLine; 23 | } 24 | 25 | try { 26 | return applyWslPatch(commandLine, distribution, loginShell); 27 | } catch (ExecutionException ignored) { 28 | return commandLine; 29 | } 30 | } 31 | 32 | @NotNull 33 | private static T applyWslPatch(T generalCommandLine, WSLDistribution distribution, boolean loginShell) throws ExecutionException { 34 | WSLCommandLineOptions options = new WSLCommandLineOptions() 35 | .setExecuteCommandInLoginShell(loginShell); 36 | 37 | return distribution.patchCommandLine(generalCommandLine, null, options); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/database/AutoConfigureDataSourceListener.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.database; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.DatabaseInfoChangedListener; 5 | import de.php_perfect.intellij.ddev.cmd.DatabaseInfo; 6 | import de.php_perfect.intellij.ddev.settings.DdevSettingsState; 7 | import de.php_perfect.intellij.ddev.util.FeatureRequiredPlugins; 8 | import de.php_perfect.intellij.ddev.util.PluginChecker; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | public final class AutoConfigureDataSourceListener implements DatabaseInfoChangedListener { 13 | private static final @NotNull String HOST = "127.0.0.1"; 14 | private final @NotNull Project project; 15 | 16 | 17 | public AutoConfigureDataSourceListener(@NotNull Project project) { 18 | this.project = project; 19 | } 20 | 21 | @Override 22 | public void onDatabaseInfoChanged(@Nullable DatabaseInfo databaseInfo) { 23 | if (databaseInfo == null) { 24 | return; 25 | } 26 | 27 | if (!DdevSettingsState.getInstance(this.project).autoConfigureDataSource) { 28 | return; 29 | } 30 | 31 | if (databaseInfo.type() == null || databaseInfo.version() == null || databaseInfo.name() == null || databaseInfo.username() == null || databaseInfo.password() == null) { 32 | return; 33 | } 34 | 35 | if (PluginChecker.isMissingRequiredPlugins(this.project, FeatureRequiredPlugins.DATABASE, "database auto-registration")) { 36 | return; 37 | } 38 | 39 | final DataSourceConfig.Type type = switch (databaseInfo.type()) { 40 | case MYSQL -> DataSourceConfig.Type.MYSQL; 41 | case MARIADB -> DataSourceConfig.Type.MARIADB; 42 | case POSTGRESQL -> DataSourceConfig.Type.POSTGRESQL; 43 | }; 44 | 45 | DdevDataSourceManager.getInstance(this.project).updateDdevDataSource(new DataSourceConfig("DDEV", "DDEV generated data source", type, databaseInfo.version(), HOST, databaseInfo.publishedPort(), databaseInfo.name(), databaseInfo.username(), databaseInfo.password())); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/database/DataSourceConfig.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.database; 2 | 3 | import de.php_perfect.intellij.ddev.index.IndexableConfiguration; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.Objects; 7 | 8 | public record DataSourceConfig(@NotNull String name, @NotNull String description, @NotNull Type type, 9 | @NotNull String version, @NotNull String host, int port, @NotNull String database, 10 | @NotNull String username, @NotNull String password) implements IndexableConfiguration { 11 | public enum Type { 12 | MYSQL, 13 | MARIADB, 14 | POSTGRESQL, 15 | } 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | DataSourceConfig that = (DataSourceConfig) o; 22 | return port == that.port && Objects.equals(name, that.name) && Objects.equals(description, that.description) && type == that.type && Objects.equals(version, that.version) && Objects.equals(host, that.host) && Objects.equals(database, that.database) && Objects.equals(username, that.username) && Objects.equals(password, that.password); 23 | } 24 | 25 | @Override 26 | public int hashCode() { 27 | return Objects.hash(name, description, type, version, host, port, database, username, password); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return "DataSourceConfig{" + 33 | "name='" + name + '\'' + 34 | ", description='" + description + '\'' + 35 | ", type=" + type + 36 | ", version='" + version + '\'' + 37 | ", host='" + host + '\'' + 38 | ", port=" + port + 39 | ", database='" + database + '\'' + 40 | ", username='" + username + '\'' + 41 | ", password='" + password + '\'' + 42 | '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/database/DataSourceProvider.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.database; 2 | 3 | import com.intellij.database.dataSource.LocalDataSource; 4 | import com.intellij.openapi.application.ApplicationManager; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public interface DataSourceProvider { 8 | void updateDataSource(final @NotNull LocalDataSource dataSource, final @NotNull DataSourceConfig dataSourceConfig); 9 | 10 | static DataSourceProvider getInstance() { 11 | return ApplicationManager.getApplication().getService(DataSourceProvider.class); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/database/DdevDataSourceManager.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.database; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public interface DdevDataSourceManager { 7 | void updateDdevDataSource(final @NotNull DataSourceConfig dataSourceConfig); 8 | 9 | static DdevDataSourceManager getInstance(final @NotNull Project project) { 10 | return project.getService(DdevDataSourceManager.class); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/docker_compose/DdevComposeFileLoader.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.docker_compose; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | public interface DdevComposeFileLoader { 9 | @Nullable VirtualFile load(); 10 | 11 | static DdevComposeFileLoader getInstance(@NotNull Project project) { 12 | return project.getService(DdevComposeFileLoader.class); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/docker_compose/DdevComposeFileLoaderImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.docker_compose; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | import com.intellij.openapi.vfs.VirtualFileManager; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.nio.file.Path; 10 | import java.nio.file.Paths; 11 | 12 | public final class DdevComposeFileLoaderImpl implements DdevComposeFileLoader { 13 | private static final @NotNull String DDEV_COMPOSE_PATH = ".ddev/.ddev-docker-compose-full.yaml"; 14 | 15 | private final @NotNull Project project; 16 | 17 | public DdevComposeFileLoaderImpl(@NotNull Project project) { 18 | this.project = project; 19 | } 20 | 21 | @Override 22 | public @Nullable VirtualFile load() { 23 | final String basePath = this.project.getBasePath(); 24 | 25 | if (basePath == null) { 26 | return null; 27 | } 28 | 29 | final Path path = Paths.get(basePath, DDEV_COMPOSE_PATH); 30 | 31 | return VirtualFileManager.getInstance().refreshAndFindFileByNioPath(path); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/docker_compose/DockerComposeConfig.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.docker_compose; 2 | 3 | import de.php_perfect.intellij.ddev.index.IndexableConfiguration; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.List; 7 | import java.util.Objects; 8 | 9 | public record DockerComposeConfig(@NotNull List composeFilePaths, 10 | @NotNull String projectName) implements IndexableConfiguration { 11 | @Override 12 | public boolean equals(Object o) { 13 | if (this == o) return true; 14 | if (o == null || getClass() != o.getClass()) return false; 15 | DockerComposeConfig that = (DockerComposeConfig) o; 16 | return Objects.equals(composeFilePaths, that.composeFilePaths) && Objects.equals(projectName, that.projectName); 17 | } 18 | 19 | @Override 20 | public int hashCode() { 21 | return Objects.hash(composeFilePaths, projectName); 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "DockerComposeConfig{" + 27 | "composeFilePaths=" + composeFilePaths + 28 | ", projectName='" + projectName + '\'' + 29 | '}'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/docker_compose/DockerComposeCredentialProvider.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.docker_compose; 2 | 3 | import com.intellij.docker.remote.DockerComposeCredentialsHolder; 4 | import com.intellij.openapi.application.ApplicationManager; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public interface DockerComposeCredentialProvider { 8 | DockerComposeCredentialsHolder getDdevDockerComposeCredentials(@NotNull DockerComposeConfig dockerComposeConfig); 9 | 10 | static DockerComposeCredentialProvider getInstance() { 11 | return ApplicationManager.getApplication().getService(DockerComposeCredentialProvider.class); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/docker_compose/DockerComposeCredentialProviderImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.docker_compose; 2 | 3 | import com.intellij.docker.DockerCloudType; 4 | import com.intellij.docker.remote.DockerComposeCredentialsHolder; 5 | import com.intellij.docker.remote.DockerComposeCredentialsType; 6 | import com.intellij.docker.remote.DockerCredentialsEditor; 7 | import com.intellij.execution.configuration.EnvironmentVariablesData; 8 | import com.intellij.openapi.diagnostic.Logger; 9 | import com.intellij.remoteServer.configuration.RemoteServersManager; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.Map; 13 | 14 | public final class DockerComposeCredentialProviderImpl implements DockerComposeCredentialProvider { 15 | private static final String DOCKER_NAME = "Docker"; 16 | private static final String SERVICE_NAME = "web"; 17 | private static final String COMPOSE_PROJECT_NAME_ENV = "COMPOSE_PROJECT_NAME"; 18 | private static final @NotNull Logger LOG = Logger.getInstance(DockerComposeCredentialProviderImpl.class); 19 | 20 | public DockerComposeCredentialsHolder getDdevDockerComposeCredentials(@NotNull DockerComposeConfig dockerComposeConfig) { 21 | this.ensureDockerRemoteServer(); 22 | 23 | LOG.debug("Providing new docker credentials"); 24 | 25 | final DockerComposeCredentialsHolder credentials = DockerComposeCredentialsType.getInstance().createCredentials(); 26 | credentials.setAccountName(DOCKER_NAME); 27 | credentials.setComposeFilePaths(dockerComposeConfig.composeFilePaths()); 28 | credentials.setComposeServiceName(SERVICE_NAME); 29 | credentials.setRemoteProjectPath(DockerCredentialsEditor.DEFAULT_DOCKER_PROJECT_PATH); 30 | credentials.setEnvs(EnvironmentVariablesData.create(Map.of(COMPOSE_PROJECT_NAME_ENV, "ddev-" + dockerComposeConfig.projectName().replace(".", "").toLowerCase()), true)); 31 | 32 | return credentials; 33 | } 34 | 35 | private void ensureDockerRemoteServer() { 36 | final var type = DockerCloudType.getInstance(); 37 | final var remoteServerManager = RemoteServersManager.getInstance(); 38 | 39 | if (remoteServerManager.findByName(DOCKER_NAME, type) == null) { 40 | remoteServerManager.addServer(remoteServerManager.createServer(type, DOCKER_NAME)); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/error_reporting/SentrySdkInitializer.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.error_reporting; 2 | 3 | import com.intellij.openapi.application.ApplicationInfo; 4 | import io.sentry.Sentry; 5 | import io.sentry.protocol.OperatingSystem; 6 | import io.sentry.protocol.SentryRuntime; 7 | 8 | public class SentrySdkInitializer { 9 | private static final String DSN = "https://92fd27b7c1fe48a98b040b3a1b603570@o1261149.ingest.sentry.io/6438173"; 10 | 11 | private SentrySdkInitializer() { 12 | } 13 | 14 | public static void init() { 15 | Sentry.init(options -> { 16 | options.setDsn(DSN); 17 | options.setEnableUncaughtExceptionHandler(false); 18 | 19 | options.setBeforeSend((event, hint) -> { 20 | event.setServerName(null); 21 | event.setEnvironment(null); 22 | event.setLevel(null); 23 | 24 | event.getContexts().setRuntime(buildRuntimeContext()); 25 | event.getContexts().setOperatingSystem(buildOperatingSystemContext()); 26 | 27 | event.setExtra("jdk.vendor", System.getProperty("java.vendor")); 28 | event.setExtra("jdk.version", System.getProperty("java.version")); 29 | 30 | return event; 31 | }); 32 | }); 33 | } 34 | 35 | private static SentryRuntime buildRuntimeContext() { 36 | SentryRuntime runtime = new SentryRuntime(); 37 | 38 | runtime.setName(ApplicationInfo.getInstance().getVersionName()); 39 | runtime.setVersion(ApplicationInfo.getInstance().getFullVersion()); 40 | runtime.setRawDescription(ApplicationInfo.getInstance().getBuild().asString()); 41 | 42 | return runtime; 43 | } 44 | 45 | private static OperatingSystem buildOperatingSystemContext() { 46 | OperatingSystem os = new OperatingSystem(); 47 | 48 | os.setName(System.getProperty("os.name")); 49 | os.setVersion(System.getProperty("os.version")); 50 | 51 | return os; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/icons/DdevIntegrationIcons.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.icons; 2 | 3 | import com.intellij.ui.IconManager; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import javax.swing.*; 7 | 8 | public final class DdevIntegrationIcons { 9 | public static final @NotNull Icon DdevLogoColor = IconManager.getInstance().getIcon("/icons/ddevLogoColor.svg", DdevIntegrationIcons.class.getClassLoader()); 10 | public static final @NotNull Icon DdevLogoMono = IconManager.getInstance().getIcon("/icons/ddevLogoGrey.svg", DdevIntegrationIcons.class.getClassLoader()); 11 | 12 | private DdevIntegrationIcons() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/index/IndexEntry.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.index; 2 | 3 | import org.jetbrains.annotations.NonNls; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.util.Objects; 8 | 9 | public record IndexEntry(@NonNls @NotNull String id, @NonNls @Nullable String hash) { 10 | public boolean hashEquals(int hash) { 11 | return this.hashEquals(Integer.toHexString(hash)); 12 | } 13 | 14 | public boolean hashEquals(@Nullable String hash) { 15 | return Objects.equals(hash(), hash); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/index/IndexableConfiguration.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.index; 2 | 3 | public interface IndexableConfiguration { 4 | int hashCode(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/index/ManagedConfigurationIndex.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.index; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NonNls; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | public interface ManagedConfigurationIndex { 9 | static ManagedConfigurationIndex getInstance(@NotNull Project project) { 10 | return project.getService(ManagedConfigurationIndex.class); 11 | } 12 | 13 | void set(@NonNls @NotNull String id, @NotNull Class type, int hash); 14 | 15 | @Nullable IndexEntry get(@NotNull Class type); 16 | 17 | void remove(@NotNull Class type); 18 | 19 | boolean isManaged(@NonNls @NotNull String id, @NotNull Class type); 20 | 21 | boolean isUpToDate(@NotNull Class type, int hash); 22 | 23 | void purge(); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/node/AutoConfigureNodeInterpreterListener.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.node; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | import de.php_perfect.intellij.ddev.DescriptionChangedListener; 6 | import de.php_perfect.intellij.ddev.cmd.Description; 7 | import de.php_perfect.intellij.ddev.docker_compose.DdevComposeFileLoader; 8 | import de.php_perfect.intellij.ddev.settings.DdevSettingsState; 9 | import de.php_perfect.intellij.ddev.util.FeatureRequiredPlugins; 10 | import de.php_perfect.intellij.ddev.util.PluginChecker; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | public final class AutoConfigureNodeInterpreterListener implements DescriptionChangedListener { 15 | 16 | private final @NotNull Project project; 17 | 18 | public AutoConfigureNodeInterpreterListener(@NotNull Project project) { 19 | this.project = project; 20 | } 21 | 22 | @Override 23 | public void onDescriptionChanged(@Nullable Description description) { 24 | if (description == null || description.getName() == null) { 25 | return; 26 | } 27 | 28 | if (!DdevSettingsState.getInstance(this.project).autoConfigureNodeJsInterpreter) { 29 | return; 30 | } 31 | 32 | final VirtualFile composeFile = DdevComposeFileLoader.getInstance(this.project).load(); 33 | 34 | if (composeFile == null || !composeFile.exists()) { 35 | return; 36 | } 37 | 38 | if (PluginChecker.isMissingRequiredPlugins(this.project, FeatureRequiredPlugins.NODE_INTERPRETER, "Node.js interpreter auto-registration")) { 39 | return; 40 | } 41 | 42 | final NodeInterpreterConfig nodeInterpreterConfig = new NodeInterpreterConfig(description.getName(), composeFile.getPath(), "node"); 43 | NodeInterpreterProvider.getInstance(this.project).configureNodeInterpreter(nodeInterpreterConfig); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/node/NodeInterpreterConfig.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.node; 2 | 3 | import de.php_perfect.intellij.ddev.index.IndexableConfiguration; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.Objects; 7 | 8 | public record NodeInterpreterConfig(@NotNull String name, @NotNull String composeFilePath, 9 | @NotNull String binaryPath) implements IndexableConfiguration { 10 | @Override 11 | public boolean equals(Object o) { 12 | if (this == o) return true; 13 | if (o == null || getClass() != o.getClass()) return false; 14 | NodeInterpreterConfig that = (NodeInterpreterConfig) o; 15 | return Objects.equals(name, that.name) && Objects.equals(composeFilePath, that.composeFilePath) && Objects.equals(binaryPath, that.binaryPath); 16 | } 17 | 18 | @Override 19 | public int hashCode() { 20 | return Objects.hash(name, composeFilePath, binaryPath); 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "NodeInterpreterConfig{" + 26 | "name='" + name + '\'' + 27 | ", composeFilePath='" + composeFilePath + '\'' + 28 | ", binaryPath='" + binaryPath + '\'' + 29 | '}'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/node/NodeInterpreterProvider.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.node; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public interface NodeInterpreterProvider { 7 | void configureNodeInterpreter(final @NotNull NodeInterpreterConfig nodeInterpreterConfig); 8 | 9 | static NodeInterpreterProvider getInstance(final @NotNull Project project) { 10 | return project.getService(NodeInterpreterProvider.class); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/node/NodeInterpreterProviderImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.node; 2 | 3 | import com.intellij.docker.remote.DockerComposeCredentialsHolder; 4 | import com.intellij.docker.remote.DockerComposeCredentialsType; 5 | import com.intellij.execution.ExecutionException; 6 | import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterManager; 7 | import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterRef; 8 | import com.intellij.openapi.diagnostic.Logger; 9 | import com.intellij.openapi.project.Project; 10 | import com.intellij.util.PathMappingSettings; 11 | import com.jetbrains.nodejs.remote.NodeJSRemoteInterpreterManager; 12 | import com.jetbrains.nodejs.remote.NodeJSRemoteSdkAdditionalData; 13 | import com.jetbrains.nodejs.remote.NodeRemoteInterpreters; 14 | import de.php_perfect.intellij.ddev.docker_compose.DockerComposeConfig; 15 | import de.php_perfect.intellij.ddev.docker_compose.DockerComposeCredentialProvider; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | import java.util.List; 19 | 20 | public final class NodeInterpreterProviderImpl implements NodeInterpreterProvider { 21 | public static final @NotNull String NODEJS_HELPERS_PATH = ".webstorm_nodejs_helpers"; 22 | private static final @NotNull Logger LOG = Logger.getInstance(NodeInterpreterProviderImpl.class); 23 | private final @NotNull Project project; 24 | 25 | public NodeInterpreterProviderImpl(final @NotNull Project project) { 26 | this.project = project; 27 | } 28 | 29 | public void configureNodeInterpreter(final @NotNull NodeInterpreterConfig nodeInterpreterConfig) { 30 | final NodeRemoteInterpreters nodeRemoteInterpreters = NodeRemoteInterpreters.getInstance(); 31 | 32 | if (!nodeRemoteInterpreters.getInterpreters().isEmpty()) { 33 | return; 34 | } 35 | 36 | LOG.debug("Creating nodejs interpreter"); 37 | 38 | final DockerComposeCredentialsHolder credentials = DockerComposeCredentialProvider.getInstance().getDdevDockerComposeCredentials(new DockerComposeConfig(List.of(nodeInterpreterConfig.composeFilePath()), nodeInterpreterConfig.name())); 39 | final NodeJSRemoteSdkAdditionalData sdkData = this.buildNodeJSRemoteSdkAdditionalData(credentials, nodeInterpreterConfig.binaryPath()); 40 | nodeRemoteInterpreters.add(sdkData); 41 | 42 | NodeJsInterpreterManager.getInstance(this.project).setInterpreterRef(NodeJsInterpreterRef.create(sdkData.getSdkId())); 43 | } 44 | 45 | private @NotNull NodeJSRemoteSdkAdditionalData buildNodeJSRemoteSdkAdditionalData(DockerComposeCredentialsHolder credentials, @NotNull String binaryPath) { 46 | final NodeJSRemoteSdkAdditionalData sdkData = new NodeJSRemoteSdkAdditionalData(binaryPath); 47 | sdkData.setCredentials(DockerComposeCredentialsType.getInstance().getCredentialsKey(), credentials); 48 | sdkData.setHelpersPath(NODEJS_HELPERS_PATH); 49 | sdkData.setPathMappings(this.loadPathMappings(sdkData)); 50 | 51 | return sdkData; 52 | } 53 | 54 | private PathMappingSettings loadPathMappings(NodeJSRemoteSdkAdditionalData sdkData) { 55 | final NodeJSRemoteInterpreterManager nodeRemoteInterpreterManager = NodeJSRemoteInterpreterManager.getInstance(); 56 | 57 | try { 58 | return nodeRemoteInterpreterManager.setupMappings(this.project, sdkData); 59 | } catch (ExecutionException e) { 60 | return null; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/notification/DdevNotifier.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.notification; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public interface DdevNotifier { 7 | void notifyInstallDdev(); 8 | 9 | void notifyNewVersionAvailable(@NotNull String currentVersion, @NotNull String newVersion); 10 | 11 | void notifyAlreadyLatestVersion(); 12 | 13 | void notifyMissingPlugin(@NotNull String pluginName, @NotNull String featureName); 14 | 15 | void notifyMissingPlugins(@NotNull String pluginNames, @NotNull String featureName); 16 | 17 | void notifyPhpInterpreterUpdated(@NotNull String phpVersion); 18 | 19 | void notifyUnknownStateEntered(); 20 | 21 | void notifyErrorReportSent(@NotNull String id); 22 | 23 | void notifyDdevDetected(@NotNull String binary); 24 | 25 | void notifyDockerNotAvailable(final @NotNull String context); 26 | 27 | static DdevNotifier getInstance(@NotNull Project project) { 28 | return project.getService(DdevNotifier.class); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/php/AutoConfigurePhpInterpreterListener.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.DescriptionChangedListener; 5 | import de.php_perfect.intellij.ddev.cmd.Description; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | public final class AutoConfigurePhpInterpreterListener implements DescriptionChangedListener { 10 | private final @NotNull Project project; 11 | 12 | public AutoConfigurePhpInterpreterListener(@NotNull Project project) { 13 | this.project = project; 14 | } 15 | 16 | @Override 17 | public void onDescriptionChanged(@Nullable Description description) { 18 | if (description == null) { 19 | return; 20 | } 21 | 22 | ConfigurationProvider.getInstance(this.project).configure(description); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/php/ConfigurationProvider.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.cmd.Description; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public interface ConfigurationProvider { 8 | void configure(@NotNull Description description); 9 | 10 | static ConfigurationProvider getInstance(@NotNull Project project) { 11 | return project.getService(ConfigurationProvider.class); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/php/ConfigurationProviderImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | import de.php_perfect.intellij.ddev.cmd.Description; 6 | import de.php_perfect.intellij.ddev.docker_compose.DdevComposeFileLoader; 7 | import de.php_perfect.intellij.ddev.settings.DdevSettingsState; 8 | import de.php_perfect.intellij.ddev.util.FeatureRequiredPlugins; 9 | import de.php_perfect.intellij.ddev.util.PluginChecker; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public final class ConfigurationProviderImpl implements ConfigurationProvider { 13 | 14 | private final @NotNull Project project; 15 | 16 | public ConfigurationProviderImpl(@NotNull Project project) { 17 | this.project = project; 18 | } 19 | 20 | @Override 21 | public void configure(@NotNull Description description) { 22 | if (!DdevSettingsState.getInstance(this.project).autoConfigurePhpInterpreter) { 23 | return; 24 | } 25 | 26 | if (description.getName() == null || description.getPhpVersion() == null) { 27 | return; 28 | } 29 | 30 | final VirtualFile composeFile = DdevComposeFileLoader.getInstance(this.project).load(); 31 | 32 | if (composeFile == null || !composeFile.exists()) { 33 | return; 34 | } 35 | 36 | if (PluginChecker.isMissingRequiredPlugins(this.project, FeatureRequiredPlugins.PHP_INTERPRETER, "PHP interpreter auto-registration")) { 37 | return; 38 | } 39 | 40 | final DdevInterpreterConfig ddevInterpreterConfig = new DdevInterpreterConfig(description.getName(), "php" + description.getPhpVersion(), composeFile.getPath()); 41 | PhpInterpreterProvider.getInstance(this.project).registerInterpreter(ddevInterpreterConfig); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/php/DdevInterpreterConfig.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php; 2 | 3 | import de.php_perfect.intellij.ddev.index.IndexableConfiguration; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.Objects; 7 | 8 | public record DdevInterpreterConfig(@NotNull String name, @NotNull String phpVersion, 9 | @NotNull String composeFilePath) implements IndexableConfiguration { 10 | @Override 11 | public boolean equals(Object o) { 12 | if (this == o) return true; 13 | if (o == null || getClass() != o.getClass()) return false; 14 | DdevInterpreterConfig that = (DdevInterpreterConfig) o; 15 | return name().equals(that.name()) && phpVersion().equals(that.phpVersion()) && composeFilePath().equals(that.composeFilePath()); 16 | } 17 | 18 | @Override 19 | public int hashCode() { 20 | return Objects.hash(name(), phpVersion(), composeFilePath()); 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "DdevInterpreterConfig{" + 26 | "name='" + name + '\'' + 27 | ", phpVersion='" + phpVersion + '\'' + 28 | ", composeFilePath='" + composeFilePath + '\'' + 29 | '}'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/php/PhpInterpreterProvider.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public interface PhpInterpreterProvider { 7 | void registerInterpreter(final @NotNull DdevInterpreterConfig interpreterConfig); 8 | 9 | static PhpInterpreterProvider getInstance(final @NotNull Project project) { 10 | return project.getService(PhpInterpreterProvider.class); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/php/PhpVersionArgumentProvider.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.jetbrains.php.config.PhpLanguageLevel; 5 | import de.php_perfect.intellij.ddev.DdevConfigArgumentProvider; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.List; 9 | 10 | public final class PhpVersionArgumentProvider implements DdevConfigArgumentProvider { 11 | @Override 12 | public @NotNull List<@NotNull String> getAdditionalArguments(@NotNull Project project) { 13 | return List.of("--php-version", PhpLanguageLevel.current(project).getPresentableName()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/php/composer/DdevComposerExecutionProvider.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php.composer; 2 | 3 | import com.intellij.openapi.Disposable; 4 | import com.intellij.openapi.project.Project; 5 | import com.jetbrains.php.composer.execution.ComposerExecution; 6 | import com.jetbrains.php.composer.execution.ComposerExecutionProvider; 7 | import de.php_perfect.intellij.ddev.DdevIntegrationBundle; 8 | import org.jdom.Element; 9 | import org.jetbrains.annotations.Nls; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | /** 14 | * Provides DDEV-based Composer execution for projects using DDEV. 15 | */ 16 | public class DdevComposerExecutionProvider implements ComposerExecutionProvider { 17 | 18 | @Nls 19 | @NotNull 20 | @Override 21 | public String getPresentableName() { 22 | return DdevIntegrationBundle.message("composer.execution.provider.ddev.name"); 23 | } 24 | 25 | @Override 26 | public boolean isMyExecution(@NotNull ComposerExecution execution) { 27 | return execution instanceof DdevComposerExecution; 28 | } 29 | 30 | @NotNull 31 | @Override 32 | public Form createForm(@NotNull Project project, @NotNull Disposable disposable) { 33 | return new DdevComposerForm(project, disposable); 34 | } 35 | 36 | @Nullable 37 | @Override 38 | public DdevComposerExecution loadExecution(@NotNull Element element) { 39 | return DdevComposerExecution.load(element); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/php/composer/DdevComposerForm.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php.composer; 2 | 3 | import com.intellij.openapi.Disposable; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.ui.ValidationInfo; 6 | import com.intellij.ui.components.JBLabel; 7 | import com.intellij.util.ui.FormBuilder; 8 | import com.jetbrains.php.composer.execution.ComposerExecution; 9 | import com.jetbrains.php.composer.execution.ComposerExecutionProvider; 10 | import de.php_perfect.intellij.ddev.DdevIntegrationBundle; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import javax.swing.*; 15 | import java.awt.*; 16 | 17 | /** 18 | * Configuration form for DDEV Composer execution. 19 | */ 20 | public class DdevComposerForm implements ComposerExecutionProvider.Form { 21 | private final JPanel myMainPanel; 22 | private DdevComposerExecution myExecution; 23 | 24 | @SuppressWarnings({"unused", "UnusedParameters"}) // Parameters required by ComposerExecutionProvider interface 25 | public DdevComposerForm(@NotNull Project project, @NotNull Disposable disposable) { 26 | myExecution = new DdevComposerExecution(); 27 | myMainPanel = createUIComponents(); 28 | } 29 | 30 | private JPanel createUIComponents() { 31 | JBLabel descriptionLabel = new JBLabel(DdevIntegrationBundle.message("composer.form.ddev.description")); 32 | descriptionLabel.setFont(descriptionLabel.getFont().deriveFont(Font.ITALIC)); 33 | 34 | return FormBuilder.createFormBuilder() 35 | .addComponent(descriptionLabel) 36 | .getPanel(); 37 | } 38 | 39 | @NotNull 40 | @Override 41 | public JComponent getComponent() { 42 | return myMainPanel; 43 | } 44 | 45 | @Nullable 46 | @Override 47 | public ValidationInfo validate() { 48 | // No validation needed for DDEV composer 49 | return null; 50 | } 51 | 52 | @Override 53 | public boolean reset(@NotNull ComposerExecution execution) { 54 | if (execution instanceof DdevComposerExecution ddevExecution) { 55 | myExecution = ddevExecution; 56 | return true; 57 | } 58 | return false; 59 | } 60 | 61 | @Override 62 | public void apply() { 63 | // No additional configuration needed for DDEV 64 | } 65 | 66 | 67 | 68 | @NotNull 69 | @Override 70 | public ComposerExecution getExecution() { 71 | return myExecution; 72 | } 73 | 74 | @Override 75 | public boolean isModified(@NotNull ComposerExecution execution) { 76 | // Check if the current execution is different from the form's execution 77 | return !(execution instanceof DdevComposerExecution) || !myExecution.equals(execution); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/php/composer/DdevComposerProcessHandler.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php.composer; 2 | 3 | import com.intellij.execution.process.OSProcessHandler; 4 | import com.intellij.execution.process.ProcessTerminatedListener; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.nio.charset.Charset; 8 | 9 | /** 10 | * Process handler for DDEV Composer commands. 11 | */ 12 | public class DdevComposerProcessHandler extends OSProcessHandler { 13 | 14 | public DdevComposerProcessHandler(@NotNull Process process, @NotNull String commandLine) { 15 | super(process, commandLine, Charset.defaultCharset()); 16 | ProcessTerminatedListener.attach(this); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/php/server/ConfigureServerListener.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php.server; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.DescriptionChangedListener; 5 | import de.php_perfect.intellij.ddev.cmd.Description; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.net.URI; 10 | import java.net.URISyntaxException; 11 | 12 | public final class ConfigureServerListener implements DescriptionChangedListener { 13 | private final @NotNull Project project; 14 | 15 | public ConfigureServerListener(@NotNull Project project) { 16 | this.project = project; 17 | } 18 | 19 | @Override 20 | public void onDescriptionChanged(@Nullable Description description) { 21 | String localPath = this.project.getBasePath(); 22 | 23 | if (description == null || localPath == null) { 24 | return; 25 | } 26 | 27 | if (description.getPrimaryUrl() == null) { 28 | return; 29 | } 30 | 31 | 32 | URI uri; 33 | try { 34 | uri = new URI(description.getPrimaryUrl()); 35 | } catch (URISyntaxException ignored) { 36 | return; 37 | } 38 | 39 | ServerConfig serverConfig = new ServerConfig(localPath, "/var/www/html", uri); 40 | ServerConfigManager.getInstance(this.project).configure(serverConfig); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/php/server/ServerConfig.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php.server; 2 | 3 | import de.php_perfect.intellij.ddev.index.IndexableConfiguration; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.net.URI; 7 | import java.util.Objects; 8 | 9 | public record ServerConfig(@NotNull String localPath, @NotNull String remotePathPath, 10 | @NotNull URI uri) implements IndexableConfiguration { 11 | @Override 12 | public boolean equals(Object o) { 13 | if (this == o) return true; 14 | if (o == null || getClass() != o.getClass()) return false; 15 | ServerConfig that = (ServerConfig) o; 16 | return Objects.equals(localPath, that.localPath) && Objects.equals(remotePathPath, that.remotePathPath) && Objects.equals(uri, that.uri); 17 | } 18 | 19 | @Override 20 | public int hashCode() { 21 | return Objects.hash(localPath, remotePathPath, uri); 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "ServerConfig{" + 27 | "localPath='" + localPath + '\'' + 28 | ", remotePathPath='" + remotePathPath + '\'' + 29 | ", uri=" + uri + 30 | '}'; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/php/server/ServerConfigManager.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php.server; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public interface ServerConfigManager { 7 | void configure(@NotNull ServerConfig serverConfig); 8 | 9 | static ServerConfigManager getInstance(@NotNull Project project) { 10 | return project.getService(ServerConfigManager.class); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/php/server/ServerConfigManagerImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php.server; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.util.PathMappingSettings; 6 | import com.jetbrains.php.config.servers.PhpServer; 7 | import com.jetbrains.php.config.servers.PhpServersWorkspaceStateComponent; 8 | import de.php_perfect.intellij.ddev.index.IndexEntry; 9 | import de.php_perfect.intellij.ddev.index.ManagedConfigurationIndex; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.List; 13 | import java.util.Objects; 14 | 15 | public final class ServerConfigManagerImpl implements ServerConfigManager { 16 | private static final @NotNull String LEGACY_SERVER_NAME = "DDEV"; 17 | private static final @NotNull Logger LOG = Logger.getInstance(ServerConfigManagerImpl.class); 18 | 19 | private final @NotNull Project project; 20 | 21 | public ServerConfigManagerImpl(final @NotNull Project project) { 22 | this.project = project; 23 | } 24 | 25 | public void configure(final @NotNull ServerConfig serverConfig) { 26 | final int hash = serverConfig.hashCode(); 27 | final String fqdn = serverConfig.uri().getHost(); 28 | final ManagedConfigurationIndex managedConfigurationIndex = ManagedConfigurationIndex.getInstance(this.project); 29 | final IndexEntry indexEntry = managedConfigurationIndex.get(ServerConfig.class); 30 | final List servers = PhpServersWorkspaceStateComponent.getInstance(project).getServers(); 31 | PhpServer phpServer = null; 32 | 33 | if (indexEntry != null && (phpServer = servers.stream() 34 | .filter(currentPhpServer -> currentPhpServer.getId().equals(indexEntry.id())) 35 | .findFirst() 36 | .orElse(null)) != null && indexEntry.hashEquals(hash)) { 37 | LOG.debug(String.format("server configuration %s is up to date", fqdn)); 38 | return; 39 | } 40 | 41 | LOG.debug(String.format("Updating server configuration %s", fqdn)); 42 | 43 | if (phpServer == null) { 44 | phpServer = servers.stream() 45 | .filter(currentPhpServer -> Objects.equals(currentPhpServer.getName(), LEGACY_SERVER_NAME)) 46 | .findFirst() 47 | .orElse(null); 48 | } 49 | 50 | if (phpServer == null) { 51 | phpServer = new PhpServer(); 52 | servers.add(phpServer); 53 | } 54 | 55 | phpServer.setName(fqdn); 56 | phpServer.setHost(fqdn); 57 | phpServer.setPort(80); 58 | phpServer.setUsePathMappings(true); 59 | 60 | final List mappings = phpServer.getMappings(); 61 | final PathMappingSettings.PathMapping mapping = new PathMappingSettings.PathMapping(serverConfig.localPath(), serverConfig.remotePathPath()); 62 | mappings.clear(); 63 | mappings.add(mapping); 64 | 65 | managedConfigurationIndex.set(phpServer.getId(), ServerConfig.class, hash); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/service_actions/ServiceActionChangedListener.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.service_actions; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.DescriptionChangedListener; 5 | import de.php_perfect.intellij.ddev.cmd.Description; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | public class ServiceActionChangedListener implements DescriptionChangedListener { 10 | private final @NotNull Project project; 11 | 12 | public ServiceActionChangedListener(@NotNull Project project) { 13 | this.project = project; 14 | } 15 | 16 | @Override 17 | public void onDescriptionChanged(@Nullable Description description) { 18 | ServiceActionManager.getInstance(this.project).updateActionsByDescription(description); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/service_actions/ServiceActionManager.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.service_actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.project.Project; 5 | import de.php_perfect.intellij.ddev.cmd.Description; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | public interface ServiceActionManager { 10 | 11 | AnAction @NotNull [] getServiceActions(); 12 | 13 | void updateActionsByDescription(@Nullable Description description); 14 | 15 | static ServiceActionManager getInstance(@NotNull Project project) { 16 | return project.getService(ServiceActionManager.class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/settings/DdevSettingsState.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.settings; 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent; 4 | import com.intellij.openapi.components.Service; 5 | import com.intellij.openapi.components.State; 6 | import com.intellij.openapi.components.Storage; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.util.xmlb.XmlSerializerUtil; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | @State(name = "de.php_perfect.intellij.ddev.settings.DdevSettingsState", storages = @Storage("DdevIntegration.xml")) 12 | @Service(Service.Level.PROJECT) 13 | public final class DdevSettingsState implements PersistentStateComponent { 14 | public @NotNull String ddevBinary; 15 | public boolean checkForUpdates; 16 | public boolean watchDdev; 17 | public boolean autoConfigureDataSource; 18 | public boolean autoConfigurePhpInterpreter; 19 | public boolean autoConfigureNodeJsInterpreter; 20 | 21 | public DdevSettingsState() { 22 | // Set default values for new installations 23 | this.ddevBinary = ""; 24 | this.checkForUpdates = true; 25 | this.watchDdev = true; 26 | this.autoConfigureDataSource = true; 27 | this.autoConfigurePhpInterpreter = true; 28 | this.autoConfigureNodeJsInterpreter = true; 29 | } 30 | 31 | public static @NotNull DdevSettingsState getInstance(Project project) { 32 | return project.getService(DdevSettingsState.class); 33 | } 34 | 35 | @Override 36 | public @NotNull DdevSettingsState getState() { 37 | return this; 38 | } 39 | 40 | @Override 41 | public void loadState(@NotNull DdevSettingsState state) { 42 | XmlSerializerUtil.copyBean(state, this); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/state/DdevConfigLoader.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.state; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | public interface DdevConfigLoader { 9 | boolean exists(); 10 | 11 | @Nullable VirtualFile load(); 12 | 13 | static DdevConfigLoader getInstance(@NotNull Project project) { 14 | return project.getService(DdevConfigLoader.class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/state/DdevConfigLoaderImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.state; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | import com.intellij.openapi.vfs.VirtualFileManager; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.nio.file.Path; 10 | import java.nio.file.Paths; 11 | 12 | public final class DdevConfigLoaderImpl implements DdevConfigLoader { 13 | private static final @NotNull String DDEV_CONFIG_PATH = ".ddev/config.yaml"; 14 | private final @NotNull Project project; 15 | 16 | public DdevConfigLoaderImpl(@NotNull Project project) { 17 | this.project = project; 18 | } 19 | 20 | @Override 21 | public @Nullable VirtualFile load() { 22 | final String basePath = this.project.getBasePath(); 23 | 24 | if (basePath == null) { 25 | return null; 26 | } 27 | 28 | final Path path = Paths.get(basePath, DDEV_CONFIG_PATH); 29 | 30 | VirtualFile config = VirtualFileManager.getInstance().refreshAndFindFileByNioPath(path); 31 | 32 | if (config == null) { 33 | return null; 34 | } 35 | 36 | config.refresh(false, false); 37 | 38 | return config; 39 | } 40 | 41 | @Override 42 | public boolean exists() { 43 | final VirtualFile ddevConfig = this.load(); 44 | 45 | return ddevConfig != null && ddevConfig.exists(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/state/DdevStateManager.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.state; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.TestOnly; 6 | 7 | public interface DdevStateManager { 8 | @NotNull State getState(); 9 | 10 | void initialize(); 11 | 12 | void reinitialize(); 13 | 14 | void updateConfiguration(); 15 | 16 | void updateDescription(); 17 | 18 | @TestOnly 19 | void resetState(); 20 | 21 | static DdevStateManager getInstance(@NotNull Project project) { 22 | return project.getService(DdevStateManager.class); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/state/StartWatcherListener.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.state; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.StateInitializedListener; 5 | import de.php_perfect.intellij.ddev.settings.DdevSettingsState; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public final class StartWatcherListener implements StateInitializedListener { 9 | private final @NotNull Project project; 10 | 11 | public StartWatcherListener(@NotNull Project project) { 12 | this.project = project; 13 | } 14 | 15 | @Override 16 | public void onStateInitialized(@NotNull State state) { 17 | if (!DdevSettingsState.getInstance(this.project).watchDdev) { 18 | return; 19 | } 20 | 21 | StateWatcher.getInstance(this.project).startWatching(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/state/State.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.state; 2 | 3 | import de.php_perfect.intellij.ddev.cmd.Description; 4 | import de.php_perfect.intellij.ddev.version.Version; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | public interface State { 8 | boolean isBinaryConfigured(); 9 | 10 | boolean isAvailable(); 11 | 12 | boolean isConfigured(); 13 | 14 | @Nullable Version getDdevVersion(); 15 | 16 | @Nullable Description getDescription(); 17 | 18 | @Nullable String getDdevBinary(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/state/StateWatcher.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.state; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public interface StateWatcher { 7 | void startWatching(); 8 | 9 | void stopWatching(); 10 | 11 | boolean isWatching(); 12 | 13 | static StateWatcher getInstance(@NotNull Project project) { 14 | return project.getService(StateWatcher.class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/state/StateWatcherImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.state; 2 | 3 | import com.intellij.openapi.Disposable; 4 | import com.intellij.openapi.application.ApplicationManager; 5 | import com.intellij.openapi.diagnostic.Logger; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.util.concurrency.AppExecutorUtil; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.util.concurrent.ScheduledFuture; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | public final class StateWatcherImpl implements StateWatcher, Disposable { 15 | private static final @NotNull Logger LOG = Logger.getInstance(StateWatcherImpl.class); 16 | 17 | private @Nullable ScheduledFuture scheduledFuture = null; 18 | 19 | private final @NotNull Project project; 20 | 21 | public StateWatcherImpl(@NotNull Project project) { 22 | this.project = project; 23 | } 24 | 25 | @Override 26 | public void startWatching() { 27 | if (this.isWatching()) { 28 | this.stopWatching(); 29 | } 30 | 31 | this.scheduledFuture = AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay(() -> { 32 | LOG.debug("DDEV state watcher triggering update"); 33 | ApplicationManager.getApplication().executeOnPooledThread(() -> { 34 | DdevStateManager ddevStateManager = DdevStateManager.getInstance(this.project); 35 | ddevStateManager.updateConfiguration(); 36 | ddevStateManager.updateDescription(); 37 | }); 38 | }, 10L, 10L, TimeUnit.SECONDS); 39 | LOG.info("DDEV state watcher started"); 40 | } 41 | 42 | @Override 43 | public void stopWatching() { 44 | if (this.scheduledFuture != null) { 45 | this.scheduledFuture.cancel(true); 46 | } 47 | LOG.info("DDEV state watcher stopped"); 48 | } 49 | 50 | @Override 51 | public boolean isWatching() { 52 | return this.scheduledFuture != null && !this.scheduledFuture.isCancelled(); 53 | } 54 | 55 | @Override 56 | public void dispose() { 57 | this.stopWatching(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/state/UnknownStateListener.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.state; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.openapi.project.Project; 5 | import de.php_perfect.intellij.ddev.StateChangedListener; 6 | import de.php_perfect.intellij.ddev.cmd.Description; 7 | import de.php_perfect.intellij.ddev.notification.DdevNotifier; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.concurrent.atomic.AtomicLong; 11 | import java.util.concurrent.atomic.AtomicBoolean; 12 | 13 | public final class UnknownStateListener implements StateChangedListener { 14 | private final Project project; 15 | private final AtomicLong lastNullDescriptionTime = new AtomicLong(0); 16 | private final AtomicBoolean notificationScheduled = new AtomicBoolean(false); 17 | private static final long NOTIFICATION_DELAY_MS = 30_000; 18 | 19 | public UnknownStateListener(Project project) { 20 | this.project = project; 21 | } 22 | 23 | @Override 24 | public void onDdevChanged(@NotNull State state) { 25 | if (!isDdevReady(state)) { 26 | resetTimer(); 27 | return; 28 | } 29 | 30 | Description description = state.getDescription(); 31 | 32 | if (isDescriptionMissing(description)) { 33 | handleMissingDescription(); 34 | } else { 35 | resetTimer(); 36 | } 37 | } 38 | 39 | private boolean isDdevReady(@NotNull State state) { 40 | return state.isAvailable() && state.isConfigured(); 41 | } 42 | 43 | private boolean isDescriptionMissing(Description description) { 44 | return description == null || description.getStatus() == null; 45 | } 46 | 47 | private void handleMissingDescription() { 48 | long currentTime = System.currentTimeMillis(); 49 | 50 | if (lastNullDescriptionTime.compareAndSet(0, currentTime)) { 51 | scheduleDelayedNotificationCheck(); 52 | } 53 | } 54 | 55 | private void scheduleDelayedNotificationCheck() { 56 | if (notificationScheduled.compareAndSet(false, true)) { 57 | ApplicationManager.getApplication().executeOnPooledThread(() -> { 58 | try { 59 | Thread.sleep(NOTIFICATION_DELAY_MS); 60 | checkAndNotifyIfStillUnknown(); 61 | } catch (InterruptedException e) { 62 | Thread.currentThread().interrupt(); 63 | } finally { 64 | notificationScheduled.set(false); 65 | } 66 | }); 67 | } 68 | } 69 | 70 | private void checkAndNotifyIfStillUnknown() { 71 | State currentState = DdevStateManager.getInstance(this.project).getState(); 72 | Description currentDescription = currentState.getDescription(); 73 | 74 | if (isDdevReady(currentState) && isDescriptionMissing(currentDescription)) { 75 | DdevNotifier.getInstance(this.project).notifyUnknownStateEntered(); 76 | } 77 | } 78 | 79 | private void resetTimer() { 80 | lastNullDescriptionTime.set(0); 81 | notificationScheduled.set(false); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/status_bar/DdevStatusBarWidgetFactoryImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.status_bar; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.wm.StatusBar; 5 | import com.intellij.openapi.wm.StatusBarWidget; 6 | import com.intellij.openapi.wm.impl.status.widget.StatusBarEditorBasedWidgetFactory; 7 | import de.php_perfect.intellij.ddev.DdevIntegrationBundle; 8 | import org.jetbrains.annotations.Nls; 9 | import org.jetbrains.annotations.NonNls; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public final class DdevStatusBarWidgetFactoryImpl extends StatusBarEditorBasedWidgetFactory { 13 | @Override 14 | public @NonNls @NotNull String getId() { 15 | return DdevStatusBarWidgetImpl.WIDGET_ID; 16 | } 17 | 18 | @Override 19 | public @Nls @NotNull String getDisplayName() { 20 | return DdevIntegrationBundle.message("statusBar.displayName"); 21 | } 22 | 23 | @Override 24 | public boolean canBeEnabledOn(@NotNull StatusBar statusBar) { 25 | Project project = statusBar.getProject(); 26 | return project != null; 27 | } 28 | 29 | @Override 30 | public @NotNull StatusBarWidget createWidget(@NotNull Project project) { 31 | return new DdevStatusBarWidgetImpl(project); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/terminal/DdevPredefinedTerminalActionProvider.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.terminal; 2 | 3 | import com.intellij.openapi.actionSystem.ActionManager; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.project.Project; 6 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 7 | import de.php_perfect.intellij.ddev.state.State; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.plugins.terminal.ui.OpenPredefinedTerminalActionProvider; 10 | 11 | import java.util.List; 12 | 13 | public final class DdevPredefinedTerminalActionProvider implements OpenPredefinedTerminalActionProvider { 14 | @Override 15 | public @NotNull List listOpenPredefinedTerminalActions(@NotNull Project project) { 16 | State state = DdevStateManager.getInstance(project).getState(); 17 | 18 | if (!state.isAvailable() || !state.isConfigured()) { 19 | return List.of(); 20 | } 21 | 22 | return List.of(ActionManager.getInstance().getAction("DdevIntegration.Terminal")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/terminal/DdevTerminalRunner.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.terminal; 2 | 3 | import com.intellij.execution.configurations.PtyCommandLine; 4 | import com.intellij.openapi.diagnostic.Logger; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.util.NlsContexts; 7 | import com.intellij.terminal.pty.PtyProcessTtyConnector; 8 | import com.intellij.util.concurrency.AppExecutorUtil; 9 | import com.jediterm.terminal.TtyConnector; 10 | import com.pty4j.PtyProcess; 11 | import com.pty4j.unix.UnixPtyProcess; 12 | import de.php_perfect.intellij.ddev.cmd.wsl.WslAware; 13 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 14 | import de.php_perfect.intellij.ddev.state.State; 15 | import org.jetbrains.annotations.NotNull; 16 | import org.jetbrains.plugins.terminal.AbstractTerminalRunner; 17 | import org.jetbrains.plugins.terminal.ShellStartupOptions; 18 | 19 | import java.nio.charset.StandardCharsets; 20 | import java.util.List; 21 | import java.util.Objects; 22 | import java.util.concurrent.ExecutionException; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | public final class DdevTerminalRunner extends AbstractTerminalRunner { 26 | private static final Logger LOG = Logger.getInstance(DdevTerminalRunner.class); 27 | 28 | public DdevTerminalRunner(@NotNull Project project) { 29 | super(project); 30 | } 31 | 32 | @Override 33 | public @NotNull PtyProcess createProcess(@NotNull ShellStartupOptions startupOptions) throws ExecutionException { 34 | State ddevState = DdevStateManager.getInstance(this.myProject).getState(); 35 | 36 | if (!ddevState.isAvailable()) { 37 | throw new ExecutionException("DDEV not installed", null); 38 | } 39 | 40 | final PtyCommandLine commandLine = new PtyCommandLine(List.of(Objects.requireNonNull(ddevState.getDdevBinary()), "ssh")) 41 | .withConsoleMode(false); 42 | 43 | commandLine.setWorkDirectory(getProject().getBasePath()); 44 | 45 | final PtyCommandLine patchedCommandLine = WslAware.patchCommandLine(commandLine); 46 | 47 | try { 48 | return (PtyProcess) patchedCommandLine.createProcess(); 49 | } catch (com.intellij.execution.ExecutionException e) { 50 | throw new ExecutionException("Opening DDEV Terminal failed", e); 51 | } 52 | } 53 | 54 | @Override 55 | public @NotNull TtyConnector createTtyConnector(@NotNull PtyProcess process) { 56 | return new PtyProcessTtyConnector(process, StandardCharsets.UTF_8) { 57 | @Override 58 | public void close() { 59 | if (process instanceof UnixPtyProcess unixPtyProcess) { 60 | unixPtyProcess.hangup(); 61 | AppExecutorUtil.getAppScheduledExecutorService().schedule(() -> { 62 | if (process.isAlive()) { 63 | LOG.info("Terminal hasn't been terminated by SIGHUP, performing default termination"); 64 | process.destroy(); 65 | } 66 | }, 1000, TimeUnit.MILLISECONDS); 67 | } else { 68 | process.destroy(); 69 | } 70 | } 71 | }; 72 | } 73 | 74 | @Override 75 | public @NlsContexts.TabTitle String getDefaultTabTitle() { 76 | return "DDEV Web Container"; 77 | } 78 | 79 | @Override 80 | public boolean isTerminalSessionPersistent() { 81 | return false; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/terminal/TutorialListener.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.terminal; 2 | 3 | import com.intellij.openapi.actionSystem.impl.ActionButton; 4 | import com.intellij.openapi.wm.ToolWindow; 5 | import com.intellij.openapi.wm.ex.ToolWindowManagerListener; 6 | import com.intellij.openapi.wm.impl.InternalDecorator; 7 | import com.intellij.ui.ComponentUtil; 8 | import de.php_perfect.intellij.ddev.tutorial.GotItTutorial; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.plugins.terminal.TerminalToolWindowFactory; 11 | import org.jetbrains.plugins.terminal.action.TerminalNewPredefinedSessionAction; 12 | 13 | public final class TutorialListener implements ToolWindowManagerListener { 14 | 15 | @Override 16 | public void toolWindowShown(final @NotNull ToolWindow toolWindow) { 17 | if (!TerminalToolWindowFactory.TOOL_WINDOW_ID.equals(toolWindow.getId())) { 18 | return; 19 | } 20 | 21 | final InternalDecorator decorator = ComponentUtil.getParentOfType(InternalDecorator.class, toolWindow.getComponent()); 22 | 23 | ComponentUtil.findComponentsOfType(decorator, ActionButton.class).stream() 24 | .filter(button -> button.getAction() instanceof TerminalNewPredefinedSessionAction) 25 | .findFirst().ifPresent(button -> GotItTutorial.getInstance().showTerminalTutorial(button, toolWindow.getDisposable())); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/tutorial/GotItTutorial.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.tutorial; 2 | 3 | import com.intellij.openapi.Disposable; 4 | import com.intellij.openapi.application.ApplicationManager; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import javax.swing.*; 8 | 9 | public interface GotItTutorial { 10 | void showStatusBarTutorial(@NotNull JComponent component, @NotNull Disposable disposable); 11 | 12 | void showTerminalTutorial(@NotNull JComponent component, @NotNull Disposable disposable); 13 | 14 | static GotItTutorial getInstance() { 15 | return ApplicationManager.getApplication().getService(GotItTutorial.class); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/tutorial/GotItTutorialImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.tutorial; 2 | 3 | import com.intellij.openapi.Disposable; 4 | import com.intellij.openapi.diagnostic.Logger; 5 | import com.intellij.ui.GotItTooltip; 6 | import de.php_perfect.intellij.ddev.DdevIntegrationBundle; 7 | import de.php_perfect.intellij.ddev.icons.DdevIntegrationIcons; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import javax.swing.*; 11 | import java.net.MalformedURLException; 12 | import java.net.URI; 13 | 14 | public final class GotItTutorialImpl implements GotItTutorial { 15 | private static final @NotNull Logger LOG = Logger.getInstance(GotItTutorialImpl.class); 16 | 17 | private static final @NotNull String ID_PREFIX = "ddev.features."; 18 | 19 | @Override 20 | public void showStatusBarTutorial(@NotNull JComponent component, @NotNull Disposable disposable) { 21 | try { 22 | new GotItTooltip(ID_PREFIX + "status", DdevIntegrationBundle.message("tutorial.status.text"), disposable) 23 | .withHeader(DdevIntegrationBundle.message("tutorial.status.title")) 24 | .withIcon(DdevIntegrationIcons.DdevLogoColor) 25 | .withBrowserLink( 26 | DdevIntegrationBundle.message("tutorial.status.link"), 27 | URI.create("https://github.com/ddev/ddev-intellij-plugin/wiki/Features#quick-access-to-ddev-services").toURL() 28 | ) 29 | .show(component, GotItTooltip.TOP_MIDDLE); 30 | } catch (MalformedURLException e) { 31 | LOG.error(e); 32 | } 33 | } 34 | 35 | @Override 36 | public void showTerminalTutorial(@NotNull JComponent component, @NotNull Disposable disposable) { 37 | try { 38 | new GotItTooltip(ID_PREFIX + "terminal", DdevIntegrationBundle.message("tutorial.terminal.text"), disposable) 39 | .withHeader(DdevIntegrationBundle.message("tutorial.terminal.title")) 40 | .withIcon(DdevIntegrationIcons.DdevLogoColor) 41 | .withBrowserLink( 42 | DdevIntegrationBundle.message("tutorial.terminal.link"), 43 | URI.create("https://github.com/ddev/ddev-intellij-plugin/wiki/Features#integrated-ddev-terminal").toURL() 44 | ) 45 | .show(component, GotItTooltip.BOTTOM_MIDDLE); 46 | } catch (MalformedURLException e) { 47 | LOG.error(e); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/util/FeatureRequiredPlugins.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.util; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Utility class to hold the required plugins for each feature. 9 | */ 10 | public final class FeatureRequiredPlugins { 11 | private FeatureRequiredPlugins() { 12 | // Utility class, no instances 13 | } 14 | 15 | /** 16 | * Required plugins for PHP interpreter auto-registration. 17 | */ 18 | public static final @NotNull List PHP_INTERPRETER = List.of( 19 | "com.jetbrains.php", 20 | "org.jetbrains.plugins.phpstorm-remote-interpreter", 21 | "org.jetbrains.plugins.phpstorm-docker" 22 | ); 23 | 24 | /** 25 | * Required plugins for Node.js interpreter auto-registration. 26 | */ 27 | public static final @NotNull List NODE_INTERPRETER = List.of( 28 | "NodeJS", 29 | "org.jetbrains.plugins.node-remote-interpreter" 30 | ); 31 | 32 | /** 33 | * Required plugins for database auto-registration. 34 | */ 35 | public static final @NotNull List DATABASE = List.of( 36 | "com.intellij.database" 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/util/PluginChecker.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.util; 2 | 3 | import com.intellij.ide.plugins.PluginManager; 4 | import com.intellij.openapi.extensions.PluginId; 5 | import com.intellij.openapi.project.Project; 6 | import de.php_perfect.intellij.ddev.notification.DdevNotifier; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * Utility class to check for required plugins. 14 | */ 15 | public final class PluginChecker { 16 | private PluginChecker() { 17 | // Utility class, no instances 18 | } 19 | 20 | /** 21 | * Checks if any required plugin is missing. 22 | * If any plugins are missing, it will notify the user and return true. 23 | * 24 | * @param project The current project 25 | * @param requiredPlugins List of plugin IDs to check 26 | * @param featureName The name of the feature that requires these plugins 27 | * @return true if any plugin is missing, false if all are available 28 | */ 29 | public static boolean isMissingRequiredPlugins(@NotNull Project project, @NotNull List requiredPlugins, @NotNull String featureName) { 30 | List missingPluginNames = getMissingPlugins(requiredPlugins); 31 | 32 | if (!missingPluginNames.isEmpty()) { 33 | String missingPluginsDisplay = String.join(", ", missingPluginNames); 34 | if (missingPluginNames.size() == 1) { 35 | DdevNotifier.getInstance(project).notifyMissingPlugin(missingPluginsDisplay, featureName); 36 | } else { 37 | DdevNotifier.getInstance(project).notifyMissingPlugins(missingPluginsDisplay, featureName); 38 | } 39 | return true; 40 | } 41 | 42 | return false; 43 | } 44 | 45 | /** 46 | * Checks if any required plugin is missing without showing notifications. 47 | * This is useful for UI components that want to check dependencies without showing notifications. 48 | * 49 | * @param requiredPlugins List of plugin IDs to check 50 | * @return List of missing plugin display names, empty if all are available 51 | */ 52 | public static @NotNull List getMissingPlugins(@NotNull List requiredPlugins) { 53 | final var pluginManager = PluginManager.getInstance(); 54 | final List missingPluginNames = new ArrayList<>(); 55 | 56 | for (final String id : requiredPlugins) { 57 | final PluginId pluginId = PluginId.findId(id); 58 | 59 | if (pluginId == null || pluginManager.findEnabledPlugin(pluginId) == null) { 60 | String displayName = PluginDisplayNameMapper.getDisplayName(id); 61 | missingPluginNames.add(displayName); 62 | } 63 | } 64 | 65 | return missingPluginNames; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/util/PluginDisplayNameMapper.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.util; 2 | 3 | import de.php_perfect.intellij.ddev.DdevIntegrationBundle; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * Utility class to map plugin IDs to display names. 11 | */ 12 | public final class PluginDisplayNameMapper { 13 | private static final Map PLUGIN_DISPLAY_NAMES = new HashMap<>(); 14 | 15 | static { 16 | // Initialize with known plugin IDs and their message bundle keys 17 | PLUGIN_DISPLAY_NAMES.put("com.jetbrains.php", "plugin.name.php"); 18 | PLUGIN_DISPLAY_NAMES.put("org.jetbrains.plugins.phpstorm-remote-interpreter", "plugin.name.phpstorm-remote-interpreter"); 19 | PLUGIN_DISPLAY_NAMES.put("org.jetbrains.plugins.phpstorm-docker", "plugin.name.phpstorm-docker"); 20 | PLUGIN_DISPLAY_NAMES.put("Docker", "plugin.name.docker"); 21 | PLUGIN_DISPLAY_NAMES.put("NodeJS", "plugin.name.nodejs"); 22 | PLUGIN_DISPLAY_NAMES.put("org.jetbrains.plugins.node-remote-interpreter", "plugin.name.node-remote-interpreter"); 23 | PLUGIN_DISPLAY_NAMES.put("com.intellij.database", "plugin.name.database"); 24 | PLUGIN_DISPLAY_NAMES.put("org.jetbrains.plugins.terminal", "plugin.name.terminal"); 25 | } 26 | 27 | private PluginDisplayNameMapper() { 28 | // Utility class, no instances 29 | } 30 | 31 | /** 32 | * Get the display name for a plugin ID. 33 | * First tries to get a localized name from the message bundle. 34 | * If not found, returns the plugin ID. 35 | * 36 | * @param pluginId The plugin ID 37 | * @return The display name 38 | */ 39 | public static @NotNull String getDisplayName(@NotNull String pluginId) { 40 | // First try to get a localized name from our message bundle 41 | if (PLUGIN_DISPLAY_NAMES.containsKey(pluginId)) { 42 | String messageKey = PLUGIN_DISPLAY_NAMES.get(pluginId); 43 | String localizedName = DdevIntegrationBundle.message(messageKey); 44 | 45 | if (!localizedName.equals(messageKey)) { 46 | return localizedName; 47 | } 48 | } 49 | 50 | // Fall back to plugin ID if we couldn't find a label 51 | return pluginId; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/version/CheckVersionListener.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.version; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import de.php_perfect.intellij.ddev.StateInitializedListener; 5 | import de.php_perfect.intellij.ddev.settings.DdevSettingsState; 6 | import de.php_perfect.intellij.ddev.state.State; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public final class CheckVersionListener implements StateInitializedListener { 10 | private final @NotNull Project project; 11 | 12 | public CheckVersionListener(@NotNull Project project) { 13 | this.project = project; 14 | } 15 | 16 | @Override 17 | public void onStateInitialized(@NotNull State state) { 18 | if (DdevSettingsState.getInstance(this.project).checkForUpdates) { 19 | VersionChecker.getInstance(this.project).checkDdevVersion(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/version/GithubClient.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.version; 2 | 3 | import com.google.gson.FieldNamingPolicy; 4 | import com.google.gson.Gson; 5 | import com.google.gson.GsonBuilder; 6 | import com.intellij.openapi.diagnostic.Logger; 7 | import com.intellij.openapi.progress.ProgressIndicator; 8 | import com.intellij.util.io.HttpRequests; 9 | import com.intellij.util.io.RequestBuilder; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import java.io.IOException; 14 | 15 | public final class GithubClient implements ReleaseClient { 16 | private static final @NotNull String RELEASE_URL = "https://github.com/ddev/ddev/releases/latest"; 17 | 18 | private static final Logger LOG = Logger.getInstance(GithubClient.class); 19 | 20 | @Override 21 | public @Nullable LatestRelease loadCurrentVersion(@NotNull ProgressIndicator indicator) { 22 | final RequestBuilder requestBuilder = HttpRequests.request(RELEASE_URL).accept("application/json").redirectLimit(2); 23 | indicator.checkCanceled(); 24 | 25 | try { 26 | LOG.info("Loading latest DDEV release meta data from GitHub"); 27 | return createParser().fromJson(requestBuilder.readString(indicator), LatestRelease.class); 28 | } catch (IOException e) { 29 | LOG.error(e); 30 | return null; 31 | } 32 | } 33 | 34 | private Gson createParser() { 35 | return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/version/LatestRelease.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.version; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | public record LatestRelease(@Nullable String tagName) { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/version/ReleaseClient.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.version; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.openapi.progress.ProgressIndicator; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | public interface ReleaseClient { 9 | @Nullable LatestRelease loadCurrentVersion(@NotNull ProgressIndicator indicator); 10 | 11 | static @NotNull ReleaseClient getInstance() { 12 | return ApplicationManager.getApplication().getService(ReleaseClient.class); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/version/Version.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.version; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.Arrays; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | public final class Version implements Comparable { 11 | 12 | public final int @NotNull [] numbers; 13 | 14 | public final @NotNull String string; 15 | 16 | /** 17 | * The build information for head versions (e.g., "36-ge74e3a95f" in "v1.24.4-36-ge74e3a95f") 18 | * Will be null for regular release versions 19 | */ 20 | @Nullable 21 | private final String buildInfo; 22 | 23 | /** 24 | * Pattern to match head versions like "v1.24.4-36-ge74e3a95f" 25 | * Group 1: The semantic version part (v1.24.4) 26 | * Group 2: The build info part (36-ge74e3a95f) 27 | */ 28 | private static final Pattern HEAD_VERSION_PATTERN = Pattern.compile("^(v?\\d+(?:\\.\\d+)?(?:\\.\\d+)?)-(\\d+-g[a-f0-9]+)$"); 29 | 30 | public Version(@NotNull String version) { 31 | this.string = version; 32 | 33 | // Check if this is a head version 34 | Matcher headVersionMatcher = HEAD_VERSION_PATTERN.matcher(version); 35 | String versionForParsing; 36 | 37 | if (headVersionMatcher.matches()) { 38 | // This is a head version, use the semantic version part for parsing 39 | versionForParsing = headVersionMatcher.group(1); 40 | this.buildInfo = headVersionMatcher.group(2); 41 | } else { 42 | // Regular version handling 43 | versionForParsing = version.split("-")[0]; 44 | this.buildInfo = null; 45 | } 46 | 47 | final String[] split = versionForParsing.replaceAll("^v", "").split("\\."); 48 | numbers = new int[split.length]; 49 | for (int i = 0; i < split.length; i++) { 50 | numbers[i] = Integer.parseInt(split[i]); 51 | } 52 | } 53 | 54 | public boolean isHeadVersion() { 55 | return buildInfo != null; 56 | } 57 | 58 | @Nullable 59 | public String getBuildInfo() { 60 | return buildInfo; 61 | } 62 | 63 | @Override 64 | public int compareTo(@NotNull Version another) { 65 | final int maxLength = Math.max(numbers.length, another.numbers.length); 66 | for (int i = 0; i < maxLength; i++) { 67 | final int left = i < numbers.length ? numbers[i] : 0; 68 | final int right = i < another.numbers.length ? another.numbers[i] : 0; 69 | if (left != right) { 70 | return left < right ? -1 : 1; 71 | } 72 | } 73 | return 0; 74 | } 75 | 76 | @Override 77 | public boolean equals(Object o) { 78 | if (this == o) return true; 79 | if (!(o instanceof Version version)) return false; 80 | return Arrays.equals(numbers, version.numbers); 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | return Arrays.hashCode(numbers); 86 | } 87 | 88 | @Override 89 | public String toString() { 90 | return this.string; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/version/VersionChecker.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.version; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public interface VersionChecker { 7 | void checkDdevVersion(); 8 | 9 | void checkDdevVersion(boolean confirmNewestVersion); 10 | 11 | static @NotNull VersionChecker getInstance(@NotNull Project project) { 12 | return project.getService(VersionChecker.class); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/version/VersionCheckerImpl.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.version; 2 | 3 | import com.intellij.openapi.progress.ProgressIndicator; 4 | import com.intellij.openapi.progress.ProgressManager; 5 | import com.intellij.openapi.progress.Task; 6 | import com.intellij.openapi.project.Project; 7 | import de.php_perfect.intellij.ddev.notification.DdevNotifier; 8 | import de.php_perfect.intellij.ddev.settings.DdevSettingsState; 9 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 10 | import de.php_perfect.intellij.ddev.state.State; 11 | import de.php_perfect.intellij.ddev.version.util.VersionCompare; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | public final class VersionCheckerImpl implements VersionChecker { 16 | private final @NotNull Project project; 17 | 18 | public VersionCheckerImpl(@NotNull Project project) { 19 | this.project = project; 20 | } 21 | 22 | @Override 23 | public void checkDdevVersion() { 24 | this.checkDdevVersion(false); 25 | } 26 | 27 | @Override 28 | public void checkDdevVersion(boolean confirmNewestVersion) { 29 | final DdevSettingsState settings = DdevSettingsState.getInstance(this.project); 30 | 31 | if (!settings.checkForUpdates) { 32 | return; 33 | } 34 | 35 | final State state = DdevStateManager.getInstance(this.project).getState(); 36 | final Version currentVersion = this.getCurrentVersion(state); 37 | 38 | if (currentVersion == null) { 39 | if (state.isConfigured()) { 40 | DdevNotifier.getInstance(project).notifyInstallDdev(); 41 | } 42 | return; 43 | } 44 | 45 | ProgressManager.getInstance().run(new Task.Backgroundable(project, "Checking DDEV version", true) { 46 | @Override 47 | public void run(@NotNull ProgressIndicator progressIndicator) { 48 | final LatestRelease latestRelease = ReleaseClient.getInstance().loadCurrentVersion(progressIndicator); 49 | progressIndicator.checkCanceled(); 50 | 51 | if (latestRelease == null || latestRelease.tagName() == null) { 52 | return; 53 | } 54 | 55 | final Version latestVersion = new Version(latestRelease.tagName()); 56 | 57 | if (VersionCompare.needsUpdate(currentVersion, latestVersion)) { 58 | DdevNotifier.getInstance(project).notifyNewVersionAvailable(currentVersion.toString(), latestVersion.toString()); 59 | } else if (confirmNewestVersion) { 60 | DdevNotifier.getInstance(project).notifyAlreadyLatestVersion(); 61 | } 62 | } 63 | }); 64 | } 65 | 66 | private @Nullable Version getCurrentVersion(State state) { 67 | if (!state.isAvailable()) { 68 | return null; 69 | } 70 | 71 | return state.getDdevVersion(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/de/php_perfect/intellij/ddev/version/util/VersionCompare.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.version.util; 2 | 3 | import de.php_perfect.intellij.ddev.version.Version; 4 | 5 | public final class VersionCompare { 6 | public static boolean needsUpdate(Version currentVersion, Version latestVersion) { 7 | return currentVersion.compareTo(latestVersion) < 0; 8 | } 9 | 10 | private VersionCompare() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/DdevIntegration-withDatabase.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/DdevIntegration-withDocker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/DdevIntegration-withNodeRemoteInterpreter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/DdevIntegration-withPhp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/DdevIntegration-withTerminal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/resources/icons/ddevLogoColor.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/resources/icons/ddevLogoGrey.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/resources/icons/ddevLogoGrey_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/cmd/BinaryLocatorTest.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.execution.process.ProcessOutput; 4 | import com.intellij.openapi.application.ApplicationManager; 5 | import com.intellij.openapi.util.SystemInfo; 6 | import com.intellij.testFramework.fixtures.BasePlatformTestCase; 7 | import org.junit.jupiter.api.AfterEach; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | 12 | final class BinaryLocatorTest extends BasePlatformTestCase { 13 | @Override 14 | @BeforeEach 15 | protected void setUp() throws Exception { 16 | super.setUp(); 17 | } 18 | 19 | @Override 20 | @AfterEach 21 | protected void tearDown() throws Exception { 22 | super.tearDown(); 23 | } 24 | 25 | @Test 26 | void findBinary() { 27 | String expectedWhich = "which"; 28 | if (SystemInfo.isWindows) { 29 | expectedWhich = "where"; 30 | } 31 | 32 | ProcessOutput processOutput = new ProcessOutput("/foo/bar/bin/ddev", "", 0, false, false); 33 | 34 | MockProcessExecutor mockProcessExecutor = (MockProcessExecutor) ApplicationManager.getApplication().getService(ProcessExecutor.class); 35 | mockProcessExecutor.addProcessOutput(expectedWhich + " ddev", processOutput); 36 | 37 | Assertions.assertEquals("/foo/bar/bin/ddev", new BinaryLocatorImpl().findInPath(getProject())); 38 | } 39 | 40 | @Test 41 | void isNotInstalled() { 42 | String expectedWhich = "which"; 43 | if (SystemInfo.isWindows) { 44 | expectedWhich = "where"; 45 | } 46 | 47 | ProcessOutput processOutput = new ProcessOutput("", "", 1, false, false); 48 | 49 | MockProcessExecutor mockProcessExecutor = (MockProcessExecutor) ApplicationManager.getApplication().getService(ProcessExecutor.class); 50 | mockProcessExecutor.addProcessOutput(expectedWhich + " ddev", processOutput); 51 | 52 | Assertions.assertNull(new BinaryLocatorImpl().findInPath(getProject())); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/cmd/DdevShellCommandHandlerTest.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.testFramework.fixtures.BasePlatformTestCase; 4 | import org.junit.jupiter.api.AfterEach; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.params.ParameterizedTest; 9 | import org.junit.jupiter.params.provider.ValueSource; 10 | 11 | final class DdevShellCommandHandlerTest extends BasePlatformTestCase { 12 | @Override 13 | @BeforeEach 14 | protected void setUp() throws Exception { 15 | super.setUp(); 16 | } 17 | 18 | @Override 19 | @AfterEach 20 | protected void tearDown() throws Exception { 21 | super.tearDown(); 22 | } 23 | 24 | @ParameterizedTest 25 | @ValueSource(strings = {"cat abc", "ddev ", "ddev foo"}) 26 | void invalidDdevCommands(String command) { 27 | final DdevShellCommandHandlerImpl ddevShellCommandHandlerImpl = new DdevShellCommandHandlerImpl(); 28 | 29 | Assertions.assertFalse(ddevShellCommandHandlerImpl.matches(this.getProject(), null, true, command)); 30 | } 31 | 32 | @Test 33 | void ddevCommand() { 34 | final DdevShellCommandHandlerImpl ddevShellCommandHandlerImpl = new DdevShellCommandHandlerImpl(); 35 | 36 | Assertions.assertTrue(ddevShellCommandHandlerImpl.matches(this.getProject(), null, true, "ddev start")); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/cmd/DockerTest.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.execution.process.ProcessOutput; 4 | import com.intellij.openapi.application.ApplicationManager; 5 | import com.intellij.testFramework.fixtures.BasePlatformTestCase; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | 11 | final class DockerTest extends BasePlatformTestCase { 12 | @Override 13 | @BeforeEach 14 | protected void setUp() throws Exception { 15 | super.setUp(); 16 | } 17 | 18 | @Override 19 | @AfterEach 20 | protected void tearDown() throws Exception { 21 | super.tearDown(); 22 | } 23 | 24 | @Test 25 | void dockerIsRunning() { 26 | ProcessOutput processOutput = new ProcessOutput(0); 27 | 28 | MockProcessExecutor mockProcessExecutor = (MockProcessExecutor) ApplicationManager.getApplication().getService(ProcessExecutor.class); 29 | mockProcessExecutor.addProcessOutput("docker info", processOutput); 30 | 31 | Assertions.assertTrue(new DockerImpl().isRunning(this.getProject().getBasePath())); 32 | } 33 | 34 | @Test 35 | void dockerIsNotRunning() { 36 | ProcessOutput processOutput = new ProcessOutput(1); 37 | 38 | MockProcessExecutor mockProcessExecutor = (MockProcessExecutor) ApplicationManager.getApplication().getService(ProcessExecutor.class); 39 | mockProcessExecutor.addProcessOutput("docker info", processOutput); 40 | 41 | Assertions.assertFalse(new DockerImpl().isRunning(this.getProject().getBasePath())); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/cmd/MockProcessExecutor.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd; 2 | 3 | import com.intellij.execution.ExecutionException; 4 | import com.intellij.execution.configurations.GeneralCommandLine; 5 | import com.intellij.execution.process.ProcessOutput; 6 | import com.intellij.openapi.util.SystemInfo; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public final class MockProcessExecutor implements ProcessExecutor { 13 | private final Map processList = new HashMap<>(); 14 | 15 | public MockProcessExecutor() { 16 | this.addProcessOutput("docker info", new ProcessOutput(0)); 17 | 18 | if (SystemInfo.isWindows) { 19 | this.addProcessOutput("where ddev", new ProcessOutput(1)); 20 | } else { 21 | this.addProcessOutput("which ddev", new ProcessOutput(1)); 22 | } 23 | } 24 | 25 | public void addProcessOutput(@NotNull String command, @NotNull ProcessOutput processOutput) { 26 | this.processList.put(command, processOutput); 27 | } 28 | 29 | @Override 30 | public @NotNull ProcessOutput executeCommandLine(GeneralCommandLine commandLine, int timeout, boolean loginShell) throws ExecutionException { 31 | String commandLineString = commandLine.getCommandLineString(); 32 | 33 | if (!this.processList.containsKey(commandLineString)) { 34 | throw new ExecutionException(String.format("[TEST] Command '%s' was not expected", commandLineString)); 35 | } 36 | 37 | ProcessOutput processOutput = this.processList.get(commandLineString); 38 | this.processList.remove(commandLineString); 39 | return processOutput; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/cmd/parser/JsonParserTest.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd.parser; 2 | 3 | import de.php_perfect.intellij.ddev.cmd.Description; 4 | import de.php_perfect.intellij.ddev.cmd.Versions; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | 12 | final class JsonParserTest { 13 | @Test 14 | void parseValidJson() throws JsonParserException { 15 | TestObject expected = new TestObject(); 16 | expected.foo = "Bar"; 17 | 18 | String json = "{" + " \"level\": \"info\"," + " \"msg\": \"Abc\"," + " \"raw\": {" + " \"foo\": \"Bar\"" + " }," + " \"time\": \"2022-02-05T14:11:53+01:00\"" + "}"; 19 | 20 | Assertions.assertEquals(expected, new JsonParserImpl().parse(json, TestObject.class)); 21 | } 22 | 23 | @Test 24 | void parseInvalidJson() { 25 | String json = "{]"; 26 | 27 | Assertions.assertThrows(JsonParserException.class, () -> new JsonParserImpl().parse(json, TestObject.class)); 28 | } 29 | 30 | @Test 31 | void parseValidButEmptyJson() { 32 | String json = "{}"; 33 | 34 | Assertions.assertThrows(JsonParserException.class, () -> new JsonParserImpl().parse(json, TestObject.class)); 35 | } 36 | 37 | @Test 38 | void parseValidJsonWithoutRawProperty() { 39 | String json = "{" + " \"level\": \"info\"," + " \"msg\": \"Abc\"," + " \"time\": \"2022-02-05T14:11:53+01:00\"" + "}"; 40 | 41 | Assertions.assertThrows(JsonParserException.class, () -> new JsonParserImpl().parse(json, TestObject.class)); 42 | } 43 | 44 | @Test 45 | void statusSuccessfully() throws JsonParserException, IOException { 46 | String json = Files.readString(Path.of("src/test/resources/ddev_describe.json")); 47 | Description actual = new JsonParserImpl().parse(json, Description.class); 48 | 49 | Assertions.assertEquals("8.1", actual.getPhpVersion()); 50 | } 51 | 52 | @Test 53 | void statusWithDebugSuccessfully() throws JsonParserException, IOException { 54 | String json = Files.readString(Path.of("src/test/resources/ddev_describe_w_debug.json")); 55 | Description actual = new JsonParserImpl().parse(json, Description.class); 56 | 57 | Assertions.assertEquals("8.1", actual.getPhpVersion()); 58 | } 59 | 60 | 61 | @Test 62 | void parseVersionSuccessfully() throws JsonParserException, IOException { 63 | String json = Files.readString(Path.of("src/test/resources/ddev_version.json")); 64 | Versions actual = new JsonParserImpl().parse(json, Versions.class); 65 | 66 | Assertions.assertEquals("v1.19.0", actual.getDdevVersion()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/cmd/parser/TestObject.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.cmd.parser; 2 | 3 | import java.util.Objects; 4 | 5 | final class TestObject { 6 | public String foo; 7 | 8 | @Override 9 | public boolean equals(Object o) { 10 | if (this == o) return true; 11 | if (o == null || getClass() != o.getClass()) return false; 12 | TestObject that = (TestObject) o; 13 | return Objects.equals(foo, that.foo); 14 | } 15 | 16 | @Override 17 | public int hashCode() { 18 | return Objects.hash(foo); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/php/server/ServerConfigManagerImplTest.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.php.server; 2 | 3 | import com.intellij.testFramework.fixtures.BasePlatformTestCase; 4 | import com.jetbrains.php.config.servers.PhpServer; 5 | import com.jetbrains.php.config.servers.PhpServersWorkspaceStateComponent; 6 | import org.junit.Assert; 7 | import org.junit.jupiter.api.AfterEach; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.net.URI; 12 | import java.net.URISyntaxException; 13 | import java.util.List; 14 | import java.util.Objects; 15 | 16 | final class ServerConfigManagerImplTest extends BasePlatformTestCase { 17 | 18 | @Override 19 | @BeforeEach 20 | protected void setUp() throws Exception { 21 | super.setUp(); 22 | } 23 | 24 | @Override 25 | @AfterEach 26 | protected void tearDown() throws Exception { 27 | super.tearDown(); 28 | } 29 | 30 | @Test 31 | void configure() throws URISyntaxException { 32 | final ServerConfig serverConfig = new ServerConfig( 33 | Objects.requireNonNull(this.getProject().getBasePath()), 34 | "/var/www/html", 35 | new URI("https://test.ddev.site") 36 | ); 37 | 38 | final ServerConfigManager serverConfigManager = ServerConfigManager.getInstance(this.getProject()); 39 | serverConfigManager.configure(serverConfig); 40 | // Check server gets replaced 41 | serverConfigManager.configure(serverConfig); 42 | 43 | this.assertServerConfigMatches(serverConfig); 44 | } 45 | 46 | private void assertServerConfigMatches(ServerConfig serverConfig) { 47 | final List servers = PhpServersWorkspaceStateComponent.getInstance(this.getProject()).getServers(); 48 | 49 | Assert.assertEquals(1, servers.size()); 50 | 51 | final PhpServer server = servers.get(0); 52 | Assert.assertEquals("test.ddev.site", server.getName()); 53 | Assert.assertEquals("test.ddev.site", server.getHost()); 54 | 55 | var mappings = server.getMappings(); 56 | 57 | Assert.assertEquals(1, mappings.size()); 58 | 59 | var mapping = mappings.get(0); 60 | 61 | Assert.assertEquals(serverConfig.localPath(), mapping.getLocalRoot()); 62 | Assert.assertEquals(serverConfig.remotePathPath(), mapping.getRemoteRoot()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/state/DdevConfigLoaderTest.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.state; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.util.io.FileUtil; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import com.intellij.testFramework.fixtures.BasePlatformTestCase; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.junit.jupiter.api.AfterEach; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.nio.file.Files; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | final class DdevConfigLoaderTest extends BasePlatformTestCase { 20 | private final @NotNull List files = new ArrayList<>(); 21 | 22 | @Override 23 | @BeforeEach 24 | protected void setUp() throws Exception { 25 | super.setUp(); 26 | } 27 | 28 | @Test 29 | void nonExistentConfig() { 30 | Assertions.assertFalse(new DdevConfigLoaderImpl(this.getProject()).exists()); 31 | } 32 | 33 | @Test 34 | void existentConfig() { 35 | Project project = this.getProject(); 36 | 37 | File ddevConfig = new File(project.getBasePath() + "/.ddev/config.yaml"); 38 | this.files.add(ddevConfig); 39 | ddevConfig.deleteOnExit(); 40 | 41 | try { 42 | FileUtil.writeToFile(ddevConfig, "name: test", true); 43 | } catch (IOException ex) { 44 | ex.printStackTrace(); 45 | } 46 | 47 | Assertions.assertTrue(new DdevConfigLoaderImpl(project).exists()); 48 | } 49 | 50 | @Test 51 | void loadNonExisting() { 52 | Assertions.assertNull(new DdevConfigLoaderImpl(this.getProject()).load()); 53 | } 54 | 55 | @Test 56 | void loadExisting() { 57 | Project project = this.getProject(); 58 | 59 | File ddevConfig = new File(project.getBasePath() + "/.ddev/config.yaml"); 60 | this.files.add(ddevConfig); 61 | ddevConfig.deleteOnExit(); 62 | 63 | try { 64 | FileUtil.writeToFile(ddevConfig, "name: test", true); 65 | } catch (IOException ex) { 66 | ex.printStackTrace(); 67 | } 68 | 69 | Assertions.assertInstanceOf(VirtualFile.class, new DdevConfigLoaderImpl(project).load()); 70 | } 71 | 72 | @Override 73 | @AfterEach 74 | protected void tearDown() throws Exception { 75 | for (File file : this.files) { 76 | Files.deleteIfExists(file.toPath()); 77 | } 78 | 79 | super.tearDown(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/state/MockDdevConfigLoader.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.state; 2 | 3 | import com.intellij.openapi.vfs.VirtualFile; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | public final class MockDdevConfigLoader implements DdevConfigLoader { 7 | private boolean exists = false; 8 | 9 | public void setExists(boolean exists) { 10 | this.exists = exists; 11 | } 12 | 13 | @Override 14 | public boolean exists() { 15 | return this.exists; 16 | } 17 | 18 | @Override 19 | public @Nullable VirtualFile load() { 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/state/StateWatcherTest.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.state; 2 | 3 | import com.intellij.testFramework.fixtures.BasePlatformTestCase; 4 | import org.junit.Assert; 5 | import org.junit.jupiter.api.AfterEach; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | 9 | final class StateWatcherTest extends BasePlatformTestCase { 10 | 11 | private StateWatcherImpl stateWatcher; 12 | 13 | @Override 14 | @BeforeEach 15 | protected void setUp() throws Exception { 16 | super.setUp(); 17 | this.stateWatcher = new StateWatcherImpl(this.getProject()); 18 | } 19 | 20 | @Test 21 | void startWatching() { 22 | stateWatcher.startWatching(); 23 | Assert.assertTrue(stateWatcher.isWatching()); 24 | } 25 | 26 | @Test 27 | void startWatchingTwice() { 28 | stateWatcher.startWatching(); 29 | stateWatcher.startWatching(); 30 | Assert.assertTrue(stateWatcher.isWatching()); 31 | } 32 | 33 | @Test 34 | void startStopWatching() { 35 | stateWatcher.startWatching(); 36 | stateWatcher.stopWatching(); 37 | Assert.assertFalse(stateWatcher.isWatching()); 38 | } 39 | 40 | @Test 41 | void startStopWatchingWithoutStart() { 42 | stateWatcher.stopWatching(); 43 | Assert.assertFalse(stateWatcher.isWatching()); 44 | } 45 | 46 | @Override 47 | @AfterEach 48 | protected void tearDown() throws Exception { 49 | this.stateWatcher.dispose(); 50 | super.tearDown(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/terminal/DdevPredefinedTerminalActionProviderTest.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.terminal; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.testFramework.fixtures.BasePlatformTestCase; 5 | import org.junit.jupiter.api.AfterEach; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | final class DdevPredefinedTerminalActionProviderTest extends BasePlatformTestCase { 11 | @Override 12 | @BeforeEach 13 | protected void setUp() throws Exception { 14 | super.setUp(); 15 | } 16 | 17 | @Override 18 | @AfterEach 19 | protected void tearDown() throws Exception { 20 | super.tearDown(); 21 | } 22 | 23 | @Test 24 | void listOpenPredefinedTerminalActions() { 25 | Project project = getProject(); 26 | DdevPredefinedTerminalActionProvider ddevPredefinedTerminalActionProvider = new DdevPredefinedTerminalActionProvider(); 27 | 28 | Assertions.assertTrue(ddevPredefinedTerminalActionProvider.listOpenPredefinedTerminalActions(project).isEmpty()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/terminal/DdevTerminalRunnerTest.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.terminal; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.testFramework.fixtures.BasePlatformTestCase; 5 | import de.php_perfect.intellij.ddev.state.DdevConfigLoader; 6 | import de.php_perfect.intellij.ddev.state.DdevStateManager; 7 | import de.php_perfect.intellij.ddev.state.MockDdevConfigLoader; 8 | import de.php_perfect.intellij.ddev.state.State; 9 | import org.jetbrains.plugins.terminal.ShellStartupOptions; 10 | import org.junit.jupiter.api.AfterEach; 11 | import org.junit.jupiter.api.Assertions; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.Test; 14 | 15 | import java.lang.reflect.Field; 16 | import java.util.Map; 17 | import java.util.concurrent.ExecutionException; 18 | 19 | final class DdevTerminalRunnerTest extends BasePlatformTestCase { 20 | @Override 21 | @BeforeEach 22 | protected void setUp() throws Exception { 23 | super.setUp(); 24 | } 25 | 26 | @Test 27 | void createProcessNotExistentDdev() throws NoSuchFieldException, IllegalAccessException { 28 | Project project = getProject(); 29 | DdevTerminalRunner ddevTerminalRunner = new DdevTerminalRunner(project); 30 | 31 | State state = DdevStateManager.getInstance(project).getState(); 32 | 33 | Field field = state.getClass().getDeclaredField("ddevBinary"); 34 | field.setAccessible(true); 35 | field.set(state, null); 36 | 37 | final Map envVariables = Map.of(); 38 | final ShellStartupOptions.Builder builder = new ShellStartupOptions.Builder(project.getBasePath(), null, null, null, null, null, envVariables, null); 39 | 40 | Assertions.assertThrowsExactly(ExecutionException.class, () -> ddevTerminalRunner.createProcess(builder.build())); 41 | } 42 | 43 | @Test 44 | void terminalIsNotPersistent() { 45 | Project project = getProject(); 46 | DdevTerminalRunner ddevTerminalRunner = new DdevTerminalRunner(project); 47 | 48 | Assertions.assertFalse(ddevTerminalRunner.isTerminalSessionPersistent()); 49 | } 50 | 51 | @Override 52 | @AfterEach 53 | protected void tearDown() throws Exception { 54 | final MockDdevConfigLoader ddevConfigLoader = (MockDdevConfigLoader) DdevConfigLoader.getInstance(this.getProject()); 55 | ddevConfigLoader.setExists(false); 56 | 57 | DdevStateManager.getInstance(this.getProject()).resetState(); 58 | 59 | super.tearDown(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/version/VersionCheckerImplTest.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.version; 2 | 3 | import com.intellij.testFramework.fixtures.BasePlatformTestCase; 4 | import org.junit.jupiter.api.AfterEach; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | 8 | final class VersionCheckerImplTest extends BasePlatformTestCase { 9 | @Override 10 | @BeforeEach 11 | protected void setUp() throws Exception { 12 | super.setUp(); 13 | } 14 | 15 | @Override 16 | @AfterEach 17 | protected void tearDown() throws Exception { 18 | super.tearDown(); 19 | } 20 | 21 | @Test 22 | void checkDdevVersion() { 23 | VersionCheckerImpl versionChecker = new VersionCheckerImpl(this.getProject()); 24 | versionChecker.checkDdevVersion(); 25 | } 26 | 27 | @Test 28 | void checkDdevVersionWithConfirmation() { 29 | VersionCheckerImpl versionChecker = new VersionCheckerImpl(this.getProject()); 30 | versionChecker.checkDdevVersion(true); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/version/VersionTest.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.version; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.ValueSource; 7 | 8 | final class VersionTest { 9 | @ParameterizedTest 10 | @ValueSource(strings = {"1.26.6", "v1.26.6", "1.26.6-DEBUG"}) 11 | void versionsWithPrefixAndSuffixAreParsedCorrectly(String versionString) { 12 | final Version version = new Version(versionString); 13 | 14 | Assertions.assertArrayEquals(new int[]{1, 26, 6}, version.numbers); 15 | } 16 | 17 | @Test 18 | void headVersionIsParsedCorrectly() { 19 | final Version version = new Version("v1.24.4-36-ge74e3a95f"); 20 | 21 | Assertions.assertArrayEquals(new int[]{1, 24, 4}, version.numbers); 22 | Assertions.assertTrue(version.isHeadVersion()); 23 | Assertions.assertEquals("36-ge74e3a95f", version.getBuildInfo()); 24 | Assertions.assertEquals("v1.24.4-36-ge74e3a95f", version.toString()); 25 | } 26 | 27 | @Test 28 | void regularVersionIsNotHeadVersion() { 29 | final Version version = new Version("v1.24.4"); 30 | 31 | Assertions.assertFalse(version.isHeadVersion()); 32 | Assertions.assertNull(version.getBuildInfo()); 33 | } 34 | 35 | @Test 36 | void debugVersionIsNotHeadVersion() { 37 | final Version version = new Version("v1.24.4-DEBUG"); 38 | 39 | Assertions.assertFalse(version.isHeadVersion()); 40 | Assertions.assertNull(version.getBuildInfo()); 41 | } 42 | 43 | @Test 44 | void compareTo_withEarlierVersion_isGreaterThan() { 45 | Assertions.assertEquals(1, new Version("2.0.0").compareTo(new Version("1.0.0"))); 46 | } 47 | 48 | @Test 49 | void compareTo_withSameVersion_isEqual() { 50 | Assertions.assertEquals(0, new Version("2.0.0").compareTo(new Version("2.0.0"))); 51 | } 52 | 53 | @Test 54 | void compareTo_withLaterVersion_isLessThan() { 55 | Assertions.assertEquals(-1, new Version("1.0.0").compareTo(new Version("2.0.0"))); 56 | } 57 | 58 | @Test 59 | void compareTo_withMorePreciseSameVersion_isFalse() { 60 | Assertions.assertEquals(0, new Version("1").compareTo(new Version("1.0.0"))); 61 | } 62 | 63 | @Test 64 | void compareTo_withMorePreciseEarlierVersion_isFalse() { 65 | Assertions.assertEquals(1, new Version("2").compareTo(new Version("1.0.0"))); 66 | } 67 | 68 | @Test 69 | void compareTo_withMorePreciseLaterVersion_isLessThan() { 70 | Assertions.assertEquals(-1, new Version("1").compareTo(new Version("1.0.1"))); 71 | } 72 | 73 | @Test 74 | void compareTo_headVersionWithSameBaseVersion_isEqual() { 75 | Assertions.assertEquals(0, new Version("v1.24.4").compareTo(new Version("v1.24.4-36-ge74e3a95f"))); 76 | } 77 | 78 | @Test 79 | void compareTo_headVersionWithDifferentBaseVersion_isUnequal() { 80 | Assertions.assertEquals(-1, new Version("v1.24.3-42-gabcdef12").compareTo(new Version("v1.24.4"))); 81 | Assertions.assertEquals(1, new Version("v1.24.5-42-gabcdef12").compareTo(new Version("v1.24.4"))); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/de/php_perfect/intellij/ddev/version/util/VersionCompareTest.java: -------------------------------------------------------------------------------- 1 | package de.php_perfect.intellij.ddev.version.util; 2 | 3 | import de.php_perfect.intellij.ddev.version.Version; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | 7 | final class VersionCompareTest { 8 | @Test 9 | void needsUpdateMajor() { 10 | Assertions.assertTrue(VersionCompare.needsUpdate(new Version("1.0.0"), new Version("2.0.0"))); 11 | } 12 | 13 | @Test 14 | void needsUpdateMinor() { 15 | Assertions.assertTrue(VersionCompare.needsUpdate(new Version("1.0.0"), new Version("1.1.0"))); 16 | } 17 | 18 | @Test 19 | void needsUpdatePatch() { 20 | Assertions.assertTrue(VersionCompare.needsUpdate(new Version("1.0.0"), new Version("1.0.1"))); 21 | } 22 | 23 | @Test 24 | void needsNoUpdateSame() { 25 | Assertions.assertFalse(VersionCompare.needsUpdate(new Version("1.0.0"), new Version("1.0.0"))); 26 | } 27 | 28 | @Test 29 | void needsNoUpdateOnRc() { 30 | Assertions.assertFalse(VersionCompare.needsUpdate(new Version("v1.19.0-rc1"), new Version("1.18.9"))); 31 | } 32 | 33 | @Test 34 | void needsNoUpdateLowerMinor() { 35 | Assertions.assertFalse(VersionCompare.needsUpdate(new Version("1.0.0"), new Version("0.9.0"))); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/resources/ddev_describe.json: -------------------------------------------------------------------------------- 1 | {"level":"info","msg":"┌──────────────────────────────────┐\n│ Project: acol ~/projects/acol ht │\n│ tps://acol.ddev.site│\n├─────────┬──────┬──────────┬──────┤\n│ SERVICE │ STAT │ URL/PORT │ INFO │\n├─────────┼──────┼──────────┼──────┤\n└─────────┴──────┴──────────┴──────┘\n","raw":{"approot":"/home/nl/projects/acol","database_type":"mariadb","dbimg":"ddev/ddev-dbserver-mariadb-10.4:v1.18.2","docroot":"public","fail_on_hook_fail":false,"hostname":"acol.ddev.site","hostnames":["acol.ddev.site"],"httpURLs":["http://acol.ddev.site","http://127.0.0.1:-1"],"httpsURLs":["https://acol.ddev.site","https://127.0.0.1:-1"],"httpsurl":"https://acol.ddev.site","httpurl":"http://acol.ddev.site","mariadb_version":"10.4","mutagen_enabled":false,"name":"acol","nfs_mount_enabled":false,"php_version":"8.1","primary_url":"https://acol.ddev.site","router_disabled":false,"router_http_port":"80","router_https_port":"443","router_status":"stopped","router_status_log":"","services":{},"shortroot":"~/projects/acol","ssh_agent_status":"exited","status":"stopped","type":"php","urls":["https://acol.ddev.site","https://127.0.0.1:-1","http://acol.ddev.site","http://127.0.0.1:-1"],"webimg":"ddev/ddev-webserver:v1.18.2","webserver_type":"apache-fpm","xdebug_enabled":false},"time":"2022-02-05T14:51:31+01:00"} 2 | -------------------------------------------------------------------------------- /src/test/resources/ddev_describe2.json: -------------------------------------------------------------------------------- 1 | {"level":"info","msg":"┌──────────────────────────────────┐\n│ Project: acol ~/projects/acol ht │\n│ tps://acol.ddev.site│\n├─────────┬──────┬──────────┬──────┤\n│ SERVICE │ STAT │ URL/PORT │ INFO │\n├─────────┼──────┼──────────┼──────┤\n└─────────┴──────┴──────────┴──────┘\n","raw":{"approot":"/home/nl/projects/acol","database_type":"mariadb","dbimg":"ddev/ddev-dbserver-mariadb-10.4:v1.18.2","docroot":"public","fail_on_hook_fail":false,"hostname":"acol.ddev.site","hostnames":["acol.ddev.site"],"httpURLs":["http://acol.ddev.site","http://127.0.0.1:-1"],"httpsURLs":["https://acol.ddev.site","https://127.0.0.1:-1"],"httpsurl":"https://acol.ddev.site","httpurl":"http://acol.ddev.site","mariadb_version":"10.4","mutagen_enabled":false,"name":"acol","nfs_mount_enabled":false,"php_version":"7.4","primary_url":"https://acol.ddev.site","router_disabled":false,"router_http_port":"80","router_https_port":"443","router_status":"stopped","router_status_log":"","services":{},"shortroot":"~/projects/acol","ssh_agent_status":"exited","status":"stopped","type":"php","urls":["https://acol.ddev.site","https://127.0.0.1:-1","http://acol.ddev.site","http://127.0.0.1:-1"],"webimg":"ddev/ddev-webserver:v1.18.2","webserver_type":"apache-fpm","xdebug_enabled":false},"time":"2022-02-05T14:51:31+01:00"} 2 | -------------------------------------------------------------------------------- /src/test/resources/ddev_describe_w_debug.json: -------------------------------------------------------------------------------- 1 | {"level":"debug","msg":"detected terminal width=80 urlPortWidth=32 infoWidth=20","time":"2022-05-27T14:24:48+02:00"} 2 | {"level":"info","msg":"┌──────────────────────────────────┐\n│ Project: acol ~/projects/acol ht │\n│ tps://acol.ddev.site│\n├─────────┬──────┬──────────┬──────┤\n│ SERVICE │ STAT │ URL/PORT │ INFO │\n├─────────┼──────┼──────────┼──────┤\n└─────────┴──────┴──────────┴──────┘\n","raw":{"approot":"/home/nl/projects/acol","database_type":"mariadb","dbimg":"ddev/ddev-dbserver-mariadb-10.4:v1.18.2","docroot":"public","fail_on_hook_fail":false,"hostname":"acol.ddev.site","hostnames":["acol.ddev.site"],"httpURLs":["http://acol.ddev.site","http://127.0.0.1:-1"],"httpsURLs":["https://acol.ddev.site","https://127.0.0.1:-1"],"httpsurl":"https://acol.ddev.site","httpurl":"http://acol.ddev.site","mariadb_version":"10.4","mutagen_enabled":false,"name":"acol","nfs_mount_enabled":false,"php_version":"8.1","primary_url":"https://acol.ddev.site","router_disabled":false,"router_http_port":"80","router_https_port":"443","router_status":"stopped","router_status_log":"","services":{},"shortroot":"~/projects/acol","ssh_agent_status":"exited","status":"stopped","type":"php","urls":["https://acol.ddev.site","https://127.0.0.1:-1","http://acol.ddev.site","http://127.0.0.1:-1"],"webimg":"ddev/ddev-webserver:v1.18.2","webserver_type":"apache-fpm","xdebug_enabled":false},"time":"2022-02-05T14:51:31+01:00"} 3 | -------------------------------------------------------------------------------- /src/test/resources/ddev_version.json: -------------------------------------------------------------------------------- 1 | {"level":"info","msg":" ITEM VALUE \n architecture amd64 \n build_info BUILDINFO should have new info \n db ddev/ddev-dbserver-mariadb-10.3:20220102_gzip_snapshots \n ddev_ssh_agent ddev/ddev-ssh-agent:v1.18.0 \n ddev_version v1.19.0-alpha3-12-gdf44295f \n docker 20.10.12\n docker_compose v2.2.2 \n docker_platform docker-desktop \n docker_type Docker Desktop For Windows\n mutagen 0.12.0 \n os windows\n router ddev/ddev-router:20211128__docker-compose-networking \n web ddev/ddev-webserver:20220117_no_volume_copy \n","raw":{"architecture":"amd64","build info":"BUILDINFO should have new info","db":"ddev/ddev-dbserver-mariadb-10.3:20220102_gzip_snapshots","ddev-ssh-agent":"ddev/ddev-ssh-agent:v1.18.0","DDEV version":"v1.19.0","docker":"20.10.12","docker-compose":"v2.2.2","docker-platform":"docker-desktop","mutagen":"0.12.0","os":"windows","router":"ddev/ddev-router:20211128__docker-compose-networking","web":"ddev/ddev-webserver:20220117_no_volume_copy"},"time":"2022-02-05T15:47:35+01:00"} 2 | --------------------------------------------------------------------------------