├── .gitattributes ├── .gitignore ├── .idea ├── encodings.xml ├── misc.xml └── vcs.xml ├── LICENSE.txt ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── imgs ├── add-search-service.gif └── gearPlain.png ├── settings.gradle └── src └── main ├── java └── io │ └── heidou │ └── codesearch │ ├── CodeSearchStartupActivity.java │ ├── actions │ ├── BaiduSearchAction.java │ ├── CustomSearchAction.java │ ├── GitHubSearchAction.java │ ├── GoogleSearchAction.java │ ├── SearchAction.java │ └── StackOverflowSearchAction.java │ ├── i18n │ └── CodeSearchBundle.java │ ├── model │ └── SearchService.java │ ├── settings │ ├── CodeSearchConfigurable.java │ ├── DescriptionColumnInfo.java │ ├── IconColumnInfo.java │ ├── NameColumnInfo.java │ ├── SearchServiceColumnInfo.java │ ├── SearchServiceDialog.form │ ├── SearchServiceDialog.java │ ├── SearchServicePanel.java │ ├── SearchServiceSettings.java │ ├── SearchServiceTableModel.java │ └── UrlColumnInfo.java │ └── util │ ├── IconUtil.java │ └── UrlUtils.java └── resources ├── META-INF └── plugin.xml ├── icons ├── baidu.png ├── github.png ├── google.png └── stackoverflow.png └── messages └── CodeSearchBundle.properties /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/java,gradle,intellij 2 | 3 | ### Intellij ### 4 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 5 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 6 | 7 | # User-specific stuff: 8 | .idea/**/workspace.xml 9 | .idea/**/tasks.xml 10 | .idea/dictionaries 11 | 12 | # Sensitive or high-churn files: 13 | .idea/**/dataSources/ 14 | .idea/**/dataSources.ids 15 | .idea/**/dataSources.xml 16 | .idea/**/dataSources.local.xml 17 | .idea/**/sqlDataSources.xml 18 | .idea/**/dynamic.xml 19 | .idea/**/uiDesigner.xml 20 | 21 | # Gradle: 22 | .idea/**/gradle.xml 23 | .idea/**/libraries 24 | 25 | # CMake 26 | cmake-build-debug/ 27 | 28 | # Mongo Explorer plugin: 29 | .idea/**/mongoSettings.xml 30 | 31 | ## File-based project format: 32 | *.iws 33 | 34 | ## Plugin-specific files: 35 | 36 | # IntelliJ 37 | /out/ 38 | 39 | # mpeltonen/sbt-idea plugin 40 | .idea_modules/ 41 | 42 | # JIRA plugin 43 | atlassian-ide-plugin.xml 44 | 45 | # Cursive Clojure plugin 46 | .idea/replstate.xml 47 | 48 | # Ruby plugin and RubyMine 49 | /.rakeTasks 50 | 51 | # Crashlytics plugin (for Android Studio and IntelliJ) 52 | com_crashlytics_export_strings.xml 53 | crashlytics.properties 54 | crashlytics-build.properties 55 | fabric.properties 56 | 57 | ### Intellij Patch ### 58 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 59 | 60 | # *.iml 61 | # modules.xml 62 | # .idea/misc.xml 63 | # *.ipr 64 | 65 | # Sonarlint plugin 66 | .idea/sonarlint 67 | 68 | ### Java ### 69 | # Compiled class file 70 | *.class 71 | 72 | # Log file 73 | *.log 74 | 75 | # BlueJ files 76 | *.ctxt 77 | 78 | # Mobile Tools for Java (J2ME) 79 | .mtj.tmp/ 80 | 81 | # Package Files # 82 | *.jar 83 | *.war 84 | *.ear 85 | *.zip 86 | *.tar.gz 87 | *.rar 88 | 89 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 90 | hs_err_pid* 91 | 92 | ### Gradle ### 93 | .gradle 94 | **/build/ 95 | 96 | # Ignore Gradle GUI config 97 | gradle-app.setting 98 | 99 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 100 | !gradle-wrapper.jar 101 | 102 | # Cache of project 103 | .gradletasknamecache 104 | 105 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 106 | # gradle/wrapper/gradle-wrapper.properties 107 | 108 | 109 | # End of https://www.gitignore.io/api/java,gradle,intellij -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); 2 | you may not use this file except in compliance with the License. 3 | You may obtain a copy of the License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeSearch Plugin for IntelliJ IDEA-based IDEs 2 | 3 | This plugin provides support for code search in IntelliJ IDEA, Android Studio, GoLand, WebStorm, PyCharm, CLion, PhpStorm, RubyMine, AppCode, DataGrip, Rider and MPS. 4 | 5 | It provides search with Baidu, Google and StackOverflow. You can also customize the search service. 6 | 7 | ## Getting started 8 | 9 | ### How to install the plugin 10 | 11 | #### Install plugin from repository 12 | 13 | - Select **File | Setting... | Settings/Preferences | Plugins** 14 | - Find **CodeSearch** plugin in the **Marketplace** and click **Install**. 15 | 16 | #### Install plugin from disk 17 | 18 | - Download **CodeSearch** plugin from [JetBrains Plugins Repository](https://plugins.jetbrains.com/plugin/12578-codesearch). 19 | - Select **File | Setting... | Settings/Preferences | Plugins**. 20 | - Click ![Settings Icon](https://github.com/guojianhua/code-search/blob/master/imgs/gearPlain.png) and then click **Install Plugin from Disk**. 21 | - Select the plugin archive file and click **OK**. 22 | - Click **OK** to apply the changes and restart the IDE if prompted. 23 | 24 | ### How to customize the search service 25 | 26 | - Select **Code Search | Custom Search...** menu in the editor. 27 | - Open **Code Search** Settings, click +. 28 | - In the **Add Search Service** dialog 29 | * input search service name: **required**, for menu name. 30 | * input search service description: optional, for menu description. 31 | * specify search service URL: **required**. Click **Check** button to check the connection. 32 | * specify search service icon path: optional, recommend 16x16 png icon, for menu icon. 33 | - Click **Add** to close **Add Search Service** dialog and save custom search service. 34 | - Take effect after restart IDE. 35 | - After restart IDE, you can see the menu with the name of the custom search service in the right-click **Code Search** menu bar of the editor. 36 | 37 | ![Add Search Service](https://github.com/guojianhua/code-search/blob/master/imgs/add-search-service.gif) -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.jetbrains.intellij' version '0.5.0' 4 | } 5 | 6 | group 'io.heidou' 7 | version '1.1.0' 8 | 9 | sourceCompatibility = javaVersion 10 | targetCompatibility = javaVersion 11 | 12 | repositories { 13 | maven { url 'https://repo1.maven.org/maven2/'} 14 | } 15 | 16 | dependencies { 17 | testCompile group: 'junit', name: 'junit', version: '4.12' 18 | } 19 | 20 | intellij { 21 | version ideaVersion 22 | pluginName 'CodeSearch' 23 | updateSinceUntilBuild = false 24 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | javaVersion=1.8 2 | ideaVersion=IC-2016.3 3 | publishUsername=heidou 4 | publishChannels=Stable -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojianhua/code-search/d12d22ad3efa136c071c92616cd37efc0fd38a5c/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-6.7-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or 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 UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /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 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /imgs/add-search-service.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojianhua/code-search/d12d22ad3efa136c071c92616cd37efc0fd38a5c/imgs/add-search-service.gif -------------------------------------------------------------------------------- /imgs/gearPlain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojianhua/code-search/d12d22ad3efa136c071c92616cd37efc0fd38a5c/imgs/gearPlain.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'code-search' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/CodeSearchStartupActivity.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch; 2 | 3 | import com.intellij.openapi.actionSystem.ActionManager; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.actionSystem.Anchor; 6 | import com.intellij.openapi.actionSystem.Constraints; 7 | import com.intellij.openapi.actionSystem.DefaultActionGroup; 8 | import com.intellij.openapi.actionSystem.Separator; 9 | import com.intellij.openapi.project.Project; 10 | import com.intellij.openapi.startup.StartupActivity; 11 | import com.intellij.openapi.util.text.StringUtil; 12 | import io.heidou.codesearch.actions.SearchAction; 13 | import io.heidou.codesearch.model.SearchService; 14 | import io.heidou.codesearch.settings.SearchServiceSettings; 15 | import io.heidou.codesearch.util.IconUtil; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | import javax.swing.Icon; 19 | import java.util.List; 20 | 21 | /** 22 | * Code search startup activity 23 | * 24 | * @author guojianhua 25 | * @since 2020-07-05 26 | */ 27 | public class CodeSearchStartupActivity implements StartupActivity { 28 | private volatile boolean registered = false; 29 | 30 | private static final String CODE_SEARCH_ACTION_GROUP_ID = "HeiDou.CodeSearch"; 31 | private static final String CUSTOM_SEARCH_ACTION_GROUP_NAME = "Custom Search Group"; 32 | private static final String CUSTOM_SEARCH_ACTION_ID = "HeiDou.CodeSearch.CustomSearch"; 33 | 34 | @Override 35 | public void runActivity(@NotNull Project project) { 36 | if (!registered) { 37 | addCustomSearchServiceAction(); 38 | registered = true; 39 | } 40 | } 41 | 42 | private void addCustomSearchServiceAction() { 43 | final ActionManager actionManager = ActionManager.getInstance(); 44 | final AnAction codeSearchMenu = actionManager.getAction(CODE_SEARCH_ACTION_GROUP_ID); 45 | if (!(codeSearchMenu instanceof DefaultActionGroup)) { 46 | return; 47 | } 48 | 49 | final DefaultActionGroup codeSearchGroup = ((DefaultActionGroup) codeSearchMenu); 50 | final DefaultActionGroup customActionGroup = new DefaultActionGroup(CUSTOM_SEARCH_ACTION_GROUP_NAME, false); 51 | final List searchServiceList = SearchServiceSettings.getInstance().getSearchServiceList(); 52 | for (SearchService searchService : searchServiceList) { 53 | final String name = searchService.getName(); 54 | final String url = searchService.getUrl(); 55 | if (StringUtil.isEmptyOrSpaces(name) || StringUtil.isEmptyOrSpaces(url)) { 56 | continue; 57 | } 58 | 59 | String description = searchService.getDescription(); 60 | description = StringUtil.isEmptyOrSpaces(description) ? "" : description.trim(); 61 | final Icon icon = IconUtil.getIcon(searchService.getIconPath()); 62 | customActionGroup.add(new SearchAction(name.trim(), description, icon, url.trim())); 63 | } 64 | 65 | customActionGroup.add(new Separator()); 66 | 67 | codeSearchGroup.add(customActionGroup, new Constraints(Anchor.BEFORE, CUSTOM_SEARCH_ACTION_ID)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/actions/BaiduSearchAction.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.actions; 2 | 3 | import io.heidou.codesearch.util.IconUtil; 4 | 5 | /** 6 | * Search with Baidu 7 | * 8 | * @author guojianhua 9 | * @since 2019-04-21 10 | */ 11 | public class BaiduSearchAction extends SearchAction { 12 | private static final String BAIDU_URL = "https://www.baidu.com/s?wd="; 13 | 14 | public BaiduSearchAction() { 15 | super("Search with Baidu", "Search with Baidu", IconUtil.BAIDU, BAIDU_URL); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/actions/CustomSearchAction.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.options.ShowSettingsUtil; 6 | import com.intellij.openapi.project.Project; 7 | import io.heidou.codesearch.i18n.CodeSearchBundle; 8 | import io.heidou.codesearch.settings.CodeSearchConfigurable; 9 | 10 | /** 11 | * Custom search action 12 | * 13 | * @author guojianhua 14 | * @since 2019-03-25 15 | */ 16 | public class CustomSearchAction extends AnAction { 17 | public CustomSearchAction() { 18 | super(CodeSearchBundle.message("custom.search.action.name")); 19 | } 20 | 21 | @Override 22 | public void actionPerformed(AnActionEvent e) { 23 | final Project project = e.getProject(); 24 | ShowSettingsUtil.getInstance().showSettingsDialog(project, CodeSearchConfigurable.class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/actions/GitHubSearchAction.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.actions; 2 | 3 | import io.heidou.codesearch.util.IconUtil; 4 | 5 | /** 6 | * Search with GitHub 7 | * 8 | * @author guojianhua 9 | * @since 2020-10-17 10 | */ 11 | public class GitHubSearchAction extends SearchAction { 12 | private static final String GITHUB_URL = "https://github.com/search?q="; 13 | 14 | public GitHubSearchAction() { 15 | super("Search with GitHub", "Search with GitHub", IconUtil.GITHUB, GITHUB_URL); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/actions/GoogleSearchAction.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.actions; 2 | 3 | import io.heidou.codesearch.util.IconUtil; 4 | 5 | /** 6 | * Search with Google 7 | * 8 | * @author guojianhua 9 | * @since 2019-04-21 10 | */ 11 | public class GoogleSearchAction extends SearchAction { 12 | private static final String GOOGLE_URL = "https://www.google.com/search?q="; 13 | 14 | public GoogleSearchAction() { 15 | super("Search with Google", "Search with Google", IconUtil.GOOGLE, GOOGLE_URL); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/actions/SearchAction.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.actions; 2 | 3 | import com.intellij.ide.BrowserUtil; 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.intellij.openapi.util.text.StringUtil; 10 | import io.heidou.codesearch.util.UrlUtils; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import javax.swing.Icon; 15 | 16 | /** 17 | * Search base class 18 | * 19 | * @author guojianhua 20 | * @since 2019-04-21 21 | */ 22 | public class SearchAction extends AnAction { 23 | private final String myUrl; 24 | 25 | public SearchAction(@NotNull String text, @Nullable String description, @Nullable Icon icon, 26 | @NotNull String url) { 27 | super(text, description, icon); 28 | this.myUrl = url; 29 | } 30 | 31 | @Override 32 | public void actionPerformed(AnActionEvent e) { 33 | final Project project = e.getProject(); 34 | if (null == project || !project.isInitialized() || project.isDisposed()) { 35 | return; 36 | } 37 | 38 | final Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext()); 39 | String selectedText = editor != null ? editor.getSelectionModel().getSelectedText() : ""; 40 | selectedText = StringUtil.isEmptyOrSpaces(selectedText) ? "" : selectedText.trim(); 41 | BrowserUtil.browse(myUrl + UrlUtils.encodeURIComponent(selectedText)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/actions/StackOverflowSearchAction.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.actions; 2 | 3 | import io.heidou.codesearch.util.IconUtil; 4 | 5 | /** 6 | * Search with StackOverflow 7 | * 8 | * @author guojianhua 9 | * @since 2019-04-21 10 | */ 11 | public class StackOverflowSearchAction extends SearchAction { 12 | private static final String STACKOVERFLOW_URL = "https://stackoverflow.com/search?q="; 13 | 14 | public StackOverflowSearchAction() { 15 | super("Search with StackOverflow", "Search with StackOverflow", IconUtil.STACKOVERFLOW, STACKOVERFLOW_URL); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/i18n/CodeSearchBundle.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.i18n; 2 | 3 | import com.intellij.CommonBundle; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.PropertyKey; 6 | 7 | import java.lang.ref.Reference; 8 | import java.lang.ref.SoftReference; 9 | import java.util.ResourceBundle; 10 | 11 | /** 12 | * Code search bundle 13 | * 14 | * @author guojianhua 15 | * @since 2019-04-07 16 | */ 17 | public class CodeSearchBundle { 18 | private static Reference codeSearchBundle; 19 | private static final String BUNDLE = "messages.CodeSearchBundle"; 20 | 21 | private CodeSearchBundle() { 22 | } 23 | 24 | public static String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, @NotNull Object... params) { 25 | return CommonBundle.message(getBundle(), key, params); 26 | } 27 | 28 | private static ResourceBundle getBundle() { 29 | ResourceBundle bundle = com.intellij.reference.SoftReference.dereference(codeSearchBundle); 30 | if (null == bundle) { 31 | bundle = ResourceBundle.getBundle(BUNDLE); 32 | codeSearchBundle = new SoftReference(bundle); 33 | } 34 | return bundle; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/model/SearchService.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.model; 2 | 3 | import com.intellij.util.xmlb.annotations.Attribute; 4 | import com.intellij.util.xmlb.annotations.Property; 5 | import com.intellij.util.xmlb.annotations.Tag; 6 | 7 | import java.util.Objects; 8 | 9 | /** 10 | * Search type model 11 | * 12 | * @author guojianhua 13 | * @since 2019-04-07 14 | */ 15 | @Tag("searchService") 16 | @Property(style = Property.Style.ATTRIBUTE) 17 | public class SearchService { 18 | /** 19 | * the name of the search type, used as a action menu name 20 | */ 21 | @Attribute("name") 22 | private String name; 23 | 24 | /** 25 | * the description of the search type, used as a action menu description 26 | */ 27 | @Attribute("description") 28 | private String description; 29 | 30 | /** 31 | * search service url, such as https://www.google.com/search?q= 32 | */ 33 | @Attribute("url") 34 | private String url; 35 | 36 | /** 37 | * the icon of the search type, used as a action menu icon 38 | */ 39 | @Attribute("icon") 40 | private String iconPath; 41 | 42 | public SearchService() { 43 | } 44 | 45 | public String getName() { 46 | return name; 47 | } 48 | 49 | public void setName(String name) { 50 | this.name = name; 51 | } 52 | 53 | public String getDescription() { 54 | return description; 55 | } 56 | 57 | public void setDescription(String description) { 58 | this.description = description; 59 | } 60 | 61 | public String getUrl() { 62 | return url; 63 | } 64 | 65 | public void setUrl(String url) { 66 | this.url = url; 67 | } 68 | 69 | public String getIconPath() { 70 | return iconPath; 71 | } 72 | 73 | public void setIconPath(String iconPath) { 74 | this.iconPath = iconPath; 75 | } 76 | 77 | @Override 78 | public boolean equals(Object o) { 79 | if (this == o) { 80 | return true; 81 | } 82 | if (o == null || getClass() != o.getClass()) { 83 | return false; 84 | } 85 | SearchService that = (SearchService) o; 86 | return Objects.equals(name, that.name); 87 | } 88 | 89 | @Override 90 | public int hashCode() { 91 | return Objects.hash(name); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/settings/CodeSearchConfigurable.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.settings; 2 | 3 | import com.intellij.openapi.options.Configurable; 4 | import com.intellij.openapi.options.ConfigurationException; 5 | import com.intellij.openapi.options.SearchableConfigurable; 6 | import io.heidou.codesearch.i18n.CodeSearchBundle; 7 | import org.jetbrains.annotations.Nls; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import javax.swing.JComponent; 12 | import javax.swing.JPanel; 13 | import java.awt.BorderLayout; 14 | 15 | /** 16 | * Code search setting 17 | * 18 | * @author guojianhua <781377344@qq.com> 19 | * @since 2019-04-07 20 | */ 21 | public class CodeSearchConfigurable implements SearchableConfigurable, Configurable.NoScroll { 22 | private static final String ID = "HeiDou.CodeSearch.Settings"; 23 | 24 | @NotNull 25 | @Override 26 | public String getId() { 27 | return ID; 28 | } 29 | 30 | @Nls(capitalization = Nls.Capitalization.Title) 31 | @Override 32 | public String getDisplayName() { 33 | return CodeSearchBundle.message("codesearch.settings.title"); 34 | } 35 | 36 | @Nullable 37 | @Override 38 | public JComponent createComponent() { 39 | final JPanel rootPanel = new JPanel(); 40 | rootPanel.setLayout(new BorderLayout()); 41 | 42 | final SearchServicePanel searchServicePanel = new SearchServicePanel(); 43 | rootPanel.add(searchServicePanel, BorderLayout.CENTER); 44 | return rootPanel; 45 | } 46 | 47 | @Override 48 | public boolean isModified() { 49 | return false; 50 | } 51 | 52 | @Override 53 | public void apply() throws ConfigurationException { 54 | } 55 | 56 | @Override 57 | public Runnable enableSearch(String option) { 58 | return null; 59 | } 60 | 61 | @Override 62 | public String getHelpTopic() { 63 | return null; 64 | } 65 | 66 | @Override 67 | public void reset() { 68 | } 69 | 70 | @Override 71 | public void disposeUIResources() { 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/settings/DescriptionColumnInfo.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.settings; 2 | 3 | import io.heidou.codesearch.i18n.CodeSearchBundle; 4 | import io.heidou.codesearch.model.SearchService; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | /** 8 | * Description column 9 | * 10 | * @author guojianhua 11 | * @since 2019-04-12 12 | */ 13 | public class DescriptionColumnInfo extends SearchServiceColumnInfo { 14 | public DescriptionColumnInfo() { 15 | super(CodeSearchBundle.message("desc.column.name")); 16 | } 17 | 18 | @Nullable 19 | @Override 20 | public String valueOf(SearchService searchService) { 21 | return searchService.getDescription(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/settings/IconColumnInfo.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.settings; 2 | 3 | import com.intellij.ui.components.JBLabel; 4 | import com.intellij.util.ui.ColumnInfo; 5 | import io.heidou.codesearch.i18n.CodeSearchBundle; 6 | import io.heidou.codesearch.model.SearchService; 7 | import io.heidou.codesearch.util.IconUtil; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import javax.swing.Icon; 11 | import javax.swing.JTable; 12 | import javax.swing.table.DefaultTableCellRenderer; 13 | import javax.swing.table.TableCellRenderer; 14 | import java.awt.Component; 15 | 16 | /** 17 | * Icon column 18 | * 19 | * @author guojianhua 20 | * @since 2019-04-11 21 | */ 22 | public class IconColumnInfo extends ColumnInfo { 23 | private static final int WIDTH = 50; 24 | 25 | public IconColumnInfo() { 26 | super(CodeSearchBundle.message("icon.column.name")); 27 | } 28 | 29 | @Nullable 30 | @Override 31 | public Icon valueOf(SearchService searchService) { 32 | return IconUtil.getIcon(searchService.getIconPath()); 33 | } 34 | 35 | @Override 36 | public int getWidth(JTable table) { 37 | return WIDTH; 38 | } 39 | 40 | @Nullable 41 | @Override 42 | public TableCellRenderer getRenderer(SearchService searchService) { 43 | return ourIconRenderer; 44 | } 45 | 46 | /** 47 | * Renders an icon in a small square field 48 | */ 49 | private static final TableCellRenderer ourIconRenderer = new DefaultTableCellRenderer() { 50 | @Override 51 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, 52 | boolean hasFocus, int row, int column) { 53 | JBLabel label = new JBLabel((Icon)value); 54 | if (table.getSelectedRow() == row) { 55 | label.setBackground(table.getSelectionBackground()); 56 | label.setForeground(table.getSelectionForeground()); 57 | label.setOpaque(true); 58 | } 59 | 60 | return label; 61 | } 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/settings/NameColumnInfo.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.settings; 2 | 3 | import io.heidou.codesearch.i18n.CodeSearchBundle; 4 | import io.heidou.codesearch.model.SearchService; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | /** 8 | * Name column 9 | * 10 | * @author guojianhua 11 | * @since 2019-04-12 12 | */ 13 | public class NameColumnInfo extends SearchServiceColumnInfo { 14 | public NameColumnInfo() { 15 | super(CodeSearchBundle.message("name.column.name")); 16 | } 17 | 18 | @Nullable 19 | @Override 20 | public String valueOf(SearchService searchService) { 21 | return searchService.getName(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/settings/SearchServiceColumnInfo.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.settings; 2 | 3 | import com.intellij.openapi.util.Comparing; 4 | import com.intellij.util.ui.ColumnInfo; 5 | import io.heidou.codesearch.model.SearchService; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import javax.swing.JTable; 9 | import java.util.Comparator; 10 | 11 | /** 12 | * Search service column 13 | * 14 | * @author guojianhua 15 | * @since 2019-04-12 16 | */ 17 | public abstract class SearchServiceColumnInfo extends ColumnInfo { 18 | private final int myWidth; 19 | 20 | public SearchServiceColumnInfo(String name, int width) { 21 | super(name); 22 | myWidth = width; 23 | } 24 | 25 | public SearchServiceColumnInfo(String name) { 26 | this(name, -1); 27 | } 28 | 29 | @Nullable 30 | @Override 31 | public Comparator getComparator() { 32 | return (o1, o2) -> { 33 | final String s1 = valueOf(o1); 34 | final String s2 = valueOf(o2); 35 | return Comparing.compare(s1, s2); 36 | }; 37 | } 38 | 39 | @Override 40 | public int getWidth(JTable table) { 41 | return myWidth; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/settings/SearchServiceDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 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 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 |
111 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/settings/SearchServiceDialog.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.settings; 2 | 3 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; 4 | import com.intellij.openapi.progress.ProgressIndicator; 5 | import com.intellij.openapi.progress.ProgressManager; 6 | import com.intellij.openapi.progress.Task; 7 | import com.intellij.openapi.progress.impl.ProgressManagerImpl; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.openapi.ui.DialogWrapper; 10 | import com.intellij.openapi.ui.Messages; 11 | import com.intellij.openapi.ui.TextFieldWithBrowseButton; 12 | import com.intellij.openapi.ui.ValidationInfo; 13 | import com.intellij.openapi.util.text.StringUtil; 14 | import com.intellij.ui.SimpleColoredComponent; 15 | import com.intellij.ui.SimpleTextAttributes; 16 | import com.intellij.util.io.HttpRequests; 17 | import io.heidou.codesearch.i18n.CodeSearchBundle; 18 | import io.heidou.codesearch.model.SearchService; 19 | import org.jetbrains.annotations.NotNull; 20 | import org.jetbrains.annotations.Nullable; 21 | 22 | import javax.swing.JButton; 23 | import javax.swing.JComponent; 24 | import javax.swing.JPanel; 25 | import javax.swing.JTextField; 26 | import java.io.IOException; 27 | import java.util.HashMap; 28 | import java.util.Map; 29 | import java.util.concurrent.atomic.AtomicReference; 30 | 31 | /** 32 | * Add or edit search service dialog 33 | * 34 | * @author guojianhua 35 | * @since 2019-04-14 36 | */ 37 | public class SearchServiceDialog extends DialogWrapper { 38 | // 3 milliseconds 39 | private static final int READ_TIMEOUT = 3 * 1000; 40 | 41 | private static final String ASTERISK = "*"; 42 | 43 | private static final SimpleTextAttributes REQUIRED_ATTRS = SimpleTextAttributes.ERROR_ATTRIBUTES; 44 | 45 | private final Project myProject; 46 | private final SearchService mySearchService; 47 | private final SearchServicePanel mySearchServicePanel; 48 | private final boolean isAdd; 49 | 50 | private JPanel contentPanel; 51 | private JTextField nameTextField; 52 | private JTextField descriptionTextField; 53 | private JTextField urlTextField; 54 | private JButton checkButton; 55 | private TextFieldWithBrowseButton iconPathTextField; 56 | private SimpleColoredComponent nameLabel; 57 | private SimpleColoredComponent descriptionLabel; 58 | private SimpleColoredComponent urlLabel; 59 | private SimpleColoredComponent iconLabel; 60 | 61 | public SearchServiceDialog(@Nullable Project project, @Nullable SearchService searchService, 62 | @NotNull SearchServicePanel searchServicePanel) { 63 | super(project); 64 | 65 | myProject = project; 66 | mySearchService = searchService; 67 | this.mySearchServicePanel = searchServicePanel; 68 | isAdd = (null == searchService); 69 | final String title = isAdd ? CodeSearchBundle.message("add.title") : CodeSearchBundle.message("edit.title"); 70 | setTitle(title); 71 | setResizable(false); 72 | 73 | final String okButtonText = isAdd ? CodeSearchBundle.message("add") : CodeSearchBundle.message("edit"); 74 | setOKButtonText(okButtonText); 75 | init(); 76 | } 77 | 78 | @Override 79 | public void init() { 80 | super.init(); 81 | 82 | nameLabel.append("Name: "); 83 | nameLabel.append(ASTERISK, REQUIRED_ATTRS); 84 | 85 | descriptionLabel.append("Description: "); 86 | 87 | urlLabel.append("Url: "); 88 | urlLabel.append(ASTERISK, REQUIRED_ATTRS); 89 | 90 | iconLabel.append("Icon Path: "); 91 | 92 | nameTextField.setToolTipText(CodeSearchBundle.message("name.tooltip")); 93 | descriptionTextField.setToolTipText(CodeSearchBundle.message("desc.tooltip")); 94 | iconPathTextField.getTextField().setToolTipText(CodeSearchBundle.message("icon.tooltip")); 95 | 96 | initData(); 97 | 98 | addListener(); 99 | } 100 | 101 | private void initData() { 102 | if (!isAdd) { 103 | nameTextField.setText(mySearchService.getName()); 104 | descriptionTextField.setText(mySearchService.getDescription()); 105 | urlTextField.setText(mySearchService.getUrl()); 106 | iconPathTextField.setText(mySearchService.getIconPath()); 107 | } 108 | } 109 | 110 | private void addListener() { 111 | iconPathTextField.addBrowseFolderListener(CodeSearchBundle.message("config.title"), 112 | CodeSearchBundle.message("icon.config.desc"), 113 | myProject, FileChooserDescriptorFactory.createSingleFileDescriptor("png")); 114 | 115 | checkButton.addActionListener(e -> { 116 | final String url = getUrl(); 117 | if (StringUtil.isEmptyOrSpaces(url)) { 118 | return; 119 | } 120 | 121 | final String title = CodeSearchBundle.message("check.conn.title"); 122 | final AtomicReference exceptionReference = new AtomicReference<>(); 123 | ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { 124 | try { 125 | HttpRequests.request(url).readTimeout(READ_TIMEOUT).tryConnect(); 126 | } catch (IOException e1) { 127 | exceptionReference.set(e1); 128 | } 129 | }, title, true, null); 130 | 131 | final IOException exception = exceptionReference.get(); 132 | if (exception == null) { 133 | Messages.showMessageDialog(contentPanel, CodeSearchBundle.message("conn.success.msg"), title, 134 | Messages.getInformationIcon()); 135 | } else { 136 | final String message = exception.getMessage(); 137 | Messages.showErrorDialog(contentPanel, CodeSearchBundle.message("conn.fail.msg", message)); 138 | } 139 | }); 140 | } 141 | 142 | @Nullable 143 | @Override 144 | public JComponent createCenterPanel() { 145 | return contentPanel; 146 | } 147 | 148 | @Nullable 149 | @Override 150 | public ValidationInfo doValidate() { 151 | String message = null; 152 | if (StringUtil.isEmptyOrSpaces(getName())) { 153 | message = CodeSearchBundle.message("name.required"); 154 | } else if (StringUtil.isEmptyOrSpaces(getUrl())) { 155 | message = CodeSearchBundle.message("url.required"); 156 | } 157 | 158 | return message != null ? new ValidationInfo(message) : null; 159 | } 160 | 161 | @Override 162 | public void doOKAction() { 163 | final ValidationInfo validationInfo = doValidate(); 164 | if (validationInfo != null) { 165 | return; 166 | } 167 | 168 | if (existsName()) { 169 | return; 170 | } 171 | 172 | final SearchService newSearchService = getSearchService(); 173 | addOrEditTask(newSearchService); 174 | 175 | super.doOKAction(); 176 | } 177 | 178 | private boolean existsName() { 179 | final Map searchServiceMap = SearchServiceSettings.getInstance().getSearchServiceMap(); 180 | final Map mySearchServiceMap = new HashMap<>(searchServiceMap); 181 | if (!isAdd) { 182 | mySearchServiceMap.remove(mySearchService.getName()); 183 | } 184 | 185 | final String name = getName(); 186 | if (mySearchServiceMap.get(name) != null) { 187 | final String message = CodeSearchBundle.message("name.exists", name); 188 | final String title = 189 | isAdd ? CodeSearchBundle.message("add.title") : CodeSearchBundle.message("edit.title"); 190 | Messages.showWarningDialog(message, title); 191 | return true; 192 | } 193 | return false; 194 | } 195 | 196 | private void addOrEditTask(final @NotNull SearchService newSearchService) { 197 | final String title = 198 | isAdd ? CodeSearchBundle.message("add.task.title") : CodeSearchBundle.message("edit.task.title"); 199 | final Task task = new Task.Backgroundable(null, title) { 200 | @Override 201 | public void run(@NotNull ProgressIndicator progressIndicator) { 202 | if (!isAdd) { 203 | SearchServiceSettings.getInstance().removeSearchService(mySearchService.getName()); 204 | } 205 | 206 | SearchServiceSettings.getInstance().setSearchService(newSearchService.getName(), newSearchService); 207 | 208 | mySearchServicePanel.refreshSearchServices(); 209 | } 210 | }; 211 | 212 | ProgressManagerImpl.getInstance().run(task); 213 | } 214 | 215 | private SearchService getSearchService() { 216 | final SearchService searchService = new SearchService(); 217 | searchService.setName(getName()); 218 | searchService.setDescription(getDescription()); 219 | searchService.setUrl(getUrl()); 220 | searchService.setIconPath(getIconPath()); 221 | return searchService; 222 | } 223 | 224 | private String getName() { 225 | final String name = nameTextField.getText(); 226 | return StringUtil.isEmptyOrSpaces(name) ? "" : name.trim(); 227 | } 228 | 229 | private String getDescription() { 230 | final String description = descriptionTextField.getText(); 231 | return StringUtil.isEmptyOrSpaces(description) ? "" : description.trim(); 232 | } 233 | 234 | private String getUrl() { 235 | final String url = urlTextField.getText(); 236 | return StringUtil.isEmptyOrSpaces(url) ? "" : url.trim(); 237 | } 238 | 239 | private String getIconPath() { 240 | return iconPathTextField.getText().trim(); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/settings/SearchServicePanel.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.settings; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.ui.Messages; 5 | import com.intellij.openapi.ui.OnePixelDivider; 6 | import com.intellij.ui.AnActionButton; 7 | import com.intellij.ui.DoubleClickListener; 8 | import com.intellij.ui.ToolbarDecorator; 9 | import com.intellij.ui.table.TableView; 10 | import com.intellij.util.IconUtil; 11 | import com.intellij.util.ui.JBUI; 12 | import com.intellij.util.ui.ListTableModel; 13 | import com.intellij.util.ui.UIUtil; 14 | import io.heidou.codesearch.i18n.CodeSearchBundle; 15 | import io.heidou.codesearch.model.SearchService; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | import javax.swing.BorderFactory; 19 | import javax.swing.JPanel; 20 | import javax.swing.ListSelectionModel; 21 | import java.awt.BorderLayout; 22 | import java.awt.event.MouseEvent; 23 | import java.util.List; 24 | 25 | /** 26 | * Search service panel 27 | * 28 | * @author guojianhua 29 | * @since 2019-04-11 30 | */ 31 | public class SearchServicePanel extends JPanel { 32 | private final TableView searchServiceTable; 33 | private final ListTableModel searchServiceTableModel = new SearchServiceTableModel(); 34 | 35 | public SearchServicePanel() { 36 | this.setLayout(new BorderLayout()); 37 | setBorder(BorderFactory.createTitledBorder(JBUI.Borders.customLine(OnePixelDivider.BACKGROUND, 1, 0, 1, 0), 38 | CodeSearchBundle.message("custom.search.service.desc"))); 39 | 40 | searchServiceTableModel.setSortable(true); 41 | searchServiceTable = new TableView<>(); 42 | searchServiceTable.setModelAndUpdateColumns(searchServiceTableModel); 43 | searchServiceTable.setAutoCreateRowSorter(true); 44 | searchServiceTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 45 | 46 | new DoubleClickListener() { 47 | @Override 48 | protected boolean onDoubleClick(MouseEvent e) { 49 | showSearchServiceDialog(); 50 | return true; 51 | } 52 | }.installOn(searchServiceTable); 53 | 54 | ToolbarDecorator decorator = ToolbarDecorator.createDecorator(searchServiceTable); 55 | decorator.disableAddAction(); 56 | decorator.disableRemoveAction(); 57 | decorator.disableDownAction(); 58 | decorator.disableUpAction(); 59 | 60 | decorator.addExtraAction(new AddSearchServiceAction(SearchServicePanel.this)); 61 | decorator.addExtraAction(new DeleteSearchServiceAction(SearchServicePanel.this)); 62 | decorator.addExtraAction(new EditSearchServiceAction(SearchServicePanel.this)); 63 | 64 | add(decorator.createPanel(), BorderLayout.CENTER); 65 | 66 | refreshSearchServices(); 67 | } 68 | 69 | public void refreshSearchServices() { 70 | final List searchServiceList = SearchServiceSettings.getInstance().getSearchServiceList(); 71 | UIUtil.invokeLaterIfNeeded(() -> { 72 | searchServiceTableModel.setItems(searchServiceList); 73 | }); 74 | } 75 | 76 | private SearchService getSelectedSearchService() { 77 | return searchServiceTable.getSelectedObject(); 78 | } 79 | 80 | private void showSearchServiceDialog() { 81 | final SearchService selectedSearchService = getSelectedSearchService(); 82 | if (selectedSearchService == null) { 83 | return; 84 | } 85 | 86 | SearchServiceDialog searchServiceDialog = new SearchServiceDialog(null, 87 | selectedSearchService, SearchServicePanel.this); 88 | searchServiceDialog.show(); 89 | } 90 | 91 | private class AddSearchServiceAction extends AnActionButton { 92 | private final SearchServicePanel mySearchServicePanel; 93 | 94 | public AddSearchServiceAction(SearchServicePanel searchServicePanel) { 95 | super(CodeSearchBundle.message("add.action.name"), IconUtil.getAddIcon()); 96 | this.mySearchServicePanel = searchServicePanel; 97 | } 98 | 99 | @Override 100 | public void actionPerformed(@NotNull AnActionEvent event) { 101 | SearchServiceDialog searchServiceDialog = new SearchServiceDialog(null, null, 102 | mySearchServicePanel); 103 | searchServiceDialog.show(); 104 | } 105 | } 106 | 107 | private class DeleteSearchServiceAction extends AnActionButton { 108 | private final SearchServicePanel mySearchServicePanel; 109 | 110 | public DeleteSearchServiceAction(SearchServicePanel searchServicePanel) { 111 | super(CodeSearchBundle.message("remove.action.name"), IconUtil.getRemoveIcon()); 112 | this.mySearchServicePanel = searchServicePanel; 113 | } 114 | 115 | @Override 116 | public void actionPerformed(@NotNull AnActionEvent event) { 117 | final int result = Messages.showYesNoDialog(CodeSearchBundle.message("remove.message"), 118 | CodeSearchBundle.message("remove.title"), Messages.getWarningIcon()); 119 | if (result == Messages.YES) { 120 | final SearchService selectedSearchService = mySearchServicePanel.getSelectedSearchService(); 121 | SearchServiceSettings.getInstance().removeSearchService(selectedSearchService.getName()); 122 | mySearchServicePanel.refreshSearchServices(); 123 | } 124 | } 125 | 126 | @Override 127 | public void updateButton(@NotNull AnActionEvent event) { 128 | super.updateButton(event); 129 | final SearchService selectedSearchService = mySearchServicePanel.getSelectedSearchService(); 130 | event.getPresentation().setEnabled(selectedSearchService != null); 131 | } 132 | } 133 | 134 | private class EditSearchServiceAction extends AnActionButton { 135 | private final SearchServicePanel mySearchServicePanel; 136 | 137 | public EditSearchServiceAction(SearchServicePanel searchServicePanel) { 138 | super(CodeSearchBundle.message("edit.action.name"), IconUtil.getEditIcon()); 139 | this.mySearchServicePanel = searchServicePanel; 140 | } 141 | 142 | @Override 143 | public void actionPerformed(@NotNull AnActionEvent event) { 144 | showSearchServiceDialog(); 145 | } 146 | 147 | @Override 148 | public void updateButton(@NotNull AnActionEvent event) { 149 | super.updateButton(event); 150 | final SearchService selectedSearchService = mySearchServicePanel.getSelectedSearchService(); 151 | event.getPresentation().setEnabled(selectedSearchService != null); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/settings/SearchServiceSettings.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.settings; 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent; 4 | import com.intellij.openapi.components.ServiceManager; 5 | import com.intellij.openapi.components.State; 6 | import com.intellij.openapi.components.Storage; 7 | import com.intellij.openapi.util.text.StringUtil; 8 | import com.intellij.util.xmlb.XmlSerializer; 9 | import io.heidou.codesearch.model.SearchService; 10 | import org.jdom.Element; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.LinkedHashMap; 15 | import java.util.LinkedList; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.concurrent.locks.ReentrantReadWriteLock; 19 | 20 | /** 21 | * Search service settings 22 | * 23 | * @author guojianhua 24 | * @since 2019-04-07 25 | */ 26 | @State(name = "CustomSearchServiceSettings", storages = {@Storage("search-services.xml")}) 27 | public class SearchServiceSettings implements PersistentStateComponent { 28 | private final Map searchServiceMap = new LinkedHashMap<>(); 29 | 30 | private final ReentrantReadWriteLock myLock = new ReentrantReadWriteLock(); 31 | 32 | private static final String ELEMENT_SEARCH_SERVICE = "searchService"; 33 | private static final String ATTRIBUTE_NAME = "name"; 34 | 35 | private SearchServiceSettings() { 36 | } 37 | 38 | public static SearchServiceSettings getInstance() { 39 | return ServiceManager.getService(SearchServiceSettings.class); 40 | } 41 | 42 | @Nullable 43 | @Override 44 | public Element getState() { 45 | final Element element = new Element("state"); 46 | 47 | for (Map.Entry entry : searchServiceMap.entrySet()) { 48 | final SearchService searchService = entry.getValue(); 49 | final Element ele = XmlSerializer.serialize(searchService); 50 | element.addContent(ele); 51 | } 52 | 53 | return element; 54 | } 55 | 56 | @Override 57 | public void loadState(@NotNull Element element) { 58 | searchServiceMap.clear(); 59 | 60 | final List searchServices = element.getChildren(ELEMENT_SEARCH_SERVICE); 61 | for (Element searchTypeElement : searchServices) { 62 | final String name = searchTypeElement.getAttributeValue(ATTRIBUTE_NAME); 63 | if (!StringUtil.isEmptyOrSpaces(name)) { 64 | SearchService searchService = XmlSerializer.deserialize(searchTypeElement, SearchService.class); 65 | searchServiceMap.put(name, searchService); 66 | } 67 | } 68 | } 69 | 70 | public List getSearchServiceList() { 71 | myLock.readLock().lock(); 72 | try { 73 | return new LinkedList<>(searchServiceMap.values()); 74 | } finally { 75 | myLock.readLock().unlock(); 76 | } 77 | } 78 | 79 | public void setSearchService(@NotNull String name, @NotNull SearchService searchService) { 80 | try { 81 | myLock.writeLock().lock(); 82 | searchServiceMap.put(name, searchService); 83 | } finally { 84 | myLock.writeLock().unlock(); 85 | } 86 | } 87 | 88 | public void removeSearchService(@NotNull String name) { 89 | try { 90 | myLock.writeLock().lock(); 91 | searchServiceMap.remove(name); 92 | } finally { 93 | myLock.writeLock().unlock(); 94 | } 95 | } 96 | 97 | public Map getSearchServiceMap() { 98 | return searchServiceMap; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/settings/SearchServiceTableModel.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.settings; 2 | 3 | import com.intellij.util.ui.ListTableModel; 4 | import io.heidou.codesearch.model.SearchService; 5 | 6 | /** 7 | * Search service table model 8 | * 9 | * @author guojianhua 10 | * @since 2019-04-11 11 | */ 12 | public class SearchServiceTableModel extends ListTableModel { 13 | public SearchServiceTableModel() { 14 | super(new IconColumnInfo(), new NameColumnInfo(), new DescriptionColumnInfo(), 15 | new UrlColumnInfo()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/settings/UrlColumnInfo.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.settings; 2 | 3 | import io.heidou.codesearch.i18n.CodeSearchBundle; 4 | import io.heidou.codesearch.model.SearchService; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | /** 8 | * Url column 9 | * 10 | * @author guojianhua 11 | * @since 2019-04-12 12 | */ 13 | public class UrlColumnInfo extends SearchServiceColumnInfo { 14 | public UrlColumnInfo() { 15 | super(CodeSearchBundle.message("url.column.name")); 16 | } 17 | 18 | @Nullable 19 | @Override 20 | public String valueOf(SearchService searchService) { 21 | return searchService.getUrl(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/util/IconUtil.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.util; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import com.intellij.openapi.util.IconLoader; 5 | import com.intellij.openapi.util.text.StringUtil; 6 | 7 | import javax.swing.Icon; 8 | import java.io.File; 9 | import java.net.MalformedURLException; 10 | 11 | /** 12 | * Icon utility class 13 | * 14 | * @author guojianhua 15 | * @since 2019-04-21 16 | */ 17 | public class IconUtil { 18 | private static final Logger LOG = Logger.getInstance(IconUtil.class); 19 | 20 | /** 21 | * Baidu icon 22 | */ 23 | public static final Icon BAIDU = IconLoader.getIcon("/icons/baidu.png"); 24 | 25 | /** 26 | * Google icon 27 | */ 28 | public static final Icon GOOGLE = IconLoader.getIcon("/icons/google.png"); 29 | 30 | /** 31 | * StackOverflow icon 32 | */ 33 | public static final Icon STACKOVERFLOW = IconLoader.getIcon("/icons/stackoverflow.png"); 34 | 35 | /** 36 | * GitHub icon 37 | */ 38 | public static final Icon GITHUB = IconLoader.getIcon("/icons/github.png"); 39 | 40 | private IconUtil() { 41 | } 42 | 43 | public static Icon getIcon(String iconPath) { 44 | try { 45 | if (!StringUtil.isEmptyOrSpaces(iconPath)) { 46 | final File iconFile = new File(iconPath); 47 | if (iconFile.exists() && iconFile.isFile()) { 48 | return IconLoader.findIcon(iconFile.toURI().toURL()); 49 | } 50 | } 51 | } catch (MalformedURLException e) { 52 | LOG.warn(e.getMessage()); 53 | } 54 | 55 | return null; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/io/heidou/codesearch/util/UrlUtils.java: -------------------------------------------------------------------------------- 1 | package io.heidou.codesearch.util; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.io.UnsupportedEncodingException; 6 | import java.net.URLEncoder; 7 | import java.nio.charset.StandardCharsets; 8 | 9 | /** 10 | * Url util 11 | * 12 | * @author guojianhua 13 | * @since 2020-10-17 14 | */ 15 | public class UrlUtils { 16 | private UrlUtils() { 17 | } 18 | 19 | /** 20 | * Compatibility with 2016.1+ 21 | * Encodes a URI component by replacing each instance of certain characters by one, two, three, 22 | * or four escape sequences representing the UTF-8 encoding of the character. 23 | * Behaves similarly to standard JavaScript build-in function encodeURIComponent. 24 | * 25 | * @param str a component of a URI 26 | * @return a new string representing the provided string encoded as a URI component 27 | */ 28 | @NotNull 29 | public static String encodeURIComponent(@NotNull String str) { 30 | try { 31 | return URLEncoder.encode(str, StandardCharsets.UTF_8.name()) 32 | .replace("+", "%20") 33 | .replace("%21", "!") 34 | .replace("%27", "'") 35 | .replace("%28", "(") 36 | .replace("%29", ")") 37 | .replace("%7E", "~"); 38 | } catch (UnsupportedEncodingException e) { 39 | return str; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | io.heidou.codesearch 3 | CodeSearch 4 | guojianhua 5 | 6 | 9 | 10 | 1.1.0 (2020-10-17)

12 |
    13 |
  • Support for search with GitHub
  • 14 |
  • fixes issue #1 and #2: compatibility with JetBrains series IDE 2020.1+
  • 15 |
16 |

1.0.0 (2019-06-18)

17 |
    18 |
  • Support for search with Baidu, Google and StackOverflow.
  • 19 |
  • Support for custom search service
  • 20 |
21 | ]]>
22 | 23 | 25 | 28 | com.intellij.modules.platform 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
-------------------------------------------------------------------------------- /src/main/resources/icons/baidu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojianhua/code-search/d12d22ad3efa136c071c92616cd37efc0fd38a5c/src/main/resources/icons/baidu.png -------------------------------------------------------------------------------- /src/main/resources/icons/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojianhua/code-search/d12d22ad3efa136c071c92616cd37efc0fd38a5c/src/main/resources/icons/github.png -------------------------------------------------------------------------------- /src/main/resources/icons/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojianhua/code-search/d12d22ad3efa136c071c92616cd37efc0fd38a5c/src/main/resources/icons/google.png -------------------------------------------------------------------------------- /src/main/resources/icons/stackoverflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojianhua/code-search/d12d22ad3efa136c071c92616cd37efc0fd38a5c/src/main/resources/icons/stackoverflow.png -------------------------------------------------------------------------------- /src/main/resources/messages/CodeSearchBundle.properties: -------------------------------------------------------------------------------- 1 | custom.search.action.name=Custom Search... 2 | 3 | codesearch.settings.title=Code Search 4 | custom.search.service.desc=Custom search service (Take effect after restart IDE) 5 | 6 | icon.column.name=Icon 7 | name.column.name=Name 8 | desc.column.name=Description 9 | url.column.name=Url 10 | 11 | add.action.name=Add Search Service 12 | remove.action.name=Remove Search Service 13 | edit.action.name=Edit Search Service 14 | 15 | add=Add 16 | edit=Edit 17 | 18 | add.title=Add Search Service 19 | edit.title=Edit Search Service 20 | 21 | remove.message=Are you sure you want to delete it? 22 | remove.title=Remove Search Service 23 | 24 | name.tooltip=For menu name 25 | desc.tooltip=For menu description 26 | icon.tooltip=For menu icon. Please specify png icon 27 | 28 | name.required=Please input search service name 29 | url.required=Please input search service url 30 | 31 | add.task.title=Adding Search Service... 32 | edit.task.title=Editing Search Service... 33 | 34 | config.title=Custom Search Service Configuration 35 | icon.config.desc=Select search service icon path (*.png) 36 | 37 | check.conn.title=Check Connection 38 | conn.success.msg=Connection successful 39 | conn.fail.msg=Problem with connection: {0} 40 | 41 | name.exists=The name[{0}] already exists --------------------------------------------------------------------------------