├── .clj-kondo
├── babashka
│ └── fs
│ │ └── config.edn
├── config.edn
├── http-kit
│ └── http-kit
│ │ ├── config.edn
│ │ └── httpkit
│ │ └── with_channel.clj
└── rewrite-clj
│ └── rewrite-clj
│ └── config.edn
├── .dir-locals.el
├── .gitignore
├── .lsp
└── config.edn
├── CHANGELOG.md
├── LICENSE
├── README.md
├── bb.edn
├── build.gradle.kts
├── deps.edn
├── doc
└── intellij-plugin-development.md
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── pom.properties
├── pom.xml
├── settings.gradle.kts
└── src
├── main
├── clojure
│ └── com
│ │ └── github
│ │ └── ericdallo
│ │ └── clj4intellij
│ │ ├── action.clj
│ │ ├── app_manager.clj
│ │ ├── config.clj
│ │ ├── extension.clj
│ │ ├── extension
│ │ └── nrepl_startup.clj
│ │ ├── listener
│ │ └── class_loader.clj
│ │ ├── logger.clj
│ │ ├── tasks.clj
│ │ ├── test.clj
│ │ └── util.clj
├── java
│ └── com
│ │ └── github
│ │ └── ericdallo
│ │ └── clj4intellij
│ │ └── ClojureClassLoader.java
└── resources
│ ├── clj-kondo.exports
│ └── ericdallo
│ │ └── clj4intellij
│ │ ├── clj4intellij
│ │ └── proxy_plus.clj
│ │ └── config.edn
│ └── clojure-lsp.exports
│ └── ericdallo
│ └── clj4intellij
│ └── config.edn
└── scripts
└── ci.clj
/.clj-kondo/babashka/fs/config.edn:
--------------------------------------------------------------------------------
1 | {:lint-as {babashka.fs/with-temp-dir clojure.core/let}}
2 |
--------------------------------------------------------------------------------
/.clj-kondo/config.edn:
--------------------------------------------------------------------------------
1 | {:config-paths ["ericdallo/clj4intellij"]}
2 |
--------------------------------------------------------------------------------
/.clj-kondo/http-kit/http-kit/config.edn:
--------------------------------------------------------------------------------
1 |
2 | {:hooks
3 | {:analyze-call {org.httpkit.server/with-channel httpkit.with-channel/with-channel}}}
4 |
--------------------------------------------------------------------------------
/.clj-kondo/http-kit/http-kit/httpkit/with_channel.clj:
--------------------------------------------------------------------------------
1 | (ns httpkit.with-channel
2 | (:require [clj-kondo.hooks-api :as api]))
3 |
4 | (defn with-channel [{node :node}]
5 | (let [[request channel & body] (rest (:children node))]
6 | (when-not (and request channel) (throw (ex-info "No request or channel provided" {})))
7 | (when-not (api/token-node? channel) (throw (ex-info "Missing channel argument" {})))
8 | (let [new-node
9 | (api/list-node
10 | (list*
11 | (api/token-node 'let)
12 | (api/vector-node [channel (api/vector-node [])])
13 | request
14 | body))]
15 |
16 | {:node new-node})))
17 |
--------------------------------------------------------------------------------
/.clj-kondo/rewrite-clj/rewrite-clj/config.edn:
--------------------------------------------------------------------------------
1 | {:lint-as
2 | {rewrite-clj.zip/subedit-> clojure.core/->
3 | rewrite-clj.zip/subedit->> clojure.core/->>
4 | rewrite-clj.zip/edit-> clojure.core/->
5 | rewrite-clj.zip/edit->> clojure.core/->>}}
6 |
--------------------------------------------------------------------------------
/.dir-locals.el:
--------------------------------------------------------------------------------
1 | ((clojure-mode
2 | (cider-clojure-cli-aliases . "dev")))
3 |
--------------------------------------------------------------------------------
/.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 | target
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 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [Unreleased]
4 |
5 | ## 0.8.0
6 |
7 | - Drop support of older IntelliJ versions (2021/2022). Now requires minimum IntelliJ 2023.3 (Build 233)
8 | - Bump JAVA min version to 17
9 | - Add support for tests.
10 |
11 | ## 0.7.1
12 |
13 | - Fix clojure-lsp hook
14 |
15 | ## 0.7.0
16 |
17 | - Create `def-extension` to create plugin.xml extension points easily and more idiomatic.
18 |
19 | ## 0.6.3
20 |
21 | - Add clj-kondo hook for proxy+.
22 |
23 | ## 0.6.0
24 |
25 | - Add unregister-action! and improve register-action!
26 |
27 | ## 0.5.4
28 |
29 | - Fix keyboard shortcut side effect from 0.5.3 change.
30 |
31 | ## 0.5.3
32 |
33 | - Fix keyboard shortcut register to not always add the default shortcut.
34 |
35 | ## 0.5.2
36 |
37 | - Register actions only if not registered before.
38 |
39 | ## 0.5.1
40 |
41 | - Support multiple keystrokes on action keymap.
42 |
43 | ## 0.5.0
44 |
45 | - Add `com.github.ericdallo.clj4intellij.action` ns to register actions dynamically.
46 |
47 | ## 0.4.0
48 |
49 | - Add tasks and util namespace.
50 |
51 | ## 0.3.7
52 |
53 | ## 0.3.6
54 |
55 | ## 0.3.5
56 |
57 | ## 0.3.4
58 |
59 | - Allow skip nrepl server setup if nrepl key is nullable.
60 |
61 | ## 0.3.3
62 |
63 | - Fix prod jar
64 |
65 | ## 0.3.2
66 |
67 | - Include clojure code to jar.
68 |
69 | ## 0.3.1
70 |
71 | - Support custom nrepl ports
72 |
73 | ## 0.3.0
74 |
75 | - Add NREPL and logger support.
76 |
77 | ## 0.2.1
78 |
79 | - Include clojure source to jar.
80 |
81 | ## 0.2.0
82 |
83 | - Add `com.github.ericdallo.clj4intellij.app-manager` namespace with functions to access `ApplicationManager`.
84 |
85 | ## 0.1.3
86 |
87 | - First release
88 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Eric Dallo
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://clojars.org/com.github.ericdallo/clj4intellij)
2 |
3 | # clj4intellij
4 |
5 | Library for create IntelliJ plugins with Clojure.
6 |
7 | ## How to use it?
8 |
9 | Configure [clojurephant](https://clojurephant.dev) first:
10 |
11 | `build.gradle.kts`
12 | ```kotlin
13 | plugins {
14 | ...
15 | id("dev.clojurephant.clojure") version "VERSION"
16 | }
17 | clojure.builds.named("main") {
18 | classpath.from(sourceSets.main.get().runtimeClasspath.asPath)
19 | checkAll()
20 | aotAll()
21 | reflection.set("fail")
22 | }
23 | ```
24 |
25 | Add clj4intellij as dependency:
26 |
27 | `build.gradle.kts`
28 | ```kotlin
29 | repositories {
30 | ...
31 | maven {
32 | name = "Clojars"
33 | url = uri("https://repo.clojars.org")
34 | }
35 | }
36 | dependencies {
37 | ...
38 | implementation ("com.github.ericdallo:clj4intellij:VERSION")
39 | }
40 | ```
41 |
42 | Add an application listener that will change the classloader on IntelliJ startup to load your plugin Clojure code:
43 |
44 | `src/main/resources/META-INF/plugin.xml`
45 | ```xml
46 |
47 |
49 |
50 | ```
51 |
52 | Now you can create clojure namespaces in your sourcepath (ex `src/main/clojure`), use clj4intellij helpers to create extensions like `def-extension` or implement yourself extensions using Clojure's `gen-class`.
53 |
54 | ### Repl support
55 |
56 | Add this extension and after startup a random port will be logged in IntelliJ's log, then you can connect from any editor to that port to development:
57 |
58 | `src/main/resources/META-INF/plugin.xml`
59 | ```xml
60 |
61 |
62 |
63 | ```
64 |
65 | You can specify a port to always be used in the clj4intellij config file:
66 |
67 | `src/main/resources/META-INF/clj4intellij.edn`
68 | ```xml
69 | {:nrepl {:port 9876}}
70 | ```
71 |
72 | ### Logging
73 |
74 | There is the `com.github.ericdallo.clj4intellij.logger` ns which can be used to log messages to intelliJ's log via Clojure.
75 |
76 | ## How it works?
77 |
78 | This plugin has classes required to make possible code in Clojure a Intellij plugin changing the classloader at IntelliJ's startup.
79 |
80 | Also contains useful functions for a more Clojure idiomatic development avoid the directly use of java or Intellij API.
81 |
82 | > For more information about plugin development, read the [IntelliJ Plugin development](./doc/intellij-plugin-development.md) guide.
83 |
84 | ## Useful namespaces
85 |
86 | - `com.github.ericdallo.clj4intellij.app-manager` to handle `Application` calls.
87 | - `com.github.ericdallo.clj4intellij.action` to register actions dynamically.
88 |
89 | ## Plugins using clj4intellij
90 |
91 | - [clojure-lsp-intellij](https://github.com/clojure-lsp/clojure-lsp-intellij)
92 | - [clojure-repl-intellij](https://github.com/afucher/clojure-repl-intellij)
93 |
94 | # Support
95 |
96 | Consider support the work of this project [here](https://github.com/sponsors/ericdallo) ❤️
97 |
--------------------------------------------------------------------------------
/bb.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src/scripts"]
2 | :tasks {tag ci/tag
3 | clean (shell "./gradlew clean")
4 | install ci/install
5 | deploy ci/deploy
6 | build ci/build}}
7 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | fun properties(key: String) = project.findProperty(key).toString()
2 |
3 | plugins {
4 | `maven-publish`
5 | id("org.jetbrains.kotlin.jvm") version "1.9.0"
6 | id("dev.clojurephant.clojure") version "0.7.0"
7 | id("org.jetbrains.intellij") version "1.15.0"
8 | }
9 |
10 | group = properties("group")
11 | version = properties("version")
12 |
13 | publishing {
14 | repositories {
15 | maven {
16 | // change to point to your repo, e.g. http://my.org/repo
17 | url = uri("https://repo.clojars.org")
18 | credentials {
19 | username = System.getenv("CLOJARS_USERNAME")
20 | password = System.getenv("CLOJARS_PASSWORD")
21 | }
22 | }
23 | }
24 | publications {
25 | create("maven") {
26 | from(components["java"])
27 | pom {
28 | licenses {
29 | license {
30 | name.set("MIT License")
31 | url.set("http://www.opensource.org/licenses/mit-license.php")
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
39 | repositories {
40 | mavenLocal()
41 | mavenCentral()
42 | maven {
43 | name = "Clojars"
44 | url = uri("https://repo.clojars.org")
45 | }
46 | }
47 |
48 | dependencies {
49 | implementation ("org.clojure:clojure:1.11.1")
50 | implementation ("com.rpl:proxy-plus:0.0.9")
51 | implementation ("camel-snake-kebab:camel-snake-kebab:0.4.3")
52 | implementation ("nrepl:nrepl:1.0.0")
53 | }
54 |
55 | sourceSets {
56 | main {
57 | java.setSrcDirs(listOf("src/main/java"))
58 | resources.setSrcDirs(listOf("src/main/resources"))
59 | }
60 | }
61 |
62 | // Useful to override another IC platforms from env
63 | val platformVersion = System.getenv("PLATFORM_VERSION") ?: properties("platformVersion")
64 | val platformPlugins = System.getenv("PLATFORM_PLUGINS") ?: properties("platformPlugins")
65 |
66 | intellij {
67 | pluginName.set(properties("name"))
68 | version.set(platformVersion)
69 | type.set(properties("platformType"))
70 | updateSinceUntilBuild.set(false)
71 | }
72 |
73 | java {
74 | targetCompatibility = JavaVersion.VERSION_17
75 | sourceCompatibility = JavaVersion.VERSION_17
76 | }
77 |
78 |
79 | tasks.register("classpath") {
80 | doFirst {
81 | println(sourceSets["main"].compileClasspath.asPath)
82 | }
83 | }
84 |
85 | tasks {
86 | compileKotlin {
87 | kotlinOptions {
88 | jvmTarget = "11"
89 | apiVersion = "1.5"
90 | languageVersion = "1.5"
91 | freeCompilerArgs = listOf("-Xjvm-default=all")
92 | }
93 | }
94 |
95 | wrapper {
96 | gradleVersion = properties("gradleVersion")
97 | }
98 |
99 | buildSearchableOptions {
100 | enabled = false
101 | }
102 | }
103 |
104 | clojure.builds.named("main") {
105 | classpath.from(sourceSets.main.get().runtimeClasspath.asPath)
106 | checkAll()
107 | aotAll()
108 | reflection.set("fail")
109 | }
110 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src/main/clojure" "target/classes" "resources"]}
2 |
--------------------------------------------------------------------------------
/doc/intellij-plugin-development.md:
--------------------------------------------------------------------------------
1 | # IntelliJ plugin development
2 |
3 | This docs covers the basic concepts of IntelliJ plugin development.
4 |
5 |
6 | ## Overview about IntelliJ
7 |
8 | This doc covers some common terms and short descriptions about key concepts. To go deeper and learn more, IntelliJ has a official documentation about [plugin development](https://plugins.jetbrains.com/docs/intellij/welcome.html).
9 |
10 | In some cases just the docs are not enough, so you can check the [IntelliJ community source code](https://github.com/JetBrains/intellij-community), to see the implementation code, and also find some useful examples of usage.
11 |
12 | ## IntelliJ Concepts
13 |
14 | ### Project
15 |
16 | A project encapsulates all of a project's source code, libraries, and build instructions into a single organizational unit.
17 |
18 | > Everything in the IntelliJ Platform SDK is done within the context of a project.
19 |
20 | A project defines collections referred to as Modules and Libraries. Depending on the project's logical and functional requirements, a single-module or a multi-module project can be created.
21 |
22 | Reference: [Project | IntelliJ Platform Plugin SDK](https://plugins.jetbrains.com/docs/intellij/project-model.html#project)
23 |
24 | ### Editor
25 |
26 | Represents an instance of a text editor.
27 |
28 | Once you have the Editor you can get information about Project, File and Document (handles the text of the editor).
29 |
30 | ### Actions
31 |
32 | The Action System allows plugins to add their items to IntelliJ Platform-based IDE menus and toolbars.
33 | Actions in the IntelliJ Platform require a code implementation and must be registered. The action implementation determines the contexts in which an action is available and its functionality when selected in the UI. Registration determines where an action appears in the IDE UI. Once implemented and registered, an action receives callbacks from the IntelliJ Platform in response to user gestures.
34 |
35 | Reference: [Action System | IntelliJ Platform Plugin SDK](https://plugins.jetbrains.com/docs/intellij/action-system.html)
36 |
37 | ### Run Configurations
38 |
39 | The IntelliJ Platform Execution API allows running external processes from within the IDE, e.g., the REPL connection.
40 |
41 | The name of that concept is Configurations (or Run Configurations), where the user can create/edit configurations using the UI or that can be run based on implementations created from the code.
42 |
43 | ## Clojure and Java/Kotlin
44 |
45 | IntelliJ is written in Java and Kotlin, so to develop a plugin and extends behaviors from IntelliJ you must write clases to be instantiated by IntelliJ.
46 | Since Clojure has interop with Java, the plugin development relays a lot in the interoperability:
47 | - working with IntelliJ Java/Kotlin classes;
48 | - generating classes using `:gen-class`;
49 | - making use of `reify`, `proxy` and `proxy+` to extend/instantiate classes;
50 |
51 | [clj4intellij](https://github.com/ericdallo/clj4intellij) was created to abstract some of those needs
52 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | group = com.github.ericdallo
2 | name = clj4intellij
3 | version = 0.8.0
4 |
5 | # IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties
6 | platformType = IC
7 | platformVersion = 2023.3
8 |
9 | # Gradle Releases -> https://github.com/gradle/gradle/releases
10 | gradleVersion = 7.6.1
11 |
12 | # Opt-out flag for bundling Kotlin standard library.
13 | # See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details.
14 | # suppress inspection "UnusedProperty"
15 | kotlin.stdlib.default.dependency = false
16 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ericdallo/clj4intellij/3ea9054586cbfde94f4bf53840c491aea74459cc/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 |
--------------------------------------------------------------------------------
/pom.properties:
--------------------------------------------------------------------------------
1 | # Generated by org.clojure/tools.build
2 | # Thu Sep 07 15:58:07 BRT 2023
3 | version=0.1.2
4 | groupId=com.github.ericdallo
5 | artifactId=clj4intellij
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | jar
5 | com.github.ericdallo
6 | clj4intellij
7 | 0.1.2
8 | clj4intellij
9 |
10 |
11 | org.clojure
12 | clojure
13 | 1.11.1
14 |
15 |
16 | com.rpl
17 | proxy-plus
18 | 0.0.9
19 |
20 |
21 |
22 | src
23 |
24 |
25 | resources
26 |
27 |
28 |
29 |
30 |
31 | clojars
32 | https://repo.clojars.org/
33 |
34 |
35 | intellij-1
36 | https://cache-redirector.jetbrains.com/intellij-dependencies
37 |
38 |
39 | intellij-2
40 | https://www.jetbrains.com/intellij-repository/releases
41 |
42 |
43 |
44 | 0.1.2
45 |
46 |
47 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "clj4intellij"
2 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/ericdallo/clj4intellij/action.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.ericdallo.clj4intellij.action
2 | (:require
3 | [com.rpl.proxy-plus :refer [proxy+]])
4 | (:import
5 | [com.intellij.openapi.actionSystem
6 | ActionManager
7 | Anchor
8 | Constraints
9 | DefaultActionGroup
10 | KeyboardShortcut]
11 | [com.intellij.openapi.keymap KeymapManager]
12 | [com.intellij.openapi.project DumbAwareAction]
13 | [javax.swing Icon KeyStroke]))
14 |
15 | (set! *warn-on-reflection* true)
16 |
17 | (defn register-action!
18 | "Dynamically register an action if not already registered."
19 | [& {:keys [id title description icon use-shortcut-of keyboard-shortcut on-performed action]
20 | :or {action #_{:clj-kondo/ignore [:destructured-or-binding-of-same-map]}
21 | (proxy+
22 | [^String title ^String description ^Icon icon]
23 | DumbAwareAction
24 | (actionPerformed [_ event] (on-performed event)))}}]
25 | (let [manager (ActionManager/getInstance)
26 | keymap-manager (KeymapManager/getInstance)
27 | keymap (.getActiveKeymap keymap-manager)]
28 | (when-not (.getAction manager id)
29 | (.registerAction manager id action)
30 | (when use-shortcut-of
31 | (.addShortcut keymap
32 | id
33 | (first (.getShortcuts (.getShortcutSet (.getAction manager use-shortcut-of))))))
34 | (when keyboard-shortcut
35 | (let [k-shortcut (KeyboardShortcut. (KeyStroke/getKeyStroke ^String (:first keyboard-shortcut))
36 | (some-> ^String (:second keyboard-shortcut) KeyStroke/getKeyStroke))]
37 | (when (empty? (.getShortcuts keymap id))
38 | (.addShortcut keymap id k-shortcut))
39 | (when (:replace-all keyboard-shortcut)
40 | (doseq [[conflict-action-id shortcuts] (.getConflicts keymap id k-shortcut)]
41 | (doseq [shortcut shortcuts]
42 | (.removeShortcut keymap conflict-action-id shortcut))))))
43 | action)))
44 |
45 | (defn unregister-action [id]
46 | (let [manager (ActionManager/getInstance)]
47 | (when (.getAction manager id)
48 | (.unregisterAction manager id))))
49 |
50 | (defn ^:private ->constraint ^Constraints [anchor relative-to]
51 | (Constraints. (case anchor
52 | :first Anchor/FIRST
53 | :last Anchor/LAST
54 | :before Anchor/BEFORE
55 | :after Anchor/AFTER) relative-to))
56 |
57 | (defn register-group!
58 | "Dynamically register an action group if not registered yet."
59 | [& {:keys [id popup ^String text icon children]}]
60 | (let [group (DefaultActionGroup.)
61 | manager (ActionManager/getInstance)]
62 | (when-not (.getAction manager id)
63 | (when popup
64 | (.setPopup group popup))
65 | (when text
66 | (.setText (.getTemplatePresentation group) text))
67 | (when icon
68 | (.setIcon (.getTemplatePresentation group) icon))
69 | (.registerAction manager id group)
70 | (doseq [{:keys [type group-id anchor relative-to ref]} children]
71 | (case type
72 | :add-to-group (.add ^DefaultActionGroup (.getAction manager group-id) group (->constraint anchor relative-to))
73 | :reference (.add group (.getAction manager ref))
74 | :separator (.addSeparator group)))
75 | group)))
76 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/ericdallo/clj4intellij/app_manager.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.ericdallo.clj4intellij.app-manager
2 | (:import
3 | [com.intellij.openapi.application ApplicationManager ModalityState]
4 | [com.intellij.openapi.command CommandProcessor WriteCommandAction]
5 | [com.intellij.openapi.command UndoConfirmationPolicy]
6 | [com.intellij.openapi.project Project]
7 | [com.intellij.openapi.util Computable]
8 | [com.intellij.util ThrowableRunnable]))
9 |
10 | (set! *warn-on-reflection* true)
11 |
12 | (defn invoke-later!
13 | "API for `Application/invokeLater`.
14 | Returns a promise which can be `deref` to await the result of `invoke-fn` execution.
15 |
16 | ref: https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/application/Application.java#L373"
17 | [{:keys [invoke-fn ^ModalityState modality-state]
18 | :or {modality-state (ModalityState/defaultModalityState)}}]
19 | (let [p (promise)]
20 | (.invokeLater
21 | (ApplicationManager/getApplication)
22 | (fn []
23 | (deliver p (invoke-fn)))
24 | modality-state)
25 | p))
26 |
27 | (defn read-action!
28 | "API for `Application/runReadAction`.
29 | Returns a promise which can be `deref` to await the result of `run-fn` execution.
30 |
31 | ref: https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/application/Application.java#L112"
32 | [{:keys [run-fn]}]
33 | (let [p (promise)]
34 | (.runReadAction
35 | (ApplicationManager/getApplication)
36 | (reify Computable
37 | (compute [_]
38 | (let [result (run-fn)]
39 | (deliver p result)
40 | result))))
41 | p))
42 |
43 | (defn write-action!
44 | "API for `Application/runWriteAction`.
45 | Returns a promise which can be `deref` to await the result of `run-fn` execution.
46 |
47 | ref: https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/application/Application.java#L157"
48 | [{:keys [run-fn]}]
49 | (let [p (promise)]
50 | (.runWriteAction
51 | (ApplicationManager/getApplication)
52 | (reify Computable
53 | (compute [_]
54 | (let [result (run-fn)]
55 | (deliver p result)
56 | result))))
57 | p))
58 |
59 | (defn execute-command!
60 | "API for `CommandProcessor/executeCommand`.
61 | Returns a promise which can be `deref` to await the result of `command-fn` execution.
62 |
63 | ref: https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/command/CommandProcessor.java"
64 | [{:keys [^String name group-id ^Project project command-fn
65 | ^UndoConfirmationPolicy undo-confirmation-policy
66 | ^Boolean record-command-for-active-document?]
67 | :or {undo-confirmation-policy UndoConfirmationPolicy/DEFAULT
68 | record-command-for-active-document? false}}]
69 | (let [p (promise)]
70 | (.executeCommand
71 | (CommandProcessor/getInstance)
72 | project
73 | (fn []
74 | (let [result (command-fn)]
75 | (deliver p result)
76 | result))
77 | name
78 | group-id
79 | undo-confirmation-policy
80 | record-command-for-active-document?)
81 | p))
82 |
83 | (defn write-command-action
84 | "API for `WriteCommandAction/writeCommandAction`."
85 | [^Project project run-fn]
86 | (.run (WriteCommandAction/writeCommandAction project)
87 | (reify ThrowableRunnable
88 | (run [_]
89 | (run-fn)))))
90 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/ericdallo/clj4intellij/config.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.ericdallo.clj4intellij.config
2 | (:require
3 | [clojure.edn :as edn]
4 | [clojure.java.io :as io]))
5 |
6 | (defonce ^:private cache* (atom {}))
7 |
8 | (defn ^:private memoize-if-not-nil [f]
9 | (fn []
10 | (if-let [cached-result (get @cache* f)]
11 | cached-result
12 | (if-let [result (f)]
13 | (do (swap! cache* assoc f result)
14 | result)
15 | nil))))
16 |
17 | (defn ^:private config* []
18 | (try
19 | (edn/read-string (slurp (io/resource "META-INF/clj4intellij.edn")))
20 | (catch Exception _ nil)))
21 |
22 | (def ^:private config (memoize-if-not-nil config*))
23 |
24 | (defn ^:private plugin* []
25 | (try
26 | (slurp (io/resource "META-INF/plugin.xml"))
27 | (catch Exception _ nil)))
28 |
29 | (def ^:private plugin (memoize-if-not-nil plugin*))
30 |
31 | (defn ^:private plugin-name* []
32 | (when-let [plugin (plugin)]
33 | (last (re-find #"(.+)" plugin))))
34 |
35 | (defn nrepl-support? []
36 | (-> (config) :nrepl))
37 |
38 | (defn nrepl-port []
39 | (-> (config) :nrepl :port))
40 |
41 | (def plugin-name (memoize-if-not-nil plugin-name*))
42 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/ericdallo/clj4intellij/extension.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.ericdallo.clj4intellij.extension
2 | (:require
3 | [camel-snake-kebab.core :as csk]))
4 |
5 | (set! *warn-on-reflection* true)
6 |
7 | (defn ^:private flatten-1 [coll]
8 | (mapcat #(if (sequential? %) % [%]) coll))
9 |
10 | (defn ^:private interface? [clazz]
11 | (and (class? clazz) (.isInterface ^Class clazz)))
12 |
13 | (defmacro def-extension
14 | "Defines a extension for plugin.xml, using `gen-class` to create a class
15 | that extends or implements the provided `super-class`.
16 |
17 | The generated class is only generated at compile time but the methods
18 | can be re-defined.
19 |
20 | Example:
21 | ```clojure
22 | (def-extension ClojureBraceMatcher []
23 | PairedBraceMatcher
24 | (getPairs [this _]
25 | [,,,])
26 | (isPairedBracesAllowedBeforeType [this _ context-type]
27 | (boolean context-type)))
28 | ```"
29 | [name super-args super-class & methods]
30 | (let [super-class-resolved (resolve super-class)
31 | prefix (str name "_")
32 | interface? (interface? super-class-resolved)
33 | init-method (when (seq super-args)
34 | (list 'defn (symbol (str prefix "init")) [] [super-args nil]))
35 | gen-class-args (->> [:name (str (csk/->snake_case (str *ns*)) "." name)
36 | :prefix prefix
37 | (when (seq super-args) [(symbol "init") (str prefix "init")])
38 | (if interface?
39 | [:implements [super-class-resolved]]
40 | [:extends super-class-resolved])]
41 | (remove nil?)
42 | (flatten-1))
43 | method-defs (map (fn [[name args & body]]
44 | (concat ['defn (symbol (str prefix name)) args] body))
45 | methods)]
46 | `(do
47 | ~init-method
48 | ~@method-defs
49 | (gen-class ~@gen-class-args))))
50 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/ericdallo/clj4intellij/extension/nrepl_startup.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.ericdallo.clj4intellij.extension.nrepl-startup
2 | (:gen-class
3 | :name com.github.ericdallo.clj4intellij.extension.NREPLStartup
4 | :implements [com.intellij.openapi.startup.StartupActivity
5 | com.intellij.openapi.project.DumbAware])
6 | (:require
7 | [com.github.ericdallo.clj4intellij.config :as plugin]
8 | [com.github.ericdallo.clj4intellij.logger :as logger])
9 | (:import
10 | [com.github.ericdallo.clj4intellij ClojureClassLoader]
11 | [com.intellij.openapi.project Project]
12 | [java.net ServerSocket]))
13 |
14 | (set! *warn-on-reflection* true)
15 |
16 | (defn get-open-port []
17 | (with-open [socket (ServerSocket. 0)]
18 | (.getLocalPort socket)))
19 |
20 | (defn -runActivity [_this ^Project _]
21 | (ClojureClassLoader/bind)
22 | (if (plugin/nrepl-support?)
23 | (let [port (or (plugin/nrepl-port)
24 | (get-open-port))]
25 | (logger/info "Starting nrepl server on port" port "...")
26 | (try
27 | ((requiring-resolve 'nrepl.server/start-server)
28 | :port port)
29 | (logger/info "Started nrepl server at port" port)
30 | (catch Exception e
31 | (logger/warn "Could not start nrepl server, error:" e))))
32 | (logger/info "Skipping nrepl server start, no config found.")))
33 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/ericdallo/clj4intellij/listener/class_loader.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.ericdallo.clj4intellij.listener.class-loader
2 | (:gen-class
3 | :name com.github.ericdallo.clj4intellij.listener.ClojureClassLoaderListener
4 | :extends com.github.ericdallo.clj4intellij.ClojureClassLoader
5 | :implements [com.intellij.ide.AppLifecycleListener])
6 | (:require
7 | [clojure.java.io :as io])
8 | (:import
9 | [com.github.ericdallo.clj4intellij ClojureClassLoader]))
10 |
11 | (set! *warn-on-reflection* true)
12 |
13 | (defn -appFrameCreated [_ _]
14 | (ClojureClassLoader/bind)
15 | (last (re-find #"(.+)" (slurp (io/resource "META-INF/plugin.xml")))))
16 |
17 | (defn -welcomeScreenDisplayed [_])
18 | (defn -appStarted [_])
19 | (defn -projectFrameClosed [_])
20 | (defn -projectOpenFailed [_])
21 | (defn -appStarting [_ _])
22 | (defn -appClosing [_])
23 | (defn -appWillBeClosed [_ _])
24 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/ericdallo/clj4intellij/logger.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.ericdallo.clj4intellij.logger
2 | (:require
3 | [clojure.string :as string]
4 | [com.github.ericdallo.clj4intellij.config :as plugin])
5 | (:import
6 | [com.github.ericdallo.clj4intellij ClojureClassLoader]
7 | [com.intellij.openapi.diagnostic Logger]))
8 |
9 | (set! *warn-on-reflection* true)
10 |
11 | (defonce ^Logger logger (Logger/getInstance ClojureClassLoader))
12 |
13 | (defn ^:private build-msg ^String [messages]
14 | (format "[%s] %s"
15 | (plugin/plugin-name)
16 | (string/join " " (mapv str messages))))
17 |
18 | (defn info [& messages]
19 | (.info logger (build-msg messages)))
20 |
21 | (defn warn [& messages]
22 | (.warn logger (build-msg messages)))
23 |
24 | (defn error [& messages]
25 | (.error logger (build-msg messages)))
26 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/ericdallo/clj4intellij/tasks.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.ericdallo.clj4intellij.tasks
2 | (:require
3 | [com.rpl.proxy-plus :refer [proxy+]])
4 | (:import
5 | [com.intellij.openapi.progress ProgressIndicator ProgressManager Task$Backgroundable]))
6 |
7 | (defn run-background-task! [project title run-fn]
8 | (.run (ProgressManager/getInstance)
9 | (proxy+
10 | [project title]
11 | Task$Backgroundable
12 | (run [_ ^ProgressIndicator indicator]
13 | (run-fn indicator)))))
14 |
15 | (defn set-progress
16 | ([^ProgressIndicator indicator text]
17 | (.setText indicator text)
18 | (.setIndeterminate indicator true))
19 | ([^ProgressIndicator indicator text percentage]
20 | (.setText indicator text)
21 | (.setIndeterminate indicator false)
22 | (.setFraction indicator (double (/ percentage 100)))))
23 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/ericdallo/clj4intellij/test.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.ericdallo.clj4intellij.test
2 | "Test utilities for clj4intellij"
3 | (:import
4 | [com.intellij.testFramework EdtTestUtil LightProjectDescriptor]
5 | [com.intellij.testFramework.fixtures CodeInsightTestFixture IdeaTestFixtureFactory TestFixtureBuilder]
6 | [com.intellij.testFramework.fixtures.impl IdeaTestFixtureFactoryImpl]
7 | [com.intellij.util ThrowableRunnable]
8 | [com.intellij.util.ui UIUtil]))
9 |
10 |
11 | (set! *warn-on-reflection* true)
12 |
13 | (defn setup
14 | "Setup fixture factory, with an empty project and return an instance of CodeInsightTestFixture
15 |
16 | ref: https://github.com/JetBrains/intellij-community/blob/2766d0bf1cec76c0478244f6ad5309af527c245e/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestFixture.java"
17 | ^CodeInsightTestFixture
18 | [project-name]
19 | (let [factory ^IdeaTestFixtureFactoryImpl (IdeaTestFixtureFactory/getFixtureFactory)
20 | raw-fixture (-> factory
21 | ^TestFixtureBuilder (.createLightFixtureBuilder LightProjectDescriptor/EMPTY_PROJECT_DESCRIPTOR project-name)
22 | (.getFixture))
23 | fixture (.createCodeInsightFixture factory raw-fixture)]
24 | (.setUp fixture)
25 | fixture))
26 |
27 | (defn dispatch-all
28 | "API for `UIUtil/dispatchAllInvocationEvents`.
29 |
30 | ref:https://github.com/JetBrains/intellij-community/blob/2766d0bf1cec76c0478244f6ad5309af527c245e/platform/util/ui/src/com/intellij/util/ui/UIUtil.java#L1450"
31 | []
32 | (EdtTestUtil/runInEdtAndWait
33 | (reify ThrowableRunnable
34 | (run [_]
35 | (UIUtil/dispatchAllInvocationEvents)))))
36 |
37 | (defn dispatch-all-until
38 | "Dispatch all events in the EDT until condition is met.
39 | Returns a promise which can be `deref` to await the the condition to be met.
40 |
41 | Receives a map with the following keys:
42 | - `:cond-fn` - a function that returns true when the condition is met.
43 | - `:millis` - the time to wait between dispatches (default: 100)
44 |
45 | See `dispatch-all` for more information."
46 | [{:keys [cond-fn millis]
47 | :or {millis 100}}]
48 | (let [p (promise)]
49 | (future
50 | (loop []
51 | (if (cond-fn)
52 | (deliver p true)
53 | (do
54 | (dispatch-all)
55 | (Thread/sleep millis)
56 | (recur)))))
57 | p))
58 |
--------------------------------------------------------------------------------
/src/main/clojure/com/github/ericdallo/clj4intellij/util.clj:
--------------------------------------------------------------------------------
1 | (ns com.github.ericdallo.clj4intellij.util
2 | (:require
3 | [clojure.java.io :as io])
4 | (:import
5 | [com.intellij.openapi.editor Editor]
6 | [com.intellij.openapi.fileEditor FileEditorManager OpenFileDescriptor TextEditor]
7 | [com.intellij.openapi.project Project]
8 | [com.intellij.openapi.util.text StringUtil]
9 | [com.intellij.openapi.vfs LocalFileSystem VirtualFile]))
10 |
11 | (defn editor->cursor-position [^Editor editor]
12 | (let [offset (.. editor getCaretModel getCurrentCaret getOffset)
13 | text (.getCharsSequence (.getDocument editor))
14 | line-col (StringUtil/offsetToLineColumn text offset)]
15 | [(.line line-col) (.column line-col)]))
16 |
17 | (defn v-file->editor ^Editor [^VirtualFile v-file ^Project project ^Boolean focus]
18 | (let [file-manager (FileEditorManager/getInstance project)
19 | editor (if (.isFileOpen file-manager v-file)
20 | (.getEditor ^TextEditor (first (.getAllEditors file-manager v-file)))
21 | (.openTextEditor file-manager (OpenFileDescriptor. project v-file) focus))]
22 | editor))
23 |
24 | (defn uri->v-file ^VirtualFile [^String uri]
25 | (.findFileByIoFile (LocalFileSystem/getInstance)
26 | (io/file (java.net.URI. uri))))
27 |
28 | (defn uri->editor ^Editor [^String uri ^Project project ^Boolean focus?]
29 | (let [v-file (uri->v-file uri)]
30 | (v-file->editor v-file project focus?)))
31 |
--------------------------------------------------------------------------------
/src/main/java/com/github/ericdallo/clj4intellij/ClojureClassLoader.java:
--------------------------------------------------------------------------------
1 | package com.github.ericdallo.clj4intellij;
2 |
3 | public class ClojureClassLoader {
4 | static {
5 | bind();
6 | }
7 | public static void bind() {
8 | Thread.currentThread().setContextClassLoader(ClojureClassLoader.class.getClassLoader());
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/resources/clj-kondo.exports/ericdallo/clj4intellij/clj4intellij/proxy_plus.clj:
--------------------------------------------------------------------------------
1 | (ns clj4intellij.proxy-plus
2 | (:require
3 | [clj-kondo.hooks-api :as hooks]))
4 |
5 | (defn proxy+
6 | "Expands a proxy+ form into a reify form with a declared proxy name.
7 |
8 | Input:
9 | (proxy+
10 | [^String title ^String description ^Icon icon]
11 | DumbAwareAction
12 | (actionPerformed [_ event] (on-performed event)))
13 | Output:
14 | (clojure.core/let [_ [title description icon]]
15 | (clojure.core/reify
16 | DumbAwareAction
17 | (actionPerformed [_ event] (on-performed event))))
18 | "
19 | [{{:keys [children]} :node :as _context}]
20 | (let [[_proxy+ & args] children
21 | [_proxy-name-sym super-args impls]
22 | (if (symbol? (hooks/sexpr (first args)))
23 | [(first args)
24 | (first (rest args))
25 | (rest (rest args))]
26 | [(gensym "proxy_plus")
27 | (first args)
28 | (rest args)])
29 | new-node (hooks/list-node
30 | [(hooks/token-node 'clojure.core/let)
31 | (hooks/vector-node [(hooks/token-node '_)
32 | super-args])
33 | (hooks/list-node
34 | (concat [(hooks/token-node 'clojure.core/reify)]
35 | impls))])]
36 | {:node new-node
37 | :defined-by 'com.rpl.proxy-plus/proxy+}))
38 |
39 | (comment
40 | ;; (require '[clojure.repl.deps :as repl])
41 | ;; (repl/add-lib 'clj-kondo/clj-kondo {:mvn/version "2025.01.16"})
42 | ;; I'd love if this worked, but it's returning:
43 | ;; ; Syntax error (IllegalArgumentException) compiling fn* at (clj_kondo/impl/analysis/java.clj:156:1).
44 | ;; ; Error - no matches found for static method ASM9 in class org.objectweb.asm.Opcodes
45 | ;; So, you need to add clj-kondo to the deps.edn file in order to evaluate
46 | ;; the following code:
47 |
48 | (->> "(proxy+ ClojureModuleType [\"CLOJURE_MODULE\"] ModuleType
49 | (getName [_] \"Clojure\")
50 | (getDescription [_] \"Create programs using the Clojure language.\")
51 | (getNodeIcon [_ _] Icons/CLOJURE))"
52 | hooks/parse-string
53 | (assoc {} :node)
54 | proxy+
55 | :node
56 | str)
57 | ;; => "(clojure.core/let [_ [\"CLOJURE_MODULE\"]] (clojure.core/reify ModuleType (getName [_] \"Clojure\") (getDescription [_] \"Create programs using the Clojure language.\") (getNodeIcon [_ _] Icons/CLOJURE)))"
58 |
59 | (->> "(proxy+
60 | [^String title ^String description ^Icon icon]
61 | DumbAwareAction
62 | (actionPerformed [_ event] (on-performed event)))"
63 | hooks/parse-string
64 | (assoc {} :node)
65 | proxy+
66 | :node
67 | str)
68 | ;; => "(clojure.core/let [_ [title description icon]] (clojure.core/reify DumbAwareAction (actionPerformed [_ event] (on-performed event))))"
69 | )
70 |
--------------------------------------------------------------------------------
/src/main/resources/clj-kondo.exports/ericdallo/clj4intellij/config.edn:
--------------------------------------------------------------------------------
1 | {:hooks {:analyze-call {com.rpl.proxy-plus/proxy+ clj4intellij.proxy-plus/proxy+
2 | com.github.ericdallo.clj4intellij.extension/def-extension clj4intellij.proxy-plus/proxy+}}}
3 |
--------------------------------------------------------------------------------
/src/main/resources/clojure-lsp.exports/ericdallo/clj4intellij/config.edn:
--------------------------------------------------------------------------------
1 | {:cljfmt {:extra-indents {proxy+ [[:block 2] [:inner 1]]
2 | def-extension [[:block 2] [:inner 1]]}}}
3 |
--------------------------------------------------------------------------------
/src/scripts/ci.clj:
--------------------------------------------------------------------------------
1 | (ns ci
2 | (:require
3 | [babashka.fs :as fs]
4 | [babashka.tasks :refer [shell]]
5 | [clojure.string :as string]))
6 |
7 | (defn ^:private replace-in-file [file regex content]
8 | (as-> (slurp file) $
9 | (string/replace $ regex content)
10 | (spit file $)))
11 |
12 | (defn ^:private add-changelog-entry [tag comment]
13 | (replace-in-file "CHANGELOG.md"
14 | #"## \[Unreleased\]"
15 | (if comment
16 | (format "## [Unreleased]\n\n## %s\n\n- %s" tag comment)
17 | (format "## [Unreleased]\n\n## %s" tag))))
18 |
19 | (defn ^:private replace-tag [tag]
20 | (replace-in-file "gradle.properties"
21 | #"pluginVersion = [0-9]+.[0-9]+.[0-9]+.*"
22 | (format "pluginVersion = %s" tag))
23 | (replace-in-file "gradle.properties"
24 | #"version = [0-9]+.[0-9]+.[0-9]+.*"
25 | (format "version = %s" tag)))
26 |
27 | #_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
28 | (defn tag [& [tag]]
29 | (shell "git fetch origin")
30 | (shell "git pull origin HEAD")
31 | (replace-tag tag)
32 | (add-changelog-entry tag nil)
33 | (shell "git add gradle.properties CHANGELOG.md")
34 | (shell (format "git commit -m \"Release: %s\"" tag))
35 | (shell (str "git tag " tag))
36 | (shell "git push origin HEAD")
37 | (shell "git push origin --tags"))
38 |
39 | (defn install [& _]
40 | (shell "./gradlew clean")
41 | (fs/copy-tree "src/main/clojure/" "src/main/resources/")
42 | (shell "./gradlew build publishToMavenLocal")
43 | (fs/delete-tree "src/main/resources/com/github/ericdallo/clj4intellij"))
44 |
45 | (defn build [& _]
46 | (shell "./gradlew clean")
47 | (fs/copy-tree "src/main/clojure/" "src/main/resources/")
48 | (shell "./gradlew build")
49 | (fs/delete-tree "src/main/resources/com/github/ericdallo/clj4intellij"))
50 |
51 | (defn deploy [& _]
52 | (shell "./gradlew clean")
53 | (fs/copy-tree "src/main/clojure/" "src/main/resources/")
54 | (shell "./gradlew build publish")
55 | (fs/delete-tree "src/main/resources/com/github/ericdallo/clj4intellij"))
56 |
--------------------------------------------------------------------------------