From 6b472189c1eb524ec1ef57802711ac3f817f79bc Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Wed, 2 Sep 2026 01:18:31 +0200 Subject: [PATCH] feat: polish plugin toward first-class IntelliJ integration Safe script creation, correct run-config identity/validation, JBang-accurate detection, richer directive editing + quick fixes, JShell completion, refreshed catalog schema, project-scoped cancellable sync, settings/UX links, accurate docs, and CI cleanup. Every change is covered by a test. --- .github/workflows/build.yml | 23 +- .github/workflows/run-ui-tests.yml | 57 ---- .gitignore | 3 + CHANGELOG.md | 26 ++ CONTRIBUTING.md | 50 ++++ README.md | 4 +- docs/IMPLEMENTATION_PLAN.md | 265 ++++++++++++++++++ docs/modules/ROOT/pages/architecture.adoc | 19 ++ docs/modules/ROOT/pages/features.adoc | 2 + docs/modules/ROOT/pages/index.adoc | 3 +- .../modules/ROOT/pages/known-limitations.adoc | 15 + docs/modules/ROOT/pages/troubleshooting.adoc | 32 +++ docs/modules/ROOT/pages/usage.adoc | 1 + docs/modules/ROOT/partials/nav.adoc | 5 +- qodana.yml | 12 - .../dev/jbang/idea/JBangCreateScriptAction.kt | 18 +- .../dev/jbang/idea/JBangCreateScriptDialog.kt | 51 +++- .../kotlin/dev/jbang/idea/JBangFeatureTips.kt | 2 +- .../kotlin/dev/jbang/idea/cli/JBangCli.kt | 3 +- .../completion/DirectiveArgumentCompletion.kt | 67 +++++ .../jbang/idea/completion/GavCompletion.kt | 7 +- .../jbang/idea/completion/PathCompletion.kt | 21 +- .../idea/highlight/JBangDirectiveAnnotator.kt | 41 +++ .../dev/jbang/idea/project/JBangAutoSync.kt | 8 +- .../jbang/idea/project/JBangProjectService.kt | 16 +- .../jbang/idea/project/JBangScriptDetector.kt | 20 +- .../dev/jbang/idea/project/JBangSyncAction.kt | 2 +- .../dev/jbang/idea/run/JBangRunConfig.kt | 18 ++ .../jbang/idea/run/JBangRunScriptAction.kt | 22 +- .../jbang/idea/settings/JBangConfigurable.kt | 15 +- .../resources/META-INF/jbang-withGroovy.xml | 2 + .../resources/META-INF/jbang-withKotlin.xml | 2 + src/main/resources/META-INF/plugin.xml | 2 + src/main/resources/jbang-catalog-schema.json | 23 +- .../dev/jbang/idea/DocumentationTest.kt | 29 +- .../jbang/idea/JBangCreateScriptActionTest.kt | 45 +++ .../dev/jbang/idea/JBangSyncActionTest.kt | 10 + .../dev/jbang/idea/cli/JBangCliParsingTest.kt | 2 +- .../idea/completion/CatalogCompletionTest.kt | 32 +++ .../completion/DirectiveCompletionTest.kt | 38 +++ .../idea/completion/GavCompletionTest.kt | 13 + .../idea/completion/PathCompletionTest.kt | 15 + .../highlight/JBangDirectiveAnnotatorTest.kt | 28 ++ .../idea/project/JBangScriptDetectorTest.kt | 16 ++ .../dev/jbang/idea/run/JBangRunConfigTest.kt | 39 +++ .../idea/settings/JBangConfigurableTest.kt | 10 + 46 files changed, 1001 insertions(+), 133 deletions(-) delete mode 100644 .github/workflows/run-ui-tests.yml create mode 100644 CONTRIBUTING.md create mode 100644 docs/IMPLEMENTATION_PLAN.md create mode 100644 docs/modules/ROOT/pages/architecture.adoc create mode 100644 docs/modules/ROOT/pages/known-limitations.adoc create mode 100644 docs/modules/ROOT/pages/troubleshooting.adoc delete mode 100644 qodana.yml create mode 100644 src/main/kotlin/dev/jbang/idea/completion/DirectiveArgumentCompletion.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e999eb2..cd72c0d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,9 +1,7 @@ # GitHub Actions Workflow created for testing and preparing the plugin release in following steps: -# - validate Gradle Wrapper, +# - validate the Gradle wrapper, # - run 'test' and 'verifyPlugin' tasks, -# - run Qodana inspections, # - run 'buildPlugin' task and prepare artifact for the further tests, -# - run 'runPluginVerifier' task, # - create a draft release. # # Workflow is triggered on push and pull_request events. @@ -31,6 +29,8 @@ jobs: steps: - name: Fetch Sources uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: Validate Gradle Wrapper + uses: gradle/actions/wrapper-validation@48b5f213c81028ace310571dc5ec0fbbca0b2947 # v4 - name: Setup Java uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 with: @@ -78,14 +78,15 @@ jobs: VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')" NAME="$(echo "$PROPERTIES" | grep "^pluginName:" | cut -f2- -d ' ')" CHANGELOG="$(./gradlew getChangelog --no-header --console=plain -q)" - CHANGELOG="${CHANGELOG//'%'/'%25'}" - CHANGELOG="${CHANGELOG//$'\n'/'%0A'}" - CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" - echo "::set-output name=version::$VERSION" - echo "::set-output name=name::$NAME" - echo "::set-output name=changelog::$CHANGELOG" - echo "::set-output name=pluginVerifierHomeDir::~/.pluginVerifier" + { + echo "version=$VERSION" + echo "name=$NAME" + echo "pluginVerifierHomeDir=~/.pluginVerifier" + echo "changelog<> "$GITHUB_OUTPUT" ./gradlew printProductsReleases # prepare list of IDEs for Plugin Verifier @@ -120,7 +121,7 @@ jobs: FILENAME=`ls *.zip` unzip "$FILENAME" -d content - echo "::set-output name=filename::$FILENAME" + echo "filename=$FILENAME" >> "$GITHUB_OUTPUT" # Store already-built plugin as an artifact for downloading - name: Upload artifact diff --git a/.github/workflows/run-ui-tests.yml b/.github/workflows/run-ui-tests.yml deleted file mode 100644 index f295ed4..0000000 --- a/.github/workflows/run-ui-tests.yml +++ /dev/null @@ -1,57 +0,0 @@ -# GitHub Actions Workflow for launching UI tests on Linux, Windows, and Mac in the following steps: -# - prepare and launch IDE with your plugin and robot-server plugin, which is needed to interact with UI -# - wait for IDE to start -# - run UI tests with separate Gradle task -# -# Please check https://github.com/JetBrains/intellij-ui-test-robot for information about UI tests with IntelliJ Platform -# -# Workflow is triggered manually. - -name: Run UI Tests -on: workflow_dispatch - -jobs: - testUI: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - runIde: | - export DISPLAY=:99.0 - Xvfb -ac :99 -screen 0 1920x1080x16 & - gradle runIdeForUiTests & - - os: windows-latest - runIde: start gradlew.bat runIdeForUiTests - - os: macos-latest - runIde: ./gradlew runIdeForUiTests & - - steps: - # Check out current repository - - name: Fetch Sources - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - # Setup Java 11 environment for the next steps - - name: Setup Java - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 - with: - distribution: zulu - java-version: 17 - cache: gradle - - # Run IDEA prepared for UI testing - - name: Run IDE - run: ${{ matrix.runIde }} - - # Wait for IDEA to be started - - name: Health Check - uses: jtalk/url-health-check-action@61a0e49fff5cde3773b0bbe069d4ebbd04d24f07 # v2 - with: - url: http://127.0.0.1:8082 - max-attempts: 15 - retry-delay: 30s - - # Run tests - - name: Tests - run: ./gradlew test diff --git a/.gitignore b/.gitignore index 5973d37..a5c2f24 100644 --- a/.gitignore +++ b/.gitignore @@ -150,3 +150,6 @@ gradle-app.setting !src/main/libs/jbang.jar bin + +# macOS metadata +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index b23628b..c9d3532 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,38 @@ ## [Unreleased] +### Added +- The New JBang Script dialog now has a Copy button beside the generated `jbang init` + command preview. +- Directive-argument completion for `//JAVA` versions, `//REPOS` shortcuts, and `//MAIN` + classes, plus multi-value completion for additional `//DEPS`, `//SOURCES`, and `//FILES` + entries on the same directive. +- Quick fixes to uppercase a mis-cased directive and to remove a duplicate `//DEPS` entry. +- JShell (`.jsh`) scripts now receive directive and directive-argument completion. +- Settings now link to the documentation and issue tracker. +- Troubleshooting, known-limitations, and architecture documentation pages, plus a + `CONTRIBUTING.md`. + +### Changed +- New Script creation validates the destination and no longer overwrites files silently: + existing files, absolute paths, and paths escaping the selected directory are rejected, + while nested destinations are created and opened. +- Run/Debug configurations created from context are matched by script path (not just name) + and created as temporary configurations, so same-named scripts in different directories no + longer collide. +- Script detection now only recognises directives in the initial comment block at column + zero, matching JBang, and consistently scans the first 200 lines. +- The catalog JSON schema accepts current Java versions and template properties. +- Feature tips link to the published documentation instead of GitHub source. + ### Fixed - JBang auto-detection now uses IntelliJ's console/shell environment PATH before the IDE process PATH, so installs from tools such as SDKMAN are found when IntelliJ was launched outside that shell. - Persisted JBang settings now read and write the loaded state correctly after IDE restart. +- Run configurations validate the script, working directory, and JBang availability before + launching. ## [0.101.0] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6e89718 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# Contributing to the JBang IntelliJ plugin + +Thanks for helping improve the plugin. This project follows a strict test-first +workflow. + +## Test-first workflow + +Every behavioral change starts with a failing test: + +1. Write a test that reproduces the bug or specifies the feature. +2. Run it and confirm it fails. +3. Implement the smallest change that makes it pass. +4. Run the targeted test, then the full suite. +5. Update `CHANGELOG.md` under `[Unreleased]` for any user-visible change. + +Every user-facing feature must have at least one test that verifies the +user-visible contract (gutter icon, action registration, completion results, +run configuration discovery), not only an internal method. + +## Build and test + +```sh +./gradlew test # run all tests +./gradlew test --tests "dev.jbang.idea.run.*" # run specific tests +./gradlew runIde # launch a sandbox IDE +./gradlew buildPlugin # build the distributable zip +./gradlew verifyPlugin --args='-mute TemplateWordInPluginId' # compatibility check +``` + +Building the plugin requires JDK 25. The plugin targets IntelliJ IDEA 2026.2+. + +## IntelliJ platform conventions + +- Override `getOptionsClass()` on run configurations. +- Register a `ProgramRunner` for every supported executor ID. +- `AdditionalLibraryRootsListener.fireAdditionalLibraryChanged()` needs write + access on EDT. +- Call terminal-widget operations on the correct thread (`createNewSession` on + EDT, `hasRunningCommands` off EDT). +- Handle both `PsiComment` and `PsiDocComment` when walking JBang files, because + the shebang parses as Javadoc. + +See [AGENTS.md](AGENTS.md) for the full set of conventions and architecture +notes, and [docs](docs/modules/ROOT/pages/architecture.adoc) for the +integration model. + +## Documentation + +User-visible changes must update the AsciiDoc pages under +`docs/modules/ROOT/pages/` and any relevant screenshots. diff --git a/README.md b/README.md index 52138bb..5aa6f1d 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ No project wizard or module setup required — just open a folder. Version 0.100.0 is a ground-up rewrite of the plugin. The old plugin (≤ 0.26) mutated IntelliJ modules, mirrored dependencies into Gradle, and used a custom tool window. The new plugin: * Uses `jbang info tools` as the single source of truth — no module mutation, no Gradle mirroring -* Overlays dependencies as synthetic libraries — existing Gradle/Maven projects are never touched +* Overlays dependencies as synthetic libraries — existing Gradle/Maven projects keep their own model (see [Architecture](docs/modules/ROOT/pages/architecture.adoc) for the one narrow Kotlin K2 exception) * Supports multiple root scripts with isolated classpaths and JDKs * Adds Debug support, terminal execution, WSL support, and detailed diagnostics * Replaces the tool window with a status bar widget and External Libraries integration @@ -81,7 +81,7 @@ Install it via Settings > Plugins > ⚙ > ## Contributing -See [AGENTS.md](AGENTS.md) for test-first workflow, IntelliJ platform conventions, and architecture notes. +See [CONTRIBUTING.md](CONTRIBUTING.md) for the test-first workflow, IntelliJ platform conventions, and architecture notes. ```sh ./gradlew test # run all tests diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..1abb861 --- /dev/null +++ b/docs/IMPLEMENTATION_PLAN.md @@ -0,0 +1,265 @@ +# JBang IntelliJ Plugin Improvement Plan + +Implement this work as small, independently reviewable PRs. Every behavioral change starts with a failing test. + +## Status + +- PR 1 Safe script creation — **done** (destination validation, no silent overwrite, nested create/open). +- PR 2 Run/Debug identity + validation — **done** (path-based reuse, temporary configs, `checkConfiguration()`). Gutter/context execute-path test still worth adding. +- PR 3 Script detection — **done** (column-zero directives, initial comment block only, consistent 200-line scan). +- PR 4 Directive editing — **done** (multi-value `//DEPS`/`//SOURCES`/`//FILES`, `//JAVA`/`//REPOS`/`//MAIN` args, uppercase + duplicate quick fixes). +- PR 5 Language support — **partial** (JShell directive-argument completion added and documented; full per-language matrix still open). +- PR 6 Catalog schema — **done** (current Java versions + template properties; bundled-schema test). +- PR 7 Lifecycle/cancellation — **done** (project-scoped cancellable `JBangProjectService.scope`). Stale-result and notification tests still open. +- PR 8 UI polish — **partial** (settings Documentation/Report links, ActionLink download, feature tips point to published docs). Native Run/Debug icons still open. +- PR 9 Documentation — **done** (architecture, troubleshooting, known-limitations pages; CONTRIBUTING.md; `.DS_Store` ignore; DocumentationTest extended). +- PR 10 CI cleanup — **partial** (wrapper validation, `$GITHUB_OUTPUT`, removed fake UI-tests workflow and unused qodana.yml). Linux CLI smoke test and deprecated-API cleanup still open. + + +## Rules for every PR + +1. Add a failing test reproducing the missing behavior. +2. Run it and confirm the expected failure. +3. Implement the smallest fix. +4. Run the targeted test. +5. Run `./gradlew test`. +6. Run `./gradlew verifyPlugin --args='-mute TemplateWordInPluginId'` for API or registration changes. +7. Update `[Unreleased]` in `CHANGELOG.md` for user-visible changes. +8. Add at least one test through IntelliJ's public UI/API wiring, not only internal helpers. + +## PR 1 — Make New Script creation safe + +### Changes + +- Reject blank, invalid, absolute, and escaping paths. +- Detect an existing destination before running JBang. +- Remove unconditional silent overwrite or require explicit confirmation. +- Support nested destination paths and open the created file. +- Show template-loading failures with Retry. +- Keep the command preview and Copy button synchronized with validation. + +### Tests + +- Existing file is not modified. +- `../outside.java` and absolute paths are rejected. +- Nested `examples/Hello.java` is created and opened. +- Overwrite proceeds only after explicit confirmation. +- Template lookup failure is visible and Retry invokes the resolver again. +- Copy copies the exact displayed command. +- Copy is disabled when the command is invalid. +- A UI test finds validation text and buttons in the actual dialog. + +## PR 2 — Correct Run/Debug configuration behavior + +### Changes + +- Match generated configurations by type and canonical script path, never display name alone. +- Give scripts with the same filename separate configurations. +- Make context-generated configurations temporary unless explicitly saved. +- Add `checkConfiguration()` validation for missing scripts, directories, missing JBang, and invalid working directories. +- Generate unambiguous names when filenames collide. + +### Tests + +- Running `a/Hello.java` cannot reuse `b/Hello.java`. +- An existing configuration for the same canonical path is reused. +- The same filename in two directories produces distinct configurations. +- A context-produced configuration is temporary. +- Invalid fields produce `RuntimeConfigurationError`. +- Both Run and Debug executors still find a `ProgramRunner`. +- The registered context/gutter action executes the selected file's configuration. + +## PR 3 — Align script detection with JBang + +First verify special filenames and directive placement against the current JBang CLI, then encode that behavior as executable tests. + +### Changes + +- Recognize directives only at column zero. +- Stop scanning after the initial directive/comment block as JBang does. +- Resolve the `jbang.java` versus `build.java` documentation/code conflict. +- Define and test the scan limit consistently. +- Ensure the file-based index uses exactly the same rules as direct detection. + +### Tests + +- Indented `//DEPS` does not create a root. +- `//DEPS` after Java code does not create a root. +- A shebang plus initial directives creates a root. +- Every officially special filename behaves like the JBang CLI. +- The direct detector and persisted index return identical results. +- Opening a falsely detected file does not show JBang actions, gutter icons, or status state. +- Real roots still expose actions and gutter markers. + +## PR 4 — Improve directive editing + +### Changes + +- Support completion for every token in multi-value `//DEPS`, `//SOURCES`, and `//FILES` directives. +- Add completion for `//JAVA` versions, `//REPOS` shortcuts, and `//MAIN` classes. +- Correct diagnostic ranges with multiple spaces and multiple arguments. +- Add quick fixes to uppercase directives, remove duplicate dependencies, synchronize unresolved dependencies, and open JBang settings when unavailable. + +### Tests + +Use realistic fixture input and `myFixture.complete()`/`doHighlighting()`. + +- Completion works after the second dependency or path. +- `target=source` completion preserves the mapping target. +- Java version and repository suggestions appear. +- Diagnostic ranges cover only the offending token. +- Applying each quick fix produces the expected document text or invokes the registered action. +- Tests fail if contributors, annotators, or quick fixes are not registered. + +## PR 5 — Define language support honestly + +### Changes + +- Audit Java, Kotlin, Groovy, JShell, and `.jbang` behavior separately. +- Add full directive editing support for JShell where IntelliJ PSI permits it. +- Decide whether `.jbang` is intentionally directive-only. +- Document partial support instead of claiming parity where parity is impossible. +- Hide unsupported actions instead of showing controls that cannot work. + +### Tests + +For every supported language, test: + +- Root recognition. +- Directive completion. +- Diagnostics. +- Path navigation. +- Run action visibility. +- Gutter visibility where applicable. +- Run and Debug runner discovery. + +For partial languages, add tests asserting the intended limited behavior so it cannot drift accidentally. + +## PR 6 — Replace the stale catalog schema + +### Changes + +- Prefer a schema maintained or generated by the main JBang repository. +- Update Java versions and current alias/template properties. +- Establish an explicit schema update process. +- Preserve local `script-ref` completion and navigation. + +### Tests + +- Parse and validate the bundled schema. +- Current Java versions are accepted. +- Template properties from a realistic catalog are accepted. +- Invalid catalog fields remain highlighted. +- `myFixture.complete()` offers local `script-ref` values. +- Ctrl/Cmd-click resolves local references. +- Record the upstream schema version/hash so accidental drift is visible. + +## PR 7 — Fix lifecycle, cancellation, and notifications + +### Changes + +- Replace bare `CoroutineScope(Dispatchers.IO)` instances with project/service-scoped coroutines. +- Cancel work when the project closes or the user cancels. +- Prevent stale sync results from replacing newer results. +- Stop showing redundant success balloons while retaining status-widget feedback. +- Keep actionable failure notifications. +- Surface installation failures instead of swallowing exceptions. + +### Tests + +- Disposing a project cancels pending resolution. +- Cancelled resolution does not update libraries or status. +- An older sync completion cannot overwrite a newer sync. +- A successful explicit sync updates the widget without a balloon. +- A failed sync produces one actionable notification. +- Install failure reports an error and offers the download/settings fallback. +- Threading tests verify model changes occur with write access on EDT. + +## PR 8 — IntelliJ UI polish and discoverability + +### Changes + +- Use native Run and Debug iconography. +- Replace the hand-styled hyperlink button with IntelliJ's link component. +- Make settings searchable. +- Add Documentation, Report Issue, and Show Logs links. +- Point feature tips to published documentation. +- Add accessible names, tooltips, and sensible mnemonics. + +### Tests + +- Action registration exposes the correct names and icons. +- Settings search terms include `JBang`, `sync`, `executable`, and `notifications`. +- Link components target expected URLs through an injected browser opener. +- Feature tips point to published docs, not GitHub source. +- UI component tests verify labels are associated with controls. +- Settings state round-trips through reset/apply. + +## PR 9 — Make documentation accurate and useful + +### Changes + +Add: + +- Troubleshooting. +- Known limitations. +- Architecture/integration model. +- Network and command execution behavior. +- Kotlin K2 module-library exception. +- Complete settings reference with defaults. +- Command preview and Copy button documentation. +- A real `CONTRIBUTING.md`. +- A `.DS_Store` ignore rule. + +Update screenshots after UI changes stabilize. + +### Tests + +Extend `DocumentationTest` to verify: + +- Every navigation entry resolves. +- Every image exists. +- Internal xrefs resolve. +- Troubleshooting covers executable lookup, sync failure, logs, WSL, and Kotlin. +- Known limitations references open issues. +- Documentation no longer claims IntelliJ modules are never modified. +- Command preview and Copy are documented. +- Feature-tip URLs target existing published pages. +- README links to `CONTRIBUTING.md`. +- `.gitignore` covers `.DS_Store`. + +## PR 10 — Repair CI and compatibility checks + +### Changes + +- Either implement genuine UI tests or remove the misleading workflow. +- Use Java 25 and `./gradlew` consistently. +- Add Gradle wrapper validation. +- Actually run Qodana, or remove claims/configuration pretending that it runs. +- Replace deprecated GitHub `set-output` usage. +- Add a Linux smoke test using a real pinned JBang release to resolve dependencies, create a script, and run a script. +- Track Plugin Verifier deprecation counts and prevent increases. + +### Tests + +- Workflow contract tests parse YAML and verify Java/Gradle commands. +- The UI workflow invokes a dedicated UI test task, not ordinary `test`. +- A smoke fixture resolves and executes with the pinned JBang version. +- The plugin artifact builds before smoke installation. +- CI fails if verifier compatibility fails or deprecated API count increases. +- A release workflow test verifies stable and EAP channels. + +## Recommended execution order + +1. Safe script creation. +2. Run configuration identity and validation. +3. Script detection correctness. +4. Directive completion and quick fixes. +5. Language-support contract. +6. Catalog schema. +7. Lifecycle and cancellation. +8. IntelliJ UI polish. +9. Documentation consistency. +10. CI and API cleanup. + +Documentation must be updated inside each PR as behavior changes. PR 9 is the final consistency and troubleshooting pass, not an excuse to defer changelog entries. diff --git a/docs/modules/ROOT/pages/architecture.adoc b/docs/modules/ROOT/pages/architecture.adoc new file mode 100644 index 0000000..24b7e81 --- /dev/null +++ b/docs/modules/ROOT/pages/architecture.adoc @@ -0,0 +1,19 @@ += Architecture and integration model +:idprefix: +:idseparator: - + +The JBang CLI is the source of truth. The plugin runs `jbang info tools --quiet` for each detected root and keeps the returned classpath, sources, files, errors, and Java version in project-scoped state. + +== Root isolation + +Each root keeps a separate synthetic library. The active root determines which classpath and JDK apply to shared `//SOURCES`; dependencies from unrelated roots are never merged. + +== IntelliJ project model + +Normal dependency overlays use `AdditionalLibraryRootsProvider` and do not modify Gradle or Maven models. Kotlin K2 has one narrow exception: for a standalone IntelliJ module, Kotlin-compiled JARs from the active root are mirrored into a transient module library so K2 can create light classes. The plugin removes or replaces this support library when the active root changes. Externally managed Gradle and Maven modules are not modified. + +== Synchronization and network access + +Synchronization invokes JBang, which may download JDKs, dependencies, sources, templates, and resources according to the script. Dependency completion reads the local Maven repository and may query Maven Central. Template creation runs `jbang template list` and `jbang init`; Run and Debug invoke `jbang run`. + +Commands and failures are written to `idea.log` without logging credentials. Repository authentication remains JBang/Maven configuration, such as `~/.m2/settings.xml`. diff --git a/docs/modules/ROOT/pages/features.adoc b/docs/modules/ROOT/pages/features.adoc index 03c637b..22f4fe8 100644 --- a/docs/modules/ROOT/pages/features.adoc +++ b/docs/modules/ROOT/pages/features.adoc @@ -81,6 +81,8 @@ Select a template or enter a catalog-qualified ID such as `template@catalog`. Th IDs against that catalog, loads their declared properties, and reports the exact resolved template and catalog. Edit the suggested filename and property values as needed; the plugin passes only changed values to `jbang init` and leaves unchanged defaults to JBang. +A live command preview shows the exact `jbang init` invocation, with a *Copy* button that copies it to the clipboard. +Destinations are validated before creation: existing files, absolute paths, and paths that escape the selected directory are rejected. image:current/create-script.png[Creating a JBang script from a template] diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 9de8dd8..e6b31ee 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -7,7 +7,8 @@ endif::[] The JBang plugin brings https://www.jbang.dev/[JBang] scripts into IntelliJ IDEA while keeping JBang itself as the source of truth. It reads script metadata with `jbang info tools` and exposes dependencies and declared sources as project overlays. -It does not rewrite IntelliJ modules, Gradle builds, or Maven projects. +It exposes dependencies as project overlays and does not rewrite Gradle builds or Maven projects. +Externally managed IntelliJ modules are left intact; the one narrow exception is a transient Kotlin support library for standalone K2 scripts, described in xref:architecture.adoc[Architecture]. == Highlights diff --git a/docs/modules/ROOT/pages/known-limitations.adoc b/docs/modules/ROOT/pages/known-limitations.adoc new file mode 100644 index 0000000..4a4c071 --- /dev/null +++ b/docs/modules/ROOT/pages/known-limitations.adoc @@ -0,0 +1,15 @@ += Known limitations +:idprefix: +:idseparator: - + +== Wildcard imports + +Wildcard imports may not resolve JBang dependencies in every case. Prefer explicit imports when a type does not resolve. See https://github.com/jbangdev/jbang-idea/issues/163[issue #163]. + +== Partial language support + +Java, Kotlin, and Groovy receive directive completion, argument completion, diagnostics, navigation, and Run/Debug gutter markers. JShell (`.jsh`) and `.jbang` build files are recognized as roots and receive directive and directive-argument completion, but not the full set of language-specific features, because their PSI does not expose the same structure. + +== Remote catalog navigation + +Completion and navigation cover local `script-ref` values. HTTP and HTTPS references are left to JBang and are not resolved inside the editor. diff --git a/docs/modules/ROOT/pages/troubleshooting.adoc b/docs/modules/ROOT/pages/troubleshooting.adoc new file mode 100644 index 0000000..9878f4f --- /dev/null +++ b/docs/modules/ROOT/pages/troubleshooting.adoc @@ -0,0 +1,32 @@ += Troubleshooting +:idprefix: +:idseparator: - + +== Show the logs + +Use *Help > Show Log in Finder/Explorer* and inspect `idea.log`. Filter for `jbang` to see every command the plugin runs and any failures. Enable verbose output with *Help > Diagnostic Tools > Debug Log Settings* and add `#dev.jbang.idea`. + +== JBang executable not found + +The plugin resolves the executable in this order: + +. The path configured under *Settings/Preferences > Tools > JBang* +. `$JBANG_HOME/bin/jbang` +. `~/.jbang/bin/jbang` +. `jbang` on `PATH` (`jbang.cmd` on Windows) + +If IntelliJ is launched from a desktop shortcut it may not inherit your shell `PATH`. Set the full path in settings, or install JBang from the same settings page. + +Tool-manager shims (mise, asdf, sdkman) report a distinct error when JBang is not activated. Run the manager's activation, for example `mise use -g jbang@latest`, or install JBang directly. + +== Sync failures + +Open the JBang status widget in the status bar. A failed status lists every JBang error on hover; the same details appear on the failing directive. Re-run *Sync JBang Project* from the editor, Project view, or status menu. Enable *Show sync errors as notifications* in settings for balloon reporting. + +== WSL + +On Windows, scripts stored under `\\wsl.localhost\\...` are executed inside the matching WSL distribution. Ensure JBang is installed in that distribution. + +== Kotlin K2 + +Kotlin-compiled dependencies resolve for a standalone JBang script through a transient module support library named after the active script. If a Kotlin import does not resolve, confirm sync succeeded and that the script is the active root, then re-sync. diff --git a/docs/modules/ROOT/pages/usage.adoc b/docs/modules/ROOT/pages/usage.adoc index c00b826..9f8f291 100644 --- a/docs/modules/ROOT/pages/usage.adoc +++ b/docs/modules/ROOT/pages/usage.adoc @@ -134,6 +134,7 @@ The filename remains editable before creation. You can also enter a catalog-qual `q-aws-lambda@jbang-cloud` directly. The plugin resolves it against the named catalog, reports the exact resolved template and catalog, and loads its property descriptions and defaults into the key/value table. Only changed values are passed to `jbang init` as `-Dkey=value` overrides. +The dialog shows a live command preview with a *Copy* button, and validates the destination before creating the file. image:current/create-script.png[Creating a script from a JBang template] diff --git a/docs/modules/ROOT/partials/nav.adoc b/docs/modules/ROOT/partials/nav.adoc index f4f191a..05b5e62 100644 --- a/docs/modules/ROOT/partials/nav.adoc +++ b/docs/modules/ROOT/partials/nav.adoc @@ -1,4 +1,7 @@ * xref:jbang-idea:ROOT:index.adoc[] * xref:jbang-idea:ROOT:features.adoc[] * xref:jbang-idea:ROOT:installation.adoc[] -* xref:jbang-idea:ROOT:usage.adoc[] \ No newline at end of file +* xref:jbang-idea:ROOT:usage.adoc[] +* xref:jbang-idea:ROOT:architecture.adoc[] +* xref:jbang-idea:ROOT:troubleshooting.adoc[] +* xref:jbang-idea:ROOT:known-limitations.adoc[] \ No newline at end of file diff --git a/qodana.yml b/qodana.yml deleted file mode 100644 index 0be9353..0000000 --- a/qodana.yml +++ /dev/null @@ -1,12 +0,0 @@ -# Qodana configuration: -# https://www.jetbrains.com/help/qodana/qodana-yaml.html - -version: 1.0 -linter: jetbrains/qodana-jvm-community:latest -projectJDK: "17" -profile: - name: qodana.recommended -exclude: - - name: All - paths: - - .qodana \ No newline at end of file diff --git a/src/main/kotlin/dev/jbang/idea/JBangCreateScriptAction.kt b/src/main/kotlin/dev/jbang/idea/JBangCreateScriptAction.kt index a827c2b..aaf53f2 100644 --- a/src/main/kotlin/dev/jbang/idea/JBangCreateScriptAction.kt +++ b/src/main/kotlin/dev/jbang/idea/JBangCreateScriptAction.kt @@ -30,7 +30,7 @@ class JBangCreateScriptAction : DumbAwareAction() { private var templates = emptyList() override fun run(indicator: ProgressIndicator) { templates = JBangCli.listTemplates() } override fun onSuccess() { - val dialog = JBangCreateScriptDialog(project, templates) + val dialog = JBangCreateScriptDialog(project, templates, directory) if (!dialog.showAndGet()) return val name = dialog.scriptName val template = dialog.selectedTemplate @@ -48,10 +48,13 @@ class JBangCreateScriptAction : DumbAwareAction() { ) { object : Task.Backgroundable(project, "Creating JBang script", true) { private var error: Exception? = null + private var destination: File? = null override fun run(indicator: ProgressIndicator) { try { - val path = File(directory.path, name).path - JBangCli.initScript(path, template, properties) + val target = File(directory.path, name).normalize() + target.parentFile?.mkdirs() + JBangCli.initScript(target.path, template, properties) + destination = target } catch (e: Exception) { error = e } @@ -61,8 +64,13 @@ class JBangCreateScriptAction : DumbAwareAction() { Messages.showErrorDialog(project, it.message ?: "JBang init failed", "New JBang Script") return } - directory.refresh(false, false) - directory.findChild(name)?.let { FileEditorManager.getInstance(project).openFile(it, true) } + val created = destination ?: return + val file = com.intellij.openapi.vfs.LocalFileSystem.getInstance().refreshAndFindFileByIoFile(created) + if (file == null) { + Messages.showErrorDialog(project, "JBang did not create ${created.path}", "New JBang Script") + return + } + FileEditorManager.getInstance(project).openFile(file, true) } }.queue() } diff --git a/src/main/kotlin/dev/jbang/idea/JBangCreateScriptDialog.kt b/src/main/kotlin/dev/jbang/idea/JBangCreateScriptDialog.kt index 8cd32b3..6016c9f 100644 --- a/src/main/kotlin/dev/jbang/idea/JBangCreateScriptDialog.kt +++ b/src/main/kotlin/dev/jbang/idea/JBangCreateScriptDialog.kt @@ -1,11 +1,13 @@ package dev.jbang.idea +import com.intellij.openapi.ide.CopyPasteManager import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.LabeledComponent import com.intellij.openapi.ui.ValidationInfo +import com.intellij.openapi.vfs.VirtualFile import com.intellij.ui.DocumentAdapter import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBList @@ -19,6 +21,9 @@ import dev.jbang.idea.cli.JBangCli import dev.jbang.idea.cli.TemplateInfo import dev.jbang.idea.cli.TemplateLookupResult import java.awt.BorderLayout +import java.awt.datatransfer.StringSelection +import java.nio.file.InvalidPathException +import java.nio.file.Path import javax.swing.JButton import javax.swing.JComponent import javax.swing.ListSelectionModel @@ -58,11 +63,15 @@ class JBangCreateScriptDialog internal constructor( project: Project, templates: List, private val templateResolver: TemplateResolver, + private val destinationDirectory: VirtualFile? = null, ) : DialogWrapper(project) { constructor(project: Project, templates: List) : this(project, templates, backgroundTemplateResolver(project)) + internal constructor(project: Project, templates: List, destinationDirectory: VirtualFile) : + this(project, templates, backgroundTemplateResolver(project), destinationDirectory) + internal val templateList = JBList(templates).apply { selectionMode = ListSelectionModel.SINGLE_SELECTION setCellRenderer { _, value, _, isSelected, _ -> @@ -98,6 +107,17 @@ class JBangCreateScriptDialog internal constructor( foreground = javax.swing.UIManager.getColor("Label.disabledForeground") font = font.deriveFont(font.size2D - 1f) } + private val copyCommandButton = JButton("Copy").apply { + toolTipText = "Copy command to clipboard" + isEnabled = false + addActionListener { + CopyPasteManager.getInstance().setContents(StringSelection(commandPreview.text)) + } + } + private val commandPreviewPanel = JPanel(BorderLayout(6, 0)).apply { + add(commandPreview, BorderLayout.CENTER) + add(copyCommandButton, BorderLayout.EAST) + } private val propertyColumns: Array> = arrayOf( object : ColumnInfo("Property") { @@ -259,7 +279,9 @@ class JBangCreateScriptDialog internal constructor( } private fun updateOk() { - isOKActionEnabled = nameField.text.isNotBlank() + val problem = validateDestination() + isOKActionEnabled = problem == null + setErrorText(problem?.message, problem?.component) updateCommandPreview() } @@ -267,10 +289,12 @@ class JBangCreateScriptDialog internal constructor( val name = nameField.text.trim() if (name.isBlank()) { commandPreview.text = " " + copyCommandButton.isEnabled = false return } val cmd = JBangCli.buildInitCommand(name, selectedTemplate, propertyOverrides) commandPreview.text = cmd.joinToString(" ") + copyCommandButton.isEnabled = true } public override fun createCenterPanel(): JComponent { @@ -283,7 +307,7 @@ class JBangCreateScriptDialog internal constructor( .addLabeledComponent("File name:", nameField) .addComponent(propertyComponent) .addSeparator() - .addLabeledComponent("Command:", commandPreview) + .addLabeledComponent("Command:", commandPreviewPanel) .panel } @@ -292,10 +316,29 @@ class JBangCreateScriptDialog internal constructor( super.dispose() } - override fun doValidate(): ValidationInfo? { - if (nameField.text.isBlank()) return ValidationInfo("File name is required", nameField) + internal fun validateDestination(): ValidationInfo? { + val name = nameField.text.trim() + if (name.isBlank()) return ValidationInfo("File name is required", nameField) + val path = try { + Path.of(name.replace('\\', '/')) + } catch (_: InvalidPathException) { + return ValidationInfo("File name is not valid", nameField) + } + if (path.isAbsolute || Regex("^[A-Za-z]:[/\\\\]").containsMatchIn(name)) { + return ValidationInfo("File name must be relative to the selected directory", nameField) + } + val normalized = path.normalize() + if (normalized.nameCount == 0 || normalized.firstOrNull()?.toString() == "..") { + return ValidationInfo("File name must stay inside the selected directory", nameField) + } + val relativePath = normalized.toString().replace(java.io.File.separatorChar, '/') + if (destinationDirectory?.findFileByRelativePath(relativePath) != null) { + return ValidationInfo("A file named $name already exists", nameField) + } return null } + override fun doValidate(): ValidationInfo? = validateDestination() + override fun getPreferredFocusedComponent() = nameField } diff --git a/src/main/kotlin/dev/jbang/idea/JBangFeatureTips.kt b/src/main/kotlin/dev/jbang/idea/JBangFeatureTips.kt index 3727ee5..b090065 100644 --- a/src/main/kotlin/dev/jbang/idea/JBangFeatureTips.kt +++ b/src/main/kotlin/dev/jbang/idea/JBangFeatureTips.kt @@ -26,7 +26,7 @@ object JBangFeatureTips { private const val RUN_ID = "jbang.features.run" private const val DEPENDENCIES_ID = "jbang.features.dependencies" private const val CLASSPATH_ID = "jbang.features.classpath" - private val FEATURES_URL = URI("https://github.com/jbangdev/jbang-idea/blob/main/docs/modules/ROOT/pages/features.adoc").toURL() + private val FEATURES_URL = URI("https://www.jbang.dev/documentation/jbang-idea/latest/features.html").toURL() private val statusScheduled = Key.create("$STATUS_ID.scheduled") private val runScheduled = Key.create("$RUN_ID.scheduled") diff --git a/src/main/kotlin/dev/jbang/idea/cli/JBangCli.kt b/src/main/kotlin/dev/jbang/idea/cli/JBangCli.kt index 3a71c82..b1c952f 100644 --- a/src/main/kotlin/dev/jbang/idea/cli/JBangCli.kt +++ b/src/main/kotlin/dev/jbang/idea/cli/JBangCli.kt @@ -287,7 +287,7 @@ object JBangCli { ) } - /** Calls `jbang init [properties] [--template