├── .editorconfig ├── .gitattributes ├── .gitignore ├── .idea ├── .gitignore ├── checkstyle-idea.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── encodings.xml ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── build.gradle ├── checkstyle.xml ├── gradlew ├── gradlew.bat ├── screenshots └── 1.gif ├── settings.gradle └── src └── main ├── java └── com │ └── jsdelivr │ └── pluginintellij │ ├── InsertModel.java │ ├── JsDelivrPackageSearch.java │ ├── actions │ ├── OpenGithub.java │ ├── OpenJsDelivr.java │ └── OpenNpm.java │ ├── packagefile │ ├── FileInput.java │ ├── FileThread.java │ ├── PackageFiles.java │ └── remotetypes │ │ ├── ApiFilesError.java │ │ ├── ApiPackageFile.java │ │ └── ApiPackageFiles.java │ ├── packageinsert │ ├── InsertOptions.java │ └── TextEdit.java │ ├── packagename │ ├── AlgoliaSearch.java │ ├── ListNameItem.java │ ├── NameInput.java │ ├── NameThread.java │ └── remotetypes │ │ ├── AlgoliaPackage.java │ │ └── AlgoliaRepository.java │ ├── packageversion │ └── VersionInput.java │ └── ui │ ├── DefaultListItem.java │ ├── IJsDelivrListItem.java │ ├── JsDelivrInput.java │ ├── JsDelivrList.java │ └── JsDelivrPopup.java └── resources └── META-INF ├── plugin.xml └── pluginIcon.svg /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_style = tab 9 | indent_size = 4 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .local/ 3 | .sync/ 4 | build/ 5 | gradle/ 6 | out/ 7 | local.properties 8 | plugin-intellij.iml 9 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml 3 | 4 | # Sensitive or high-churn files 5 | /uiDesigner.xml 6 | 7 | # Gradle 8 | /gradle.xml 9 | /libraries 10 | 11 | # Gradle and Maven with auto-import 12 | /artifacts 13 | /compiler.xml 14 | /jarRepositories.xml 15 | /modules.xml 16 | /*.iml 17 | /modules 18 | *.iml 19 | *.ipr 20 | -------------------------------------------------------------------------------- /.idea/checkstyle-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Open Software License ("OSL") v. 3.0 2 | 3 | This Open Software License (the "License") applies to any original work of 4 | authorship (the "Original Work") whose owner (the "Licensor") has placed the 5 | following licensing notice adjacent to the copyright notice for the Original 6 | Work: 7 | 8 | Licensed under the Open Software License version 3.0 9 | 10 | 1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, 11 | non-exclusive, sublicensable license, for the duration of the copyright, to do 12 | the following: 13 | 14 | a) to reproduce the Original Work in copies, either alone or as part of a 15 | collective work; 16 | 17 | b) to translate, adapt, alter, transform, modify, or arrange the Original 18 | Work, thereby creating derivative works ("Derivative Works") based upon the 19 | Original Work; 20 | 21 | c) to distribute or communicate copies of the Original Work and Derivative 22 | Works to the public, with the proviso that copies of Original Work or 23 | Derivative Works that You distribute or communicate shall be licensed under 24 | this Open Software License; 25 | 26 | d) to perform the Original Work publicly; and 27 | 28 | e) to display the Original Work publicly. 29 | 30 | 2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, 31 | non-exclusive, sublicensable license, under patent claims owned or controlled 32 | by the Licensor that are embodied in the Original Work as furnished by the 33 | Licensor, for the duration of the patents, to make, use, sell, offer for sale, 34 | have made, and import the Original Work and Derivative Works. 35 | 36 | 3) Grant of Source Code License. The term "Source Code" means the preferred 37 | form of the Original Work for making modifications to it and all available 38 | documentation describing how to modify the Original Work. Licensor agrees to 39 | provide a machine-readable copy of the Source Code of the Original Work along 40 | with each copy of the Original Work that Licensor distributes. Licensor 41 | reserves the right to satisfy this obligation by placing a machine-readable 42 | copy of the Source Code in an information repository reasonably calculated to 43 | permit inexpensive and convenient access by You for as long as Licensor 44 | continues to distribute the Original Work. 45 | 46 | 4) Exclusions From License Grant. Neither the names of Licensor, nor the names 47 | of any contributors to the Original Work, nor any of their trademarks or 48 | service marks, may be used to endorse or promote products derived from this 49 | Original Work without express prior permission of the Licensor. Except as 50 | expressly stated herein, nothing in this License grants any license to 51 | Licensor's trademarks, copyrights, patents, trade secrets or any other 52 | intellectual property. No patent license is granted to make, use, sell, offer 53 | for sale, have made, or import embodiments of any patent claims other than the 54 | licensed claims defined in Section 2. No license is granted to the trademarks 55 | of Licensor even if such marks are included in the Original Work. Nothing in 56 | this License shall be interpreted to prohibit Licensor from licensing under 57 | terms different from this License any Original Work that Licensor otherwise 58 | would have a right to license. 59 | 60 | 5) External Deployment. The term "External Deployment" means the use, 61 | distribution, or communication of the Original Work or Derivative Works in any 62 | way such that the Original Work or Derivative Works may be used by anyone 63 | other than You, whether those works are distributed or communicated to those 64 | persons or made available as an application intended for use over a network. 65 | As an express condition for the grants of license hereunder, You must treat 66 | any External Deployment by You of the Original Work or a Derivative Work as a 67 | distribution under section 1(c). 68 | 69 | 6) Attribution Rights. You must retain, in the Source Code of any Derivative 70 | Works that You create, all copyright, patent, or trademark notices from the 71 | Source Code of the Original Work, as well as any notices of licensing and any 72 | descriptive text identified therein as an "Attribution Notice." You must cause 73 | the Source Code for any Derivative Works that You create to carry a prominent 74 | Attribution Notice reasonably calculated to inform recipients that You have 75 | modified the Original Work. 76 | 77 | 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that 78 | the copyright in and to the Original Work and the patent rights granted herein 79 | by Licensor are owned by the Licensor or are sublicensed to You under the 80 | terms of this License with the permission of the contributor(s) of those 81 | copyrights and patent rights. Except as expressly stated in the immediately 82 | preceding sentence, the Original Work is provided under this License on an "AS 83 | IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without 84 | limitation, the warranties of non-infringement, merchantability or fitness for 85 | a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK 86 | IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this 87 | License. No license to the Original Work is granted by this License except 88 | under this disclaimer. 89 | 90 | 8) Limitation of Liability. Under no circumstances and under no legal theory, 91 | whether in tort (including negligence), contract, or otherwise, shall the 92 | Licensor be liable to anyone for any indirect, special, incidental, or 93 | consequential damages of any character arising as a result of this License or 94 | the use of the Original Work including, without limitation, damages for loss 95 | of goodwill, work stoppage, computer failure or malfunction, or any and all 96 | other commercial damages or losses. This limitation of liability shall not 97 | apply to the extent applicable law prohibits such limitation. 98 | 99 | 9) Acceptance and Termination. If, at any time, You expressly assented to this 100 | License, that assent indicates your clear and irrevocable acceptance of this 101 | License and all of its terms and conditions. If You distribute or communicate 102 | copies of the Original Work or a Derivative Work, You must make a reasonable 103 | effort under the circumstances to obtain the express assent of recipients to 104 | the terms of this License. This License conditions your rights to undertake 105 | the activities listed in Section 1, including your right to create Derivative 106 | Works based upon the Original Work, and doing so without honoring these terms 107 | and conditions is prohibited by copyright law and international treaty. 108 | Nothing in this License is intended to affect copyright exceptions and 109 | limitations (including "fair use" or "fair dealing"). This License shall 110 | terminate immediately and You may no longer exercise any of the rights granted 111 | to You by this License upon your failure to honor the conditions in Section 112 | 1(c). 113 | 114 | 10) Termination for Patent Action. This License shall terminate automatically 115 | and You may no longer exercise any of the rights granted to You by this 116 | License as of the date You commence an action, including a cross-claim or 117 | counterclaim, against Licensor or any licensee alleging that the Original Work 118 | infringes a patent. This termination provision shall not apply for an action 119 | alleging patent infringement by combinations of the Original Work with other 120 | software or hardware. 121 | 122 | 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this 123 | License may be brought only in the courts of a jurisdiction wherein the 124 | Licensor resides or in which Licensor conducts its primary business, and under 125 | the laws of that jurisdiction excluding its conflict-of-law provisions. The 126 | application of the United Nations Convention on Contracts for the 127 | International Sale of Goods is expressly excluded. Any use of the Original 128 | Work outside the scope of this License or after its termination shall be 129 | subject to the requirements and penalties of copyright or patent law in the 130 | appropriate jurisdiction. This section shall survive the termination of this 131 | License. 132 | 133 | 12) Attorneys' Fees. In any action to enforce the terms of this License or 134 | seeking damages relating thereto, the prevailing party shall be entitled to 135 | recover its costs and expenses, including, without limitation, reasonable 136 | attorneys' fees and costs incurred in connection with such action, including 137 | any appeal of such action. This section shall survive the termination of this 138 | License. 139 | 140 | 13) Miscellaneous. If any provision of this License is held to be 141 | unenforceable, such provision shall be reformed only to the extent necessary 142 | to make it enforceable. 143 | 144 | 14) Definition of "You" in This License. "You" throughout this License, 145 | whether in upper or lower case, means an individual or a legal entity 146 | exercising rights under, and complying with all of the terms of, this License. 147 | For legal entities, "You" includes any entity that controls, is controlled by, 148 | or is under common control with you. For purposes of this definition, 149 | "control" means (i) the power, direct or indirect, to cause the direction or 150 | management of such entity, whether by contract or otherwise, or (ii) ownership 151 | of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial 152 | ownership of such entity. 153 | 154 | 15) Right to Use. You may use the Original Work in all ways not otherwise 155 | restricted or conditioned by this License or by law, and Licensor promises not 156 | to interfere with or be responsible for such uses by You. 157 | 158 | 16) Modification of This License. This License is Copyright © 2005 Lawrence 159 | Rosen. Permission is granted to copy, distribute, or communicate this License 160 | without modification. Nothing in this License permits You to modify this 161 | License as applied to the Original Work or to Derivative Works. However, You 162 | may modify the text of this License and copy, distribute or communicate your 163 | modified version (the "Modified License") and apply it to other original works 164 | of authorship subject to the following conditions: (i) You may not indicate in 165 | any way that your Modified License is the "Open Software License" or "OSL" and 166 | you may not use those names in the name of your Modified License; (ii) You 167 | must replace the notice specified in the first paragraph above with the notice 168 | "Licensed under " or with a notice of your own 169 | that is not confusingly similar to the notice in this License; and (iii) You 170 | may not claim that your original works are open source software unless your 171 | Modified License has been approved by Open Source Initiative (OSI) and You 172 | comply with its license review and certification process. 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # plugin-intellij 2 | 3 | Quickly insert any npm package from [jsDelivr CDN](https://www.jsdelivr.com). 4 | 5 | Use a keyboard shortcut (Alt + A by default) or choose "Add jsDelivr package" 6 | from the context menu to bring up the search window. 7 | 8 | ![Screenshot 1](screenshots/1.gif) 9 | 10 | ## Installation 11 | 12 | In your IDE, go to `File -> Settings -> Plugins`, click `Browse repositories`, 13 | and search for `jsDelivr`. 14 | 15 | ## Features 16 | 17 | - Insert just the URL, HTML code, or HTML + SRI. 18 | - Offers [minified versions](https://www.jsdelivr.com/features#minify) of all CSS/JS files. 19 | - Quick open (press in the search results list): 20 | - F1 - jsDelivr package page 21 | - F2 - npm package page 22 | - F3 - GitHub page 23 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.jetbrains.intellij' version '1.17.3' 4 | } 5 | 6 | version '1.0.10' 7 | 8 | project.sourceCompatibility = JavaVersion.VERSION_17 9 | project.targetCompatibility = JavaVersion.VERSION_17 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | implementation 'com.algolia:algoliasearch-core:3.16.9' 17 | implementation 'com.algolia:algoliasearch-apache:3.16.9' 18 | runtimeOnly 'com.github.cliftonlabs:json-simple:3.1.0' 19 | implementation 'com.github.zafarkhaja:java-semver:0.9.0' 20 | testImplementation group: 'junit', name: 'junit', version: '4.13.1' 21 | } 22 | 23 | intellij { 24 | version = '2022.2' 25 | updateSinceUntilBuild = false 26 | } 27 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 93 | 94 | 95 | 97 | 98 | 99 | 100 | 102 | 103 | 104 | 105 | 107 | 108 | 109 | 110 | 111 | 112 | 114 | 115 | 116 | 117 | 119 | 120 | 121 | 122 | 124 | 125 | 126 | 127 | 129 | 131 | 133 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 155 | 156 | 157 | 158 | 159 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /screenshots/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsdelivr/plugin-intellij/50bc4c4b81929d5d52c077b3e10297f523f9bf00/screenshots/1.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'plugin-intellij' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/InsertModel.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij; 2 | 3 | import com.jsdelivr.pluginintellij.packagefile.remotetypes.ApiPackageFile; 4 | 5 | public class InsertModel { 6 | public String name; 7 | public String version; 8 | public ApiPackageFile file; 9 | 10 | public InsertModel(String name, String version, ApiPackageFile file) { 11 | this.name = name; 12 | this.version = version; 13 | this.file = file; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/JsDelivrPackageSearch.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij; 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | import com.intellij.openapi.actionSystem.CommonDataKeys; 7 | import com.intellij.openapi.editor.Editor; 8 | import com.intellij.openapi.project.Project; 9 | import com.jsdelivr.pluginintellij.packagename.NameInput; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.awt.*; 13 | 14 | public class JsDelivrPackageSearch extends AnAction { 15 | private Editor editor; 16 | private Project project; 17 | 18 | @Override 19 | public void update(AnActionEvent event) { 20 | project = event.getProject(); 21 | editor = event.getData(CommonDataKeys.EDITOR); 22 | event.getPresentation().setVisible(project != null && editor != null); 23 | } 24 | 25 | @Override 26 | public void actionPerformed(AnActionEvent event) { 27 | editor = event.getData(CommonDataKeys.EDITOR); 28 | 29 | if (editor == null) { 30 | return; 31 | } 32 | 33 | project = event.getProject(); 34 | 35 | Font editorFont = editor.getContentComponent().getFont(); 36 | new NameInput(editor, project).setFont(new Font(editorFont.getFontName(), Font.PLAIN, editorFont.getSize() + 1)); 37 | } 38 | 39 | @Override 40 | public @NotNull ActionUpdateThread getActionUpdateThread() { 41 | return ActionUpdateThread.EDT; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/actions/OpenGithub.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.actions; 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public class OpenGithub extends AnAction { 9 | @Override 10 | public void actionPerformed(AnActionEvent event) { 11 | 12 | } 13 | 14 | @Override 15 | public @NotNull ActionUpdateThread getActionUpdateThread() { 16 | return ActionUpdateThread.EDT; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/actions/OpenJsDelivr.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.actions; 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public class OpenJsDelivr extends AnAction { 9 | @Override 10 | public void actionPerformed(AnActionEvent event) { 11 | 12 | } 13 | 14 | @Override 15 | public @NotNull ActionUpdateThread getActionUpdateThread() { 16 | return ActionUpdateThread.EDT; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/actions/OpenNpm.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.actions; 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public class OpenNpm extends AnAction { 9 | @Override 10 | public void actionPerformed(AnActionEvent event) { 11 | 12 | } 13 | 14 | @Override 15 | public @NotNull ActionUpdateThread getActionUpdateThread() { 16 | return ActionUpdateThread.EDT; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packagefile/FileInput.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packagefile; 2 | 3 | import com.intellij.openapi.editor.Editor; 4 | import com.intellij.openapi.project.Project; 5 | import com.jsdelivr.pluginintellij.InsertModel; 6 | import com.jsdelivr.pluginintellij.packagefile.remotetypes.ApiPackageFile; 7 | import com.jsdelivr.pluginintellij.packagefile.remotetypes.ApiPackageFiles; 8 | import com.jsdelivr.pluginintellij.packageinsert.InsertOptions; 9 | import com.jsdelivr.pluginintellij.packagename.AlgoliaSearch; 10 | import com.jsdelivr.pluginintellij.packagename.NameThread; 11 | import com.jsdelivr.pluginintellij.ui.DefaultListItem; 12 | import com.jsdelivr.pluginintellij.ui.JsDelivrInput; 13 | 14 | import java.util.List; 15 | 16 | public class FileInput extends JsDelivrInput { 17 | private static final String messageLoading = "Loading..."; 18 | private static final String messageNotFound = "File not found"; 19 | private static final String messagePlaceholder = "Package file"; 20 | 21 | private String pkgName; 22 | private String pkgVersion; 23 | 24 | String pkgFile; 25 | List files; 26 | ApiPackageFiles apiPackageFiles; 27 | 28 | public FileInput(Editor editor, Project project, String pkgName, String pkgVersion) { 29 | super(editor, project, messagePlaceholder); 30 | 31 | this.pkgName = pkgName; 32 | this.pkgVersion = pkgVersion; 33 | 34 | list.getDefaultModel().clear(); 35 | list.setEmptyText(messageLoading); 36 | 37 | FileThread fileThread = new FileThread(pkgName, pkgVersion, this); 38 | Thread thread = new Thread(fileThread); 39 | thread.start(); 40 | } 41 | 42 | public FileInput(Editor editor, Project project, String pkgName, String pkgVersion, String pkgFile) { 43 | super(editor, project, messagePlaceholder); 44 | 45 | this.pkgName = pkgName; 46 | this.pkgVersion = pkgVersion; 47 | this.pkgFile = pkgFile; 48 | 49 | list.getDefaultModel().clear(); 50 | list.setEmptyText(messageLoading); 51 | 52 | FileThread fileThread = new FileThread(pkgName, pkgVersion, this); 53 | Thread thread = new Thread(fileThread); 54 | thread.start(); 55 | } 56 | 57 | @Override 58 | protected void updateAutocomplete(String text) { 59 | list.getDefaultModel().clear(); 60 | list.setEmptyText(messageLoading); 61 | 62 | for (String file : files) { 63 | String woMin = text; 64 | 65 | if (text.endsWith(".min.js")) { 66 | woMin = text.substring(0, text.lastIndexOf(".min.js")) + ".js"; 67 | } else if (text.endsWith(".min.css")) { 68 | woMin = text.substring(0, text.lastIndexOf(".min.css")) + ".css"; 69 | } 70 | 71 | if ((file.contains(text) || file.contains(woMin)) && (file.endsWith(".js") || file.endsWith(".css"))) { 72 | if (file.contains(text) && !list.contains(file.replaceFirst("/", ""))) { 73 | list.getDefaultModel().addElement(new DefaultListItem(file.replaceFirst("/", ""))); 74 | } 75 | 76 | String tmp = file.replaceFirst("/", ""); 77 | 78 | if (tmp.endsWith(".min.js") || tmp.endsWith(".min.css")) { 79 | if (!list.contains(tmp)) { 80 | list.getDefaultModel().addElement(new DefaultListItem(tmp)); 81 | } 82 | } 83 | 84 | if (tmp.endsWith(".js") && !tmp.endsWith(".min.js")) { 85 | tmp = tmp.substring(0, tmp.lastIndexOf(".js")) + ".min.js"; 86 | 87 | if (!list.contains(tmp)) { 88 | list.getDefaultModel().addElement(new DefaultListItem(tmp)); 89 | } 90 | } 91 | 92 | if (tmp.endsWith(".css") && !tmp.endsWith(".min.css")) { 93 | tmp = tmp.substring(0, tmp.lastIndexOf(".css")) + ".min.css"; 94 | 95 | if (!list.contains(tmp)) { 96 | list.getDefaultModel().addElement(new DefaultListItem(tmp)); 97 | } 98 | } 99 | } 100 | } 101 | 102 | if (list.getDefaultModel().isEmpty()) { 103 | list.setEmptyText(messageNotFound); 104 | } 105 | } 106 | 107 | @Override 108 | protected boolean inputComplete(String text) { 109 | list.getDefaultModel().clear(); 110 | list.setEmptyText(messageLoading); 111 | 112 | ApiPackageFile file = null; 113 | boolean generatedMin = false; 114 | text = "/" + text; 115 | 116 | if (apiPackageFiles.files == null || apiPackageFiles.files.length == 0) { 117 | list.setEmptyText(messageNotFound); 118 | return false; 119 | } 120 | 121 | for (ApiPackageFile tmp : apiPackageFiles.files) { 122 | if (tmp.name.equals(text)) { 123 | file = tmp; 124 | break; 125 | } 126 | } 127 | 128 | if (file == null) { 129 | String notMin; 130 | 131 | if (text.endsWith(".min.js")) { 132 | notMin = text.substring(0, text.lastIndexOf(".min")) + ".js"; 133 | } else if (text.endsWith(".min.css")) { 134 | notMin = text.substring(0, text.lastIndexOf(".min")) + ".css"; 135 | } else { 136 | list.setEmptyText("File not found"); 137 | return false; 138 | } 139 | 140 | for (ApiPackageFile tmp : apiPackageFiles.files) { 141 | if (tmp.name.equals(notMin)) { 142 | file = tmp; 143 | generatedMin = true; 144 | break; 145 | } 146 | } 147 | } 148 | 149 | if (file == null) { 150 | list.setEmptyText("File not found"); 151 | return false; 152 | } 153 | 154 | file.name = text; 155 | 156 | new InsertOptions(editor, project, new InsertModel(pkgName, pkgVersion, file), generatedMin).setFont(getFont()); 157 | return true; 158 | } 159 | 160 | @Override 161 | protected void previousInput() { 162 | list.getDefaultModel().clear(); 163 | list.setEmptyText("Loading..."); 164 | 165 | NameThread thread = new NameThread(new AlgoliaSearch(), pkgName, pkgVersion, this, true); 166 | Thread thr = new Thread(thread); 167 | thr.start(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packagefile/FileThread.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packagefile; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.jsdelivr.pluginintellij.packagefile.remotetypes.ApiPackageFile; 5 | import com.jsdelivr.pluginintellij.ui.JsDelivrInput; 6 | 7 | import java.util.ArrayList; 8 | 9 | public class FileThread implements Runnable { 10 | private String pkgName; 11 | private String pkgVersion; 12 | private FileInput fileInput; 13 | 14 | FileThread(String pkgName, String pkgVersion, FileInput fileInput) { 15 | this.pkgName = pkgName; 16 | this.pkgVersion = pkgVersion; 17 | this.fileInput = fileInput; 18 | } 19 | 20 | @Override 21 | public void run() { 22 | fileInput.loading = true; 23 | fileInput.apiPackageFiles = PackageFiles.getFiles(pkgName, pkgVersion); 24 | 25 | ApplicationManager.getApplication().invokeLater(() -> { 26 | ApplicationManager.getApplication().runWriteAction(() -> { 27 | fileInput.files = new ArrayList<>(); 28 | 29 | if (fileInput.apiPackageFiles != null) { 30 | for (ApiPackageFile file : fileInput.apiPackageFiles.files) { 31 | fileInput.files.add(file.name); 32 | } 33 | 34 | if (fileInput.pkgFile == null) { 35 | fileInput.updateAutocomplete(fileInput.inputField.getText().equals(fileInput.placeholder) ? "" : fileInput.inputField.getText()); 36 | JsDelivrInput.list.resetSelection(); 37 | } else { 38 | fileInput.inputField.setText(fileInput.pkgFile); 39 | fileInput.inputField.setForeground(fileInput.editor.getContentComponent().getForeground()); 40 | fileInput.updateAutocomplete(fileInput.pkgFile); 41 | JsDelivrInput.list.resetSelection(); 42 | } 43 | } else if (PackageFiles.error != null) { 44 | JsDelivrInput.list.setEmptyText(PackageFiles.error.message); 45 | } else { 46 | JsDelivrInput.list.setEmptyText("Package not found"); 47 | } 48 | }); 49 | }); 50 | 51 | fileInput.loading = false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packagefile/PackageFiles.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packagefile; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.jsdelivr.pluginintellij.packagefile.remotetypes.ApiFilesError; 5 | import com.jsdelivr.pluginintellij.packagefile.remotetypes.ApiPackageFiles; 6 | import org.apache.http.HttpResponse; 7 | import org.apache.http.client.HttpClient; 8 | import org.apache.http.client.methods.HttpGet; 9 | import org.apache.http.impl.client.HttpClients; 10 | import org.apache.http.util.EntityUtils; 11 | 12 | import java.net.URI; 13 | 14 | class PackageFiles { 15 | static ApiPackageFiles pkgFiles; 16 | static ApiFilesError error; 17 | 18 | private static final String jsDelivrPackageFilesEndpoint = "https://data.jsdelivr.com/v1/package/npm/"; 19 | private static final String userAgentHeader = "jsDelivr intelliJ plugin/" + PackageFiles.class.getPackage().getImplementationVersion() + " (https://github.com/jsdelivr/plugin-intellij)"; 20 | 21 | static ApiPackageFiles getFiles(String packageName, String packageVersion) { 22 | String response; 23 | 24 | try { 25 | HttpClient client = HttpClients.custom().setUserAgent(userAgentHeader).build(); 26 | HttpGet httpGet = new HttpGet(); 27 | httpGet.setURI(new URI(jsDelivrPackageFilesEndpoint + packageName + "@" + packageVersion + "/flat")); 28 | HttpResponse resp = client.execute(httpGet); 29 | 30 | response = EntityUtils.toString(resp.getEntity()); 31 | 32 | if (resp.getStatusLine().getStatusCode() == 200) { 33 | return new ObjectMapper().readValue(response, ApiPackageFiles.class); 34 | } else if (resp.getStatusLine().getStatusCode() == 403) { 35 | ApiFilesError err = new ApiFilesError(); 36 | err.status = 0; 37 | err.message = "Package size exceeded the limit of 50 MB"; 38 | error = err; 39 | } else if (resp.getStatusLine().getStatusCode() == 404) { 40 | error = new ObjectMapper().readValue(response, ApiFilesError.class); 41 | } else { 42 | ApiFilesError err = new ApiFilesError(); 43 | err.status = 0; 44 | err.message = "Unknown API error occurred"; 45 | error = err; 46 | } 47 | } catch (Exception exception) { 48 | pkgFiles = null; 49 | error = null; 50 | } 51 | 52 | return null; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packagefile/remotetypes/ApiFilesError.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packagefile.remotetypes; 2 | 3 | public class ApiFilesError { 4 | public int status; 5 | public String message; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packagefile/remotetypes/ApiPackageFile.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packagefile.remotetypes; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class ApiPackageFile { 7 | public String name; 8 | public String hash; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packagefile/remotetypes/ApiPackageFiles.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packagefile.remotetypes; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class ApiPackageFiles { 7 | public ApiPackageFile[] files; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packageinsert/InsertOptions.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packageinsert; 2 | 3 | import com.intellij.openapi.editor.Editor; 4 | import com.intellij.openapi.project.Project; 5 | import com.jsdelivr.pluginintellij.InsertModel; 6 | import com.jsdelivr.pluginintellij.packagefile.FileInput; 7 | import com.jsdelivr.pluginintellij.ui.DefaultListItem; 8 | import com.jsdelivr.pluginintellij.ui.JsDelivrList; 9 | import com.jsdelivr.pluginintellij.ui.JsDelivrPopup; 10 | 11 | import java.awt.*; 12 | import java.awt.event.KeyEvent; 13 | 14 | public class InsertOptions { 15 | private static final String jsDelivrUrl = "https://cdn.jsdelivr.net/npm/"; 16 | 17 | private static final String messageInsertUrl = "Insert URL"; 18 | private static final String messageInsertHtml = "Insert HTML"; 19 | private static final String messageInsertHtmlSri = "Insert HTML + SRI"; 20 | 21 | private JsDelivrList list; 22 | private JsDelivrPopup popup; 23 | private InsertModel insertModel; 24 | private Editor editor; 25 | private Project project; 26 | private Font font; 27 | 28 | public InsertOptions(Editor editor, Project project, InsertModel insertModel, boolean generatedMin) { 29 | this.editor = editor; 30 | this.insertModel = insertModel; 31 | this.project = project; 32 | 33 | list = new JsDelivrList(s -> { 34 | inputComplete(s.toString()); 35 | popup.closePopup(); 36 | return null; 37 | }); 38 | 39 | list.getDefaultModel().addElement(new DefaultListItem(messageInsertUrl)); 40 | list.getDefaultModel().addElement(new DefaultListItem(messageInsertHtml)); 41 | 42 | if (!generatedMin) { 43 | list.getDefaultModel().addElement(new DefaultListItem(messageInsertHtmlSri)); 44 | } 45 | 46 | popup = new JsDelivrPopup(editor, ke -> { 47 | if (ke.getID() == KeyEvent.KEY_RELEASED) { 48 | if (ke.getKeyCode() == KeyEvent.VK_TAB && ke.isShiftDown()) { 49 | new FileInput(editor, project, insertModel.name, insertModel.version, insertModel.file.name.replaceFirst("/", "")).setFont(font); 50 | popup.closePopup(); 51 | } else if (ke.getKeyCode() == KeyEvent.VK_ENTER || ke.getKeyCode() == KeyEvent.VK_TAB) { 52 | inputComplete(list.getSelectedItem().toString()); 53 | popup.closePopup(); 54 | } 55 | } 56 | 57 | list.keyEvent(ke); 58 | return false; 59 | }, list.getPane()); 60 | 61 | list.resetSelection(); 62 | } 63 | 64 | private void inputComplete(String text) { 65 | if (text.equals(messageInsertUrl)) { 66 | new TextEdit(editor, project).insertText(getUrl()); 67 | } else if (text.equals(messageInsertHtml)) { 68 | if (insertModel.file.name.endsWith(".js")) { 69 | new TextEdit(editor, project).insertText(""); 70 | } else { 71 | new TextEdit(editor, project).insertText(""); 72 | } 73 | } else if (text.equals(messageInsertHtmlSri)) { 74 | if (insertModel.file.name.endsWith(".js")) { 75 | new TextEdit(editor, project).insertText(""); 76 | } else { 77 | new TextEdit(editor, project).insertText(""); 78 | } 79 | } 80 | } 81 | 82 | private String getUrl() { 83 | return jsDelivrUrl + insertModel.name + "@" + insertModel.version + insertModel.file.name; 84 | } 85 | 86 | public void setFont(Font font) { 87 | list.setFont(font); 88 | this.font = font; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packageinsert/TextEdit.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packageinsert; 2 | 3 | import com.intellij.openapi.command.WriteCommandAction; 4 | import com.intellij.openapi.editor.Document; 5 | import com.intellij.openapi.editor.Editor; 6 | import com.intellij.openapi.editor.SelectionModel; 7 | import com.intellij.openapi.project.Project; 8 | 9 | class TextEdit { 10 | private Editor editor; 11 | private Project project; 12 | 13 | TextEdit(Editor editor, Project project) { 14 | this.editor = editor; 15 | this.project = project; 16 | } 17 | 18 | void insertText(String text) { 19 | final Document document = editor.getDocument(); 20 | final SelectionModel selectionModel = editor.getSelectionModel(); 21 | final int start = selectionModel.getSelectionStart(); 22 | final int end = selectionModel.getSelectionEnd(); 23 | WriteCommandAction.runWriteCommandAction(project, () -> document.replaceString(start, end, text)); 24 | selectionModel.removeSelection(); 25 | editor.getCaretModel().moveToOffset(editor.getCaretModel().getOffset() + text.length()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packagename/AlgoliaSearch.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packagename; 2 | 3 | import com.algolia.search.DefaultSearchClient; 4 | import com.algolia.search.SearchClient; 5 | import com.algolia.search.SearchConfig; 6 | import com.algolia.search.SearchIndex; 7 | import com.algolia.search.models.indexing.Query; 8 | import com.algolia.search.models.indexing.SearchResult; 9 | import com.jsdelivr.pluginintellij.packagename.remotetypes.AlgoliaPackage; 10 | 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | public class AlgoliaSearch { 15 | private static final String appId = "OFCNCOG2CU"; 16 | private static final String apiKey = "f54e21fa3a2a0160595bb058179bfb1e"; 17 | private static final String indexName = "npm-search"; 18 | 19 | private SearchIndex index; 20 | 21 | public AlgoliaSearch() { 22 | java.security.Security.setProperty("networkaddress.cache.ttl", "60"); 23 | SearchConfig config = new SearchConfig.Builder(appId, apiKey).build(); 24 | SearchClient client = DefaultSearchClient.create(config); 25 | index = client.initIndex(indexName, AlgoliaPackage.class); 26 | } 27 | 28 | List search(String packageName) { 29 | try { 30 | SearchResult result = index.search(new Query(packageName)); 31 | return result.getHits(); 32 | } catch (Exception e) { 33 | e.printStackTrace(); 34 | } 35 | 36 | return Collections.emptyList(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packagename/ListNameItem.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packagename; 2 | 3 | import com.jsdelivr.pluginintellij.packagename.remotetypes.AlgoliaRepository; 4 | import com.jsdelivr.pluginintellij.ui.IJsDelivrListItem; 5 | 6 | public class ListNameItem implements IJsDelivrListItem { 7 | private String name; 8 | private AlgoliaRepository repository; 9 | 10 | ListNameItem(String name, AlgoliaRepository repository) { 11 | this.name = name; 12 | this.repository = repository; 13 | } 14 | 15 | public String getRepositoryUrl() { 16 | return this.repository != null ? this.repository.getUrl() : null; 17 | } 18 | 19 | public String toString() { 20 | return name; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packagename/NameInput.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packagename; 2 | 3 | import com.intellij.openapi.actionSystem.ActionManager; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.actionSystem.Shortcut; 6 | import com.intellij.openapi.editor.Editor; 7 | import com.intellij.openapi.keymap.KeymapUtil; 8 | import com.intellij.openapi.project.Project; 9 | import com.jsdelivr.pluginintellij.ui.JsDelivrInput; 10 | 11 | import javax.swing.*; 12 | import java.awt.*; 13 | import java.awt.event.KeyEvent; 14 | import java.io.IOException; 15 | import java.net.URI; 16 | import java.net.URISyntaxException; 17 | 18 | public class NameInput extends JsDelivrInput { 19 | private static final String messageLoading = "Loading..."; 20 | private static final String messagePlaceholder = "Package name"; 21 | 22 | private AlgoliaSearch algoliaSearch; 23 | 24 | Thread thread = null; 25 | 26 | public NameInput(Editor editor, Project project) { 27 | super(editor, project, messagePlaceholder); 28 | 29 | this.algoliaSearch = new AlgoliaSearch(); 30 | updateAutocomplete(""); 31 | list.resetSelection(); 32 | } 33 | 34 | public NameInput(Editor editor, Project project, String name) { 35 | super(editor, project, messagePlaceholder); 36 | 37 | this.algoliaSearch = new AlgoliaSearch(); 38 | inputField.setText(name); 39 | inputField.setForeground(editor.getContentComponent().getForeground()); 40 | updateAutocomplete(name); 41 | list.resetSelection(); 42 | } 43 | 44 | @Override 45 | protected void updateAutocomplete(String text) { 46 | list.getDefaultModel().clear(); 47 | list.setEmptyText(messageLoading); 48 | 49 | NameThread thread = new NameThread(algoliaSearch, text, this, false); 50 | Thread thr = new Thread(thread); 51 | thr.start(); 52 | } 53 | 54 | @Override 55 | protected boolean inputComplete(String text) { 56 | list.getDefaultModel().clear(); 57 | list.setEmptyText(messageLoading); 58 | 59 | NameThread pkgNameThr = new NameThread(algoliaSearch, text, this, true); 60 | 61 | if (thread != null) { 62 | thread.interrupt(); 63 | } 64 | 65 | thread = new Thread(pkgNameThr); 66 | thread.start(); 67 | 68 | return false; 69 | } 70 | 71 | @Override 72 | protected void previousInput() { 73 | // no op 74 | } 75 | 76 | @Override 77 | protected void onKeyEvent(KeyEvent keyEvent) { 78 | if (keyEvent.getID() == KeyEvent.KEY_RELEASED) { 79 | if (isShortcutPressed("com.jsdelivr.pluginintellij.actions.OpenJsDelivr", keyEvent)) { 80 | if (list.getSelectedItem() != null && !list.getSelectedItem().toString().isEmpty()) { 81 | openBrowser("https://www.jsdelivr.com/package/npm/" + list.getSelectedItem().toString()); 82 | } 83 | } 84 | 85 | if (isShortcutPressed("com.jsdelivr.pluginintellij.actions.OpenNpm", keyEvent)) { 86 | if (list.getSelectedItem() != null && !list.getSelectedItem().toString().isEmpty()) { 87 | openBrowser("https://www.npmjs.com/package/" + list.getSelectedItem().toString()); 88 | } 89 | } 90 | 91 | if (isShortcutPressed("com.jsdelivr.pluginintellij.actions.OpenGithub", keyEvent) && list.getSelectedItem() != null) { 92 | String url = ((ListNameItem) list.getSelectedItem()).getRepositoryUrl(); 93 | 94 | if (url != null && !url.isEmpty()) { 95 | openBrowser(url); 96 | } 97 | } 98 | } 99 | } 100 | 101 | private boolean isShortcutPressed(String action, KeyEvent ke) { 102 | AnAction jsdelivrSite = ActionManager.getInstance().getAction(action); 103 | 104 | for (Shortcut shortcut : jsdelivrSite.getShortcutSet().getShortcuts()) { 105 | KeyStroke stroke = KeyStroke.getKeyStroke(getKeyStrokeString(shortcut)); 106 | 107 | if (ke.getKeyCode() == stroke.getKeyCode()) { 108 | if ((stroke.getModifiers() & KeyEvent.CTRL_DOWN_MASK) != 0 && !ke.isControlDown()) { 109 | continue; 110 | } 111 | 112 | if ((stroke.getModifiers() & KeyEvent.ALT_DOWN_MASK) != 0 && !ke.isAltDown()) { 113 | continue; 114 | } 115 | 116 | if ((stroke.getModifiers() & KeyEvent.SHIFT_DOWN_MASK) != 0 && !ke.isShiftDown()) { 117 | continue; 118 | } 119 | 120 | return true; 121 | } 122 | } 123 | 124 | return false; 125 | } 126 | 127 | private void openBrowser(String website) { 128 | if (Desktop.isDesktopSupported()) { 129 | Desktop desktop = Desktop.getDesktop(); 130 | try { 131 | desktop.browse(new URI(website)); 132 | } catch (IOException | URISyntaxException e) { 133 | e.printStackTrace(); 134 | } 135 | } else { 136 | Runtime runtime = Runtime.getRuntime(); 137 | try { 138 | runtime.exec(new String[]{"xdg-open", website}); 139 | } catch (IOException e) { 140 | e.printStackTrace(); 141 | } 142 | } 143 | } 144 | 145 | private String getKeyStrokeString(Shortcut shortcut) { 146 | return KeymapUtil.getShortcutText(shortcut) 147 | .replaceAll("\\+", " ") 148 | .replace("Ctrl", "ctrl") 149 | .replace("Alt", "alt") 150 | .replace("Shift", "shift"); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packagename/NameThread.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packagename; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.jsdelivr.pluginintellij.packagename.remotetypes.AlgoliaPackage; 5 | import com.jsdelivr.pluginintellij.packageversion.VersionInput; 6 | import com.jsdelivr.pluginintellij.ui.JsDelivrInput; 7 | 8 | import java.util.List; 9 | 10 | public class NameThread implements Runnable { 11 | private static final String messageNotFound = "Package not found"; 12 | 13 | private volatile List packages; 14 | private AlgoliaSearch algoliaSearch; 15 | private String text; 16 | private String version; 17 | private NameInput nameInput; 18 | private JsDelivrInput jsDelivrInput; 19 | private boolean complete; 20 | 21 | NameThread(AlgoliaSearch algoliaSearch, String text, NameInput nameInput, boolean complete) { 22 | this.algoliaSearch = algoliaSearch; 23 | this.text = text; 24 | this.nameInput = nameInput; 25 | this.complete = complete; 26 | this.jsDelivrInput = nameInput; 27 | } 28 | 29 | public NameThread(AlgoliaSearch algoliaSearch, String text, String version, JsDelivrInput nameInput, boolean complete) { 30 | this.algoliaSearch = algoliaSearch; 31 | this.text = text; 32 | this.complete = complete; 33 | this.jsDelivrInput = nameInput; 34 | this.version = version; 35 | } 36 | 37 | @Override 38 | public void run() { 39 | jsDelivrInput.loading = true; 40 | packages = algoliaSearch.search(text); 41 | 42 | if (complete) { 43 | ApplicationManager.getApplication().invokeLater(() -> { 44 | ApplicationManager.getApplication().runWriteAction(this::runComplete); 45 | }); 46 | } else { 47 | ApplicationManager.getApplication().invokeLater(() -> { 48 | ApplicationManager.getApplication().runWriteAction(() -> { 49 | synchronized (this) { 50 | if (packages == null || packages.isEmpty()) { 51 | JsDelivrInput.list.setEmptyText(messageNotFound); 52 | jsDelivrInput.loading = false; 53 | return; 54 | } 55 | 56 | JsDelivrInput.list.getDefaultModel().clear(); 57 | 58 | for (AlgoliaPackage pkg : packages) { 59 | JsDelivrInput.list.getDefaultModel().addElement(new ListNameItem(pkg.getName(), pkg.getRepository())); 60 | } 61 | 62 | JsDelivrInput.list.resetSelection(); 63 | } 64 | }); 65 | }); 66 | } 67 | 68 | jsDelivrInput.loading = false; 69 | 70 | if (nameInput != null) { 71 | nameInput.thread = null; 72 | } 73 | } 74 | 75 | private void runComplete() { 76 | synchronized (this) { 77 | AlgoliaPackage chosenPackage = null; 78 | 79 | if (packages == null || packages.isEmpty()) { 80 | JsDelivrInput.list.getDefaultModel().clear(); 81 | JsDelivrInput.list.setEmptyText(messageNotFound); 82 | jsDelivrInput.loading = false; 83 | return; 84 | } 85 | 86 | for (AlgoliaPackage pkg : packages) { 87 | if (text.equals(pkg.getName())) { 88 | chosenPackage = pkg; 89 | } 90 | } 91 | 92 | if (chosenPackage == null) { 93 | JsDelivrInput.list.getDefaultModel().clear(); 94 | JsDelivrInput.list.setEmptyText(messageNotFound); 95 | jsDelivrInput.loading = false; 96 | return; 97 | } 98 | 99 | if (version == null) { 100 | new VersionInput(jsDelivrInput.editor, jsDelivrInput.project, chosenPackage).setFont(jsDelivrInput.getFont()); 101 | } else { 102 | new VersionInput(jsDelivrInput.editor, jsDelivrInput.project, chosenPackage, version).setFont(jsDelivrInput.getFont()); 103 | } 104 | jsDelivrInput.popup.closePopup(); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packagename/remotetypes/AlgoliaPackage.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packagename.remotetypes; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | public class AlgoliaPackage { 8 | private String name; 9 | private List versions; 10 | private String version; 11 | private AlgoliaRepository repository; 12 | 13 | public String getName() { 14 | return name; 15 | } 16 | 17 | public AlgoliaPackage setName(String name) { 18 | this.name = name; 19 | return this; 20 | } 21 | 22 | public AlgoliaRepository getRepository() { 23 | return repository; 24 | } 25 | 26 | public AlgoliaPackage setRepository(AlgoliaRepository repository) { 27 | this.repository = repository; 28 | return this; 29 | } 30 | 31 | public String getVersion() { 32 | return version; 33 | } 34 | 35 | public AlgoliaPackage setVersion(String version) { 36 | this.version = version; 37 | return this; 38 | } 39 | 40 | public List getVersions() { 41 | return versions; 42 | } 43 | 44 | public AlgoliaPackage setVersions(Map versions) { 45 | this.versions = new ArrayList<>(versions.keySet()); 46 | return this; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packagename/remotetypes/AlgoliaRepository.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packagename.remotetypes; 2 | 3 | public class AlgoliaRepository { 4 | private String url; 5 | 6 | public String getUrl() { 7 | return url; 8 | } 9 | 10 | public AlgoliaRepository setUrl(String url) { 11 | this.url = url; 12 | return this; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/packageversion/VersionInput.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.packageversion; 2 | 3 | import com.github.zafarkhaja.semver.Version; 4 | import com.intellij.openapi.editor.Editor; 5 | import com.intellij.openapi.project.Project; 6 | import com.jsdelivr.pluginintellij.packagefile.FileInput; 7 | import com.jsdelivr.pluginintellij.packagename.NameInput; 8 | import com.jsdelivr.pluginintellij.packagename.remotetypes.AlgoliaPackage; 9 | import com.jsdelivr.pluginintellij.ui.DefaultListItem; 10 | import com.jsdelivr.pluginintellij.ui.JsDelivrInput; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Comparator; 14 | import java.util.List; 15 | 16 | public class VersionInput extends JsDelivrInput { 17 | private static final String messageLoading = "Loading..."; 18 | private static final String messageNotFound = "Version not found"; 19 | private static final String messagePlaceholder = "Package version"; 20 | 21 | private AlgoliaPackage pkg; 22 | private Comparator versionAscending = Comparator.reverseOrder(); 23 | 24 | public VersionInput(Editor editor, Project project, AlgoliaPackage pkg) { 25 | super(editor, project, messagePlaceholder); 26 | 27 | loading = true; 28 | 29 | this.pkg = pkg; 30 | updateAutocomplete(""); 31 | list.resetSelection(); 32 | 33 | loading = false; 34 | } 35 | 36 | public VersionInput(Editor editor, Project project, AlgoliaPackage pkg, String version) { 37 | super(editor, project, messagePlaceholder); 38 | 39 | loading = true; 40 | 41 | this.pkg = pkg; 42 | inputField.setText(version); 43 | inputField.setForeground(editor.getContentComponent().getForeground()); 44 | updateAutocomplete(version); 45 | list.resetSelection(); 46 | 47 | loading = false; 48 | } 49 | 50 | @Override 51 | protected void updateAutocomplete(String text) { 52 | List list = new ArrayList<>(); 53 | 54 | JsDelivrInput.list.setEmptyText(messageLoading); 55 | 56 | if (pkg.getVersions() == null) { 57 | JsDelivrInput.list.setEmptyText(messageNotFound); 58 | } 59 | 60 | for (String version : pkg.getVersions()) { 61 | if (version.startsWith(text)) { 62 | try { 63 | list.add(Version.valueOf(version)); 64 | } catch (Exception e) { 65 | System.out.println("Unrecognized version " + version); 66 | } 67 | } 68 | } 69 | 70 | if (list.isEmpty()) { 71 | JsDelivrInput.list.setEmptyText(messageNotFound); 72 | } 73 | 74 | list.sort(versionAscending); 75 | JsDelivrInput.list.getDefaultModel().clear(); 76 | 77 | for (Version version : list) { 78 | JsDelivrInput.list.getDefaultModel().addElement(new DefaultListItem(version.toString())); 79 | } 80 | } 81 | 82 | @Override 83 | protected boolean inputComplete(String text) { 84 | list.getDefaultModel().clear(); 85 | list.setEmptyText(messageLoading); 86 | 87 | if (!pkg.getVersions().contains(text)) { 88 | list.setEmptyText(messageNotFound); 89 | return false; 90 | } 91 | 92 | new FileInput(editor, project, pkg.getName(), text).setFont(getFont()); 93 | return true; 94 | } 95 | 96 | @Override 97 | protected void previousInput() { 98 | new NameInput(editor, project, pkg.getName()).setFont(getFont()); 99 | popup.closePopup(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/ui/DefaultListItem.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.ui; 2 | 3 | public class DefaultListItem implements IJsDelivrListItem { 4 | private String value; 5 | 6 | public DefaultListItem(String value) { 7 | this.value = value; 8 | } 9 | 10 | public String toString() { 11 | return value; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/ui/IJsDelivrListItem.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.ui; 2 | 3 | public interface IJsDelivrListItem { 4 | String toString(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/ui/JsDelivrInput.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.ui; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.openapi.editor.Editor; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.ui.JBColor; 7 | import com.intellij.ui.components.JBTextField; 8 | 9 | import javax.swing.event.DocumentEvent; 10 | import javax.swing.event.DocumentListener; 11 | import java.awt.*; 12 | import java.awt.event.*; 13 | 14 | public abstract class JsDelivrInput implements FocusListener { 15 | public String placeholder; 16 | public JBTextField inputField; 17 | public Editor editor; 18 | public Project project; 19 | public JsDelivrPopup popup; 20 | public boolean loading = false; 21 | public static JsDelivrList list; 22 | 23 | private Font font; 24 | 25 | public JsDelivrInput(Editor editor, Project project, String placeholder) { 26 | this.editor = editor; 27 | this.project = project; 28 | this.placeholder = placeholder; 29 | 30 | inputField = new JBTextField(); 31 | inputField.addMouseListener(new JsDelivrMouseListener()); 32 | inputField.getDocument().addDocumentListener(new PasteListener()); 33 | 34 | list = new JsDelivrList(s -> { 35 | inputComplete(s.toString()); 36 | popup.closePopup(); 37 | return null; 38 | }); 39 | 40 | list.setFont(getFont()); 41 | list.setMaximumSize(JsDelivrPopup.popupDim); 42 | popup = new JsDelivrPopup(editor, new JsDelivrKeyDispatcher(), inputField, list.getPane()); 43 | 44 | inputField.requestFocus(); 45 | inputField.setForeground(JBColor.GRAY); 46 | inputField.setText(placeholder); 47 | inputField.setCaretPosition(0); 48 | } 49 | 50 | /** 51 | * Update autocomplete hints in variable list. 52 | * 53 | * @param text Text in input field 54 | */ 55 | protected abstract void updateAutocomplete(String text); 56 | 57 | /** 58 | * Continue to next stage of input process. 59 | * 60 | * @param text Text in input field 61 | * @return Completion success 62 | */ 63 | protected abstract boolean inputComplete(String text); 64 | 65 | /** 66 | * Go to previous stage of input process. 67 | */ 68 | protected abstract void previousInput(); 69 | 70 | /** 71 | * Support custom key event actions. 72 | * 73 | * @param keyEvent key event 74 | */ 75 | protected void onKeyEvent(KeyEvent keyEvent) { 76 | 77 | } 78 | 79 | @Override 80 | public void focusLost(FocusEvent event) { 81 | popup.closePopup(); 82 | } 83 | 84 | @Override 85 | public void focusGained(FocusEvent event) { 86 | // no op 87 | } 88 | 89 | public Font getFont() { 90 | return this.font; 91 | } 92 | 93 | public void setFont(Font font) { 94 | list.setFont(font); 95 | inputField.setFont(font); 96 | this.font = font; 97 | } 98 | 99 | class JsDelivrKeyDispatcher implements KeyEventDispatcher { 100 | boolean shift = false; 101 | boolean tab = false; 102 | private String lastUpdate = ""; 103 | 104 | private boolean isAllowedWhenInputEmpty(KeyEvent event) { 105 | return event.getKeyCode() != KeyEvent.VK_DELETE 106 | && event.getKeyCode() != KeyEvent.VK_BACK_SPACE 107 | && event.getKeyCode() != KeyEvent.VK_LEFT 108 | && event.getKeyCode() != KeyEvent.VK_RIGHT; 109 | } 110 | 111 | private boolean isAllowedWhenListEmpty(KeyEvent event) { 112 | return event.getKeyCode() != KeyEvent.VK_ENTER 113 | && event.getKeyCode() != KeyEvent.VK_TAB 114 | && event.getKeyCode() != KeyEvent.VK_UP 115 | && event.getKeyCode() != KeyEvent.VK_DOWN; 116 | } 117 | 118 | private boolean mustResetPlaceholder(KeyEvent event) { 119 | return event.getKeyCode() == KeyEvent.VK_SHIFT 120 | || event.isActionKey() 121 | || event.getKeyCode() == KeyEvent.VK_TAB 122 | || event.getKeyCode() == KeyEvent.VK_ALT 123 | || event.getKeyCode() == KeyEvent.VK_CONTROL 124 | || event.getKeyCode() == KeyEvent.VK_CAPS_LOCK 125 | || event.getKeyCode() == KeyEvent.VK_NUM_LOCK 126 | || event.getKeyCode() == KeyEvent.VK_DELETE 127 | || event.getKeyCode() == KeyEvent.VK_BACK_SPACE 128 | || (event.getKeyCode() == KeyEvent.VK_DELETE && event.isShiftDown()); 129 | } 130 | 131 | @Override 132 | public boolean dispatchKeyEvent(KeyEvent ke) { 133 | if ((!isAllowedWhenInputEmpty(ke) && inputField.getText().equals(placeholder)) 134 | || (!isAllowedWhenListEmpty(ke) && list.getDefaultModel().isEmpty()) 135 | ) { 136 | ke.consume(); 137 | return true; 138 | } 139 | 140 | if (ke.getID() == KeyEvent.KEY_PRESSED) { 141 | // input was empty 142 | if (inputField.getText().equals(placeholder)) { 143 | // user pressed a symbol, input field will get rid of placeholder 144 | if (isAllowedWhenInputEmpty(ke) && !mustResetPlaceholder(ke)) { 145 | inputField.setForeground(editor.getContentComponent().getForeground()); 146 | inputField.setText(""); 147 | // user pressed an action key, must reset placeholder so it doesn't flicker 148 | } else { 149 | inputField.setText(placeholder); 150 | inputField.setCaretPosition(0); 151 | inputField.setForeground(JBColor.GRAY); 152 | } 153 | } 154 | 155 | // update shift + tab combo state 156 | if (ke.getKeyCode() == KeyEvent.VK_SHIFT) { 157 | shift = true; 158 | ke.consume(); 159 | } else if (ke.getKeyCode() == KeyEvent.VK_TAB) { 160 | tab = true; 161 | ke.consume(); 162 | } 163 | } 164 | 165 | if (ke.getID() == KeyEvent.KEY_RELEASED) { 166 | // shift + tab was pressed, going to previous input 167 | if ((ke.getKeyCode() == KeyEvent.VK_TAB && (shift || ke.isShiftDown())) || (ke.getKeyCode() == KeyEvent.VK_SHIFT && tab)) { 168 | shift = ke.getKeyCode() == KeyEvent.VK_TAB; 169 | tab = ke.getKeyCode() == KeyEvent.VK_SHIFT; 170 | 171 | ke.consume(); 172 | 173 | if (!loading) { 174 | previousInput(); 175 | } 176 | // released tab or enter, going to next input 177 | } else if (ke.getKeyCode() == KeyEvent.VK_ENTER || ke.getKeyCode() == KeyEvent.VK_TAB) { 178 | tab = tab && ke.getKeyCode() != KeyEvent.VK_TAB; 179 | ke.consume(); 180 | 181 | // if nothing was selected, reset the placeholder to avoid flickering 182 | if (list.getDefaultModel().isEmpty()) { 183 | inputField.setText(placeholder); 184 | inputField.setForeground(JBColor.GRAY); 185 | inputField.setCaretPosition(0); 186 | return false; 187 | } 188 | 189 | // go to next input if list is not loading 190 | if (inputComplete(list.getSelectedItem().toString()) && !loading) { 191 | popup.closePopup(); 192 | } 193 | // updating shift state 194 | } else if (ke.getKeyCode() == KeyEvent.VK_SHIFT) { 195 | shift = false; 196 | ke.consume(); 197 | // if a key that doesn't suit any of the above was released (except up & down arrow keys) 198 | } else if (ke.getKeyCode() != KeyEvent.VK_UP && ke.getKeyCode() != KeyEvent.VK_DOWN) { 199 | // update input field (for example if backspace was pressed and no text is left in input) 200 | if (inputField.getText().equals("")) { 201 | inputField.setForeground(JBColor.GRAY); 202 | inputField.setText(placeholder); 203 | inputField.setCaretPosition(0); 204 | updateAutocomplete(""); 205 | list.resetSelection(); 206 | lastUpdate = ""; 207 | } else if ((!mustResetPlaceholder(ke) || ke.getKeyCode() == KeyEvent.VK_BACK_SPACE || ke.getKeyCode() == KeyEvent.VK_DELETE) && !inputField.getText().equals(placeholder)) { 208 | // if input changed, update list 209 | if (!inputField.getText().equals(lastUpdate)) { 210 | lastUpdate = inputField.getText(); 211 | updateAutocomplete(inputField.getText()); 212 | list.resetSelection(); 213 | } 214 | } 215 | } 216 | } 217 | 218 | // run custom handlers which may be implemented in subclasses 219 | onKeyEvent(ke); 220 | 221 | // pass up & down arrow keys to list 222 | list.keyEvent(ke); 223 | 224 | return false; 225 | } 226 | } 227 | 228 | class PasteListener implements DocumentListener { 229 | @Override 230 | public void insertUpdate(DocumentEvent event) { 231 | ApplicationManager.getApplication().invokeLater(() -> { 232 | ApplicationManager.getApplication().runWriteAction(() -> { 233 | if (inputField.getText().contains(placeholder) && inputField.getText().length() > placeholder.length()) { 234 | inputField.setForeground(editor.getContentComponent().getForeground()); 235 | inputField.setText(inputField.getText().substring(0, event.getLength())); 236 | } 237 | }); 238 | }); 239 | } 240 | 241 | @Override 242 | public void removeUpdate(DocumentEvent event) { 243 | // no op 244 | } 245 | 246 | @Override 247 | public void changedUpdate(DocumentEvent event) { 248 | // no op 249 | } 250 | } 251 | 252 | class JsDelivrMouseListener implements MouseListener { 253 | @Override 254 | public void mouseClicked(MouseEvent event) { 255 | if (inputField.getText().equals(placeholder)) { 256 | event.consume(); 257 | inputField.select(0, 0); 258 | } 259 | } 260 | 261 | @Override 262 | public void mousePressed(MouseEvent event) { 263 | if (inputField.getText().equals(placeholder)) { 264 | event.consume(); 265 | inputField.select(0, 0); 266 | } 267 | } 268 | 269 | @Override 270 | public void mouseReleased(MouseEvent event) { 271 | if (inputField.getText().equals(placeholder)) { 272 | event.consume(); 273 | inputField.select(0, 0); 274 | } 275 | } 276 | 277 | @Override 278 | public void mouseEntered(MouseEvent event) { 279 | // no op 280 | } 281 | 282 | @Override 283 | public void mouseExited(MouseEvent event) { 284 | // no op 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/ui/JsDelivrList.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.ui; 2 | 3 | import com.intellij.ui.components.JBList; 4 | import com.intellij.ui.components.JBScrollPane; 5 | 6 | import javax.swing.*; 7 | import java.awt.*; 8 | import java.awt.event.KeyEvent; 9 | import java.awt.event.MouseAdapter; 10 | import java.awt.event.MouseEvent; 11 | import java.awt.event.MouseMotionAdapter; 12 | import java.util.Collections; 13 | import java.util.function.Function; 14 | import java.util.stream.Collectors; 15 | 16 | public class JsDelivrList extends JBList { 17 | private Font font; 18 | private JBScrollPane pane; 19 | 20 | public JsDelivrList(Function onItemSelected) { 21 | super(new DefaultListModel<>()); 22 | 23 | setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 24 | setSelectedIndex(0); 25 | setVisibleRowCount(5); 26 | setEmptyText("Loading..."); 27 | 28 | addMouseListener(new MouseAdapter() { 29 | @Override 30 | public void mouseClicked(MouseEvent mouseEvent) { 31 | JBList lst = (JBList) mouseEvent.getSource(); 32 | int index = lst.locationToIndex(mouseEvent.getPoint()); 33 | 34 | if (index >= 0) { 35 | IJsDelivrListItem object = (IJsDelivrListItem) lst.getModel().getElementAt(index); 36 | onItemSelected.apply(object); 37 | } 38 | } 39 | }); 40 | 41 | addMouseMotionListener(new MouseMotionAdapter() { 42 | @Override 43 | public void mouseMoved(MouseEvent event) { 44 | JBList lst = (JBList) event.getSource(); 45 | int index = lst.locationToIndex(event.getPoint()); 46 | 47 | if (index >= 0) { 48 | lst.setSelectedIndex(index); 49 | } 50 | } 51 | }); 52 | 53 | pane = new JBScrollPane(this); 54 | pane.setBorder(null); 55 | } 56 | 57 | public Font getFont() { 58 | return this.font; 59 | } 60 | 61 | public void setFont(Font font) { 62 | super.setFont(font); 63 | this.font = font; 64 | } 65 | 66 | public JBScrollPane getPane() { 67 | return pane; 68 | } 69 | 70 | public IJsDelivrListItem getSelectedItem() { 71 | if (getSelectedValue() == null) { 72 | return null; 73 | } 74 | 75 | return getSelectedValue(); 76 | } 77 | 78 | public boolean contains(String item) { 79 | return Collections.list(this.getDefaultModel().elements()).stream().anyMatch(i -> i.toString().equals(item)); 80 | } 81 | 82 | public void resetSelection() { 83 | if (!isEmpty()) { 84 | setSelectedIndex(0); 85 | } 86 | } 87 | 88 | public void keyEvent(KeyEvent ke) { 89 | if (ke.getID() == KeyEvent.KEY_PRESSED) { 90 | if (ke.getKeyCode() == KeyEvent.VK_DOWN) { 91 | setSelectedIndex(getSelectedIndex() + 1); 92 | } else if (ke.getKeyCode() == KeyEvent.VK_UP) { 93 | setSelectedIndex(getSelectedIndex() - 1); 94 | } 95 | } 96 | 97 | ensureIndexIsVisible(getSelectedIndex()); 98 | } 99 | 100 | public DefaultListModel getDefaultModel() { 101 | return (DefaultListModel) getModel(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/jsdelivr/pluginintellij/ui/JsDelivrPopup.java: -------------------------------------------------------------------------------- 1 | package com.jsdelivr.pluginintellij.ui; 2 | 3 | import com.intellij.openapi.editor.Editor; 4 | import com.intellij.openapi.ui.popup.*; 5 | import com.intellij.util.ui.FormBuilder; 6 | 7 | import javax.swing.*; 8 | import java.awt.*; 9 | 10 | public class JsDelivrPopup { 11 | JBPopup popup; 12 | Editor editor; 13 | 14 | public static final Dimension popupDim = new Dimension(200, 62); 15 | 16 | public JsDelivrPopup(Editor editor, KeyEventDispatcher keyDispatcher, JComponent... components) { 17 | this.editor = editor; 18 | 19 | FormBuilder formBuilder = FormBuilder.createFormBuilder(); 20 | 21 | for (JComponent component : components) { 22 | formBuilder.addComponent(component); 23 | } 24 | 25 | ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(formBuilder.getPanel(), null); 26 | popup = builder.setModalContext(true).setRequestFocus(true).setResizable(true).setMovable(true).createPopup(); 27 | 28 | popup.setMinimumSize(popupDim); 29 | 30 | // Under caret position 31 | popup.showInBestPositionFor(editor); 32 | KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(keyDispatcher); 33 | 34 | popup.addListener(new JBPopupListener() { 35 | @Override 36 | public void onClosed(LightweightWindowEvent event) { 37 | if (keyDispatcher != null) { 38 | KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(keyDispatcher); 39 | } 40 | } 41 | }); 42 | } 43 | 44 | public void closePopup() { 45 | if (popup != null) { 46 | popup.cancel(); 47 | popup.dispose(); 48 | popup = null; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | jsDelivr package search 3 | 4 | com.jsdelivr.pluginintellij.JsDelivrPackageSearch 5 | 6 | https://www.jsdelivr.com 8 | ]]> 9 | 10 | v1.0.10
12 | Resolve deprecation warnings in latest IntelliJ versions.

13 | 14 | v1.0.9
15 | Resolve deprecation warnings in IntelliJ 2024.1.

16 | 17 | v1.0.8
18 | Update dependencies.

19 | 20 | v1.0.7
21 | Update IntelliJ platform to 2020.1, add plugin logo.

22 | 23 | v1.0.6
24 | Update IntelliJ platform to 2019.3.

25 | 26 | v1.0.5
27 | Update IntelliJ platform to 2019.2.

28 | 29 | v1.0.4
30 | Fix handling of invalid semver versions.

31 | 32 | v1.0.3
33 | Update IntelliJ platform to 2019.1.

34 | 35 | v1.0.2
36 | Add keyboard shortcuts to quickly open jsDelivr, npm, or GitHub page of the selected package.

37 | 38 | v1.0.1
39 | Fix pasting into empty input.

40 | 41 | v1.0.0
42 | Initial release. 43 | ]]>
44 | 45 | 1.0.7 46 | 47 | 48 | jsDelivr 49 | 50 | com.intellij.modules.lang 51 | 52 | 53 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | 66 | 67 | 68 | 71 | 72 | 73 | 74 |
75 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | --------------------------------------------------------------------------------