├── .clj-kondo
└── config.edn
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── pull_request_template.md
└── workflows
│ ├── build_plugin.yaml
│ ├── publish_plugin.yaml
│ └── test.yaml
├── .gitignore
├── .lsp
└── config.edn
├── .projectile
├── CHANGELOG.md
├── LICENSE
├── README.md
├── bb.edn
├── build.gradle.kts
├── deps.edn
├── doc
├── CONTRIBUTING.md
├── developing.md
├── eval-and-ns.md
├── how-to-use.md
├── shortcuts.md
└── troubleshooting.md
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── configuration-add-new.png
├── configuration-run.png
├── configuration-settings.png
├── connected-repl.png
├── demo.png
└── logo.svg
├── settings.gradle.kts
└── src
├── main
├── clojure
│ └── com
│ │ └── github
│ │ └── clojure_repl
│ │ └── intellij
│ │ ├── action
│ │ ├── eval.clj
│ │ └── test.clj
│ │ ├── actions.clj
│ │ ├── app_info.clj
│ │ ├── config.clj
│ │ ├── configuration
│ │ ├── factory
│ │ │ ├── base.clj
│ │ │ ├── local.clj
│ │ │ └── remote.clj
│ │ ├── repl_type.clj
│ │ └── settings_editor
│ │ │ ├── local.clj
│ │ │ └── remote.clj
│ │ ├── db.clj
│ │ ├── editor.clj
│ │ ├── extension
│ │ ├── color_settings_page.clj
│ │ ├── init_db_startup.clj
│ │ ├── inlay_esc_handler.clj
│ │ ├── inlay_listener_startup.clj
│ │ ├── register_actions_startup.clj
│ │ └── run_test_line_marker_provider.clj
│ │ ├── interop.clj
│ │ ├── keyboard_manager.clj
│ │ ├── nrepl.clj
│ │ ├── parser.clj
│ │ ├── project.clj
│ │ ├── repl_command.clj
│ │ ├── tests.clj
│ │ ├── tool_window
│ │ └── repl_test.clj
│ │ └── ui
│ │ ├── color.clj
│ │ ├── components.clj
│ │ ├── font.clj
│ │ ├── hint.clj
│ │ ├── inlay_hint.clj
│ │ ├── repl.clj
│ │ └── text.clj
├── dev-resources
│ └── META-INF
│ │ ├── clj4intellij.edn
│ │ └── clojure-repl-intellij.edn
├── kotlin
│ ├── icons.kt
│ └── run-configuration-options.kt
└── resources
│ ├── META-INF
│ ├── plugin.xml
│ └── pluginIcon.svg
│ └── icons
│ ├── clojure-mono.svg
│ ├── clojure.svg
│ └── clojure_repl.svg
├── scripts
└── scripts.clj
└── test
└── clojure
└── com
└── github
└── clojure_repl
└── intellij
├── repl_command_test.clj
├── repl_eval_test.clj
└── ui
└── repl_test.clj
/.clj-kondo/config.edn:
--------------------------------------------------------------------------------
1 | {:lint-as {com.rpl.proxy-plus/proxy+ clojure.core/reify}
2 | :linters {:warn-on-reflection {:level :info
3 | :warn-only-on-interop true}
4 | :unresolved-symbol {:exclude [(com.rpl.proxy-plus/proxy+)]}}}
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug, triage
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Environment (please complete the following information):**
27 | - IntelliJ Version: [e.g. IDEA 2023.2.5 (Community Edition)]
28 | - OS [e.g. MacOS, Windows, Linux]
29 |
30 | **Additional context**
31 | Add any other context about the problem here.
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 | ## Checklist
3 | - [ ] Added changes in the `Unreleased` section of CHANGELOG.md
4 |
--------------------------------------------------------------------------------
/.github/workflows/build_plugin.yaml:
--------------------------------------------------------------------------------
1 | name: Build Plugin zip
2 | on:
3 | push:
4 |
5 | jobs:
6 | build-plugin:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v4
10 | with:
11 | fetch-depth: 0
12 |
13 | - name: Gradle wrapper validate
14 | uses: gradle/actions/wrapper-validation@v4
15 |
16 | - name: Setup Java
17 | uses: actions/setup-java@v4
18 | with:
19 | distribution: 'temurin'
20 | java-version: 17
21 | cache: 'gradle'
22 |
23 | - name: Install Babashka
24 | uses: DeLaGuardo/setup-clojure@master
25 | with:
26 | bb: '1.12.196'
27 |
28 | - name: Build plugin
29 | run: bb build-plugin
30 |
31 | - name: Prepare Plugin Artifact
32 | id: artifact
33 | shell: bash
34 | run: |
35 | cd ${{ github.workspace }}/build/distributions
36 | FILENAME=`ls *.zip`
37 | unzip "$FILENAME" -d content
38 |
39 | echo "filename=${FILENAME:0:-4}" >> $GITHUB_OUTPUT
40 |
41 | - name: Upload artifact
42 | uses: actions/upload-artifact@v4
43 | with:
44 | name: ${{ steps.artifact.outputs.filename }}
45 | path: ./build/distributions/content/*/*
46 |
--------------------------------------------------------------------------------
/.github/workflows/publish_plugin.yaml:
--------------------------------------------------------------------------------
1 | name: Publish plugin
2 |
3 | on:
4 | push:
5 | tags:
6 | - '[0-9]+.[0-9]+.[0-9]+*'
7 |
8 | jobs:
9 | publish-plugin:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | with:
14 | fetch-depth: 0
15 |
16 | - name: Prepare java
17 | uses: actions/setup-java@v4
18 | with:
19 | distribution: 'temurin'
20 | java-version: 17
21 | cache: 'gradle'
22 |
23 | - name: Setup Gradle
24 | uses: gradle/actions/setup-gradle@v4
25 |
26 | - name: Install Babashka
27 | uses: DeLaGuardo/setup-clojure@master
28 | with:
29 | bb: '1.12.196'
30 |
31 | - name: Publish to Jetbrains
32 | env:
33 | JETBRAINS_TOKEN: ${{ secrets.JETBRAINS_TOKEN }}
34 | run: bb publish-plugin
35 |
--------------------------------------------------------------------------------
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: Tests
2 | on:
3 | pull_request:
4 |
5 | permissions:
6 | contents: read
7 | checks: write
8 | pull-requests: write
9 |
10 | jobs:
11 | test:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 | with:
16 | fetch-depth: 0
17 |
18 | - name: Setup Java
19 | uses: actions/setup-java@v4
20 | with:
21 | distribution: 'temurin'
22 | java-version: 17
23 | cache: 'gradle'
24 |
25 | - name: Install Clojure
26 | uses: DeLaGuardo/setup-clojure@master
27 | with:
28 | bb: '1.12.196'
29 | cli: 1.12.0.1530
30 |
31 | - name: Run tests
32 | run: ./gradlew test
33 |
34 | - name: Publish Test Report
35 | uses: mikepenz/action-junit-report@v5
36 | if: success() || failure() # always run even if the previous step fails
37 | with:
38 | report_paths: '**/build/test-results/test/TEST-*.xml'
39 | simplified_summary: true
40 | comment: false
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .gradle
3 | build
4 | bin
5 | .DS_Store
6 | .cpcache
7 | .classpath
8 | /.nrepl-port
9 | .lsp/.cache
10 | .clj-kondo/
11 | !.clj-kondo/config.edn
12 | .project
13 | .settings
14 | output.calva-repl
15 |
--------------------------------------------------------------------------------
/.lsp/config.edn:
--------------------------------------------------------------------------------
1 | {:cljfmt {:indents {proxy+ [[:block 2] [:inner 1]]}}
2 | :project-specs [{:classpath-cmd ["./gradlew" "-q" "classpath"]
3 | :project-path "build.gradle.kts"}
4 | {:classpath-cmd ["clojure" "-Spath"]
5 | :project-path "deps.edn"}]}
6 |
--------------------------------------------------------------------------------
/.projectile:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afucher/clojure-repl-intellij/a481e4c29a5e1ef7aaa61fc4d3e154d8c60f6c76/.projectile
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [Unreleased]
4 |
5 | ## 2.5.3
6 |
7 | - Add client info to clone op for metrics.
8 | - Bump nrepl + cider-nrepl version on local repl.
9 |
10 | ## 2.5.2
11 |
12 | - Disable "clear REPL" action when REPL is not connected. #126
13 | - Bump clj4intellij to 0.8.0
14 | - Configure project with IntelliJ integration tests (headless)
15 |
16 | ## 2.5.1
17 |
18 | - Fix REPL window horizontal scrollbar not working.
19 | - Fix REPL window broken after making any change to its layout. #144
20 |
21 | ## 2.5.0
22 |
23 | - Enhance REPL evaluations. #108
24 | - Isolate ns from REPL windows and file editors
25 | - Evaluate ns form from file automatically to avoid namespace-not-found errors.
26 |
27 | ## 2.4.0
28 |
29 | - Send to REPL eval results. #92
30 | - Fix repl input when evaluated the same input of last eval.
31 | - Fix history navigation via shortcut not working after 2.0.0.
32 |
33 | ## 2.3.0
34 |
35 | - Update repl window ns after switching ns.
36 | - Fix exception on settings page.
37 | - Fix special form evaluations. #135
38 | - Add support for JVM args on local REPL configuration. #124
39 |
40 | ## 2.2.0
41 |
42 | - Fix `eval defun at cursor` action error. #121
43 | - Create view error on test error. #128
44 | - Block backspace on repl input.
45 |
46 | ## 2.1.0
47 |
48 | - Add default name for RunConfigurations instead of save as Unnamed. #123
49 | - Add REPL syntax highlight. #18
50 |
51 | ## 2.0.0
52 |
53 | - Add icons of REPL commands to REPL window (clear and entry history navigation). #99
54 | - Drop support of older IntelliJ versions (2021/2022). Now requires minimum IntelliJ 2023.3 (Build 233)
55 | - Fix namespace-not-found error handling. Now shows a message to the user. #107
56 | - Add eval inlay hint support. #106
57 | - Add action to interrupt evals on the REPL session (`shift alt R` + `shift alt S`). #104
58 | - Add color settings page for customization of some tokens.
59 |
60 | ## 1.6.2
61 |
62 | - Fix remote config running wrongly as repl-file.
63 |
64 | ## 1.6.1
65 |
66 | - Fix error when running tests after open multiple projects
67 |
68 | ## 1.6.0
69 |
70 | - Fix shortcuts not being added after 1.5.1.
71 | - Add Re-run last test action. #93
72 |
73 | ## 1.5.1
74 |
75 | - Fix entries history navigation in REPL.
76 | - Fix Local repl configuration setup when more than one project is opened.
77 | - Fix default shortcuts being added for already customized shortcuts. #94
78 |
79 | ## 1.5.0
80 |
81 | - Add support for env vars on local repl.
82 | - Fix support for windows. #85
83 |
84 | ## 1.4.2
85 |
86 | - Fix ignore namespace metadata in REPL
87 | - add dynamic background color to REPL
88 | - add instructions to download babashka
89 |
90 | ## 1.4.1
91 |
92 | - Fix REPL input parse of functions with `>` char. #79
93 |
94 | ## 1.4.0
95 |
96 | - Fix freezing when running actions.
97 | - Add action to clear REPL output. #78
98 | - Add action to refresh all namespaces. #80
99 | - Add action to refresh changed namespaces. #81
100 |
101 | ## 1.3.1
102 |
103 | - Fix regression on 1.2.0: unable to run old local REPL configurations.
104 |
105 | ## 1.3.0
106 |
107 | - Add entry history navigation with `ctrl + PG_UP` and `ctrl + PG_DOWN`
108 |
109 | ## 1.2.0
110 |
111 | - Fix remote run configuration wrong state after open a previously saved configuration.
112 | - Add support alias for Clojure and Lein project types
113 | - Add support choosing project type for local repl instead of only guess, now we guess when creating the run configuration but let user choose a different one.
114 |
115 | ## 1.1.2
116 |
117 | - Fix ANSI chars in console removing it. #70
118 |
119 | ## 1.1.1
120 |
121 | - Fix minor regression exception on 1.1.0.
122 |
123 | ## 1.1.0
124 |
125 | - Fixes Prints to stdout/stderr do not show on REPL when happens async (tests, async threads) #65
126 | - Fix a exception that happens after some seconds of repl running
127 | - Add new eval defun action.
128 |
129 | ## 1.0.4
130 |
131 | - Fix print failing tests. #62
132 |
133 | ## 1.0.3
134 |
135 | - Fix Load file action not showing the error in case of eval error.
136 | - Fix Override configuration when editing multiple configs in same project. #42
137 |
138 | ## 1.0.2
139 |
140 | - Fix noisy exceptions introduced on 1.0.1 when opening multiple projects.
141 |
142 | ## 1.0.1
143 |
144 | - Fix support for IntelliJ 2024.1
145 |
146 | ## 1.0.0
147 |
148 | - Improve success test report message UI.
149 | - Support multiple opened projects. #51
150 | - Fix eval not using same session as load-file. #52
151 |
152 | ## 0.1.7
153 |
154 | - Use cider-nrepl middleware to support more features.
155 | - Add test support. #46
156 | - Fix freeze on evaluation. #48
157 |
158 | ## 0.1.6
159 |
160 | ## 0.1.5
161 |
162 | - Fix cwd of the spawned repl process to be the project dir.
163 |
164 | ## 0.1.4
165 |
166 | - Add support for starting REPL from inside IntelliJ. #40
167 | - Add support to read .nrepl-port file to connect to a running nREPL process. #5
168 |
169 | ## 0.1.3
170 |
171 | - Fix repl output duplicated.
172 | - Clear repl state on repl disconnection.
173 |
174 | ## 0.1.2
175 |
176 | ## 0.1.1
177 |
178 | ## 0.1.0
179 |
180 | ## 0.1.0
181 |
182 | - Connect to an existing nREPL process via host and port
183 | - Load file to REPL
184 | - Eval code at point
185 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Arthur Fücher
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://plugins.jetbrains.com/plugin/23073-clojure-repl)
2 | [](https://clojurians.slack.com/archives/C06DZSPDCPJ)
3 |
4 |
5 |
6 | # clojure-repl-intellij
7 |
8 |
9 |
10 | Free OpenSource IntelliJ plugin for Clojure REPL development.
11 |
12 | Checkout all available [features](https://github.com/afucher/clojure-repl-intellij#features)
13 |
14 |
15 |
16 | 
17 |
18 | ---
19 | ## Getting Started
20 | After installing [the plugin](https://plugins.jetbrains.com/plugin/23073-clojure-repl) in IntelliJ, you can add a REPL to your Run
21 | configurations.
22 |
23 | ### Local: Start a nREPL server from IntelliJ
24 | 1. Go to `Run` > `Edit Configurations`
25 | 2. If you don't have any existing configurations, click `Add new...` or `Add new run configuration`. Otherwise, click the `+` ("Add New Configuration").
26 | 3. Select `Clojure REPL` > `Local`
27 | 4. Optional: Name your Configuration (e.g. "Local REPL")
28 | 5. Click `OK`
29 |
30 |
31 | ### Remote: Connecting to an existing nREPL process
32 | 1. Ensure you have an existing nREPL process running outside IntelliJ
33 | 2. Within Intellij, go to `Run` > `Edit Configurations`
34 | 3. If you don't have any existing configurations, click `Add new...` or `Add new run configuration`. Otherwise, click the `+` ("Add New Configuration").
35 | 4. Select `Clojure REPL` > `Remote`
36 | 5. In the boxes for `Host` and `Port` copy and paste the values from your existing nREPL process
37 | 6. Optional: Name your Configuration (e.g. "Remote REPL")
38 | 7. Click `OK`
39 |
40 | ## Features
41 |
42 | - Start a nREPL server from IntelliJ
43 | - Connect to an existing nREPL process
44 | - Load file to REPL (`alt/opt + shift + L`)
45 | - Eval code at point (`alt/opt + shift + E`)
46 | - Eval defun at point (`alt/opt + shift + D`)
47 | - Run ns tests (`alt/opt + shift + T` `alt/opt + shift + N`)
48 | - Run test at cursor (`alt/opt + shift + T` `alt/opt + shift + T`)
49 | - Re-run last test (`alt/opt + shift + T` `alt/opt + shift + A`)
50 | - Switch to file namespace (`alt/opt + shift + N`)
51 | - Clear REPL output (`alt/opt + shift + R` `alt/opt + shift + C`)
52 | - Refresh all ns (`alt/opt + shift + R` `alt/opt + shift + A`)
53 | - Refresh changed ns (`alt/opt + shift + R` `alt/opt + shift + R`)
54 | - Stop (interrupt) evaluation of REPL session (`alt/opt + shift + R` `alt/opt + shift + S`)
55 | - Entry history navigation in REPL (`ctrl + PAGE_UP` or `ctrl + PAGE_DOWN`)
56 | - Isolate REPL window and file editors namespace. [Read more about evaluation and namespace](https://github.com/afucher/clojure-repl-intellij/blob/master/doc/eval-and-ns.md)
57 |
58 |
59 | ## Contributing
60 |
61 | Contributions are very welcome, check the [issues page](https://github.com/afucher/clojure-repl-intellij/issues) for more information about what are good first issues or open an issue describing the desired support.
62 |
63 |
64 | ## Developing
65 | Check [developing doc](./doc/developing.md).
66 |
67 | ## Release
68 |
69 | 1. `bb tag x.y.z` to tag and push the new tag
70 | 2. `bb publish-plugin` to publish to Jetbrains Marketplace (requires JETBRAINS_TOKEN on env).
71 |
--------------------------------------------------------------------------------
/bb.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src/scripts"]
2 | :tasks {tag scripts/tag
3 | build-plugin scripts/build-plugin
4 | test scripts/tests
5 | install-plugin scripts/install-plugin
6 | run-ide scripts/run-ide
7 | publish-plugin scripts/publish-plugin}}
8 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.changelog.markdownToHTML
2 |
3 | fun properties(key: String) = project.findProperty(key).toString()
4 |
5 | plugins {
6 | id("org.jetbrains.kotlin.jvm") version "1.9.0"
7 | id("dev.clojurephant.clojure") version "0.7.0"
8 | id("org.jetbrains.intellij") version "1.15.0"
9 | id("org.jetbrains.changelog") version "1.3.1"
10 | id("org.jetbrains.grammarkit") version "2021.2.2"
11 | }
12 |
13 | group = properties("pluginGroup")
14 | version = properties("pluginVersion")
15 |
16 | repositories {
17 | mavenLocal()
18 | mavenCentral()
19 | maven {
20 | name = "Clojars"
21 | url = uri("https://repo.clojars.org")
22 | }
23 | }
24 |
25 | dependencies {
26 | implementation ("org.clojure:clojure:1.12.0")
27 | implementation ("org.clojure:core.async:1.5.648") {
28 | because("issue https://clojure.atlassian.net/browse/ASYNC-248")
29 | }
30 | implementation ("com.github.ericdallo:clj4intellij:0.8.0")
31 | implementation ("babashka:fs:0.5.22")
32 | implementation ("com.rpl:proxy-plus:0.0.9")
33 | implementation ("seesaw:seesaw:1.5.0")
34 | implementation ("rewrite-clj:rewrite-clj:1.1.47")
35 | implementation ("nrepl:nrepl:1.3.1")
36 |
37 | testImplementation("junit:junit:latest.release")
38 | testImplementation("org.junit.platform:junit-platform-launcher:latest.release")
39 | testRuntimeOnly ("dev.clojurephant:jovial:0.4.2")
40 | }
41 |
42 | sourceSets {
43 | main {
44 | java.srcDirs("src/main", "src/gen")
45 | if (project.gradle.startParameter.taskNames.contains("buildPlugin") ||
46 | project.gradle.startParameter.taskNames.contains("clojureRepl") ||
47 | project.gradle.startParameter.taskNames.contains("runIde")) {
48 | resources.srcDirs("src/main/dev-resources")
49 | }
50 | }
51 | test {
52 | java.srcDirs("src/test")
53 | }
54 | }
55 |
56 | // Useful to override another IC platforms from env
57 | val platformVersion = System.getenv("PLATFORM_VERSION") ?: properties("platformVersion")
58 | val platformPlugins = System.getenv("PLATFORM_PLUGINS") ?: properties("platformPlugins")
59 |
60 | intellij {
61 | pluginName.set(properties("pluginName"))
62 | version.set(platformVersion)
63 | type.set(properties("platformType"))
64 | plugins.set(platformPlugins.split(',').map(String::trim).filter(String::isNotEmpty))
65 | updateSinceUntilBuild.set(false)
66 | }
67 |
68 | changelog {
69 | version.set(properties("pluginVersion"))
70 | groups.set(emptyList())
71 | }
72 |
73 | java {
74 | targetCompatibility = JavaVersion.VERSION_17
75 | sourceCompatibility = JavaVersion.VERSION_17
76 | }
77 |
78 | tasks.register("classpath") {
79 | doFirst {
80 | println(sourceSets["main"].compileClasspath.asPath)
81 | }
82 | }
83 |
84 | tasks {
85 | compileKotlin {
86 | kotlinOptions {
87 | jvmTarget = "17"
88 | apiVersion = "1.9"
89 | languageVersion = "1.9"
90 | freeCompilerArgs = listOf("-Xjvm-default=all")
91 | }
92 | }
93 |
94 | wrapper {
95 | gradleVersion = properties("gradleVersion")
96 | }
97 |
98 | patchPluginXml {
99 | version.set(properties("pluginVersion"))
100 | sinceBuild.set(properties("pluginSinceBuild"))
101 |
102 | // Extract the section from README.md and provide for the plugin's manifest
103 | pluginDescription.set(
104 | projectDir.resolve("README.md").readText().lines().run {
105 | val start = ""
106 | val end = ""
107 |
108 | if (!containsAll(listOf(start, end))) {
109 | throw GradleException("Plugin description section not found in README.md:\n$start ... $end")
110 | }
111 | subList(indexOf(start) + 1, indexOf(end))
112 | }.joinToString("\n").run { markdownToHTML(this) }
113 | )
114 |
115 | // Get the latest available change notes from the changelog file
116 | changeNotes.set(provider {
117 | changelog.run {
118 | getOrNull(properties("pluginVersion")) ?: getLatest()
119 | }.toHTML()
120 | })
121 | }
122 |
123 | runPluginVerifier {
124 | ideVersions.set(properties("pluginVerifierIdeVersions").split(',').map(String::trim).filter(String::isNotEmpty))
125 | }
126 |
127 | // Configure UI tests plugin
128 | // Read more: https://github.com/JetBrains/intellij-ui-test-robot
129 | runIdeForUiTests {
130 | systemProperty("robot-server.port", "8082")
131 | systemProperty("ide.mac.message.dialogs.as.sheets", "false")
132 | systemProperty("jb.privacy.policy.text", "")
133 | systemProperty("jb.consents.confirmation.enabled", "false")
134 | }
135 |
136 | test {
137 | systemProperty("idea.mimic.jar.url.connection", "true")
138 | }
139 |
140 | signPlugin {
141 | certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
142 | privateKey.set(System.getenv("PRIVATE_KEY"))
143 | password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
144 | }
145 |
146 | publishPlugin {
147 | dependsOn("patchChangelog")
148 | token.set(System.getenv("JETBRAINS_TOKEN"))
149 | // pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3
150 | // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more:
151 | // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel
152 | channels.set(listOf("default"))
153 | }
154 |
155 | buildSearchableOptions {
156 | enabled = false
157 | }
158 |
159 | clojureRepl {
160 | dependsOn("compileClojure")
161 | classpath.from(sourceSets.main.get().runtimeClasspath
162 | + file("build/classes/kotlin/main")
163 | + file("build/clojure/main")
164 | )
165 | // doFirst {
166 | // println(classpath.asPath)
167 | // }
168 | forkOptions.jvmArgs = listOf("--add-opens=java.desktop/java.awt=ALL-UNNAMED",
169 | "--add-opens=java.desktop/java.awt.event=ALL-UNNAMED",
170 | "--add-opens=java.desktop/sun.awt=ALL-UNNAMED",
171 | "--add-opens=java.desktop/sun.font=ALL-UNNAMED",
172 | "--add-opens=java.base/java.lang=ALL-UNNAMED",
173 | "-Djava.system.class.loader=com.intellij.util.lang.PathClassLoader",
174 | "-Didea.mimic.jar.url.connection=true",
175 | "-Didea.force.use.core.classloader=true"
176 | )
177 | }
178 | }
179 |
180 | tasks.withType().configureEach {
181 | useJUnitPlatform()
182 | }
183 |
184 | grammarKit {
185 | jflexRelease.set("1.7.0-1")
186 | grammarKitRelease.set("2021.1.2")
187 | intellijRelease.set("203.7717.81")
188 | }
189 |
190 | clojure.builds.named("main") {
191 | classpath.from(sourceSets.main.get().runtimeClasspath.asPath + "build/classes/kotlin/main")
192 | checkAll()
193 | aotAll()
194 | reflection.set("fail")
195 | }
196 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src/main/clojure"]
2 | :aliases {:test {:extra-paths ["src/test/clojure"]}}}
3 |
--------------------------------------------------------------------------------
/doc/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to clojure-repl-intellij
2 |
3 | First off, thanks for taking the time to contribute!
4 |
5 | If you like the project, but just don’t have time to contribute, that’s fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
6 |
7 | - Star the project
8 | - Talk about it on social media
9 | - Refer this project in your blog posts
10 | - Mention the project at local meetups and tell your friends/colleagues
11 |
12 |
13 | ## Do
14 |
15 | - remember that a gift, while appreciated, is also a burden. We value your input but start with an issue to propose your change before investing your valuable time in a PR.
16 | - read the [clojure-repl-intellij developing guide](./developing.md).
17 | - include/update tests for your change.
18 | - ensure that the Continuous Integration checks pass.
19 | - feel free to pester the project maintainers about your PR if it hasn't been responded to. Sometimes notifications can be missed.
20 |
21 |
22 | ## Don't
23 |
24 | - include more than one feature or fix in a single PR.
25 | - include changes unrelated to the purpose of the PR. This includes changing the project version number, adding lines to the .gitignore file, or changing the indentation or formatting.
26 | - open a new PR if changes are requested. Just push to the same branch and the PR will be updated.
27 | - overuse vertical whitespace; avoid multiple sequential blank lines.
28 |
29 | Inspired by [rewrite-clj](https://github.com/clj-commons/rewrite-clj/blob/main/CONTRIBUTING.md)
30 |
--------------------------------------------------------------------------------
/doc/developing.md:
--------------------------------------------------------------------------------
1 | # Developing
2 |
3 | ⚠️ Before start developing, if you are not familiar with developing IntelliJ plugins, is recommended to read the [IntelliJ Plugin Development](https://github.com/ericdallo/clj4intellij/blob/master/doc/intellij-plugin-development.md) doc to onboard you.
4 |
5 | The flow to develop the plugin is usually:
6 | 1. build locally;
7 | 2. install in a local IntelliJ;
8 | 3. connect REPl and evaluate your changes;
9 |
10 | ## Prerequisites
11 |
12 | Java JDK 17 or above
13 | [Babashka](https://github.com/babashka/babashka#installation): Used to make run required tasks easier during development.
14 |
15 |
16 | ## Build and run locally
17 |
18 | There is some ways to build and run this plugin inside the InteliJ locally, they are listed below in the order that we consider that is more efficient when developing:
19 |
20 | `bb install-plugin` to builds the plugin, and install it from disk in IntelliJ automatically, then restart your IntelliJ. You need to pass the IntelliJ plugins path:
21 | e.g: ```bb install-plugin /home/youruser/.local/share/JetBrains/IdeaIC2024.3```
22 |
23 | or
24 |
25 | `bb build-plugin` to build the plugin, then install it manually from disk in IntelliJ, the zip should be on `./build/distributions/*.zip`. IntelliJ will ask to restart to get the new version.
26 |
27 | or
28 |
29 | `bb run-ide` to spawn a new IntelliJ session with the plugin.
30 |
31 | ## NREPL
32 |
33 | Unless you need to edit some generated extension file or kotlin file, mostly clojure code is editable via repl while your plugin is running!
34 |
35 | NREPL is included in the plugin during development, so you can jack in and edit most of the plugin behavior while running it.
36 |
37 | It runs on port `7770`, you can configure the port in the [clj4intellij.edn](../src/main/dev-resources/META-INF/clj4intellij.edn) file.
38 |
--------------------------------------------------------------------------------
/doc/eval-and-ns.md:
--------------------------------------------------------------------------------
1 | # Eval and ns
2 |
3 | This document describes how this plugin handle evaluation and namespaces for different buffers (files and REPL window).
4 |
5 | ## REPL window
6 | The REPL window uses by default the `user` namespace.
7 | If you want to change the namespace from it you can use the `Switch REPL namespace (alt + shift + N)` action or use the `in-ns` function.
8 | The window shows the namespace in the last line, where you can type to eval your code:
9 | ```
10 | user> *ns*
11 | => #namespace[user]
12 | user>
13 | ```
14 |
15 | ## Files
16 | Each file uses by default its own namespace for evaluation, if they do not have a ns form, in this case the default is `user`.
17 | The only exception is the `ns` form, that uses the `user` namespace to avoid `namespace-not-found` error.
18 | When evaluating a code for the first time in a namespace, the `ns` form is evaluated first to also avoid `namespace-not-found` error.
19 | If you want to change the namespace from it you can use the `in-ns` function, after that all evaluations from this file will start to use the new namespace.
20 |
--------------------------------------------------------------------------------
/doc/how-to-use.md:
--------------------------------------------------------------------------------
1 | # How to use
2 |
3 | ## Starting the REPL
4 |
5 | ### Configuration
6 |
7 | Create a new Run Configuration of type `Clojure REPL`
8 |
9 | 
10 |
11 | Fill in the host and port of your REPL server.
12 | Also, check if the selected project is the one you want to use.
13 |
14 | 
15 |
16 | ### Running
17 |
18 | Run the configuration you just created.
19 |
20 | 
21 |
22 | A new REPL window should appear, and you should be able to evaluate code in it.
23 |
24 | 
25 |
--------------------------------------------------------------------------------
/doc/shortcuts.md:
--------------------------------------------------------------------------------
1 | # Shortcuts
2 |
3 | ## Actions
4 |
5 | | Shortcut | Description |
6 | |--------------------------------------------|----------------------------|
7 | | `alt/opt + shift + E` | Evaluate last sexp |
8 | | `alt/opt +shift + D` | Evaluate defun at cursor |
9 | | `alt/opt +shift + L` | Load file |
10 | | `alt/opt +shift + T` `alt/opt + shift + T` | Run test at cursor |
11 | | `alt/opt +shift + T` `alt/opt + shift + N` | Run ns tests |
12 | | `alt/opt +shift + T` `alt/opt + shift + A` | Re-run last test |
13 | | `alt/opt +shift + N` | Switch namespace |
14 | | `alt/opt +shift + R` `alt/opt + shift + C` | Clear REPL output |
15 | | `alt/opt +shift + R` `alt/opt + shift + A` | Refresh all namespaces |
16 | | `alt/opt +shift + R` `alt/opt + shift + R` | Refresh changed namespaces |
17 | | `alt/opt +shift + R` `alt/opt + shift + S` | Interrupt session eval |
18 | | `ctrl + PAGE_UP` or `ctrl + PAGE_DOWN` | REPL history navigation |
19 |
--------------------------------------------------------------------------------
/doc/troubleshooting.md:
--------------------------------------------------------------------------------
1 | # Troubleshooting
2 |
3 | ## I can't connect to the REPL
4 |
5 | Please check the Configuration settings, the host and port should match the ones from your REPL server.
6 |
7 | ## Shortcuts are not working
8 |
9 | Shortcuts in IntelliJ can not work for different reasons, like Conflicting shortcuts of different plugins or keystroke not reaching IDE due to an external program.
10 | Follow the [Keyboard Shortcuts Troubleshooting](https://www.jetbrains.com/help/idea/keyboard-shortcuts-troubleshooting.html) guide to solve your issue. If they do not work, please open [an issue](https://github.com/afucher/clojure-repl-intellij/issues)
11 |
12 | ## Finding logs
13 |
14 | You can access the IntelliJ logs from the menu `Help > Show Log in Finder` or `Help > Show Log in Explorer` depending on your OS.
15 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # IntelliJ Platform Artifacts Repositories
2 | # -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html
3 |
4 | pluginGroup = com.github.clojure-repl-intellij
5 | pluginName = clojure-repl
6 | pluginVersion = 2.5.3
7 |
8 | # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
9 | # for insight into build numbers and IntelliJ Platform versions.
10 | pluginSinceBuild = 233
11 |
12 | # Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl
13 | # See https://jb.gg/intellij-platform-builds-list for available build versions.
14 | pluginVerifierIdeVersions = 2023.3
15 |
16 | # IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties
17 | platformType = IC
18 | platformVersion = 2023.3
19 |
20 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
21 | # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
22 | platformPlugins =
23 |
24 | # Gradle Releases -> https://github.com/gradle/gradle/releases
25 | gradleVersion = 7.6.1
26 |
27 | # Opt-out flag for bundling Kotlin standard library.
28 | # See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details.
29 | # suppress inspection "UnusedProperty"
30 | kotlin.stdlib.default.dependency = false
31 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afucher/clojure-repl-intellij/a481e4c29a5e1ef7aaa61fc4d3e154d8c60f6c76/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-7.6.1-bin.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
147 | # shellcheck disable=SC3045
148 | MAX_FD=$( ulimit -H -n ) ||
149 | warn "Could not query maximum file descriptor limit"
150 | esac
151 | case $MAX_FD in #(
152 | '' | soft) :;; #(
153 | *)
154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
155 | # shellcheck disable=SC3045
156 | ulimit -n "$MAX_FD" ||
157 | warn "Could not set maximum file descriptor limit to $MAX_FD"
158 | esac
159 | fi
160 |
161 | # Collect all arguments for the java command, stacking in reverse order:
162 | # * args from the command line
163 | # * the main class name
164 | # * -classpath
165 | # * -D...appname settings
166 | # * --module-path (only if needed)
167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
168 |
169 | # For Cygwin or MSYS, switch paths to Windows format before running java
170 | if "$cygwin" || "$msys" ; then
171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
173 |
174 | JAVACMD=$( cygpath --unix "$JAVACMD" )
175 |
176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
177 | for arg do
178 | if
179 | case $arg in #(
180 | -*) false ;; # don't mess with options #(
181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
182 | [ -e "$t" ] ;; #(
183 | *) false ;;
184 | esac
185 | then
186 | arg=$( cygpath --path --ignore --mixed "$arg" )
187 | fi
188 | # Roll the args list around exactly as many times as the number of
189 | # args, so each arg winds up back in the position where it started, but
190 | # possibly modified.
191 | #
192 | # NB: a `for` loop captures its iteration list before it begins, so
193 | # changing the positional parameters here affects neither the number of
194 | # iterations, nor the values presented in `arg`.
195 | shift # remove old arg
196 | set -- "$@" "$arg" # push replacement arg
197 | done
198 | fi
199 |
200 | # Collect all arguments for the java command;
201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
202 | # shell script including quotes and variable substitutions, so put them in
203 | # double quotes to make sure that they get re-expanded; and
204 | # * put everything else in single quotes, so that it's not re-expanded.
205 |
206 | set -- \
207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
208 | -classpath "$CLASSPATH" \
209 | org.gradle.wrapper.GradleWrapperMain \
210 | "$@"
211 |
212 | # Stop when "xargs" is not available.
213 | if ! command -v xargs >/dev/null 2>&1
214 | then
215 | die "xargs is not available"
216 | fi
217 |
218 | # Use "xargs" to parse quoted args.
219 | #
220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
221 | #
222 | # In Bash we could simply go:
223 | #
224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
225 | # set -- "${ARGS[@]}" "$@"
226 | #
227 | # but POSIX shell has neither arrays nor command substitution, so instead we
228 | # post-process each arg (as a line of input to sed) to backslash-escape any
229 | # character that might be a shell metacharacter, then use eval to reverse
230 | # that process (while maintaining the separation between arguments), and wrap
231 | # the whole thing up as a single "set" statement.
232 | #
233 | # This will of course break if any of these variables contains a newline or
234 | # an unmatched quote.
235 | #
236 |
237 | eval "set -- $(
238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
239 | xargs -n1 |
240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
241 | tr '\n' ' '
242 | )" '"$@"'
243 |
244 | exec "$JAVACMD" "$@"
245 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/images/configuration-add-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afucher/clojure-repl-intellij/a481e4c29a5e1ef7aaa61fc4d3e154d8c60f6c76/images/configuration-add-new.png
--------------------------------------------------------------------------------
/images/configuration-run.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afucher/clojure-repl-intellij/a481e4c29a5e1ef7aaa61fc4d3e154d8c60f6c76/images/configuration-run.png
--------------------------------------------------------------------------------
/images/configuration-settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afucher/clojure-repl-intellij/a481e4c29a5e1ef7aaa61fc4d3e154d8c60f6c76/images/configuration-settings.png
--------------------------------------------------------------------------------
/images/connected-repl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afucher/clojure-repl-intellij/a481e4c29a5e1ef7aaa61fc4d3e154d8c60f6c76/images/connected-repl.png
--------------------------------------------------------------------------------
/images/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afucher/clojure-repl-intellij/a481e4c29a5e1ef7aaa61fc4d3e154d8c60f6c76/images/demo.png
--------------------------------------------------------------------------------
/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
80 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "clojure-repl-intellij"
2 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/action/eval.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.action.eval
2 | (:require
3 | [clojure.string :as string]
4 | [com.github.clojure-repl.intellij.actions :as actions]
5 | [com.github.clojure-repl.intellij.db :as db]
6 | [com.github.clojure-repl.intellij.nrepl :as nrepl]
7 | [com.github.clojure-repl.intellij.parser :as parser]
8 | [com.github.clojure-repl.intellij.ui.hint :as ui.hint]
9 | [com.github.clojure-repl.intellij.ui.inlay-hint :as ui.inlay-hint]
10 | [com.github.clojure-repl.intellij.ui.repl :as ui.repl]
11 | [com.github.ericdallo.clj4intellij.app-manager :as app-manager]
12 | [com.github.ericdallo.clj4intellij.tasks :as tasks]
13 | [com.github.ericdallo.clj4intellij.util :as util]
14 | [rewrite-clj.zip :as z])
15 | (:import
16 | [com.intellij.openapi.actionSystem CommonDataKeys]
17 | [com.intellij.openapi.actionSystem AnActionEvent]
18 | [com.intellij.openapi.editor Editor]
19 | [com.intellij.openapi.vfs VirtualFile]))
20 |
21 | (set! *warn-on-reflection* true)
22 |
23 | (defn ^:private send-result-to-repl [^AnActionEvent event text prefix?]
24 | (ui.repl/append-output
25 | (actions/action-event->project event)
26 | (str "\n" (if prefix? "=> " "") text)))
27 |
28 | (defn ^:private eval-action
29 | [& {:keys [^AnActionEvent event loading-msg eval-fn success-msg-fn post-success-fn inlay-hint-feedback?]
30 | :or {post-success-fn identity}}]
31 | (when-let [editor ^Editor (.getData event CommonDataKeys/EDITOR_EVEN_IF_INACTIVE)]
32 | (let [project (.getProject editor)]
33 | (if (db/get-in project [:current-nrepl :session-id])
34 | (tasks/run-background-task!
35 | project
36 | loading-msg
37 | (fn [_indicator]
38 | (let [{:keys [status err] :as response} (eval-fn editor)]
39 | (app-manager/invoke-later!
40 | {:invoke-fn
41 | (fn []
42 | (cond
43 | (and (contains? status "eval-error") err)
44 | (ui.hint/show-repl-error :message err :editor editor)
45 |
46 | (contains? status "namespace-not-found")
47 | (ui.hint/show-error {:message (str "Namespace not found: " (:ns response)) :editor editor})
48 |
49 | :else
50 | (do
51 | (if inlay-hint-feedback?
52 | (ui.inlay-hint/show-code (success-msg-fn response) editor)
53 | (ui.hint/show-repl-info :message (success-msg-fn response) :editor editor))
54 | (post-success-fn response))))}))))
55 | (ui.hint/show-error :message "No REPL connected" :editor editor)))))
56 |
57 | (defn load-file-action [^AnActionEvent event]
58 | (when-let [virtual-file ^VirtualFile (.getData event CommonDataKeys/VIRTUAL_FILE)]
59 | (let [msg (str "Loaded file " (.getPath ^VirtualFile virtual-file))]
60 | (eval-action
61 | :event event
62 | :loading-msg "REPL: Loading file"
63 | :eval-fn (fn [^Editor editor]
64 | (nrepl/load-file (.getProject editor) editor virtual-file))
65 | :success-msg-fn (fn [_response] msg)
66 | :post-success-fn (fn [_response]
67 | (send-result-to-repl event (str ";; " msg) false))))))
68 |
69 | (defn eval-last-sexpr-action [^AnActionEvent event]
70 | (when-let [editor ^Editor (.getData event CommonDataKeys/EDITOR_EVEN_IF_INACTIVE)]
71 | (let [[row col] (util/editor->cursor-position editor)]
72 | (eval-action
73 | :event event
74 | :loading-msg "REPL: Evaluating"
75 | :eval-fn (fn [^Editor editor]
76 | (let [text (.getText (.getDocument editor))
77 | root-zloc (z/of-string text)
78 | zloc (parser/find-form-at-pos root-zloc (inc row) col)
79 | special-form? (contains? #{:quote :syntax-quote :var :reader-macro} (-> zloc z/up z/tag))
80 | code (if special-form?
81 | (-> zloc z/up z/string)
82 | (z/string zloc))]
83 | (nrepl/eval-from-editor {:editor editor :code code})))
84 | :success-msg-fn (fn [response]
85 | (string/join "\n" (:value response)))
86 | :post-success-fn (fn [response]
87 | (send-result-to-repl event (string/join "\n" (:value response)) true))
88 | :inlay-hint-feedback? true))))
89 |
90 | (defn interrupt [^AnActionEvent event]
91 | (-> event
92 | actions/action-event->project
93 | nrepl/interrupt))
94 |
95 | (defn eval-defun-action [^AnActionEvent event]
96 | (when-let [editor ^Editor (.getData event CommonDataKeys/EDITOR_EVEN_IF_INACTIVE)]
97 | (let [[row col] (util/editor->cursor-position editor)]
98 | (eval-action
99 | :event event
100 | :loading-msg "REPL: Evaluating"
101 | :eval-fn (fn [^Editor editor]
102 | (let [text (.getText (.getDocument editor))
103 | root-zloc (z/of-string text)
104 | zloc (-> (parser/find-form-at-pos root-zloc (inc row) col)
105 | parser/to-top)
106 | code (z/string zloc)]
107 | (nrepl/eval-from-editor {:editor editor :code code})))
108 | :success-msg-fn (fn [response]
109 | (string/join "\n" (:value response)))
110 | :post-success-fn (fn [response]
111 | (send-result-to-repl event (string/join "\n" (:value response)) true))
112 | :inlay-hint-feedback? true))))
113 |
114 | (defn clear-repl-output-action [^AnActionEvent event]
115 | (let [project (actions/action-event->project event)]
116 | (ui.repl/clear-repl project)))
117 |
118 | (defn history-up-action [^AnActionEvent event]
119 | (-> event
120 | actions/action-event->project
121 | ui.repl/history-up))
122 |
123 | (defn history-down-action [^AnActionEvent event]
124 | (-> event
125 | actions/action-event->project
126 | ui.repl/history-down))
127 |
128 | (defn switch-ns-action [^AnActionEvent event]
129 | (eval-action
130 | :event event
131 | :loading-msg "REPL: Switching ns"
132 | :eval-fn (fn [^Editor editor]
133 | (let [text (.getText (.getDocument editor))
134 | root-zloc (z/of-string text)
135 | zloc (parser/find-namespace root-zloc)
136 | namespace (parser/remove-metadata (z/string zloc))]
137 | (nrepl/switch-ns {:project (.getProject editor) :ns namespace})))
138 | :success-msg-fn (fn [response]
139 | (string/join "\n" (:value response)))
140 | :post-success-fn (fn [_response]
141 | (ui.repl/clear-input (actions/action-event->project event)))))
142 |
143 | (defn refresh-all-action [^AnActionEvent event]
144 | (let [msg "Refreshed all sucessfully"]
145 | (eval-action
146 | :event event
147 | :loading-msg "REPL: Refreshing all ns"
148 | :eval-fn (fn [^Editor editor]
149 | (nrepl/refresh-all (.getProject editor)))
150 | :success-msg-fn (fn [response]
151 | (if (contains? (:status response) "ok")
152 | msg
153 | (str "Refresh failed:\n" (:error response))))
154 | :post-success-fn (fn [_response]
155 | (send-result-to-repl event (str ";; " msg) false)))))
156 |
157 | (defn refresh-changed-action [^AnActionEvent event]
158 | (let [msg "Refreshed sucessfully"]
159 | (eval-action
160 | :event event
161 | :loading-msg "REPL: Refreshing changed ns"
162 | :eval-fn (fn [^Editor editor]
163 | (nrepl/refresh (.getProject editor)))
164 | :success-msg-fn (fn [response]
165 | (if (contains? (:status response) "ok")
166 | msg
167 | (str "Refresh failed:\n" (:error response))))
168 | :post-success-fn (fn [_response]
169 | (send-result-to-repl event (str ";; " msg) false)))))
170 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/action/test.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.action.test
2 | (:require
3 | [com.github.clojure-repl.intellij.tests :as tests])
4 | (:import
5 | [com.intellij.openapi.actionSystem CommonDataKeys]
6 | [com.intellij.openapi.actionSystem AnActionEvent]
7 | [com.intellij.openapi.editor Editor]))
8 |
9 | (set! *warn-on-reflection* true)
10 |
11 | (defn run-ns-tests-action [^AnActionEvent event]
12 | (when-let [editor ^Editor (.getData event CommonDataKeys/EDITOR_EVEN_IF_INACTIVE)]
13 | (tests/run-ns-tests editor)))
14 |
15 | (defn run-cursor-test-action [^AnActionEvent event]
16 | (when-let [editor ^Editor (.getData event CommonDataKeys/EDITOR_EVEN_IF_INACTIVE)]
17 | (tests/run-at-cursor editor)))
18 |
19 | (defn re-run-test-action [^AnActionEvent event]
20 | (when-let [editor ^Editor (.getData event CommonDataKeys/EDITOR_EVEN_IF_INACTIVE)]
21 | (tests/re-run-test editor)))
22 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/actions.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.actions
2 | (:import
3 | [com.intellij.openapi.actionSystem AnActionEvent CommonDataKeys]
4 | [com.intellij.openapi.editor Editor]
5 | [com.intellij.openapi.project Project]))
6 |
7 | (set! *warn-on-reflection* true)
8 |
9 | (defn action-event->project ^Project [^AnActionEvent event]
10 | (let [editor ^Editor (.getData event CommonDataKeys/EDITOR_EVEN_IF_INACTIVE)
11 | project ^Project (or (.getData event CommonDataKeys/PROJECT)
12 | (.getProject editor))]
13 | project))
14 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/app_info.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.app-info
2 | (:import
3 | [com.intellij.openapi.application ApplicationInfo]
4 | [com.intellij.openapi.util BuildNumber]))
5 |
6 | (set! *warn-on-reflection* true)
7 |
8 | (defn at-least-version? [version]
9 | (>= (.compareTo (.withoutProductCode (.getBuild (ApplicationInfo/getInstance)))
10 | (BuildNumber/fromString version))
11 | 0))
12 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/config.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.config
2 | (:require
3 | [clojure.edn :as edn]
4 | [clojure.java.io :as io])
5 | (:import
6 | [com.intellij.ide.plugins PluginManagerCore]
7 | [com.intellij.openapi.extensions PluginId]))
8 |
9 | (set! *warn-on-reflection* true)
10 |
11 | (defonce ^:private cache* (atom {}))
12 |
13 | (defn ^:private memoize-if-not-nil [f]
14 | (fn []
15 | (if-let [cached-result (get @cache* f)]
16 | cached-result
17 | (if-let [result (f)]
18 | (do (swap! cache* assoc f result)
19 | result)
20 | nil))))
21 |
22 | (defn ^:private config* []
23 | (try
24 | (edn/read-string (slurp (io/resource "META-INF/clojure-repl-intellij.edn")))
25 | (catch Exception _ nil)))
26 |
27 | (def ^:private config (memoize-if-not-nil config*))
28 |
29 | (defn nrepl-debug? []
30 | (-> (config) :nrepl-debug))
31 |
32 | (defn plugin-version* []
33 | (.getVersion (PluginManagerCore/getPlugin (PluginId/getId "com.github.clojure-repl"))))
34 |
35 | (def plugin-version (memoize plugin-version*))
36 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/configuration/factory/base.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.configuration.factory.base
2 | (:require
3 | [com.github.clojure-repl.intellij.db :as db]
4 | [com.github.clojure-repl.intellij.nrepl :as nrepl]
5 | [com.github.clojure-repl.intellij.ui.repl :as ui.repl]
6 | [com.rpl.proxy-plus :refer [proxy+]])
7 | (:import
8 | [com.intellij.execution.ui ConsoleView]
9 | [com.intellij.ide ActivityTracker]
10 | [com.intellij.ide.plugins PluginManagerCore]
11 | [com.intellij.openapi.actionSystem ActionManager AnAction]
12 | [com.intellij.openapi.extensions PluginId]
13 | [com.intellij.openapi.project Project]))
14 |
15 | (set! *warn-on-reflection* true)
16 |
17 | (defn ^:private initial-repl-text [project]
18 | (let [{:keys [clojure java nrepl]} (db/get-in project [:current-nrepl :versions])]
19 | (str (format ";; Connected to nREPL server - nrepl://%s:%s\n"
20 | (db/get-in project [:current-nrepl :nrepl-host])
21 | (db/get-in project [:current-nrepl :nrepl-port]))
22 | (format ";; Clojure REPL Intellij %s\n"
23 | (.getVersion
24 | (PluginManagerCore/getPlugin (PluginId/getId "com.github.clojure-repl"))))
25 | (format ";; Clojure %s, Java %s, nREPL %s"
26 | (:version-string clojure)
27 | (:version-string java)
28 | (:version-string nrepl)))))
29 |
30 | (defn ^:private build-console-actions
31 | []
32 | (let [manager (ActionManager/getInstance)
33 | clear-repl (.getAction manager "ClojureREPL.ClearReplOutput")
34 | history-up (.getAction manager "ClojureREPL.HistoryUp")
35 | history-down (.getAction manager "ClojureREPL.HistoryDown")
36 | interrupt (.getAction manager "ClojureREPL.Interrupt")]
37 | [clear-repl history-up history-down interrupt]))
38 |
39 | (defn build-console-view [project loading-text]
40 | (db/assoc-in! project [:console :process-handler] nil)
41 | (db/assoc-in! project [:console :ui] (ui.repl/build-console
42 | project
43 | {:on-eval (fn [code]
44 | (nrepl/eval-from-repl
45 | {:project project
46 | :code code
47 | :ns (db/get-in project [:current-nrepl :ns])}))}))
48 | (ui.repl/append-output project loading-text)
49 | (proxy+ [] ConsoleView
50 | (getComponent [_] (db/get-in project [:console :ui]))
51 | (getPreferredFocusableComponent [_] (db/get-in project [:console :ui]))
52 | (dispose [_])
53 | (print [_ _ _])
54 | (clear [_])
55 | (scrollTo [_ _])
56 | (attachToProcess [_ _])
57 | (setOutputPaused [_ _])
58 | (isOutputPaused [_] false)
59 | (hasDeferredOutput [_] false)
60 | (performWhenNoDeferredOutput [_ _])
61 | (setHelpId [_ _])
62 | (addMessageFilter [_ _])
63 | (printHyperlink [_ _ _])
64 | (getContentSize [_] 0)
65 | (canPause [_] false)
66 | (createConsoleActions [_] (into-array AnAction (build-console-actions)))
67 | (allowHeavyFilters [_])))
68 |
69 | (defn ^:private on-repl-evaluated [project {:keys [out err]}]
70 | (when err
71 | (ui.repl/append-output project (str "\n" err)))
72 | (when out
73 | (ui.repl/append-output project (str "\n" out))))
74 |
75 | (defn ^:private on-ns-changed [project _]
76 | (ui.repl/clear-input project))
77 |
78 | (defn ^:private trigger-ui-update
79 | "IntelliJ actions status (visibility/enable) depend on IntelliJ calls an update of the UI
80 | but the call of update is not guaranteed. This function triggers the update of the UI.
81 | @see https://github.com/JetBrains/intellij-community/blob/08d00166f92aaf0eedfa6fc9c147ef10ea86da27/platform/editor-ui-api/src/com/intellij/openapi/actionSystem/AnAction.java#L361"
82 | [_ _]
83 | (.inc (ActivityTracker/getInstance)))
84 |
85 | (defn repl-disconnected [^Project project]
86 | (ui.repl/close-console project (db/get-in project [:console :ui]))
87 | (db/assoc-in! project [:console :process-handler] nil)
88 | (db/assoc-in! project [:console :ui] nil)
89 | (db/assoc-in! project [:current-nrepl] nil)
90 | (db/update-in! project [:on-repl-evaluated-fns] (fn [fns] (remove #(contains? #{on-repl-evaluated trigger-ui-update} %) fns))))
91 |
92 | (defn repl-started [project extra-initial-text]
93 | (nrepl/start-client!
94 | :project project
95 | :on-receive-async-message (fn [msg]
96 | (when (:out msg)
97 | (ui.repl/append-output project (:out msg)))))
98 | (nrepl/clone-session project)
99 | (let [description (nrepl/describe project)]
100 | (when (:out-subscribe (:ops description))
101 | (nrepl/out-subscribe project))
102 | (db/assoc-in! project [:current-nrepl :ops] (:ops description))
103 | (db/assoc-in! project [:current-nrepl :versions] (:versions description))
104 | (db/assoc-in! project [:current-nrepl :entry-history] '())
105 | (db/assoc-in! project [:current-nrepl :entry-index] -1)
106 | (db/assoc-in! project [:current-nrepl :ns] "user")
107 | (db/assoc-in! project [:file->ns] {})
108 | (ui.repl/set-repl-started-initial-text project
109 | (db/get-in project [:console :ui])
110 | (str (initial-repl-text project) extra-initial-text))
111 | (db/update-in! project [:on-repl-evaluated-fns] #(conj % on-repl-evaluated trigger-ui-update))
112 | (db/update-in! project [:on-ns-changed-fns] #(conj % on-ns-changed trigger-ui-update))))
113 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/configuration/factory/local.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.configuration.factory.local
2 | (:require
3 | [clojure.string :as string]
4 | [com.github.clojure-repl.intellij.configuration.factory.base :as config.factory.base]
5 | [com.github.clojure-repl.intellij.db :as db]
6 | [com.github.clojure-repl.intellij.interop :as interop]
7 | [com.github.clojure-repl.intellij.project :as project]
8 | [com.github.clojure-repl.intellij.repl-command :as repl-command]
9 | [com.github.clojure-repl.intellij.ui.repl :as ui.repl]
10 | [com.github.ericdallo.clj4intellij.logger :as logger]
11 | [com.rpl.proxy-plus :refer [proxy+]])
12 | (:import
13 | [com.github.clojure_repl.intellij.configuration ReplLocalRunOptions]
14 | [com.intellij.execution.configurations
15 | CommandLineState
16 | ConfigurationFactory
17 | ConfigurationType
18 | GeneralCommandLine
19 | RunConfigurationBase]
20 | [com.intellij.execution.process ColoredProcessHandler ProcessEvent ProcessListener]
21 | [com.intellij.execution.runners ExecutionEnvironment]
22 | [com.intellij.openapi.project Project ProjectManager]
23 | [com.intellij.util.io BaseOutputReader$Options]
24 | [java.nio.charset Charset]))
25 |
26 | (set! *warn-on-reflection* false)
27 | (defn ^:private project-name [configuration] (.getProject (.getOptions configuration)))
28 | (defn ^:private project-type [configuration] (keyword (.getProjectType (.getOptions configuration))))
29 | (defn ^:private aliases [configuration] (seq (.getAliases (.getOptions configuration))))
30 | (defn ^:private env-vars [configuration]
31 | (into {}
32 | (mapv #(string/split % #"=")
33 | (seq (.getEnvVars (.getOptions configuration))))))
34 | (defn ^:private jvm-args [configuration]
35 | (into {}
36 | (mapv #(string/split % #"=")
37 | (seq (.getJvmArgs (.getOptions configuration))))))
38 | (set! *warn-on-reflection* true)
39 |
40 | (def ^:private options-class ReplLocalRunOptions)
41 |
42 | (defn ^:private repl-started-initial-text [command]
43 | (str "\n;; Startup: " command))
44 |
45 | (defn ^:private process-output->nrepl-uri [text]
46 | (or (when-let [[_ port] (re-find #"nREPL server started on port (\d+)" text)]
47 | ["localhost" (parse-long port)])
48 | (when-let [[_ host port] (re-find #"Started nREPL server at (\w+):(\d+)" text)]
49 | [host (parse-long port)])))
50 |
51 | (defn ^:private setup-process [^Project project command env-vars]
52 | (let [command-str (string/join " " command)
53 | command-line (doto (GeneralCommandLine. ^java.util.List command)
54 | (.setCharset (Charset/forName "UTF-8"))
55 | (.setWorkDirectory (.getBasePath project))
56 | (.withEnvironment env-vars))
57 | handler (proxy [ColoredProcessHandler] [command-line]
58 | (readerOptions []
59 | (BaseOutputReader$Options/forMostlySilentProcess)))]
60 | (db/assoc-in! project [:console :process-handler] handler)
61 | (.addProcessListener handler
62 | (proxy+ [] ProcessListener
63 | (onTextAvailable [_ ^ProcessEvent event _key]
64 | (if-let [[host port] (process-output->nrepl-uri (.getText event))]
65 | (do
66 | (db/assoc-in! project [:current-nrepl :nrepl-host] host)
67 | (db/assoc-in! project [:current-nrepl :nrepl-port] port)
68 | (config.factory.base/repl-started project (repl-started-initial-text command-str)))
69 | (ui.repl/append-output project (.getText event))))
70 | (processWillTerminate [_ _ _] (config.factory.base/repl-disconnected project))))
71 | (logger/info "Starting nREPL process:" command-str)
72 | handler))
73 |
74 | (defn configuration-factory ^ConfigurationFactory [^ConfigurationType type]
75 | (proxy [ConfigurationFactory] [type]
76 | (getId [] "clojure-repl-local")
77 | (getName [] "Local")
78 | (getOptionsClass [] options-class)
79 | (createTemplateConfiguration
80 | ([project _]
81 | (.createTemplateConfiguration ^ConfigurationFactory this project))
82 | ([^Project project]
83 | (proxy [RunConfigurationBase] [project this "Start a local nREPL process"]
84 | (getConfigurationEditor []
85 | (interop/new-class "com.github.clojure_repl.intellij.configuration.settings_editor.Local"))
86 |
87 | (getState
88 | ([])
89 | ([executor ^ExecutionEnvironment env]
90 | (let [project ^Project (->> (ProjectManager/getInstance)
91 | .getOpenProjects
92 | (filter #(= (project-name this) (.getName ^Project %)))
93 | first)
94 | config-project-type (project-type this)
95 | project-type (if (contains? project/known-project-types config-project-type)
96 | config-project-type
97 | (project/project->project-type project))
98 | command (repl-command/project->repl-start-command project-type (aliases this) (jvm-args this))
99 | env-vars (env-vars this)]
100 | (proxy [CommandLineState] [env]
101 | (createConsole [_]
102 | (config.factory.base/build-console-view project "Starting nREPL server via: "))
103 | (startProcess []
104 | (setup-process project command env-vars)))))))))))
105 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/configuration/factory/remote.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.configuration.factory.remote
2 | (:require
3 | [clojure.java.io :as io]
4 | [com.github.clojure-repl.intellij.configuration.factory.base :as config.factory.base]
5 | [com.github.clojure-repl.intellij.db :as db]
6 | [com.github.clojure-repl.intellij.interop :as interop]
7 | [com.github.ericdallo.clj4intellij.logger :as logger]
8 | [com.rpl.proxy-plus :refer [proxy+]])
9 | (:import
10 | [com.github.clojure_repl.intellij.configuration ReplRemoteRunOptions]
11 | [com.intellij.execution.configurations
12 | CommandLineState
13 | ConfigurationFactory
14 | ConfigurationType
15 | RunConfigurationBase]
16 | [com.intellij.execution.process NopProcessHandler ProcessListener]
17 | [com.intellij.execution.runners ExecutionEnvironment]
18 | [com.intellij.openapi.project Project]))
19 |
20 | (set! *warn-on-reflection* false)
21 | (defn ^:private nrepl-host [configuration] (.getNreplHost (.getOptions configuration)))
22 | (defn ^:private nrepl-port [configuration] (.getNreplPort (.getOptions configuration)))
23 | (defn ^:private mode [configuration] (keyword (.getMode (.getOptions configuration))))
24 | (set! *warn-on-reflection* true)
25 |
26 | (def ^:private options-class ReplRemoteRunOptions)
27 |
28 | (defn ^:private read-port-file? [configuration]
29 | (= (mode configuration) :file-config))
30 |
31 | (defn ^:private setup-process [configuration project]
32 | (logger/info "Connecting to nREPL process...")
33 | (let [host (nrepl-host configuration)
34 | port (nrepl-port configuration)]
35 | (db/assoc-in! project [:current-nrepl :nrepl-host] host)
36 | (db/assoc-in! project [:current-nrepl :nrepl-port] (parse-long port))
37 | (when (read-port-file? configuration)
38 | (let [base-path (.getBasePath ^Project project)
39 | repl-file (io/file base-path ".nrepl-port")
40 | ;TODO: handle when file does not exist
41 | port (slurp repl-file)]
42 | (db/assoc-in! project [:current-nrepl :nrepl-port] (parse-long port))))
43 |
44 | (let [handler (NopProcessHandler.)]
45 | (db/assoc-in! project [:console :process-handler] handler)
46 | (.addProcessListener handler
47 | (proxy+ [] ProcessListener
48 | (startNotified [_ _] (config.factory.base/repl-started project ""))
49 | (processWillTerminate [_ _ _] (config.factory.base/repl-disconnected project))))
50 | handler)))
51 |
52 | (defn configuration-factory ^ConfigurationFactory [^ConfigurationType type]
53 | (proxy [ConfigurationFactory] [type]
54 | (getId [] "clojure-repl-remote")
55 | (getName [] "Remote")
56 | (getOptionsClass [] options-class)
57 | (createTemplateConfiguration
58 | ([project _]
59 | (.createTemplateConfiguration ^ConfigurationFactory this project))
60 | ([^Project project]
61 | (proxy [RunConfigurationBase] [project this "Connect to an existing nREPL process"]
62 | (getConfigurationEditor []
63 | (interop/new-class "com.github.clojure_repl.intellij.configuration.settings_editor.Remote"))
64 |
65 | (getState
66 | ([])
67 | ([executor ^ExecutionEnvironment env]
68 | (proxy+ [env] CommandLineState
69 | (createConsole [_ _]
70 | (config.factory.base/build-console-view project "Connecting to nREPL server..."))
71 | (startProcess [_]
72 | (setup-process this project))))))))))
73 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/configuration/repl_type.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.configuration.repl-type
2 | (:gen-class
3 | :init init
4 | :constructors {[] [String String String com.intellij.openapi.util.NotNullLazyValue]}
5 | :name com.github.clojure_repl.intellij.configuration.ReplRunConfigurationType
6 | :extends com.intellij.execution.configurations.ConfigurationTypeBase)
7 | (:require
8 | [com.github.clojure-repl.intellij.configuration.factory.local :as config.factory.local]
9 | [com.github.clojure-repl.intellij.configuration.factory.remote :as config.factory.remote])
10 | (:import
11 | [com.github.clojure_repl.intellij Icons]
12 | [com.intellij.execution.configurations
13 | ConfigurationFactory]
14 | [com.intellij.openapi.util NotNullFactory NotNullLazyValue]))
15 |
16 | (set! *warn-on-reflection* true)
17 |
18 | (defn -init []
19 | [["clojure-repl"
20 | "Clojure REPL"
21 | "Connect to a local or remote REPL"
22 | (NotNullLazyValue/createValue
23 | (reify NotNullFactory (create [_] Icons/CLOJURE)))] nil])
24 |
25 | (defn -getConfigurationFactories [this]
26 | (into-array ConfigurationFactory [(config.factory.local/configuration-factory this)
27 | (config.factory.remote/configuration-factory this)]))
28 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/configuration/settings_editor/local.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.configuration.settings-editor.local
2 | (:gen-class
3 | :state state
4 | :init init
5 | :name com.github.clojure_repl.intellij.configuration.settings_editor.Local
6 | :extends com.intellij.openapi.options.SettingsEditor)
7 | (:require
8 | [com.github.clojure-repl.intellij.project :as project]
9 | [com.github.clojure-repl.intellij.ui.components :as ui.components]
10 | [seesaw.core :as seesaw]
11 | [seesaw.layout]
12 | [seesaw.mig :as mig])
13 | (:import
14 | [com.github.clojure_repl.intellij.configuration ReplLocalRunOptions]
15 | [com.intellij.execution.configurations RunConfigurationBase]
16 | [com.intellij.openapi.project Project ProjectManager]
17 | [com.intellij.ui IdeBorderFactory]))
18 |
19 | (set! *warn-on-reflection* true)
20 |
21 | (defn ^:private build-editor-ui []
22 | (let [opened-projects (.getOpenProjects (ProjectManager/getInstance))
23 | project-type (name (or (project/project->project-type (first opened-projects)) :clojure))]
24 | (mig/mig-panel
25 | :border (IdeBorderFactory/createTitledBorder "nREPL connection")
26 | :items (->> [[(seesaw/label "Project") ""]
27 | [(seesaw/combobox :id :project
28 | :model (mapv #(.getName ^Project %) opened-projects)) "wrap"]
29 | [(seesaw/label "Type") ""]
30 | [(doto
31 | (seesaw/combobox :id :project-type
32 | :model (map name project/types))
33 | (seesaw/selection! project-type)) "wrap"]
34 | (ui.components/multi-field
35 | :label (if (= "lein" project-type) "Profiles" "Aliases")
36 | :group-id :aliases-group
37 | :button-id :add-alias
38 | :prefix-id "alias-"
39 | :columns 10
40 | :initial-text "")
41 | (ui.components/multi-field
42 | :label "Env vars"
43 | :group-id :env-vars-group
44 | :button-id :add-env-var
45 | :prefix-id "env-var-"
46 | :columns 15
47 | :initial-text "EXAMPLE_VAR=foo")
48 | (ui.components/multi-field
49 | :label "JVM args"
50 | :group-id :jvm-args-group
51 | :button-id :add-jvm-arg
52 | :prefix-id "jvm-arg-"
53 | :columns 15
54 | :initial-text "-Dexample=foo")]
55 | flatten
56 | (remove nil?)
57 | (partition 2)
58 | vec))))
59 |
60 | (defn -init []
61 | [[] (atom (build-editor-ui))])
62 |
63 | (defn ^:private update-configuration-name [^RunConfigurationBase configuration]
64 | (when (contains? #{"Unnamed" ""} (.getName configuration))
65 | (.setName configuration "Local REPL")))
66 |
67 | (set! *warn-on-reflection* false)
68 |
69 | (defn -createEditor [this]
70 | @(.state this))
71 |
72 | (defn -applyEditorTo [this configuration]
73 | (update-configuration-name configuration)
74 | (let [ui @(.state this)
75 | options ^ReplLocalRunOptions (.getOptions configuration)
76 | project-path (seesaw/text (seesaw/select ui [:#project]))
77 | type (seesaw/text (seesaw/select ui [:#project-type]))
78 | aliases (ui.components/field-values-from-multi-field ui :aliases-group)
79 | env-vars (ui.components/field-values-from-multi-field ui :env-vars-group)
80 | jvm-args (ui.components/field-values-from-multi-field ui :jvm-args-group)]
81 | (.setProject options project-path)
82 | (.setProjectType options type)
83 | (.setAliases options aliases)
84 | (.setEnvVars options env-vars)
85 | (.setJvmArgs options jvm-args)))
86 |
87 | (defn -resetEditorFrom [this configuration]
88 | (update-configuration-name configuration)
89 | (let [ui @(.state this)
90 | options ^ReplLocalRunOptions (.getOptions configuration)
91 | project-name (or (not-empty (.getProject options))
92 | (-> (ProjectManager/getInstance) .getOpenProjects first .getName))
93 | project (->> (ProjectManager/getInstance)
94 | .getOpenProjects
95 | (filter #(= project-name (.getName ^Project %)))
96 | first)
97 | type (or (some-> options .getProjectType not-empty)
98 | (name (project/project->project-type project)))
99 | aliases (.getAliases options)
100 | env-vars (.getEnvVars options)
101 | jvm-args (.getJvmArgs options)]
102 | (seesaw/selection! (seesaw/select ui [:#project-type]) type)
103 | (seesaw/text! (seesaw/select ui [:#project]) project-name)
104 | (doseq [alias aliases]
105 | (ui.components/add-field-to-multi-field! ui :aliases-group alias))
106 | (doseq [env-var env-vars]
107 | (ui.components/add-field-to-multi-field! ui :env-vars-group env-var))
108 | (doseq [jvm-arg jvm-args]
109 | (ui.components/add-field-to-multi-field! ui :jvm-args-group jvm-arg))))
110 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/configuration/settings_editor/remote.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.configuration.settings-editor.remote
2 | (:gen-class
3 | :state state
4 | :init init
5 | :name com.github.clojure_repl.intellij.configuration.settings_editor.Remote
6 | :extends com.intellij.openapi.options.SettingsEditor)
7 | (:require
8 | [seesaw.core :as seesaw]
9 | [seesaw.mig :as mig])
10 | (:import
11 | [com.intellij.execution.configurations RunConfigurationBase]
12 | [com.intellij.openapi.project Project ProjectManager]
13 | [com.intellij.ui IdeBorderFactory]
14 | [javax.swing JRadioButton]))
15 |
16 | (set! *warn-on-reflection* false)
17 |
18 | (defn ^:private mode-id-key [repl-mode]
19 | (->> (seesaw/selection repl-mode)
20 | (seesaw/id-of)))
21 |
22 | (defn ^:private build-editor-ui []
23 | (let [repl-mode-group (seesaw/button-group)
24 | panel (mig/mig-panel
25 | :border (IdeBorderFactory/createTitledBorder "NREPL connection")
26 | :items [[(seesaw/radio :text "Manual"
27 | :id :manual
28 | :group repl-mode-group
29 | :mnemonic \M
30 | :selected? true) "wrap"]
31 | [(seesaw/label "Host") ""]
32 | [(seesaw/text :id :nrepl-host
33 | :columns 20) "wrap"]
34 | [(seesaw/label "Port") ""]
35 | [(seesaw/text :id :nrepl-port
36 | :columns 8) "wrap"]
37 | [(seesaw/radio :text "Read from repl file"
38 | :id :repl-file
39 | :group repl-mode-group
40 | :mnemonic \R) "wrap"]
41 | [(seesaw/label "Project") ""]
42 | [(seesaw/combobox :id :project
43 | :model (->> (ProjectManager/getInstance)
44 | .getOpenProjects
45 | (map #(.getName ^Project %)))) "wrap"]])]
46 | (seesaw/listen repl-mode-group :action
47 | (fn [_e]
48 | (let [mode-key (mode-id-key repl-mode-group)
49 | manual? (= mode-key :manual)]
50 | (seesaw/config! (seesaw/select panel [:#nrepl-host]) :enabled? manual?)
51 | (seesaw/config! (seesaw/select panel [:#nrepl-port]) :enabled? manual?))))
52 |
53 | panel))
54 |
55 | (defn -init []
56 | [[] (atom (build-editor-ui))])
57 |
58 | (defn -createEditor [this]
59 | @(.state this))
60 |
61 | (defn ^:private manual? [editor]
62 | (.isSelected ^JRadioButton (seesaw/select editor [:#manual])))
63 |
64 | (defn ^:private update-configuration-name [^RunConfigurationBase configuration]
65 | (when (contains? #{"Unnamed" ""} (.getName configuration))
66 | (.setName configuration "Remote REPL")))
67 |
68 | (defn -applyEditorTo [this configuration]
69 | (update-configuration-name configuration)
70 | (let [ui @(.state this)
71 | host (seesaw/text (seesaw/select ui [:#nrepl-host]))
72 | project-path (seesaw/text (seesaw/select ui [:#project]))
73 | mode (if (manual? ui) :manual-config :file-config)]
74 | (.setNreplHost (.getOptions configuration) host)
75 | (.setMode (.getOptions configuration) (name mode))
76 | (when-let [nrepl-port (parse-long (seesaw/text (seesaw/select ui [:#nrepl-port])))]
77 | (.setNreplPort (.getOptions configuration) (str nrepl-port))
78 | (.setProject (.getOptions configuration) project-path))))
79 |
80 | (defn -resetEditorFrom [this configuration]
81 | (update-configuration-name configuration)
82 | (let [ui @(.state this)
83 | options (.getOptions configuration)
84 | mode (keyword (.getMode options))]
85 | (if (= :manual-config mode)
86 | (do
87 | (seesaw/config! (seesaw/select ui [:#manual]) :selected? true)
88 | (seesaw/config! (seesaw/select ui [:#repl-file]) :selected? false)
89 | (seesaw/config! (seesaw/select ui [:#nrepl-host]) :enabled? true)
90 | (seesaw/config! (seesaw/select ui [:#nrepl-port]) :enabled? true))
91 | (do
92 | (seesaw/config! (seesaw/select ui [:#manual]) :selected? false)
93 | (seesaw/config! (seesaw/select ui [:#repl-file]) :selected? true)
94 | (seesaw/config! (seesaw/select ui [:#nrepl-host]) :enabled? false)
95 | (seesaw/config! (seesaw/select ui [:#nrepl-port]) :enabled? false)))
96 | (seesaw/text! (seesaw/select ui [:#nrepl-host]) (.getNreplHost options))
97 | (seesaw/text! (seesaw/select ui [:#nrepl-port]) (.getNreplPort options))
98 | (seesaw/text! (seesaw/select ui [:#project]) (.getProject options))))
99 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/db.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.db
2 | (:refer-clojure :exclude [get-in assoc-in update-in])
3 | (:require
4 | [com.github.clojure-repl.intellij.db :as db])
5 | (:import
6 | [com.intellij.openapi.project Project]))
7 |
8 | (set! *warn-on-reflection* true)
9 |
10 | (def ^:private empty-project
11 | {:project nil
12 | :console {:state {:last-output ""
13 | :last-input nil
14 | :initial-text nil
15 | :status nil}
16 | :process-handler nil
17 | :ui nil}
18 | :current-nrepl {:session-id nil
19 | :ns "user"
20 | :nrepl-port nil
21 | :nrepl-host nil
22 | :entry-history '()
23 | :entry-index -1
24 | :last-test nil}
25 | :file->ns {}
26 | :on-repl-file-loaded-fns []
27 | :on-repl-evaluated-fns []
28 | :on-ns-changed-fns []
29 | :on-test-failed-fns-by-key {}
30 | :on-test-succeeded-fns-by-key {}
31 | :ops {}})
32 |
33 | (defonce db* (atom {:projects {}}))
34 |
35 | (defn get-in
36 | ([project fields]
37 | (get-in project fields nil))
38 | ([^Project project fields default]
39 | (clojure.core/get-in @db* (concat [:projects (.getBasePath project)] fields) default)))
40 |
41 | (defn assoc-in! [^Project project fields value]
42 | (swap! db* clojure.core/assoc-in (concat [:projects (.getBasePath project)] fields) value))
43 |
44 | (defn update-in! [^Project project fields fn]
45 | (swap! db* clojure.core/update-in (concat [:projects (.getBasePath project)] fields) fn))
46 |
47 | (defn init-db-for-project [^Project project]
48 | (swap! db* update :projects (fn [projects]
49 | (if (clojure.core/get-in projects [(.getBasePath project) :project])
50 | projects
51 | (update projects (.getBasePath project) #(merge (assoc empty-project :project project) %))))))
52 |
53 | (defn all-projects []
54 | (remove nil?
55 | (mapv :project (vals (:projects @db*)))))
56 |
57 | (comment
58 | ;; Useful for db debugging
59 | (require '[clojure.pprint :as pp])
60 | (defn db-p [] (second (first (second (first @db*)))))
61 | (def p (:project (db-p)))
62 |
63 | (:state (:console (db-p)))
64 | (:ns (:current-nrepl (db-p)))
65 |
66 | (assoc-in! p [:ops] {})
67 | (assoc-in! p [:on-ns-changed-fns] [])
68 | (pp/pprint (:ops (db-p))))
69 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/editor.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.editor
2 | (:require
3 | [com.github.clojure-repl.intellij.parser :as parser]
4 | [rewrite-clj.zip :as z])
5 | (:import
6 | [com.intellij.openapi.editor Editor]
7 | [com.intellij.openapi.fileEditor FileDocumentManager]))
8 |
9 | (set! *warn-on-reflection* true)
10 |
11 | (defn editor->url [^Editor editor]
12 | ;; TODO sanitize URL, encode, etc
13 | (.getUrl (.getFile (FileDocumentManager/getInstance) (.getDocument editor))))
14 |
15 | (defn ns-form [^Editor editor]
16 | (-> editor
17 | .getDocument
18 | .getText
19 | z/of-string
20 | parser/find-namespace
21 | z/up))
22 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/extension/color_settings_page.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.extension.color-settings-page
2 | (:gen-class
3 | :name com.github.clojure_repl.intellij.extension.ColorSettingsPage
4 | :implements [com.intellij.openapi.options.colors.ColorSettingsPage])
5 | (:require
6 | [clojure.string :as string]
7 | [com.github.clojure-repl.intellij.ui.color :as ui.color])
8 | (:import
9 | [com.intellij.lang Language]
10 | [com.intellij.openapi.editor.colors TextAttributesKey]
11 | [com.intellij.openapi.fileTypes SyntaxHighlighterFactory]
12 | [com.intellij.openapi.options.colors AttributesDescriptor ColorDescriptor]))
13 |
14 | (set! *warn-on-reflection* true)
15 |
16 | (defn -getDisplayName [_] "Clojure REPL")
17 |
18 | (defn ^:private title-by-text-attribute []
19 | (merge (reduce (fn [m k]
20 | (let [display-name (-> (name k)
21 | (string/replace "--" "//")
22 | (string/replace "-" " ")
23 | string/capitalize)]
24 | (assoc m k display-name)))
25 | {}
26 | (keys (ui.color/text-attributes)))
27 | {:repl-window "REPL window"}))
28 |
29 | (defn -getAttributeDescriptors [_]
30 | (->> (ui.color/text-attributes)
31 | (mapv #(AttributesDescriptor. ^String ((title-by-text-attribute) (first %)) ^TextAttributesKey (second %)))
32 | (into-array AttributesDescriptor)))
33 |
34 | (defn -getColorDescriptors [_] ColorDescriptor/EMPTY_ARRAY)
35 |
36 | (defn -getHighlighter [_]
37 | (let [language (or (Language/findLanguageByID "clojure")
38 | (Language/findLanguageByID "textmate"))]
39 | (SyntaxHighlighterFactory/getSyntaxHighlighter language nil nil)))
40 |
41 | (defn -getAdditionalHighlightingTagToDescriptorMap [_]
42 | (update-keys (ui.color/text-attributes) name))
43 |
44 | (defn -getDemoText [_]
45 | "
46 | (+ 1 2) 3
47 | \"Hello World\" \"Hello World\"
48 | ")
49 |
50 | (defn -getPreviewEditorCustomizer [_])
51 | (defn -customizeColorScheme [_ scheme] scheme)
52 | (defn -getAdditionalInlineElementToDescriptorMap [_])
53 | (defn -getAdditionalHighlightingTagToColorKeyMap [_])
54 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/extension/init_db_startup.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.extension.init-db-startup
2 | (:gen-class
3 | :name com.github.clojure_repl.intellij.extension.InitDBStartup
4 | :implements [com.intellij.openapi.startup.ProjectActivity
5 | com.intellij.openapi.project.DumbAware])
6 | (:require
7 | [com.github.clojure-repl.intellij.db :as db])
8 | (:import
9 | [com.intellij.openapi.project Project]
10 | [kotlinx.coroutines CoroutineScope]))
11 |
12 | (set! *warn-on-reflection* true)
13 |
14 | (defn -execute [_this ^Project project ^CoroutineScope _]
15 | (db/init-db-for-project project))
16 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/extension/inlay_esc_handler.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.extension.inlay-esc-handler
2 | (:gen-class
3 | :name com.github.clojure_repl.intellij.extension.InlayEscHandler
4 | :extends com.intellij.openapi.editor.actionSystem.EditorActionHandler)
5 | (:require
6 | [com.github.clojure-repl.intellij.ui.inlay-hint :as ui.inlay-hint])
7 | (:import
8 | [com.intellij.openapi.editor Caret Editor]))
9 |
10 | (set! *warn-on-reflection* true)
11 |
12 | (defn -doExecute [_ ^Editor editor ^Caret _caret _data-context]
13 | (ui.inlay-hint/remove-all editor))
14 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/extension/inlay_listener_startup.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.extension.inlay-listener-startup
2 | (:gen-class
3 | :name com.github.clojure_repl.intellij.extension.InlayListenerStartup
4 | :implements [com.intellij.openapi.startup.StartupActivity
5 | com.intellij.openapi.project.DumbAware])
6 | (:require
7 | [com.github.clojure-repl.intellij.ui.inlay-hint :as ui.inlay-hint]
8 | [com.rpl.proxy-plus :refer [proxy+]])
9 | (:import
10 | [com.intellij.openapi.editor EditorFactory]
11 | [com.intellij.openapi.editor.event EditorMouseEvent EditorMouseListener EditorMouseMotionListener]
12 | [com.intellij.openapi.project Project]))
13 |
14 | (set! *warn-on-reflection* true)
15 |
16 | (defonce ^:private last-hovered-inlay* (atom nil))
17 |
18 | (defn ^:private mouse-moved [^EditorMouseEvent event]
19 | (when @last-hovered-inlay*
20 | (when-not (= @last-hovered-inlay* (.getInlay event))
21 | (ui.inlay-hint/mark-inlay-hover-status @last-hovered-inlay* false))
22 | (reset! last-hovered-inlay* nil))
23 | (when-let [inlay (.getInlay event)]
24 | (when (= (ui.inlay-hint/renderer-class) (class (.getRenderer inlay)))
25 | (ui.inlay-hint/mark-inlay-hover-status inlay true)
26 | (reset! last-hovered-inlay* inlay))))
27 |
28 | (defn ^:private mouse-released [^EditorMouseEvent event]
29 | (when-let [inlay (.getInlay event)]
30 | (when (= (ui.inlay-hint/renderer-class) (class (.getRenderer inlay)))
31 | (ui.inlay-hint/toggle-expand-inlay-hint inlay))))
32 |
33 | (defn -runActivity [_this ^Project _project]
34 | (doto (.getEventMulticaster (EditorFactory/getInstance))
35 | (.addEditorMouseMotionListener
36 | (proxy+ [] EditorMouseMotionListener
37 | (mouseMoved [_ event] (mouse-moved event))))
38 | (.addEditorMouseListener
39 | (proxy+ [] EditorMouseListener
40 | (mousePressed [_ ^EditorMouseEvent _event])
41 | (mouseReleased [_ ^EditorMouseEvent event] (mouse-released event))))))
42 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/extension/register_actions_startup.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.extension.register-actions-startup
2 | (:gen-class
3 | :name com.github.clojure_repl.intellij.extension.RegisterActionsStartup
4 | :implements [com.intellij.openapi.startup.ProjectActivity
5 | com.intellij.openapi.project.DumbAware])
6 | (:require
7 | [com.github.clojure-repl.intellij.action.eval :as a.eval]
8 | [com.github.clojure-repl.intellij.action.test :as a.test]
9 | [com.github.clojure-repl.intellij.actions :as actions]
10 | [com.github.clojure-repl.intellij.nrepl :as nrepl]
11 | [com.github.ericdallo.clj4intellij.action :as action]
12 | [com.rpl.proxy-plus :refer [proxy+]])
13 | (:import
14 | [com.github.clojure_repl.intellij Icons]
15 | [com.intellij.icons AllIcons$Actions]
16 | [com.intellij.openapi.actionSystem AnActionEvent]
17 | [com.intellij.openapi.project DumbAwareAction Project]
18 | [kotlinx.coroutines CoroutineScope]))
19 |
20 | (set! *warn-on-reflection* true)
21 |
22 | (defn -execute
23 | "Shortcuts: https://github.com/JetBrains/intellij-community/blob/master/platform/platform-resources/src/keymaps/%24default.xml"
24 | [_this ^Project _project ^CoroutineScope _]
25 | (action/register-action! :id "ClojureREPL.RunCursorTest"
26 | :title "Run test at cursor"
27 | :description "Run test at cursor"
28 | :icon Icons/CLOJURE_REPL
29 | :keyboard-shortcut {:first "shift alt T" :second "shift alt T" :replace-all true}
30 | :on-performed #'a.test/run-cursor-test-action)
31 | (action/register-action! :id "ClojureREPL.RunNsTests"
32 | :title "Run namespace tests"
33 | :description "Run all namespaces tests"
34 | :icon Icons/CLOJURE_REPL
35 | :keyboard-shortcut {:first "shift alt T" :second "shift alt N" :replace-all true}
36 | :on-performed #'a.test/run-ns-tests-action)
37 | (action/register-action! :id "ClojureREPL.ReRunTest"
38 | :title "Re-run last test"
39 | :description "Re-run last executed test"
40 | :icon Icons/CLOJURE_REPL
41 | :keyboard-shortcut {:first "shift alt T" :second "shift alt A" :replace-all true}
42 | :on-performed #'a.test/re-run-test-action)
43 | (action/register-action! :id "ClojureREPL.LoadFile"
44 | :title "Load file to REPL"
45 | :description "Load file to REPL"
46 | :icon Icons/CLOJURE_REPL
47 | :keyboard-shortcut {:first "shift alt L" :replace-all true}
48 | :on-performed #'a.eval/load-file-action)
49 | (action/register-action! :id "ClojureREPL.EvalLastSexp"
50 | :title "Eval last sexp"
51 | :description "Eval the expression preceding cursor"
52 | :icon Icons/CLOJURE_REPL
53 | :keyboard-shortcut {:first "shift alt E" :replace-all true}
54 | :on-performed #'a.eval/eval-last-sexpr-action)
55 | (action/register-action! :id "ClojureREPL.EvalDefun"
56 | :title "Eval defun at cursor"
57 | :description "Evaluate the current toplevel form"
58 | :icon Icons/CLOJURE_REPL
59 | :keyboard-shortcut {:first "shift alt D" :replace-all true}
60 | :on-performed #'a.eval/eval-defun-action)
61 | (action/register-action! :id "ClojureREPL.ClearReplOutput"
62 | :keyboard-shortcut {:first "shift alt R" :second "shift alt C" :replace-all true}
63 | :action (proxy+
64 | ["Clear REPL output" "Clear REPL output" AllIcons$Actions/GC]
65 | DumbAwareAction
66 | (update
67 | [_ ^AnActionEvent event]
68 | (let [project (actions/action-event->project event)]
69 | (.setEnabled (.getPresentation event) (boolean (nrepl/active-client? project)))))
70 | (actionPerformed
71 | [_ event]
72 | (a.eval/clear-repl-output-action event))))
73 |
74 | (action/register-action! :id "ClojureREPL.SwitchNs"
75 | :title "Switch REPL namespace"
76 | :description "Switch REPL namespace to current opened file namespace"
77 | :icon Icons/CLOJURE_REPL
78 | :keyboard-shortcut {:first "shift alt N" :replace-all true}
79 | :on-performed #'a.eval/switch-ns-action)
80 | (action/register-action! :id "ClojureREPL.RefreshAll"
81 | :title "Refresh all namespaces"
82 | :description "Refresh all namespaces"
83 | :icon Icons/CLOJURE_REPL
84 | :keyboard-shortcut {:first "shift alt R" :second "shift alt A" :replace-all true}
85 | :on-performed #'a.eval/refresh-all-action)
86 | (action/register-action! :id "ClojureREPL.RefreshChanged"
87 | :title "Refresh changed namespaces"
88 | :description "Refresh changed namespaces"
89 | :icon Icons/CLOJURE_REPL
90 | :keyboard-shortcut {:first "shift alt R" :second "shift alt R" :replace-all true}
91 | :on-performed #'a.eval/refresh-changed-action)
92 | (action/register-action! :id "ClojureREPL.HistoryUp"
93 | :title "Moves up in history"
94 | :description "Moves up in history"
95 | :icon AllIcons$Actions/PreviousOccurence
96 | :keyboard-shortcut {:first "control PAGE_UP" :replace-all true}
97 | :on-performed #'a.eval/history-up-action)
98 | (action/register-action! :id "ClojureREPL.HistoryDown"
99 | :title "Moves down in history"
100 | :description "Moves down in history"
101 | :icon AllIcons$Actions/NextOccurence
102 | :keyboard-shortcut {:first "control PAGE_DOWN" :replace-all true}
103 | :on-performed #'a.eval/history-down-action)
104 | (action/register-action! :id "ClojureREPL.Interrupt"
105 | :keyboard-shortcut {:first "shift alt R" :second "shift alt S" :replace-all true}
106 | :action (proxy+
107 | ["Interrupts session evaluation" "Interrupts session evaluation" AllIcons$Actions/Cancel]
108 | DumbAwareAction
109 | (update
110 | [_ ^AnActionEvent event]
111 | (let [project (actions/action-event->project event)]
112 | (.setEnabled (.getPresentation event) (boolean (nrepl/evaluating? project)))))
113 | (actionPerformed
114 | [_ event]
115 | (a.eval/interrupt event))))
116 |
117 | (action/register-group! :id "ClojureREPL.ReplActions"
118 | :popup true
119 | :text "Clojure REPL"
120 | :icon Icons/CLOJURE_REPL
121 | :children [{:type :add-to-group :group-id "ToolsMenu" :anchor :first}
122 | {:type :add-to-group :group-id "EditorPopupMenu" :anchor :before :relative-to "RefactoringMenu"}
123 | {:type :separator}
124 | {:type :reference :ref "ClojureREPL.RunCursorTest"}
125 | {:type :reference :ref "ClojureREPL.RunNsTests"}
126 | {:type :reference :ref "ClojureREPL.ReRunTest"}
127 | {:type :separator}
128 | {:type :reference :ref "ClojureREPL.LoadFile"}
129 | {:type :reference :ref "ClojureREPL.EvalLastSexp"}
130 | {:type :reference :ref "ClojureREPL.EvalDefun"}
131 | {:type :separator}
132 | {:type :reference :ref "ClojureREPL.ClearReplOutput"}
133 | {:type :reference :ref "ClojureREPL.RefreshAll"}
134 | {:type :reference :ref "ClojureREPL.RefreshChanged"}
135 | {:type :reference :ref "ClojureREPL.SwitchNs"}
136 | {:type :separator}]))
137 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/extension/run_test_line_marker_provider.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.extension.run-test-line-marker-provider
2 | (:gen-class
3 | :name com.github.clojure_repl.intellij.extension.RunTestLineMarkerProvider
4 | :extends com.intellij.codeInsight.daemon.LineMarkerProviderDescriptor)
5 | (:require
6 | [clojure.string :as string]
7 | [com.github.clojure-repl.intellij.tests :as tests])
8 | (:import
9 | [com.github.clojure_repl.intellij Icons]
10 | [com.intellij.codeInsight.daemon GutterIconNavigationHandler LineMarkerInfo]
11 | [com.intellij.openapi.editor.markup GutterIconRenderer$Alignment]
12 | [com.intellij.openapi.fileEditor FileEditorManager]
13 | [com.intellij.psi PsiElement]
14 | [com.intellij.psi.impl.source.tree LeafPsiElement]))
15 |
16 | (set! *warn-on-reflection* true)
17 |
18 | (defn -getName [_]
19 | "Run test")
20 |
21 | (defn -getIcon [_]
22 | Icons/CLOJURE_REPL)
23 |
24 | (defn -getLineMarkerInfo [_ ^PsiElement element]
25 | (when (and (instance? LeafPsiElement element)
26 | (or (string/ends-with? (.getText element) "-test")
27 | (contains? #{"deftest" "ns"} (some-> element .getPrevSibling .getPrevSibling .getText))))
28 | (LineMarkerInfo.
29 | element
30 | (.getTextRange element)
31 | Icons/CLOJURE_REPL
32 | nil
33 | (reify GutterIconNavigationHandler
34 | (navigate [_ _event element]
35 | (let [element ^PsiElement element
36 | project (.getProject element)
37 | editor (.getSelectedTextEditor (FileEditorManager/getInstance project))]
38 | (.moveToOffset (.getCaretModel editor) (.getTextOffset element))
39 | (tests/run-at-cursor editor))))
40 | GutterIconRenderer$Alignment/CENTER)))
41 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/interop.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.interop)
2 |
3 | (set! *warn-on-reflection* true)
4 |
5 | ;; TODO move to clj4intellij
6 | (defn new-class
7 | "Dynamically resolves the class-name in class loader and creates a new instance.
8 | Workaround to call generated java classes via gen-class from Clojure code itself."
9 | [class-name & args]
10 | (clojure.lang.Reflector/invokeConstructor (Class/forName class-name) (into-array Object args)))
11 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/keyboard_manager.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.keyboard-manager
2 | (:require
3 | [com.rpl.proxy-plus :refer [proxy+]])
4 | (:import
5 | [com.intellij.openapi.editor Editor]
6 | [java.awt KeyEventDispatcher KeyboardFocusManager]
7 | [java.awt.event KeyEvent]))
8 |
9 | (set! *warn-on-reflection* true)
10 |
11 | (defonce ^:private key-listeners* (atom {}))
12 |
13 | (defn register-listener-for-editor! [{:keys [^Editor editor on-key-pressed]}]
14 | (let [dispatcher (proxy+ [] KeyEventDispatcher
15 |
16 | (dispatchKeyEvent [_ ^KeyEvent event]
17 | (if (and (= KeyEvent/KEY_PRESSED (.getID event))
18 | (= (.getContentComponent editor) (.getComponent event)))
19 | (boolean (on-key-pressed event))
20 | false)))]
21 | (swap! key-listeners* assoc editor dispatcher)
22 | (.addKeyEventDispatcher
23 | (KeyboardFocusManager/getCurrentKeyboardFocusManager)
24 | dispatcher)))
25 |
26 | (defn unregister-listener-for-editor! [^Editor editor-to-unregister]
27 | (doseq [[^Editor editor dispatcher] @key-listeners*]
28 | (when (= editor-to-unregister editor)
29 | (.removeKeyEventDispatcher (KeyboardFocusManager/getCurrentKeyboardFocusManager) dispatcher)
30 | (swap! key-listeners* dissoc editor))))
31 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/nrepl.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.nrepl
2 | (:refer-clojure :exclude [eval load-file])
3 | (:require
4 | [clojure.core.async :as async]
5 | [clojure.string :as string]
6 | [com.github.clojure-repl.intellij.config :as config]
7 | [com.github.clojure-repl.intellij.db :as db]
8 | [com.github.clojure-repl.intellij.editor :as editor]
9 | [com.github.clojure-repl.intellij.parser :as parser]
10 | [com.github.ericdallo.clj4intellij.logger :as logger]
11 | [nrepl.core :as nrepl.core]
12 | [nrepl.transport :as transport]
13 | [rewrite-clj.zip :as z])
14 | (:import
15 | [com.intellij.openapi.editor Editor]
16 | [com.intellij.openapi.project Project]
17 | [com.intellij.openapi.vfs VirtualFile]))
18 |
19 | (set! *warn-on-reflection* true)
20 |
21 | (defprotocol REPLClient
22 | (start! [this])
23 | (log! [this level arg1] [this level arg1 arg2])
24 | (send-message! [this msg]))
25 |
26 | (defrecord ^:private NREPLClient [host port join sent-messages* received-messages* conn* on-receive-async-message]
27 | REPLClient
28 | (start! [this]
29 | (log! this :info "connecting...")
30 | (reset! conn* (nrepl.core/connect :host host :port port))
31 | (async/thread
32 | (loop []
33 | (if-let [{:keys [id status] :as resp} (transport/recv @conn*)]
34 | (do
35 | (when-not (:changed-namespaces resp)
36 | (log! this :info "received message:" resp)
37 | (if-let [sent-request (get @sent-messages* id)]
38 | (do
39 | (swap! received-messages* update id conj resp)
40 | (when (and status (contains? (set status) "done"))
41 | (let [responses (get @received-messages* id)]
42 | (swap! sent-messages* dissoc id)
43 | (swap! received-messages* dissoc id)
44 | (deliver sent-request (nrepl.core/combine-responses responses)))))
45 | (on-receive-async-message resp)))
46 | (recur))
47 | (deliver join :done))))
48 | (log! this :info "connected")
49 | join)
50 | (send-message! [this msg]
51 | (let [id (str (random-uuid))
52 | p (promise)
53 | msg (assoc msg :id id)]
54 | (log! this :info "sending message:" msg)
55 | (swap! sent-messages* assoc id p)
56 | (transport/send @conn* msg)
57 | p))
58 | (log! [this msg params]
59 | (log! this :info msg params))
60 | (log! [_this _color msg params]
61 | (when (config/nrepl-debug?)
62 | (logger/info "[NREPLClient] " (string/join " " [msg params])))))
63 |
64 | (defn ^:private nrepl-client [^Project project on-receive-async-message]
65 | (map->NREPLClient {:host (db/get-in project [:current-nrepl :nrepl-host])
66 | :port (db/get-in project [:current-nrepl :nrepl-port])
67 | :join (promise)
68 | :on-receive-async-message on-receive-async-message
69 | :conn* (atom nil)
70 | :sent-messages* (atom {})
71 | :received-messages* (atom {})}))
72 |
73 | (defn start-client! [& {:keys [project on-receive-async-message]}]
74 | (let [client (nrepl-client project on-receive-async-message)]
75 | (db/assoc-in! project [:current-nrepl :client] client)
76 | (start! client)))
77 |
78 | (defn ^:private send-msg [project message]
79 | (let [client (db/get-in project [:current-nrepl :client])]
80 | (send-message! client message)))
81 |
82 | (defn ^:private ns-form-changed [project url ns-form]
83 | (not (= ns-form
84 | (db/get-in project [:file->ns url :form]))))
85 | (defn ^:private is-ns-form [form]
86 | (parser/find-namespace (z/of-string form)))
87 |
88 | (defn ^:private eval [^Project project ns code]
89 | (let [session (db/get-in project [:current-nrepl :session-id])
90 | response @(send-msg project {:op "eval" :code code :ns ns :session session})]
91 | (doseq [fn (db/get-in project [:on-repl-evaluated-fns])]
92 | (fn project response))
93 | response))
94 |
95 | (defn prep-env-for-eval
96 | "When evaluating a form for the first time,
97 | need to evaluate the ns form first to avoid namespace-not-found error.
98 | Also evaluate the ns form when it has changed to keep the environment up-to-date."
99 | [^Editor editor form]
100 | (when-let [current-ns-form (editor/ns-form editor)]
101 | (let [url (editor/editor->url editor)
102 | project (.getProject editor)
103 | str-current-ns-form (z/string current-ns-form)
104 | ns (-> current-ns-form parser/find-namespace z/string parser/remove-metadata)]
105 | (when (and (not (is-ns-form form))
106 | (ns-form-changed project url str-current-ns-form))
107 | (eval project "user" str-current-ns-form)
108 | (when ns
109 | (db/assoc-in! project [:file->ns url]
110 | {:form str-current-ns-form
111 | :ns ns}))))))
112 |
113 | (defn ^:private cur-ns
114 | "Returns current ns to evaluate code in.
115 | If there is no ns in cache, it returns the ns from the file.
116 | If the ns form is not found in the editor, it default to 'user'."
117 | [^Editor editor]
118 | (let [project (.getProject editor)
119 | url (editor/editor->url editor)
120 | cur-ns (some-> (editor/ns-form editor) parser/find-namespace z/string parser/remove-metadata)]
121 | (or (db/get-in project [:file->ns url :ns])
122 | cur-ns
123 | "user")))
124 |
125 | (defn eval-from-editor
126 | "When evaluating code related to a file (editor) we need to:
127 | - prepare the environment by evaluating the ns form
128 | - evaluate the code
129 | - update the current ns in the project if it has changed"
130 | [& {:keys [^Editor editor ns code]}]
131 | (prep-env-for-eval editor code)
132 | (let [project (.getProject editor)
133 | url (editor/editor->url editor)
134 | ns (or ns
135 | (cur-ns editor))
136 | {:keys [ns] :as response} (eval project ns code)]
137 | (when ns
138 | (db/assoc-in! project [:file->ns url :ns] ns))
139 | response))
140 |
141 | (defn eval-from-repl
142 | "When evaluating from repl window we do not have editor associate"
143 | [& {:keys [^Project project ns code]}]
144 | (let [ns (or ns
145 | (db/get-in project [:current-nrepl :ns]))]
146 | (eval project ns code)))
147 |
148 | (defn switch-ns [{:keys [project ns]}]
149 | (let [code (format "(in-ns '%s)" ns)
150 | response @(send-msg project {:op "eval" :code code :ns ns :session (db/get-in project [:current-nrepl :session-id])})]
151 | (when (and ns
152 | (not= ns (db/get-in project [:current-nrepl :ns])))
153 | (db/assoc-in! project [:current-nrepl :ns] ns)
154 | (doseq [fn (db/get-in project [:on-ns-changed-fns])]
155 | (fn project response)))
156 | response))
157 |
158 | (defn clone-session [^Project project]
159 | (let [msg {:op "clone"
160 | :client-name "clojure-repl-intellij"
161 | :client-version (config/plugin-version)}]
162 | (db/assoc-in! project [:current-nrepl :session-id] (:new-session @(send-msg project msg)))))
163 |
164 | (defn load-file [project ^Editor editor ^VirtualFile virtual-file]
165 | (let [response @(send-msg project {:op "load-file"
166 | :session (db/get-in project [:current-nrepl :session-id])
167 | :file (.getText (.getDocument editor))
168 | :file-path (some-> virtual-file .getPath)
169 | :file-name (some-> virtual-file .getName)})]
170 | (doseq [fn (db/get-in project [:on-repl-evaluated-fns])]
171 | (fn project response))
172 | response))
173 |
174 | (defn describe [^Project project]
175 | @(send-msg project {:op "describe"}))
176 |
177 | (defn out-subscribe [^Project project]
178 | @(send-msg project {:op "out-subscribe" :session (db/get-in project [:current-nrepl :session-id])}))
179 |
180 | (defn sym-info [^Project project ns sym]
181 | @(send-msg project {:op "info" :ns ns :sym sym :session (db/get-in project [:current-nrepl :session-id])}))
182 |
183 | (defn run-tests [^Project project {:keys [ns tests on-ns-not-found on-out on-err on-succeeded on-failed]}]
184 | (let [{:keys [summary results status out err] :as response}
185 | @(send-msg project {:op "test"
186 | :load? (str (boolean ns))
187 | :session (db/get-in project [:current-nrepl :session-id])
188 | :ns ns
189 | :tests (when (seq tests) tests)
190 | :fail-fast "true"})]
191 | (when (some #(= % "namespace-not-found") status)
192 | (on-ns-not-found ns))
193 | (when out (on-out out))
194 | (when err (on-err err))
195 | (when results
196 | (if (zero? (+ (:error summary) (:fail summary)))
197 | (on-succeeded response)
198 | (on-failed response)))))
199 |
200 | (defn test-stacktrace [^Project project ns var]
201 | @(send-msg project {:op "test-stacktrace"
202 | :session (db/get-in project [:current-nrepl :session-id])
203 | :ns ns
204 | :index 0
205 | :var var}))
206 |
207 | (defn refresh-all [^Project project]
208 | @(send-msg project {:op "refresh-all"}))
209 |
210 | (defn refresh [^Project project]
211 | @(send-msg project {:op "refresh"}))
212 |
213 | (defn interrupt [^Project project]
214 | @(send-msg project {:op "interrupt" :session (db/get-in project [:current-nrepl :session-id])}))
215 |
216 | (defn evaluating? [^Project project]
217 | (when-let [client (db/get-in project [:current-nrepl :client])]
218 | (seq @(:sent-messages* client))))
219 |
220 | (defn active-client? [^Project project]
221 | (db/get-in project [:current-nrepl :client]))
222 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/parser.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.parser
2 | (:require
3 | [clojure.string :as string]
4 | [rewrite-clj.zip :as z]))
5 |
6 | (defn find-by-heritability
7 | "Find the leftmost deepest zloc from `start-zloc` that satisfies `inherits?`.
8 | `inherits?` must be a function such that if zloc satisifies it then so will
9 | all of its ancestors.
10 |
11 | If a parent node satisifies `inherits?` but none of its children do, then this
12 | returns the parent, on the assumption that the parent is the last in its
13 | lineage with the trait.
14 |
15 | If a parent node doesn't satisfy `inherits?` then none of its descendants will
16 | be inspected. Instead, the search will continue with its sibling to the
17 | z/right*. As such, this algoritihm can be much faster than ones based on
18 | z/next*, which must inspect all descendants, even if information in the parent
19 | excludes the entire family."
20 | [start-zloc inherits?]
21 | (loop [zloc (cond-> start-zloc
22 | (= :forms (z/tag start-zloc)) z/down*)]
23 | (if (z/end? zloc)
24 | zloc
25 | (if (inherits? zloc)
26 | (if-let [inner (some-> zloc z/down* (z/find z/right* inherits?))]
27 | (recur inner)
28 | zloc)
29 | (recur (z/right* zloc))))))
30 |
31 | ;; From rewrite-cljs; very similar to the private function
32 | ;; rewrite-clj.zip.findz/position-in-range? but based on zloc meta, avoiding the
33 | ;; need for :track-position?
34 | (defn in-range?
35 | "True if b is contained within a."
36 | [{:keys [row col end-row end-col] :as _a}
37 | {r :row c :col er :end-row ec :end-col :as _b}]
38 | (and (>= r row)
39 | (<= er end-row)
40 | (if (= r row) (>= c col) true)
41 | (if (= er end-row) (< ec end-col) true)))
42 |
43 | (defn ^:private zloc-in-range?
44 | "Checks whether the `loc`s node is [[in-range?]] of the given `pos`."
45 | [loc pos]
46 | (some-> loc z/node meta (in-range? pos)))
47 |
48 | (defn find-form-at-pos
49 | "Find the deepest zloc whose node is at the given `row` and `col`, seeking
50 | from initial zipper location `zloc`.
51 |
52 | This is similar to z/find-last-by-pos, but is faster, and doesn't require
53 | {:track-position? true}."
54 | [zloc row col]
55 | (let [exact-position {:row row, :col col, :end-row row, :end-col col}]
56 | (find-by-heritability zloc #(zloc-in-range? % exact-position))))
57 |
58 | (defn root? [loc]
59 | (identical? :forms (z/tag loc)))
60 |
61 | (defn top? [loc]
62 | (root? (z/up loc)))
63 |
64 | (defn to-top
65 | "Returns the loc for the top-level form above the loc, or the loc itself if it
66 | is top-level, or nil if the loc is at the `:forms` node."
67 | [loc]
68 | (z/find loc z/up top?))
69 |
70 | (defn var-name-loc-from-op [loc]
71 | (cond
72 | (not loc)
73 | nil
74 |
75 | (= :map (-> loc z/next z/tag))
76 | (-> loc z/next z/right)
77 |
78 | (and (= :meta (-> loc z/next z/tag))
79 | (= :map (-> loc z/next z/next z/tag)))
80 | (-> loc z/next z/down z/rightmost)
81 |
82 | (= :meta (-> loc z/next z/tag))
83 | (-> loc z/next z/next z/next)
84 |
85 | :else
86 | (z/next loc)))
87 |
88 | (defn find-var-at-pos
89 | [root-zloc row col]
90 | (let [loc (find-form-at-pos root-zloc row col)]
91 | (some-> loc to-top z/next var-name-loc-from-op)))
92 |
93 | (defn to-root
94 | "Returns the loc of the root `:forms` node."
95 | [loc]
96 | (z/find loc z/up root?))
97 |
98 | (defn find-namespace [zloc]
99 | (-> (to-root zloc)
100 | (z/find-value z/next 'ns)
101 | z/next))
102 |
103 | (defn remove-metadata [zloc]
104 | (-> (string/replace zloc #"(\^\S+\s+)+" "")
105 | (string/trim)))
106 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/project.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.project
2 | (:require
3 | [clojure.java.io :as io])
4 | (:import
5 | [com.intellij.openapi.project Project]))
6 |
7 | (set! *warn-on-reflection* true)
8 |
9 | (def type-by-file
10 | {"project.clj" :lein
11 | "deps.edn" :clojure
12 | "bb.edn" :babashka
13 | "shadow-cljs.edn" :shadow-cljs
14 | "build.boot" :boot
15 | "nbb.edn" :nbb
16 | "build.gradle" :gradle
17 | "build.gradle.kts" :gradle})
18 |
19 | (def known-project-types (set (vals type-by-file)))
20 |
21 | (def types
22 | (set (vals type-by-file)))
23 |
24 | (defn ^:private list-files [project-path]
25 | (.list (io/file project-path)))
26 |
27 | (defn project->project-type [^Project project]
28 | (some
29 | #(get type-by-file %)
30 | (list-files (.getBasePath project))))
31 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/repl_command.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.repl-command
2 | (:require
3 | [babashka.fs :as fs]
4 | [clojure.java.shell :as shell]
5 | [clojure.string :as string]))
6 |
7 | (set! *warn-on-reflection* true)
8 |
9 | (def ^:private project-type->command
10 | {:lein ["lein"]
11 | :clojure ["clojure"]
12 | :babashka ["bb"]
13 | :shadow-cljs ["npx" "shadow-cljs"]
14 | :boot ["boot"]
15 | :nbb ["nbb"]
16 | :gradle ["./gradlew"]})
17 |
18 | (def ^:private middleware-versions
19 | ;; TODO make version configurable in intellij settings
20 | {"nrepl/nrepl" "1.3.1"
21 | "cider/cider-nrepl" "0.55.7"})
22 |
23 | (defn ^:private psh-cmd
24 | "Return the command vector that uses the PowerShell executable PSH to
25 | invoke CMD-AND-ARGS."
26 | ([psh & cmd-and-args]
27 | (into [psh "-NoProfile" "-Command"] cmd-and-args)))
28 |
29 | (defn ^:private locate-executable
30 | "Locate and return the full path to the EXECUTABLE."
31 | [executable]
32 | (some-> ^java.nio.file.Path (fs/which executable) .toString))
33 |
34 | (defn ^:private shell
35 | [& cmd-and-args]
36 | (println cmd-and-args)
37 | (apply shell/sh cmd-and-args))
38 |
39 | (def ^:private windows-os?
40 | (.contains (System/getProperty "os.name") "Windows"))
41 |
42 | (defn ^:private normalize-command
43 | "Return CLASSPATH-CMD, but with the EXEC expanded to its full path (if found).
44 |
45 | If the EXEC cannot be found, is one of clojure or lein and the
46 | program is running on MS-Windows, then, if possible, it tries to
47 | replace it with a PowerShell cmd invocation sequence in the
48 | following manner, while keeping ARGS the same.
49 |
50 | There could be two PowerShell executable available in the system
51 | path: powershell.exe (up to version 5.1, comes with windows) and/or
52 | pwsh.exe (versions 6 and beyond, can be installed on demand).
53 |
54 | If powershell.exe is available, it checks if the EXEC is installed
55 | in it as a module, and creates an invocation sequence as such to
56 | replace the EXEC. If not, it tries the same with pwsh.exe."
57 | [[exec & args :as cmd]]
58 | (if (and windows-os?
59 | (#{"clojure" "lein"} exec)
60 | (not (locate-executable exec)))
61 | (if-let [up (some #(when-let [ps (locate-executable %)]
62 | (when (= 0 (:exit (apply shell (psh-cmd ps "Get-Command" exec))))
63 | (psh-cmd ps exec)))
64 | ["powershell" "pwsh"])]
65 | (into up args)
66 | cmd)
67 | cmd))
68 |
69 | (defn ^:private project-type->parameters [project-type aliases jvm-args]
70 | (flatten
71 | (remove
72 | nil?
73 | (get
74 | {:lein ["update-in" ":dependencies" "conj" "[nrepl/nrepl \"%nrepl/nrepl%\"]"
75 | "--" "update-in" ":plugins" "conj" "[cider/cider-nrepl \"%cider/cider-nrepl%\"]"
76 | (when (seq jvm-args)
77 | ["--" "update-in" ":jvm-opts" "concat" (str (mapv #(str (first %) "=" (second %)) jvm-args))])
78 | "--" (when (seq aliases)
79 | ["with-profile" (str "+" (string/join ",+" aliases))])
80 | "repl" ":headless" ":host" "localhost"]
81 | :clojure [(when (seq jvm-args)
82 | (map #(str "-J" (first %) "=" (second %)) jvm-args))
83 | "-Sdeps"
84 | "{:deps {nrepl/nrepl {:mvn/version \"%nrepl/nrepl%\"} cider/cider-nrepl {:mvn/version \"%cider/cider-nrepl%\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}"
85 | (str "-M:" (string/join ":" (conj aliases "cider/nrepl")))]
86 | :babashka ["nrepl-server" "localhost:0"]
87 | :shadow-cljs ["server"]
88 | :boot ["repl" "-s" "-b" "localhost" "wait"]
89 | :nbb ["nrepl-server"]
90 | :gradle ["-Pdev.clojurephant.jack-in.nrepl=nrepl:nrepl:%nrepl/nrepl%,cider:cider-nrepl:%cider/cider-nrepl%"
91 | "clojureRepl"
92 | "--middleware=cider.nrepl/cider-middleware"]}
93 | project-type))))
94 |
95 | (defn ^:private replace-versions [middleware-versions parameters]
96 | (reduce
97 | (fn [params [middleware version]]
98 | (mapv #(string/replace % (str "%" middleware "%") version) params))
99 | parameters
100 | middleware-versions))
101 |
102 | (defn project->repl-start-command [project-type aliases jvm-args]
103 | (let [command (normalize-command (project-type->command project-type))
104 | parameters (replace-versions middleware-versions (project-type->parameters project-type aliases jvm-args))]
105 | (flatten (concat command parameters))))
106 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/tests.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.tests
2 | (:require
3 | [com.github.clojure-repl.intellij.db :as db]
4 | [com.github.clojure-repl.intellij.nrepl :as nrepl]
5 | [com.github.clojure-repl.intellij.parser :as parser]
6 | [com.github.clojure-repl.intellij.ui.hint :as ui.hint]
7 | [com.github.clojure-repl.intellij.ui.repl :as ui.repl]
8 | [com.github.ericdallo.clj4intellij.app-manager :as app-manager]
9 | [com.github.ericdallo.clj4intellij.tasks :as tasks]
10 | [com.github.ericdallo.clj4intellij.util :as util]
11 | [rewrite-clj.zip :as z])
12 | (:import
13 | [com.intellij.openapi.editor Editor]))
14 |
15 | (set! *warn-on-reflection* true)
16 |
17 | (defn ^:private run [^Editor editor ns tests]
18 | (let [project (.getProject editor)]
19 | (if-not (db/get-in project [:current-nrepl :session-id])
20 | (ui.hint/show-error :message "No REPL connected" :editor editor)
21 | ;; TODO save last result and summary
22 | (tasks/run-background-task!
23 | project
24 | "REPL: Running test"
25 | (fn [_indicator]
26 | (db/assoc-in! project [:current-nrepl :last-test] {:ns ns :tests tests})
27 | (nrepl/run-tests
28 | project
29 | {:ns ns
30 | :tests tests
31 | :on-ns-not-found (fn [ns]
32 | (app-manager/invoke-later!
33 | {:invoke-fn
34 | (fn []
35 | (ui.hint/show-error :message (format "No namespace '%s' found. Did you load the file?" ns) :editor editor))}))
36 | :on-out (fn [out]
37 | (ui.repl/append-output project out))
38 | :on-err (fn [err]
39 | (ui.repl/append-output project err))
40 | :on-failed (fn [result]
41 | ;; TODO highlight errors on editor
42 | (doseq [[key fns] (db/get-in project [:on-test-failed-fns-by-key])]
43 | (doseq [fn fns]
44 | (fn project key result))))
45 | :on-succeeded (fn [{{:keys [ns test var fail error]} :summary results :results elapsed-time :elapsed-time :as response}]
46 | (app-manager/invoke-later!
47 | {:invoke-fn
48 | (fn []
49 | (if (empty? results)
50 | (ui.hint/show-info :message "No assertions (or no tests) were run. Did you forget to use `is' in your tests?\n" :editor editor)
51 | (ui.hint/show-success :message (format "%s: Ran %s assertions, in %s test functions. %s failures, %s errors%s\n"
52 | (if (= 1 ns)
53 | (name (ffirst results))
54 | (str ns " namespaces"))
55 | test
56 | var
57 | fail
58 | error
59 | (or (some->> (:ms elapsed-time) (format " in %s ms"))
60 | ".")) :editor editor))
61 | (doseq [[key fns] (db/get-in project [:on-test-succeeded-fns-by-key])]
62 | (doseq [fn fns]
63 | (fn project key response))))}))}))))))
64 |
65 | (defn run-at-cursor [^Editor editor]
66 | (let [text (.getText (.getDocument editor))
67 | root-zloc (z/of-string text)
68 | [row col] (util/editor->cursor-position editor)
69 | ns (-> (parser/find-namespace root-zloc) z/string parser/remove-metadata)]
70 | (if-let [test (some-> (parser/find-var-at-pos root-zloc (inc row) col) z/string)]
71 | (run editor ns [test])
72 | (ui.hint/show-error :message "No test var found, did you eval the var?" :editor editor))))
73 |
74 | (defn run-ns-tests [^Editor editor]
75 | (let [text (.getText (.getDocument editor))
76 | root-zloc (z/of-string text)
77 | zloc (parser/find-namespace root-zloc)
78 | ns (parser/remove-metadata (z/string zloc))]
79 | (run editor ns nil)))
80 |
81 | (defn re-run-test [^Editor editor]
82 | (if-let [{:keys [ns tests]} (db/get-in (.getProject editor) [:current-nrepl :last-test])]
83 | (run editor ns tests)
84 | (ui.hint/show-error :message "No last test found" :editor editor)))
85 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/tool_window/repl_test.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.tool-window.repl-test
2 | (:gen-class
3 | :name com.github.clojure_repl.intellij.tool_window.ReplTestToolWindow
4 | :implements [com.intellij.openapi.wm.ToolWindowFactory
5 | com.intellij.openapi.project.DumbAware])
6 | (:require
7 | [clojure.java.io :as io]
8 | [clojure.string :as string]
9 | [clojure.walk :as walk]
10 | [com.github.clojure-repl.intellij.db :as db]
11 | [com.github.clojure-repl.intellij.nrepl :as nrepl]
12 | [com.github.clojure-repl.intellij.ui.color :as ui.color]
13 | [com.github.clojure-repl.intellij.ui.components :as ui.components]
14 | [com.github.clojure-repl.intellij.ui.font :as ui.font]
15 | [com.github.clojure-repl.intellij.ui.text :as ui.text]
16 | [com.github.ericdallo.clj4intellij.app-manager :as app-manager]
17 | [com.github.ericdallo.clj4intellij.util :as util]
18 | [com.rpl.proxy-plus :refer [proxy+]]
19 | [seesaw.core :as seesaw]
20 | [seesaw.mig :as mig])
21 | (:import
22 | [com.github.clojure_repl.intellij Icons]
23 | [com.intellij.openapi.editor Editor]
24 | [com.intellij.openapi.editor.impl EditorImpl]
25 | [com.intellij.openapi.fileEditor FileDocumentManager FileEditorManager]
26 | [com.intellij.openapi.project Project ProjectManager]
27 | [com.intellij.openapi.util.text StringUtil]
28 | [com.intellij.openapi.wm ToolWindow ToolWindowAnchor]
29 | [com.intellij.ui.components ActionLink]
30 | [com.intellij.ui.content ContentFactory$SERVICE]
31 | [com.intellij.util.ui JBFont]
32 | [java.io File]
33 | [javax.swing JComponent JScrollPane]))
34 |
35 | (set! *warn-on-reflection* true)
36 |
37 | (defn ^:private any-clj-files? [dir]
38 | (->> (io/file dir)
39 | (walk/postwalk
40 | (fn [^File f]
41 | (if (.isDirectory f)
42 | (file-seq f)
43 | [f])))
44 | (some #(and (.isFile ^File %) (.endsWith (str %) ".clj")))
45 | boolean))
46 |
47 | (defn ^:private test-result-type->color [type]
48 | (get {:pass (.getForegroundColor (ui.color/test-result-pass))
49 | :fail (.getForegroundColor (ui.color/test-result-fail))
50 | :error (.getForegroundColor (ui.color/test-result-error))}
51 | type))
52 |
53 | (defn ^:private label [key value]
54 | ;; TODO support ANSI colors for libs like matcher-combinators pretty prints.
55 | (let [code (ui.text/remove-ansi-color value)
56 | any-project (first (.getOpenProjects (ProjectManager/getInstance)))
57 | font (ui.font/code-font (ui.color/test-summary-code))]
58 | [[(seesaw/label :text key :font font :foreground (.getForegroundColor (ui.color/test-summary-label))) "alignx right, aligny top"]
59 | [(let [field (ui.components/clojure-text-field
60 | :editable? false
61 | :text code
62 | :font font
63 | :project any-project)]
64 | ;; We remove the border after the editor is built
65 | (app-manager/invoke-later! {:invoke-fn
66 | (fn []
67 | (.setBorder ^JScrollPane (.getScrollPane ^EditorImpl (.getEditor field)) nil))})
68 | field) "span"]]))
69 |
70 | (defn ^:private navigate-to-test [project {:keys [ns var]}]
71 | (let [{:keys [file line column]} (nrepl/sym-info project ns var)
72 | project (first (.getOpenProjects (ProjectManager/getInstance)))
73 | editor ^Editor (util/uri->editor file project true)
74 | offset (StringUtil/lineColToOffset (.getText (.getDocument editor)) (dec line) (dec column))
75 | file-editor-manager (FileEditorManager/getInstance project)
76 | file-document-manager (FileDocumentManager/getInstance)
77 | v-file (.getFile file-document-manager (.getDocument editor))]
78 | (.openFile file-editor-manager v-file true)
79 | (.moveToOffset (.getCaretModel editor) offset)))
80 |
81 | (defn ^:private view-error [project ns var]
82 | (ui.components/collapsible
83 | :expanded-title "Hide error"
84 | :collapsed-title "View error"
85 | :title-font (.asBold (JBFont/h3))
86 | :content (fn []
87 | (let [{:keys [class stacktrace message data]} (nrepl/test-stacktrace project ns var)
88 | code-font (ui.font/code-font (ui.color/test-summary-code))
89 | [max-file-length
90 | max-line-length] (reduce (fn [[max-f-size max-l-size] {:keys [file line]}]
91 | (let [c-lines (count (str line))
92 | c-file (count file)
93 | file-l (if (> c-file max-f-size) c-file max-f-size)
94 | line-l (if (> c-lines max-l-size) c-lines max-l-size)]
95 | [file-l line-l])) [0 0] stacktrace)
96 | stacktrace-text (reduce (fn [text {:keys [fn ns name file line]}]
97 | (format (str "%s%" max-file-length "s %" max-line-length "s %s\n")
98 | text
99 | file
100 | line
101 | (if fn (str ns "/" fn) name))) "" stacktrace)]
102 | (mig/mig-panel
103 | :constraints ["insets 0, gap 8"]
104 | :items [[(seesaw/horizontal-panel
105 | :items [(seesaw/label :text "Exception: " :font (JBFont/regular))
106 | (seesaw/label :text class
107 | :foreground (.getForegroundColor (ui.color/test-result-error))
108 | :font code-font)]) "span"]
109 | [(ui.components/clojure-text-field :text (ui.text/remove-ansi-color message)
110 | :project project
111 | :font code-font
112 | :editable? false) "span"]
113 | [(ui.components/clojure-text-field :text (ui.text/pretty-printed-clojure-text data)
114 | :project project
115 | :font code-font
116 | :editable? false) "span"]
117 | ;; TODO improve this component to support clickable links for file-uri
118 | [(ui.components/clojure-text-field :text stacktrace-text
119 | :project project
120 | :font code-font
121 | :editable? false) "span"]])))))
122 |
123 | (defn ^:private test-report-content ^JComponent [^Project project vars]
124 | (seesaw/scrollable
125 | (mig/mig-panel
126 | :items
127 | (remove
128 | nil?
129 | (for [[_var tests] vars]
130 | (let [non-passing (remove #(= "pass" (:type %)) tests)]
131 | (when (seq non-passing)
132 | [(mig/mig-panel
133 | :items
134 | (for [{:keys [ns var context type message expected actual diffs error gen-input] :as test} non-passing]
135 | [(mig/mig-panel
136 | :items
137 | (->> [[(seesaw/flow-panel
138 | :vgap 0
139 | :hgap 0
140 | :items
141 | [(seesaw/label :text (string/capitalize type)
142 | :foreground (test-result-type->color (keyword type)))
143 | (seesaw/label :text " in ")
144 | (ActionLink. ^String var (proxy+ [] java.awt.event.ActionListener
145 | (actionPerformed [_ _] (navigate-to-test project test))))]) "span"]
146 | (when (seq context) [(seesaw/label :text (str context)) "span"])
147 | (when (seq message) [(seesaw/label :text (str message)) "span"])
148 | (when (seq expected)
149 | (label "expected:" expected))
150 | (if (seq diffs)
151 | (for [[actual [removed added]] diffs]
152 | [(label "actual:" actual)
153 | (label "diff:" (str "- " removed))
154 | (label "" (str "+ " added))])
155 |
156 | (when (seq actual)
157 | (label "actual:" actual)))
158 | (when (seq error)
159 | [(label "error:" error)
160 | [(view-error project ns var) "span"]])
161 | (when (seq gen-input)
162 | (label "input: " gen-input))]
163 | flatten
164 | (remove nil?)
165 | (partition 2)
166 | vec)) "span"])) "span"])))))
167 | :border nil))
168 |
169 | (defn ^:private test-report-title-summary [summary elapsed-time]
170 | (let [failed (when (> (:fail summary) 0) (:fail summary))
171 | error (when (> (:error summary) 0) (:error summary))
172 | ms (:ms elapsed-time)]
173 | (cond-> ""
174 | failed
175 | (str failed " failed")
176 |
177 | (and failed error)
178 | (str ", ")
179 |
180 | error
181 | (str error " errors")
182 |
183 | ms
184 | (str " in " ms "ms"))))
185 |
186 | (defn ^:private on-test-failed [^Project project ^ToolWindow tool-window {:keys [results summary elapsed-time]}]
187 | (when-not (.isDisposed tool-window)
188 | (let [content-factory (ContentFactory$SERVICE/getInstance)
189 | content-manager (.getContentManager tool-window)]
190 | (app-manager/invoke-later!
191 | {:invoke-fn (fn []
192 | (.removeAllContents content-manager false)
193 | (doseq [[_ vars] results]
194 | (.addContent content-manager
195 | (.createContent content-factory
196 | (test-report-content project vars)
197 | (test-report-title-summary summary elapsed-time)
198 | false)))
199 | (.setAvailable tool-window true)
200 | (.show tool-window))}))))
201 |
202 | (defn ^:private on-test-succeeded [^Project _project ^ToolWindow tool-window _]
203 | (when-not (.isDisposed tool-window)
204 | (.removeAllContents (.getContentManager tool-window) false)
205 | (.setAvailable tool-window false)
206 | (.hide tool-window)))
207 |
208 | (set! *warn-on-reflection* false)
209 | (defn -init [_ ^ToolWindow tool-window]
210 | (let [project (.getProject tool-window)]
211 | (db/update-in! project [:on-test-failed-fns-by-key tool-window] #(conj % #'on-test-failed))
212 | (db/update-in! project [:on-test-succeeded-fns-by-key tool-window] #(conj % #'on-test-succeeded))))
213 | (set! *warn-on-reflection* true)
214 |
215 | (defn -manager [_ _ _])
216 |
217 | (defn -isApplicableAsync
218 | ([_ ^Project project]
219 | (any-clj-files? (.getBasePath project)))
220 | ([_ ^Project project _]
221 | (-isApplicableAsync _ project)))
222 |
223 | (def -isApplicable -isApplicableAsync)
224 |
225 | (defn -shouldBeAvailable [_ _] false)
226 |
227 | (defn -createToolWindowContent [_ _ _])
228 |
229 | (defn -getIcon [_] Icons/CLOJURE_REPL)
230 |
231 | (defn -getAnchor [_] ToolWindowAnchor/BOTTOM)
232 |
233 | (defn -manage [_ _ _ _])
234 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/ui/color.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.ui.color
2 | (:import
3 | [com.intellij.openapi.editor.colors EditorColorsManager TextAttributesKey]
4 | [com.intellij.openapi.editor.markup TextAttributes]
5 | [com.intellij.ui JBColor]
6 | [com.intellij.util.ui UIUtil]
7 | [com.intellij.xdebugger.ui DebuggerColors]
8 | [java.awt Color Font]))
9 |
10 | (set! *warn-on-reflection* true)
11 |
12 | (defn ^:private attr ^TextAttributesKey [& {:keys [key foreground background effect effect-type font-type inherit]
13 | :or {font-type Font/PLAIN}}]
14 | (let [inherit-attrs (some-> ^TextAttributesKey inherit (.getDefaultAttributes) (.clone))
15 | ^TextAttributes attrs (or inherit-attrs (TextAttributes.))]
16 | (when background (.setBackgroundColor attrs ^Color background))
17 | (when foreground (.setForegroundColor attrs ^Color foreground))
18 | (when effect (.setEffectColor attrs ^Color effect))
19 | (when effect-type (.setEffectType attrs effect-type))
20 | (when font-type (.setFontType attrs font-type))
21 | (TextAttributesKey/createTextAttributesKey ^String key attrs)))
22 |
23 | (defn text-attributes []
24 | {:repl-window (attr :key "REPL_WINDOW"
25 | :background (JBColor/background))
26 | :eval-inline-hint (attr :key "REPL_EVAL_INLINE_INLAY_HINT"
27 | :background (JBColor. (Color/decode "#c7e8fc") (Color/decode "#16598c"))
28 | :inherit DebuggerColors/INLINED_VALUES_EXECUTION_LINE)
29 | :test-summary--label (attr :key "REPL_TEST_SUMMARY_LABEL"
30 | :foreground JBColor/GRAY)
31 | :test-summary--code (attr :key "REPL_TEST_SUMMARY_CODE")
32 |
33 | :test-result--error (attr :key "REPL_TEST_RESULT_ERROR"
34 | :foreground JBColor/RED)
35 | :test-result--fail (attr :key "REPL_TEST_RESULT_FAIL"
36 | :foreground JBColor/RED)
37 | :test-result--pass (attr :key "REPL_TEST_RESULT_PASS"
38 | :foreground (UIUtil/getToolTipForeground))})
39 |
40 | (defn ^:private global-attribute-for ^TextAttributes [key]
41 | (.. (EditorColorsManager/getInstance)
42 | getGlobalScheme
43 | (getAttributes (key (text-attributes)))))
44 |
45 | (defn test-result-pass ^TextAttributes [] (global-attribute-for :test-result--pass))
46 | (defn test-result-fail ^TextAttributes [] (global-attribute-for :test-result--fail))
47 | (defn test-result-error ^TextAttributes [] (global-attribute-for :test-result--error))
48 | (defn test-summary-label ^TextAttributes [] (global-attribute-for :test-summary--label))
49 | (defn test-summary-code ^TextAttributes [] (global-attribute-for :test-summary--code))
50 | (defn repl-window ^TextAttributes [] (global-attribute-for :repl-window))
51 | (defn eval-inline-hint ^TextAttributes [] (global-attribute-for :eval-inline-hint))
52 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/ui/components.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.ui.components
2 | (:require
3 | [com.github.clojure-repl.intellij.keyboard-manager :as key-manager]
4 | [com.rpl.proxy-plus :refer [proxy+]]
5 | [seesaw.core :as seesaw]
6 | [seesaw.layout :as s.layout]
7 | [seesaw.mig :as mig])
8 | (:import
9 | [com.intellij.icons AllIcons$General]
10 | [com.intellij.openapi.editor EditorFactory]
11 | [com.intellij.openapi.editor.ex EditorEx]
12 | [com.intellij.openapi.fileTypes FileTypeManager]
13 | [com.intellij.ui EditorSettingsProvider EditorTextField IdeBorderFactory]
14 | [java.awt Font]
15 | [java.awt.event ActionEvent]
16 | [javax.swing JButton JComponent]))
17 |
18 | (set! *warn-on-reflection* true)
19 |
20 | (defn clojure-text-field ^EditorTextField
21 | [& {:keys [id background-color text project editable? font on-key-pressed]
22 | :or {editable? true}}]
23 | (let [document (.createDocument (EditorFactory/getInstance) ^String text)
24 | clojure-file-type (.getStdFileType (FileTypeManager/getInstance) "clojure")
25 | editor-text-field (EditorTextField. document project clojure-file-type (not editable?) false)]
26 | (.addSettingsProvider
27 | editor-text-field
28 | (proxy+ [] EditorSettingsProvider
29 | (customizeSettings [_ ^EditorEx editor]
30 | (.setVerticalScrollbarVisible editor true)
31 | (.setHorizontalScrollbarVisible editor true)
32 | (key-manager/register-listener-for-editor!
33 | {:editor editor
34 | :on-key-pressed on-key-pressed}))))
35 | (when id (seesaw/config! editor-text-field :id id))
36 | (when background-color (.setBackground editor-text-field background-color))
37 | (when font (.setFont editor-text-field font))
38 | (when on-key-pressed (.putClientProperty editor-text-field :on-key-pressed on-key-pressed))
39 | editor-text-field))
40 |
41 | (defn collapsible ^JComponent
42 | [& {:keys [collapsed-title expanded-title content ^Font title-font]}]
43 | (let [content-panel (seesaw/vertical-panel :visible? false :items [])
44 | toggle ^JButton (seesaw/toggle
45 | :text collapsed-title
46 | :icon AllIcons$General/ArrowRight
47 | :selected? false)]
48 | (when title-font (.setFont toggle title-font))
49 | (.setBorder toggle (IdeBorderFactory/createBorder (.getBackground toggle)))
50 | (seesaw/listen toggle :action (fn [_]
51 | (let [toggled? (seesaw/config toggle :selected?)
52 | icon (if toggled? AllIcons$General/ArrowDown AllIcons$General/ArrowRight)
53 | text (if toggled? expanded-title collapsed-title)]
54 | (seesaw/config! toggle
55 | :icon icon
56 | :text text)
57 | (seesaw/config! content-panel :visible? toggled?)
58 | (when toggled?
59 | (seesaw/config! content-panel :items [(content)])))))
60 | (mig/mig-panel :constraints ["insets 0"]
61 | :items [[toggle "span"]
62 | [content-panel "gapx 10"]])))
63 |
64 | (defn add-field-to-multi-field! [ui group-id initial-text]
65 | (let [group ^JComponent (seesaw/select ui [(keyword (str "#" (name group-id)))])
66 | prefix (.getClientProperty group :prefix-id)
67 | columns (.getClientProperty group :columns)
68 | new-id (.getComponentCount group)
69 | text-id (keyword (str prefix new-id))
70 | text-ui (seesaw/text :id text-id :text initial-text :columns columns)]
71 | (s.layout/add-widget group text-ui)
72 | (s.layout/handle-structure-change group)))
73 |
74 | (defn multi-field ^JComponent
75 | [& {:keys [label group-id button-id prefix-id initial-text columns]
76 | :or {columns 15}}]
77 | (let [group-component ^JComponent (mig/mig-panel
78 | :constraints ["gap 0"]
79 | :id group-id
80 | :items [])]
81 | (.putClientProperty group-component :columns columns)
82 | (.putClientProperty group-component :prefix-id prefix-id)
83 | [[(seesaw/label label) ""]
84 | [group-component "gap 0"]
85 | [(seesaw/button
86 | :id button-id
87 | :size [36 :by 36]
88 | :text "+"
89 | :listen [:action (fn [^ActionEvent event]
90 | (let [button (.getSource event)
91 | ui (.getParent ^JComponent button)]
92 | (add-field-to-multi-field! ui group-id initial-text)))]) "gap 0, wrap"]]))
93 |
94 | (defn field-values-from-multi-field [ui group-id]
95 | (->> (.getComponents ^JComponent (seesaw/select ui [(keyword (str "#" (name group-id)))]))
96 | (mapv seesaw/text)
97 | (filterv not-empty)))
98 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/ui/font.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.ui.font
2 | (:import
3 | [com.intellij.openapi.editor Editor]
4 | [com.intellij.openapi.editor.colors EditorColorsManager EditorFontType]
5 | [com.intellij.openapi.editor.markup TextAttributes]
6 | [com.intellij.util.ui UIUtil]
7 | [java.awt Font]))
8 |
9 | (set! *warn-on-reflection* true)
10 |
11 | (defn code-font ^Font
12 | ([^TextAttributes base-text-attr]
13 | (code-font base-text-attr nil))
14 | ([^TextAttributes base-text-attr ^Editor editor]
15 | (UIUtil/getFontWithFallback
16 | (.getFont (if editor
17 | (.getColorsScheme editor)
18 | (.getGlobalScheme (EditorColorsManager/getInstance)))
19 | (EditorFontType/forJavaStyle (.getFontType base-text-attr))))))
20 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/ui/hint.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.ui.hint
2 | (:require
3 | [com.github.clojure-repl.intellij.app-info :as app-info])
4 | (:import
5 | [com.intellij.codeInsight.hint
6 | HintManager]
7 | [com.intellij.openapi.editor Editor]))
8 |
9 | (set! *warn-on-reflection* true)
10 |
11 | (defn show-info
12 | [& {:keys [message editor prefix]
13 | :or {prefix ""}}]
14 | (.showInformationHint (HintManager/getInstance) ^Editor editor (str prefix message) HintManager/RIGHT))
15 |
16 | (defn show-error
17 | [& {:keys [message editor prefix]
18 | :or {prefix ""}}]
19 | (.showErrorHint (HintManager/getInstance) ^Editor editor (str prefix message) HintManager/RIGHT))
20 |
21 | (set! *warn-on-reflection* false)
22 | (defn show-success
23 | [& {:keys [message ^Editor editor prefix]
24 | :or {prefix ""}}]
25 | (if (app-info/at-least-version? "233.9802.14")
26 | (.showSuccessHint (HintManager/getInstance) ^Editor editor (str prefix message) HintManager/RIGHT)
27 | (.showInformationHint (HintManager/getInstance) ^Editor editor (str prefix message) HintManager/RIGHT)))
28 | (set! *warn-on-reflection* true)
29 |
30 | (defn show-repl-info [& {:as args}]
31 | (show-info (assoc args :prefix "=> " :message (or (:message args) "nil"))))
32 |
33 | (defn show-repl-error [& {:as args}]
34 | (show-error (assoc args :prefix "=> ")))
35 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/ui/inlay_hint.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.ui.inlay-hint
2 | (:require
3 | [com.github.clojure-repl.intellij.ui.color :as ui.color]
4 | [com.github.clojure-repl.intellij.ui.components :as ui.components]
5 | [com.github.clojure-repl.intellij.ui.font :as font]
6 | [com.github.clojure-repl.intellij.ui.text :as ui.text]
7 | [com.rpl.proxy-plus :refer [proxy+]]
8 | [seesaw.core :as seesaw])
9 | (:import
10 | [com.intellij.ide.highlighter HighlighterFactory]
11 | [com.intellij.ide.ui.laf.darcula DarculaUIUtil]
12 | [com.intellij.lang Language]
13 | [com.intellij.openapi.editor Editor EditorCustomElementRenderer Inlay]
14 | [com.intellij.openapi.editor.colors EditorFontType]
15 | [com.intellij.openapi.editor.impl EditorImpl FontInfo]
16 | [com.intellij.openapi.editor.markup TextAttributes]
17 | [com.intellij.openapi.fileEditor FileDocumentManager]
18 | [com.intellij.openapi.fileTypes SyntaxHighlighterFactory]
19 | [com.intellij.openapi.ui.popup JBPopupFactory]
20 | [com.intellij.ui SimpleColoredText SimpleTextAttributes]
21 | [com.intellij.util.ui GraphicsUtil JBUI$CurrentTheme$ActionButton]
22 | [java.awt
23 | Cursor
24 | FontMetrics
25 | Graphics
26 | Graphics2D
27 | Rectangle
28 | RenderingHints]
29 | [java.awt.geom RoundRectangle2D$Float]))
30 |
31 | (set! *warn-on-reflection* true)
32 |
33 | (defonce ^:private inlays* (atom {}))
34 |
35 | (defn renderer-class [] (try (Class/forName "com.github.clojure_repl.intellij.ui.inlay_hint.EvalInlineInlayHintRenderer") (catch Exception _ nil)))
36 |
37 | (defn ^:private font+metrics [^Inlay inlay]
38 | (let [editor (.getEditor inlay)
39 | font (font/code-font (ui.color/eval-inline-hint) editor)
40 | metrics (FontInfo/getFontMetrics font (FontInfo/getFontRenderContext (.getContentComponent editor)))]
41 | [font metrics]))
42 |
43 | (defn ^:private clojure-colored-text ^SimpleColoredText
44 | [^Editor editor ^String text]
45 | (let [language (or (Language/findLanguageByID "clojure")
46 | (Language/findLanguageByID "textmate"))
47 | scheme (.getColorsScheme editor)
48 | v-file (.getFile (FileDocumentManager/getInstance) (.getDocument editor))
49 | syntax-highlighter (SyntaxHighlighterFactory/getSyntaxHighlighter language (.getProject editor) v-file)
50 | highlighter (HighlighterFactory/createHighlighter syntax-highlighter scheme)]
51 | (.setText highlighter text)
52 | (let [colored-text (SimpleColoredText.)
53 | iterator (.createIterator highlighter 0)]
54 | (while (not (.atEnd iterator))
55 | (let [start (.getStart iterator)
56 | end (.getEnd iterator)
57 | keys (.getTextAttributesKeys iterator)
58 | attributes (if (seq keys)
59 | (SimpleTextAttributes/fromTextAttributes (.getAttributes scheme (first keys)))
60 | SimpleTextAttributes/REGULAR_ATTRIBUTES)
61 | token-text (subs text start end)]
62 | (.append colored-text token-text attributes))
63 | (.advance iterator))
64 | colored-text)))
65 |
66 | (defn ^:private paint-clojure-text
67 | [^Graphics2D g ^String text ^Rectangle r ^Editor editor ^FontMetrics font-metrics {:keys [background-x margin]}]
68 | (let [text-x* (atom (int (+ background-x margin)))
69 | presentation (clojure-colored-text editor text)]
70 | (doall
71 | (for [i (range (count (.getTexts presentation)))
72 | :let [cur-text ^String (nth (.getTexts presentation) i)
73 | attr ^SimpleTextAttributes (nth (.getAttributes presentation) i)]]
74 | (do
75 | (.setColor g (.getFgColor attr))
76 | (.drawString g cur-text ^Integer @text-x* (+ (.y r) (.getAscent editor)))
77 | (reset! text-x* (+ @text-x* (.stringWidth font-metrics cur-text))))))))
78 |
79 | (defn ^:private remove-in-cur-line [^Editor editor]
80 | (let [inlays (.getAfterLineEndElementsForLogicalLine
81 | (.getInlayModel editor)
82 | (.. editor getCaretModel getLogicalPosition line))]
83 | (doseq [^Inlay inlay inlays]
84 | (.dispose inlay)
85 | (swap! inlays* dissoc (.getRenderer inlay)))))
86 |
87 | (defn ^:private remove-inlay [^Inlay inlay]
88 | (.dispose inlay)
89 | (swap! inlays* dissoc (.getRenderer inlay)))
90 |
91 | (defn ^:private paint-background-rect
92 | [^Graphics2D g {:keys [background-x background-y background-width background-height]}]
93 | (let [config (GraphicsUtil/setupAAPainting g)]
94 | (GraphicsUtil/paintWithAlpha g 0.55)
95 | (.setColor g (.getBackgroundColor (ui.color/eval-inline-hint)))
96 | (.fillRoundRect g
97 | background-x
98 | background-y
99 | background-width
100 | background-height
101 | 6
102 | 6)
103 | (.restore config)))
104 |
105 | (defn ^:private paint-hovered-background [^Graphics2D g {:keys [background-x background-y background-width background-height]}]
106 | (let [^Graphics2D g2 (.create g)
107 | arc (.getFloat DarculaUIUtil/BUTTON_ARC)]
108 | (.setRenderingHint g2 RenderingHints/KEY_ANTIALIASING RenderingHints/VALUE_ANTIALIAS_ON)
109 | (.setRenderingHint g2 RenderingHints/KEY_STROKE_CONTROL RenderingHints/VALUE_STROKE_NORMALIZE)
110 | (.setColor g2 (JBUI$CurrentTheme$ActionButton/hoverBackground))
111 | (.fill g2 (RoundRectangle2D$Float. background-x background-y background-width background-height arc arc))
112 | (.dispose g2)))
113 |
114 | (defn ^:private paint
115 | [^String text ^Inlay inlay ^Graphics2D g ^Rectangle r ^TextAttributes _text-attributes]
116 | (let [editor (.getEditor inlay)
117 | [font metrics] (font+metrics inlay)
118 | space (.charWidth ^FontMetrics metrics \s)
119 | gap 1
120 | ui-opts {:space space
121 | :gap gap
122 | :margin (/ space 3)
123 | :background-x (+ (.x r) space)
124 | :background-y (+ gap (.y r))
125 | :background-width (- (.width r) space)
126 | :background-height (- (.height r) (* 2 gap))}
127 |
128 | {:keys [hovering?]} (get @inlays* (.getRenderer inlay))]
129 | (.setFont g font)
130 | (paint-background-rect g ui-opts)
131 | (when hovering?
132 | (paint-hovered-background g ui-opts))
133 | (paint-clojure-text g text r editor metrics ui-opts)))
134 |
135 | (defn ^:private create-renderer [^String text ^Editor editor]
136 | (let [inlay-model (.getInlayModel editor)
137 | offset (.getOffset (.getCaretModel editor))
138 | renderer (proxy+ EvalInlineInlayHintRenderer []
139 | EditorCustomElementRenderer
140 | (calcWidthInPixels [_ ^Inlay _inlay]
141 | (let [font-metrics (.getFontMetrics (.getComponent editor)
142 | (.getFont (.getColorsScheme editor) EditorFontType/PLAIN))
143 | space (.charWidth font-metrics \s)
144 | margin (/ space 2)]
145 | (+ margin space (.stringWidth font-metrics text))))
146 | (paint [_ ^Inlay inlay ^Graphics g ^Rectangle r ^TextAttributes text-attributes]
147 | (paint text inlay g r text-attributes)))]
148 | (.addAfterLineEndElement inlay-model offset false renderer)
149 | renderer))
150 |
151 | (defn remove-all [^Editor editor]
152 | (when-let [renderer-class (renderer-class)]
153 | (doseq [^Inlay inlay (.getAfterLineEndElementsInRange
154 | (.getInlayModel editor)
155 | 0
156 | (.. editor getDocument getTextLength)
157 | renderer-class)]
158 | (remove-inlay inlay))))
159 |
160 | (defn mark-inlay-hover-status [^Inlay inlay hovered?]
161 | (when (get-in @inlays* [(.getRenderer inlay) :expandable?])
162 | (let [cursor (if hovered? (Cursor/getPredefinedCursor Cursor/HAND_CURSOR) nil)]
163 | (swap! inlays* assoc-in [(.getRenderer inlay) :hovering?] hovered?)
164 | (.setCustomCursor ^EditorImpl (.getEditor inlay) (renderer-class) cursor)
165 | (.update inlay))))
166 |
167 | (defn toggle-expand-inlay-hint [^Inlay inlay]
168 | (let [{:keys [expandable? text]} (get @inlays* (.getRenderer inlay))
169 | [font _] (font+metrics inlay)
170 | editor (.getEditor inlay)]
171 | (when expandable?
172 | (let [component (seesaw/scrollable
173 | (ui.components/clojure-text-field
174 | :project (.. inlay getEditor getProject)
175 | :editable? false
176 | :text text
177 | :font font)
178 | :maximum-size [800 :by 600])
179 | popup (.createPopup
180 | (doto
181 | (.createComponentPopupBuilder (JBPopupFactory/getInstance) component component)
182 | (.setResizable false)
183 | (.setRequestFocus true)
184 | (.setCancelOnClickOutside true)
185 | (.setMovable false)))]
186 | (.showInBestPositionFor popup editor)))))
187 |
188 | (defn show-code [^String text ^Editor editor]
189 | (remove-in-cur-line editor)
190 | (let [display-limit 100 ;; Move to customizable config
191 | expandable? (> (count text) display-limit)
192 | summary-text (if expandable? (str (subs text 0 display-limit) " ...") text)
193 | renderer (create-renderer summary-text editor)]
194 | (swap! inlays* assoc renderer {:hovering? false
195 | :expandable? expandable?
196 | :text (ui.text/pretty-printed-clojure-text text)
197 | :summary-text summary-text})))
198 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/ui/repl.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.ui.repl
2 | (:require
3 | [clojure.string :as string]
4 | [com.github.clojure-repl.intellij.db :as db]
5 | [com.github.clojure-repl.intellij.keyboard-manager :as key-manager]
6 | [com.github.clojure-repl.intellij.ui.color :as ui.color]
7 | [com.github.clojure-repl.intellij.ui.components :as ui.components]
8 | [com.github.clojure-repl.intellij.ui.font :as ui.font]
9 | [com.github.clojure-repl.intellij.ui.text :as ui.text]
10 | [com.github.ericdallo.clj4intellij.app-manager :as app-manager]
11 | [seesaw.core :as seesaw]
12 | [seesaw.mig :as mig])
13 | (:import
14 | [com.intellij.openapi.editor ScrollType]
15 | [com.intellij.openapi.editor.ex EditorEx]
16 | [com.intellij.openapi.project Project]
17 | [com.intellij.ui EditorTextField]
18 | [java.awt.event InputEvent KeyEvent]
19 | [java.time.format DateTimeFormatter]))
20 |
21 | (set! *warn-on-reflection* true)
22 |
23 | (defn ^:private extract-input+code-to-eval [cur-ns ^String repl-content-text]
24 | (let [input (str cur-ns "> ")]
25 | (or (when-let [input+eval-text (some->> (re-seq (re-pattern (str cur-ns "> .*")) repl-content-text)
26 | last
27 | (string/last-index-of repl-content-text)
28 | (subs repl-content-text))]
29 | [input (string/trim-newline (string/replace input+eval-text input ""))])
30 | [input ""])))
31 |
32 | (defn ^:private refresh-repl-text
33 | [^Project project]
34 | (let [repl-content ^EditorTextField (seesaw/select (db/get-in project [:console :ui]) [:#repl-content])
35 | input-text (db/get-in project [:console :state :last-input])
36 | output-text (db/get-in project [:console :state :last-output])
37 | final-text (if input-text
38 | (str output-text "\n" input-text)
39 | output-text)]
40 | (app-manager/invoke-later!
41 | {:invoke-fn (fn [] (.setText repl-content final-text))})))
42 |
43 | (defn ^:private set-output
44 | [^Project project output-text]
45 | (db/assoc-in! project [:console :state :last-output] (ui.text/remove-ansi-color output-text))
46 | (refresh-repl-text project))
47 |
48 | (defn append-output
49 | [^Project project append-text]
50 | (let [last-output (db/get-in project [:console :state :last-output])]
51 | (set-output project (str last-output append-text))))
52 |
53 | (defn ^:private set-temp-input
54 | [^Project project temp-input]
55 | (let [repl-content ^EditorTextField (seesaw/select (db/get-in project [:console :ui]) [:#repl-content])
56 | input-text (db/get-in project [:console :state :last-input])
57 | output-text (db/get-in project [:console :state :last-output])
58 | final-text (str output-text "\n" input-text temp-input)]
59 | (app-manager/invoke-later!
60 | {:invoke-fn (fn [] (.setText repl-content final-text))})))
61 |
62 | (defn clear-input [project]
63 | (let [ns-text (str (db/get-in project [:current-nrepl :ns]) "> ")]
64 | (db/assoc-in! project [:console :state :last-input] ns-text)
65 | (refresh-repl-text project)))
66 |
67 | (defn ^:private move-caret-and-scroll-to-latest [^EditorTextField repl-content]
68 | (app-manager/invoke-later!
69 | {:invoke-fn (fn []
70 | (when-let [editor ^EditorEx (.getEditor repl-content)]
71 | (.moveToOffset (.getCaretModel editor) (.getTextLength (.getDocument repl-content)))
72 | (.scrollToCaret (.getScrollingModel editor) ScrollType/MAKE_VISIBLE)))}))
73 |
74 | (defn ^:private on-repl-input [project on-eval ^KeyEvent event]
75 | (.consume event)
76 | (let [repl-content ^EditorTextField (seesaw/select (db/get-in project [:console :ui]) [:#repl-content])
77 | repl-content-text (.getText repl-content)
78 | cur-ns (db/get-in project [:current-nrepl :ns])
79 | [input code-to-eval] (extract-input+code-to-eval cur-ns repl-content-text)
80 | entries (db/get-in project [:current-nrepl :entry-history])]
81 | (db/assoc-in! project [:current-nrepl :entry-index] -1)
82 | (when-not (or (string/blank? code-to-eval)
83 | (= code-to-eval (first entries)))
84 | (db/update-in! project [:current-nrepl :entry-history] #(conj % code-to-eval)))
85 | (let [{:keys [value ns] :as response} (on-eval code-to-eval)
86 | result-text (str
87 | "\n" input code-to-eval (when value "\n")
88 | (when value (str "=> " (last value))))]
89 | (append-output project result-text)
90 | (move-caret-and-scroll-to-latest repl-content)
91 | (when (and ns (not= ns cur-ns))
92 | (db/assoc-in! project [:current-nrepl :ns] ns)
93 | (doseq [fn (db/get-in project [:on-ns-changed-fns])]
94 | (fn project response)))))
95 | true)
96 |
97 | (defn history-up
98 | [project]
99 | (let [entries (db/get-in project [:current-nrepl :entry-history])
100 | current-index (db/get-in project [:current-nrepl :entry-index])]
101 | (when (and (pos? (count entries))
102 | (< current-index (dec (count entries))))
103 | (let [entry (nth entries (inc current-index))
104 | console (db/get-in project [:console :ui])
105 | repl-content (seesaw/select console [:#repl-content])]
106 | (db/update-in! project [:current-nrepl :entry-index] inc)
107 | (set-temp-input project entry)
108 | (move-caret-and-scroll-to-latest repl-content)))))
109 |
110 | (defn history-down
111 | [project]
112 | (let [entries (db/get-in project [:current-nrepl :entry-history])
113 | current-index (db/get-in project [:current-nrepl :entry-index])]
114 | (when (and (pos? (count entries))
115 | (> current-index 0))
116 | (let [entry (nth entries (dec current-index))
117 | console (db/get-in project [:console :ui])
118 | repl-content (seesaw/select console [:#repl-content])]
119 | (db/update-in! project [:current-nrepl :entry-index] dec)
120 | (set-temp-input project entry)
121 | (move-caret-and-scroll-to-latest repl-content)))))
122 |
123 | (defn ^:private on-repl-new-line [project]
124 | (append-output project "\n")
125 | true)
126 |
127 | (defn clear-repl [^Project project]
128 | (set-output project ";; Output cleared"))
129 |
130 | (defn ^:private on-repl-clear [project]
131 | (clear-repl project)
132 | true)
133 |
134 | (defn ^:private on-repl-backspace [project ^KeyEvent event]
135 | (let [ns (db/get-in project [:current-nrepl :ns])
136 | repl-content ^EditorTextField (seesaw/select (db/get-in project [:console :ui]) [:#repl-content])
137 | repl-lines (string/split-lines (.getText repl-content))
138 | last-repl-line (last repl-lines)
139 | repl-input (re-find (re-pattern (str ns "+>\\s")) last-repl-line)]
140 | (when-not repl-input
141 | (clear-input project)
142 | (move-caret-and-scroll-to-latest repl-content)
143 | (.consume event))))
144 |
145 | (defn build-console [project {:keys [initial-text on-eval]}]
146 | (db/assoc-in! project [:console :state :status] :disabled)
147 | (db/assoc-in! project [:console :state :initial-text] initial-text)
148 | (db/assoc-in! project [:console :state :last-output] "")
149 | (db/assoc-in! project [:console :state :last-input] nil)
150 | (mig/mig-panel
151 | :id :repl-input-layout
152 | :constraints ["fill, insets 0"]
153 | :background (.getBackgroundColor (ui.color/repl-window))
154 | :items [[(ui.components/clojure-text-field
155 | :id :repl-content
156 | :project project
157 | :text ""
158 | :background-color (.getBackgroundColor (ui.color/repl-window))
159 | :font (ui.font/code-font (ui.color/repl-window))
160 | :on-key-pressed (fn [^KeyEvent event]
161 | (if (identical? :enabled (db/get-in project [:console :state :status]))
162 | (let [ctrl? (not= 0 (bit-and (.getModifiers event) InputEvent/CTRL_MASK))
163 | shift? (not= 0 (bit-and (.getModifiers event) InputEvent/SHIFT_MASK))
164 | enter? (= KeyEvent/VK_ENTER (.getKeyCode event))
165 | backspace? (= KeyEvent/VK_BACK_SPACE (.getKeyCode event))
166 | l? (= KeyEvent/VK_L (.getKeyCode event))]
167 | (cond
168 |
169 | (and shift? enter?)
170 | (on-repl-new-line project)
171 |
172 | (and enter? (not shift?))
173 | (on-repl-input project on-eval event)
174 |
175 | (and ctrl? l?)
176 | (on-repl-clear project)
177 |
178 | backspace?
179 | (on-repl-backspace project event)))
180 | false)))
181 | "grow"]]))
182 |
183 | (defn set-repl-started-initial-text [project console text]
184 | (let [output (str text "\n")
185 | repl-content (seesaw/select console [:#repl-content])]
186 | (db/assoc-in! project [:console :state :status] :enabled)
187 | (db/assoc-in! project [:console :state :initial-text] text)
188 | (append-output project output)
189 | (clear-input project)
190 | (refresh-repl-text project)
191 | (move-caret-and-scroll-to-latest repl-content)))
192 |
193 | (def ^:private ^DateTimeFormatter time-formatter (DateTimeFormatter/ofPattern "dd/MM/yyyy HH:mm:ss"))
194 |
195 | (defn close-console [project console]
196 | (let [repl-content ^EditorTextField (seesaw/select console [:#repl-content])]
197 | (db/assoc-in! project [:console :state :status] :disabled)
198 | (db/assoc-in! project [:console :state :last-input] nil)
199 | (.setViewer repl-content false)
200 | (append-output project
201 | (format "\n*** Closed on %s ***" (.format time-formatter (java.time.LocalDateTime/now))))
202 | (key-manager/unregister-listener-for-editor! (.getEditor repl-content))))
203 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/clojure_repl/intellij/ui/text.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.ui.text
2 | (:require
3 | [clojure.edn :as edn]
4 | [clojure.pprint :as pprint]
5 | [clojure.string :as string]
6 | [com.github.ericdallo.clj4intellij.logger :as logger]))
7 |
8 | (defn pretty-printed-clojure-text [text]
9 | (try
10 | (with-out-str (pprint/pprint (edn/read-string {:default (fn [tag value]
11 | (if (coll? value)
12 | (pretty-printed-clojure-text value)
13 | (symbol (str "#" tag value))))} text)))
14 | (catch Exception e
15 | (logger/warn "Can't parse clojure code for eval block" e)
16 | text)))
17 |
18 | (defn remove-ansi-color ^String [text]
19 | ;; TODO support ANSI colors for libs like matcher-combinators pretty prints.
20 | (string/replace text #"\u001B\[[;\d]*m" ""))
21 |
--------------------------------------------------------------------------------
/src/main/dev-resources/META-INF/clj4intellij.edn:
--------------------------------------------------------------------------------
1 | {:nrepl {:port 7770}}
2 |
--------------------------------------------------------------------------------
/src/main/dev-resources/META-INF/clojure-repl-intellij.edn:
--------------------------------------------------------------------------------
1 | {:nrepl-debug true}
2 |
--------------------------------------------------------------------------------
/src/main/kotlin/icons.kt:
--------------------------------------------------------------------------------
1 | package com.github.clojure_repl.intellij
2 |
3 | import com.intellij.openapi.util.IconLoader
4 | import javax.swing.Icon
5 |
6 | object Icons {
7 | @JvmField val CLOJURE = IconLoader.getIcon("/icons/clojure.svg", Icons::class.java)
8 | @JvmField val CLOJURE_REPL = IconLoader.getIcon("/icons/clojure_repl.svg", Icons::class.java)
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/kotlin/run-configuration-options.kt:
--------------------------------------------------------------------------------
1 | package com.github.clojure_repl.intellij.configuration
2 |
3 | import com.intellij.execution.configurations.RunConfigurationOptions
4 |
5 | class ReplRemoteRunOptions : RunConfigurationOptions() {
6 |
7 | private var nreplHostOption = string("localhost").provideDelegate(this, "nreplHost")
8 | private var nreplPortOption = string("").provideDelegate(this, "nreplPort")
9 | private var projectOption = string("").provideDelegate(this, "project")
10 | private var modeOption = string("manual-config").provideDelegate(this, "mode")
11 |
12 | var nreplHost: String
13 | get() = nreplHostOption.getValue(this) ?: "localhost"
14 | set(value) = nreplHostOption.setValue(this, value)
15 |
16 | var nreplPort: String
17 | get() = nreplPortOption.getValue(this) ?: ""
18 | set(value) = nreplPortOption.setValue(this, value)
19 |
20 | var project: String
21 | get() = projectOption.getValue(this) ?: ""
22 | set(value) = projectOption.setValue(this, value)
23 |
24 | var mode: String
25 | get() = modeOption.getValue(this) ?: "manual-config"
26 | set(value) = modeOption.setValue(this, value)
27 | }
28 |
29 | class ReplLocalRunOptions : RunConfigurationOptions() {
30 |
31 | private var projectOption = string("").provideDelegate(this, "project")
32 | private var projectTypeOption = string("").provideDelegate(this, "projectType")
33 | private var aliasesOption = list().provideDelegate(this, "aliases")
34 | private var envVarsOption = list().provideDelegate(this, "envVars")
35 | private var jvmArgsOption = list().provideDelegate(this, "jvmArgs")
36 |
37 | var project: String
38 | get() = projectOption.getValue(this) ?: ""
39 | set(value) = projectOption.setValue(this, value)
40 |
41 | var projectType: String?
42 | get() = projectTypeOption.getValue(this)
43 | set(value) = projectTypeOption.setValue(this, value)
44 |
45 | var aliases: MutableList
46 | get() = aliasesOption.getValue(this)
47 | set(value) = aliasesOption.setValue(this, value)
48 |
49 | var envVars: MutableList
50 | get() = envVarsOption.getValue(this)
51 | set(value) = envVarsOption.setValue(this, value)
52 |
53 | var jvmArgs: MutableList
54 | get() = jvmArgsOption.getValue(this)
55 | set(value) = jvmArgsOption.setValue(this, value)
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | com.github.clojure-repl
3 | Clojure REPL
4 | Arthur Fücher
5 |
6 | com.intellij.modules.platform
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
16 |
17 |
22 |
23 |
24 |
25 |
26 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/pluginIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
80 |
--------------------------------------------------------------------------------
/src/main/resources/icons/clojure-mono.svg:
--------------------------------------------------------------------------------
1 |
2 |
44 |
--------------------------------------------------------------------------------
/src/main/resources/icons/clojure.svg:
--------------------------------------------------------------------------------
1 |
2 |
73 |
--------------------------------------------------------------------------------
/src/main/resources/icons/clojure_repl.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
80 |
--------------------------------------------------------------------------------
/src/scripts/scripts.clj:
--------------------------------------------------------------------------------
1 | (ns scripts
2 | (:require
3 | [babashka.fs :as fs]
4 | [babashka.tasks :refer [shell]]
5 | [clojure.string :as string]))
6 |
7 | (def version-regex #"pluginVersion = ([0-9]+.[0-9]+.[0-9]+.*)")
8 |
9 | (defn ^:private replace-in-file [file regex content]
10 | (as-> (slurp file) $
11 | (string/replace $ regex content)
12 | (spit file $)))
13 |
14 | (defn ^:private add-changelog-entry [tag comment]
15 | (replace-in-file "CHANGELOG.md"
16 | #"## \[Unreleased\]"
17 | (if comment
18 | (format "## [Unreleased]\n\n## %s\n\n- %s" tag comment)
19 | (format "## [Unreleased]\n\n## %s" tag))))
20 |
21 | (defn ^:private replace-tag [tag]
22 | (replace-in-file "gradle.properties"
23 | version-regex
24 | (format "pluginVersion = %s" tag)))
25 |
26 | #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
27 | (defn tag [& [tag]]
28 | (shell "git fetch origin")
29 | (shell "git pull origin HEAD")
30 | (replace-tag tag)
31 | (add-changelog-entry tag nil)
32 | (shell "git add gradle.properties CHANGELOG.md")
33 | (shell (format "git commit -m \"Release: %s\"" tag))
34 | (shell (str "git tag " tag))
35 | (shell "git push origin HEAD")
36 | (shell "git push origin --tags"))
37 |
38 | #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
39 | (defn tests []
40 | (shell "./gradlew test"))
41 |
42 | #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
43 | (defn build-plugin []
44 | (shell "./gradlew buildPlugin"))
45 |
46 | #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
47 | (defn run-ide []
48 | (shell "./gradlew runIde"))
49 |
50 | #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
51 | (defn install-plugin [& [intellij-plugins-path]]
52 | (if-not intellij-plugins-path
53 | (println "Specify the Intellij plugins path\ne.g: bb install-plugin /home/youruser/.local/share/JetBrains/IdeaIC2024.3")
54 | (let [version (last (re-find version-regex (slurp "gradle.properties")))]
55 | (build-plugin)
56 | (fs/unzip (format "./build/distributions/clojure-repl-%s.zip" version)
57 | intellij-plugins-path
58 | {:replace-existing true})
59 | (println "Installed!"))))
60 |
61 | #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
62 | (defn publish-plugin []
63 | (shell "./gradlew publishPlugin"))
64 |
--------------------------------------------------------------------------------
/src/test/clojure/com/github/clojure_repl/intellij/repl_command_test.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.repl-command-test
2 | (:require
3 | [clojure.test :refer [deftest is testing]]
4 | [com.github.clojure-repl.intellij.repl-command :as repl-command]))
5 |
6 | (deftest project->repl-start-command-test
7 | (testing "clojure"
8 | (is (= ["clojure"
9 | "-Sdeps"
10 | "{:deps {nrepl/nrepl {:mvn/version \"1.3.1\"} cider/cider-nrepl {:mvn/version \"0.55.7\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}"
11 | "-M:cider/nrepl"]
12 | (repl-command/project->repl-start-command :clojure [] [])))
13 | (is (= ["clojure"
14 | "-Sdeps"
15 | "{:deps {nrepl/nrepl {:mvn/version \"1.3.1\"} cider/cider-nrepl {:mvn/version \"0.55.7\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}"
16 | "-M:foo-b:bar:cider/nrepl"]
17 | (repl-command/project->repl-start-command :clojure ["foo-b" "bar"] [])))
18 | (testing "windows powershell"
19 | (with-redefs [repl-command/windows-os? (constantly true)
20 | repl-command/locate-executable (fn [cmd]
21 | (when (= "powershell" cmd)
22 | "/full/path/powershell"))
23 | repl-command/shell (constantly {:exit 0})]
24 | (is (= ["/full/path/powershell" "-NoProfile" "-Command" "clojure"
25 | "-Sdeps"
26 | "{:deps {nrepl/nrepl {:mvn/version \"1.3.1\"} cider/cider-nrepl {:mvn/version \"0.55.7\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}"
27 | "-M:cider/nrepl"]
28 | (repl-command/project->repl-start-command :clojure [] [])))))
29 | (testing "windows pwsh"
30 | (with-redefs [repl-command/windows-os? (constantly true)
31 | repl-command/locate-executable (fn [cmd]
32 | (when (= "pwsh" cmd)
33 | "/full/path/pwsh"))
34 | repl-command/shell (constantly {:exit 0})]
35 | (is (= ["/full/path/pwsh" "-NoProfile" "-Command" "clojure"
36 | "-Sdeps"
37 | "{:deps {nrepl/nrepl {:mvn/version \"1.3.1\"} cider/cider-nrepl {:mvn/version \"0.55.7\"}} :aliases {:cider/nrepl {:main-opts [\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}}}"
38 | "-M:cider/nrepl"]
39 | (repl-command/project->repl-start-command :clojure [] []))))))
40 | (testing "lein"
41 | (is (= ["lein" "update-in" ":dependencies" "conj" "[nrepl/nrepl \"1.3.1\"]"
42 | "--" "update-in" ":plugins" "conj" "[cider/cider-nrepl \"0.55.7\"]"
43 | "--" "repl" ":headless" ":host" "localhost"]
44 | (repl-command/project->repl-start-command :lein [] [])))
45 | (is (= ["lein" "update-in" ":dependencies" "conj" "[nrepl/nrepl \"1.3.1\"]"
46 | "--" "update-in" ":plugins" "conj" "[cider/cider-nrepl \"0.55.7\"]"
47 | "--" "with-profile" "+foo-b,+bar" "repl" ":headless" ":host" "localhost"]
48 | (repl-command/project->repl-start-command :lein ["foo-b" "bar"] [])))
49 | (testing "windows powershell"
50 | (with-redefs [repl-command/windows-os? (constantly true)
51 | repl-command/locate-executable (fn [cmd]
52 | (when (= "powershell" cmd)
53 | "/full/path/powershell"))
54 | repl-command/shell (constantly {:exit 0})]
55 | (is (= ["/full/path/powershell" "-NoProfile" "-Command" "lein"
56 | "update-in" ":dependencies" "conj" "[nrepl/nrepl \"1.3.1\"]"
57 | "--" "update-in" ":plugins" "conj" "[cider/cider-nrepl \"0.55.7\"]"
58 | "--" "repl" ":headless" ":host" "localhost"]
59 | (repl-command/project->repl-start-command :lein [] [])))))
60 | (testing "windows pwsh"
61 | (with-redefs [repl-command/windows-os? (constantly true)
62 | repl-command/locate-executable (fn [cmd]
63 | (when (= "pwsh" cmd)
64 | "/full/path/pwsh"))
65 | repl-command/shell (constantly {:exit 0})]
66 | (is (= ["/full/path/pwsh" "-NoProfile" "-Command" "lein"
67 | "update-in" ":dependencies" "conj" "[nrepl/nrepl \"1.3.1\"]"
68 | "--" "update-in" ":plugins" "conj" "[cider/cider-nrepl \"0.55.7\"]"
69 | "--" "repl" ":headless" ":host" "localhost"]
70 | (repl-command/project->repl-start-command :lein [] []))))))
71 | (testing "babashka"
72 | (is (= ["bb" "nrepl-server" "localhost:0"]
73 | (repl-command/project->repl-start-command :babashka [] []))))
74 | (testing "shadow-cljs"
75 | (is (= ["npx" "shadow-cljs" "server"]
76 | (repl-command/project->repl-start-command :shadow-cljs [] []))))
77 | (testing "boot"
78 | (is (= ["boot" "repl" "-s" "-b" "localhost" "wait"]
79 | (repl-command/project->repl-start-command :boot [] []))))
80 | (testing "nbb"
81 | (is (= ["nbb" "nrepl-server"]
82 | (repl-command/project->repl-start-command :nbb [] []))))
83 | (testing "gradle"
84 | (is (= ["./gradlew"
85 | "-Pdev.clojurephant.jack-in.nrepl=nrepl:nrepl:1.3.1,cider:cider-nrepl:0.55.7"
86 | "clojureRepl"
87 | "--middleware=cider.nrepl/cider-middleware"]
88 | (repl-command/project->repl-start-command :gradle [] [])))))
89 |
--------------------------------------------------------------------------------
/src/test/clojure/com/github/clojure_repl/intellij/repl_eval_test.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.repl-eval-test
2 | (:require
3 | [clojure.string :as str]
4 | [clojure.test :refer [deftest is testing]]
5 | [com.github.clojure-repl.intellij.configuration.factory.local :as config.factory.local]
6 | [com.github.clojure-repl.intellij.db :as db]
7 | [com.github.ericdallo.clj4intellij.app-manager :as app-manager]
8 | [com.github.ericdallo.clj4intellij.test :as clj4intellij.test]
9 | [seesaw.core :as seesaw])
10 | (:import
11 | [com.github.clojure_repl.intellij.configuration ReplRunConfigurationType]
12 | [com.intellij.execution ProgramRunnerUtil RunManager]
13 | [com.intellij.execution.executors DefaultRunExecutor]
14 | [com.intellij.openapi.util ThrowableComputable]
15 | [com.intellij.testFramework EditorTestUtil EdtTestUtil]
16 | [java.awt.event KeyEvent]))
17 |
18 | (set! *warn-on-reflection* true)
19 |
20 | ;;Move to a helper inside tests folder
21 | (defn ^:private repl-content [project]
22 | (-> project
23 | (db/get-in [:console :ui])
24 | (seesaw/select [:#repl-content])))
25 |
26 | (defn ensure-editor
27 | "Ensure the editor was created in the UI thread"
28 | [project]
29 | (let [repl-content (repl-content project)]
30 | @(app-manager/invoke-later!
31 | {:invoke-fn (fn []
32 | (.addNotify repl-content)
33 | (.getEditor repl-content true))})))
34 |
35 | (defn wait-console-ui-creation
36 | "Waits until the console UI is set in the db*, then ensures the editor is created"
37 | [project]
38 | @(clj4intellij.test/dispatch-all-until
39 | {:cond-fn (fn [] (-> project
40 | (db/get-in [:console :ui])))})
41 | (ensure-editor project))
42 |
43 | (defn execute-configuration
44 | "API for ProgramRunnerUtil/executeConfiguration
45 |
46 | ref: https://github.com/JetBrains/intellij-community/blob/2766d0bf1cec76c0478244f6ad5309af527c245e/platform/execution-impl/src/com/intellij/execution/ProgramRunnerUtil.java#L46"
47 | [configuration-instance]
48 | (ProgramRunnerUtil/executeConfiguration
49 | configuration-instance
50 | (DefaultRunExecutor/getRunExecutorInstance)))
51 |
52 | (defn eval-code-on-repl [repl-content text]
53 | (let [editor (.getEditor repl-content)
54 | component (.getContentComponent editor)
55 | key-enter-event (KeyEvent. component KeyEvent/KEY_PRESSED (System/currentTimeMillis) 0 KeyEvent/VK_ENTER Character/MIN_VALUE)]
56 | (EdtTestUtil/runInEdtAndGet
57 | (reify ThrowableComputable
58 | (compute [_]
59 | (doseq [char text]
60 | (EditorTestUtil/performTypingAction editor char))
61 | (.dispatchEvent component key-enter-event)
62 | nil)))))
63 |
64 | (deftest repl-eval-test
65 | (let [project-name "clojure.core"
66 | fixture (clj4intellij.test/setup project-name)
67 | deps-file (.createFile fixture "deps.edn" "{}")
68 | project (.getProject fixture)]
69 | (is (= project-name (.getName project)))
70 | (is deps-file)
71 |
72 | (app-manager/write-command-action
73 | project
74 | (fn [] (.openFileInEditor fixture deps-file)))
75 |
76 | (let [run-manager (RunManager/getInstance project)
77 | configuration (config.factory.local/configuration-factory (ReplRunConfigurationType.))
78 | configuration-instance (.createConfiguration run-manager "Local REPL" configuration)]
79 | (doto (-> configuration-instance .getConfiguration .getOptions)
80 | (.setProject project-name)
81 | (.setProjectType "clojure"))
82 | (execute-configuration configuration-instance))
83 |
84 | (wait-console-ui-creation project)
85 |
86 | (testing "user input evaluation"
87 | (let [repl-content (repl-content project)]
88 |
89 | @(clj4intellij.test/dispatch-all-until
90 | {:cond-fn (fn [] (str/ends-with? (.getText repl-content) "user> "))})
91 |
92 | (eval-code-on-repl repl-content "(+ 1 1)\n")
93 | (clj4intellij.test/dispatch-all)
94 |
95 | (let [content (.getText repl-content)]
96 | (is (str/ends-with? content "user> (+ 1 1)\n=> 2\nuser> ")))))))
97 |
98 |
--------------------------------------------------------------------------------
/src/test/clojure/com/github/clojure_repl/intellij/ui/repl_test.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.clojure-repl.intellij.ui.repl-test
2 | (:require
3 | [clojure.string :as string]
4 | [clojure.test :refer [deftest is]]
5 | [com.github.clojure-repl.intellij.ui.repl :as ui.repl]))
6 |
7 | (defn code [& strings]
8 | (string/join "\n" strings))
9 |
10 | (deftest extract-code-to-eval-test
11 | (is (= ["user> " "(+ 2 3)"]
12 | (#'ui.repl/extract-input+code-to-eval "user"
13 | (code ";; some text here"
14 | "user> (+ 1 2)"
15 | ";; => 1"
16 | "user> (+ 2 3)"))))
17 | (is (= ["user> " "(tap> 1)"]
18 | (#'ui.repl/extract-input+code-to-eval "user"
19 | (code ";; some text here"
20 | "user> 1"
21 | ";; => 1"
22 | "user> (tap> 1)"))))
23 | (is (= ["user-bar.baz> " "(tap> 1)"]
24 | (#'ui.repl/extract-input+code-to-eval "user-bar.baz"
25 | (code ";; some text here"
26 | "user-bar.baz> 1"
27 | ";; => 1"
28 | "user-bar.baz> (tap> 1)"))))
29 | (is (= ["user-bar.baz> " (code "(defn foo []"
30 | " 123)")]
31 | (#'ui.repl/extract-input+code-to-eval "user-bar.baz"
32 | (code ";; some text here"
33 | "user-bar.baz> 1"
34 | ";; => 1"
35 | "user-bar.baz> (defn foo []"
36 | " 123)"))))
37 | (is (= ["user> " (code "(defn foo []"
38 | " (tap> 123)"
39 | " 123)")]
40 | (#'ui.repl/extract-input+code-to-eval "user"
41 | (code ";; some text here"
42 | "user> 1"
43 | ";; => 1"
44 | "user> (defn foo []"
45 | " (tap> 123)"
46 | " 123)"))))
47 | (is (= ["user-bar.baz> " (code "(defn foo []"
48 | " (tap> 123)"
49 | " 123)")]
50 | (#'ui.repl/extract-input+code-to-eval "user-bar.baz"
51 | (code ";; some text here"
52 | "user-bar.baz> 1"
53 | ";; => 1"
54 | "user-bar.baz> (defn foo []"
55 | " (tap> 123)"
56 | " 123)"))))
57 | (is (= ["user-bar.baz> " "(+ 2 3)"]
58 | (#'ui.repl/extract-input+code-to-eval "user-bar.baz"
59 | (code ";; some text here"
60 | "user-bar.baz> "
61 | "user-bar.baz> (+ 2 3)"))))
62 | (is (= ["user> " "1"]
63 | (#'ui.repl/extract-input+code-to-eval "user"
64 | (code ";; some text here"
65 | "user> 1"
66 | "=> 1"
67 | "user> 1")))))
68 |
69 | (comment
70 | (extract-code-to-eval-test))
71 |
--------------------------------------------------------------------------------