├── .gitignore ├── LICENSE ├── README.md ├── README_zh.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images └── switch-show.gif ├── settings.gradle.kts └── src └── main ├── kotlin └── com │ └── github │ └── qczone │ └── switch2cursor │ ├── actions │ ├── OpenFileInCursorAction.kt │ └── OpenProjectInCursorAction.kt │ ├── settings │ ├── AppSettingsConfigurable.kt │ └── AppSettingsState.kt │ └── utils │ └── WindowUtils.kt └── resources └── META-INF ├── plugin.xml └── pluginIcon.svg /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | .vscode/ 4 | build/ 5 | *.DS_Store 6 | bin/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 qczone 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Switch2Cursor 2 | 3 | [中文文档](README_zh.md) 4 | 5 | > 💡 Recommended to use with [Switch2IDEA](https://github.com/qczone/switch2idea) in Cursor 6 | 7 | 8 | [![JetBrains Plugins](https://img.shields.io/jetbrains/plugin/v/26309-switch2cursor?label=JetBrains%20Marketplace&style=for-the-badge&logo=intellij-idea)](https://plugins.jetbrains.com/plugin/26309-switch2cursor) 9 | [![Downloads](https://img.shields.io/jetbrains/plugin/d/26309-switch2cursor?style=for-the-badge&logo=intellij-idea)](https://plugins.jetbrains.com/plugin/26309-switch2cursor) 10 | [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge)](LICENSE) 11 | 12 | ## 🔍 Introduction 13 | A JetBrains IDE plugin that enhances development efficiency by enabling seamless switching between JetBrains IDE and Cursor 14 | 15 | ![Switch2Cursor Demo](images/switch-show.gif) 16 | 17 | ## 🌟 Features 18 | 19 | - 🚀 Seamless Editor Switching 20 | - One-click switch between JetBrains IDE and Cursor 21 | - Automatically positions to the same cursor location (line and column) 22 | - Perfectly maintains editing context without interrupting workflow 23 | 24 | - ⌨️ Convenient Shortcut Support 25 | - macOS: 26 | - `Option+Shift+P` - Open project in Cursor 27 | - `Option+Shift+O` - Open current file in Cursor 28 | - Windows: 29 | - `Alt+Shift+P` - Open project in Cursor 30 | - `Alt+Shift+O` - Open current file in Cursor 31 | 32 | - 🔧 Multiple Access Methods 33 | - Keyboard shortcuts 34 | - Editor context menu 35 | - IDE tools menu 36 | 37 | ## 🛠️ Installation Guide 38 | 39 | ### Method 1: Install via JetBrains Marketplace 40 | 1. Open IDE → `Settings` → `Plugins` → `Marketplace` 41 | 2. Search for switch2cursor 42 | 3. Click `Install` to complete installation 43 | 4. Click `OK` to apply changes 44 | 45 | ### Method 2: Local Installation 46 | 1. Download the latest plugin package from [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/26309-switch2cursor) 47 | 2. IDE → `Settings` → `Plugins` → `⚙️`→ `Install Plugin from Disk...` 48 | 3. Select the downloaded plugin package 49 | 4. Click `OK` to apply changes 50 | 51 | 52 | ## 🚀 Usage Guide 53 | 54 | ### Basic Usage 55 | 56 | #### Open Project 57 | - Shortcuts: 58 | - macOS: `Option+Shift+P` 59 | - Windows: `Alt+Shift+P` 60 | - Context Menu: Right-click in project view → `Open Project In Cursor` 61 | - Tools Menu: `Tools` → `Open Project In Cursor` 62 | 63 | #### Open Current File 64 | - Shortcuts: 65 | - macOS: `Option+Shift+O` 66 | - Windows: `Alt+Shift+O` 67 | - Context Menu: Right-click in editor → `Open File In Cursor` 68 | - Tools Menu: `Tools` → `Open File In Cursor` 69 | 70 | ### Configuration 71 | - In `Settings/Preferences` → `Tools` → `Switch2Cursor`: 72 | - Set Cursor executable path (default is "cursor") 73 | - Customize shortcuts through Keymap settings 74 | 75 | ### Requirements 76 | - [Cursor](https://cursor.com) installed 77 | - Compatible with all JetBrains IDEs (version 2022.3 and above) 78 | 79 | ## 🧑‍💻 Developer Guide 80 | 81 | ### Build Project 82 | ```bash 83 | # Clone repository 84 | git clone https://github.com/qczone/switch2cursor.git 85 | 86 | # Build plugin 87 | cd switch2cursor 88 | ./gradlew buildPlugin 89 | # Plugin package will be generated in build/distributions/ directory 90 | ``` 91 | 92 | ### Contributing 93 | 1. Fork this repository 94 | 2. Create a feature branch 95 | 3. Commit your changes 96 | 4. Push to the branch 97 | 5. Submit a Pull Request 98 | 99 | ## 🙋 FAQ 100 | 101 | ### 1. Why doesn't the shortcut/menu click switch to Cursor after installation? 102 | Check if the correct Cursor executable path is configured in `Settings` → `Tools` → `Switch2Cursor` 103 | 104 | ### 2. Which IDEs are supported? 105 | Supports all JetBrains IDEs, including: IntelliJ IDEA, PyCharm, WebStorm, GoLand, RustRover, Android Studio, etc. 106 | 107 | ### 3. Which versions are supported? 108 | The plugin is developed based on JDK 17 and currently only supports JetBrains IDE version 2022.3 and above 109 | 110 | ### 4. How to modify plugin shortcuts? 111 | Modify in `Settings` → `Keymap` → `Plugins` → `Switch2Cursor` 112 | 113 | ## 📄 License 114 | This project is licensed under the [MIT License](LICENSE) 115 | 116 | 117 | ## 📮 Feedback 118 | If you encounter any issues or have suggestions, please provide feedback through: 119 | - [Submit GitHub Issue](https://github.com/qczone/switch2cursor/issues) 120 | 121 | ## 🌟 Star History 122 | 123 | [![Star History Chart](https://api.star-history.com/svg?repos=qczone/switch2cursor&type=Date)](https://star-history.com/#qczone/switch2cursor&Date) -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # Switch2Cursor 2 | 3 | [English](README.md) 4 | 5 | > 💡 推荐在 Cursor 中配合 [Switch2IDEA](https://github.com/qczone/switch2idea) 使用 6 | 7 | 8 | [![JetBrains Plugins](https://img.shields.io/jetbrains/plugin/v/26309-switch2cursor?label=JetBrains%20Marketplace&style=for-the-badge&logo=intellij-idea)](https://plugins.jetbrains.com/plugin/26309-switch2cursor) 9 | [![Downloads](https://img.shields.io/jetbrains/plugin/d/26309-switch2cursor?style=for-the-badge&logo=intellij-idea)](https://plugins.jetbrains.com/plugin/26309-switch2cursor) 10 | [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge)](LICENSE) 11 | 12 | ## 🔍 项目简介 13 | 一个提升开发效率的 JetBrains IDE 插件,让你在 JetBrains IDE 和 Cursor 之间实现丝滑切换 14 | 15 | ![Switch2Cursor演示](images/switch-show.gif) 16 | 17 | ## 🌟 功能特性 18 | 19 | - 🚀 无缝编辑器切换 20 | - 在 JetBrains IDE 和 Cursor 之间一键切换 21 | - 自动定位到相同的光标位置(行号和列号) 22 | - 完美保持编辑上下文,不中断思路 23 | 24 | - ⌨️ 便捷的快捷键支持 25 | - macOS: 26 | - `Option+Shift+P` - 在 Cursor 中打开整个项目 27 | - `Option+Shift+O` - 在 Cursor 中打开当前文件 28 | - Windows: 29 | - `Alt+Shift+P` - 在 Cursor 中打开整个项目 30 | - `Alt+Shift+O` - 在 Cursor 中打开当前文件 31 | 32 | - 🔧 多样化的访问方式 33 | - 快捷键操作 34 | - 编辑器右键菜单 35 | - IDE 工具菜单 36 | 37 | ## 🛠️ 安装指南 38 | 39 | ### 方式一:通过 JetBrains 插件市场安装 40 | 1. 打开 IDE → `Settings` → `Plugins` → `Marketplace` 41 | 2. 搜索 switch2cursor 42 | 3. 点击 `Install` 完成安装 43 | 4. 点击 `OK` 生效 44 | 45 | ### 方式二:本地安装 46 | 1. 从 [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/26309-switch2cursor) 下载最新版插件包 47 | 2. IDE → `Settings` → `Plugins` → `⚙️`→ `Install Plugin from Disk...` 48 | 3. 选择下载的插件包 49 | 4. 点击 `OK` 生效 50 | 51 | 52 | ## 🚀 使用说明 53 | 54 | ### 基础使用 55 | 56 | #### 打开项目 57 | - 快捷键: 58 | - macOS: `Option+Shift+P` 59 | - Windows: `Alt+Shift+P` 60 | - 右键菜单:在项目视图中右键 → `Open Project In Cursor` 61 | - 工具菜单:`Tools` → `Open Project In Cursor` 62 | 63 | #### 打开当前文件 64 | - 快捷键: 65 | - macOS: `Option+Shift+O` 66 | - Windows: `Alt+Shift+O` 67 | - 右键菜单:在编辑器中右键 → `Open File In Cursor` 68 | - 工具菜单:`Tools` → `Open File In Cursor` 69 | 70 | ### 配置 71 | - 在 `Settings/Preferences` → `Tools` → `Switch2Cursor` 中: 72 | - 设置 Cursor 可执行文件路径(默认为 "cursor") 73 | - 通过 Keymap 设置自定义快捷键 74 | 75 | ### 环境要求 76 | - 已安装 [Cursor](https://cursor.com) 77 | - 兼容所有 JetBrains IDE(2022.3 及以上版本) 78 | 79 | ## 🧑‍💻 开发者指南 80 | 81 | ### 项目构建 82 | ```bash 83 | # 克隆仓库 84 | git clone https://github.com/qczone/switch2cursor.git 85 | 86 | # 构建插件 87 | cd switch2cursor 88 | ./gradlew buildPlugin 89 | # 生成插件包在 build/distributions/ 目录下 90 | ``` 91 | 92 | ### 贡献代码 93 | 1. Fork 本仓库 94 | 2. 创建特性分支 95 | 3. 提交修改 96 | 4. 推送分支 97 | 5. 提交 Pull Request 98 | 99 | ## 🙋 常见问题 100 | 101 | ### 1. 为什么安装之后快捷键/点击菜单不会跳转到 Cursor? 102 | 检查是否在 `Settings` → `Tools` → `Switch2Cursor` 中配置了正确的 Cursor 可执行文件路径 103 | 104 | ### 2. 都支持哪些 IDE? 105 | 支持所有 JetBrains 系列的 IDE,如:IntelliJ IDEA、PyCharm、WebStorm、GoLand、RustRover、Android Studio 等 106 | 107 | ### 3. 都支持哪些版本? 108 | 插件基于 JDK 17 开发,目前仅支持 JetBrains IDE 2022.3 及以上版本 109 | 110 | ### 4. 如何修改插件的快捷键? 111 | 在 `Settings` → `Keymap` → `Plugins` → `Switch2Cursor` 中修改 112 | 113 | ## 📄 许可证 114 | 本项目采用 [MIT License](LICENSE) 开源协议 115 | 116 | 117 | ## 📮 问题反馈 118 | 如果遇到问题或有建议,请通过以下方式反馈: 119 | - [提交 GitHub Issue](https://github.com/qczone/switch2cursor/issues) 120 | 121 | ## 🌟 Star 历史 122 | 123 | [![Star History Chart](https://api.star-history.com/svg?repos=qczone/switch2cursor&type=Date)](https://star-history.com/#qczone/switch2cursor&Date) -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java") 3 | id("org.jetbrains.kotlin.jvm") version "1.9.25" 4 | id("org.jetbrains.intellij") version "1.17.4" 5 | } 6 | 7 | group = "com.github.qczone" 8 | version = "1.0.3" 9 | 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | // Configure Gradle IntelliJ Plugin 16 | // Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html 17 | intellij { 18 | version.set("2022.3") 19 | type.set("IC") // Target IDE Platform 20 | pluginName.set("Switch2Cursor") 21 | updateSinceUntilBuild.set(true) 22 | sameSinceUntilBuild.set(false) 23 | 24 | plugins.set(listOf(/* Plugin Dependencies */)) 25 | } 26 | 27 | tasks { 28 | // Set the JVM compatibility versions 29 | withType { 30 | sourceCompatibility = "17" 31 | targetCompatibility = "17" 32 | } 33 | withType { 34 | kotlinOptions.jvmTarget = "17" 35 | } 36 | 37 | patchPluginXml { 38 | sinceBuild.set("223") 39 | untilBuild.set("") 40 | } 41 | 42 | signPlugin { 43 | certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) 44 | privateKey.set(System.getenv("PRIVATE_KEY")) 45 | password.set(System.getenv("PRIVATE_KEY_PASSWORD")) 46 | } 47 | 48 | publishPlugin { 49 | token.set(System.getenv("PUBLISH_TOKEN")) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib 2 | kotlin.stdlib.default.dependency=false 3 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html 4 | org.gradle.configuration-cache=false 5 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html 6 | org.gradle.caching=false -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qczone/switch2cursor/26020292c8ad23a107cc73e44ab914ab38344c26/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-8.11-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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/master/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 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 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 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /images/switch-show.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qczone/switch2cursor/26020292c8ad23a107cc73e44ab914ab38344c26/images/switch-show.gif -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | } 7 | 8 | rootProject.name = "switch2cursor" -------------------------------------------------------------------------------- /src/main/kotlin/com/github/qczone/switch2cursor/actions/OpenFileInCursorAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.qczone.switch2cursor.actions 2 | 3 | import com.github.qczone.switch2cursor.settings.AppSettingsState 4 | import com.github.qczone.switch2cursor.utils.WindowUtils 5 | import com.intellij.openapi.actionSystem.AnAction 6 | import com.intellij.openapi.actionSystem.AnActionEvent 7 | import com.intellij.openapi.actionSystem.CommonDataKeys 8 | import com.intellij.openapi.diagnostic.Logger 9 | import com.intellij.openapi.editor.Editor 10 | import com.intellij.openapi.project.Project 11 | import com.intellij.openapi.vfs.VirtualFile 12 | import com.intellij.openapi.actionSystem.ActionUpdateThread 13 | 14 | class OpenFileInCursorAction : AnAction() { 15 | private val logger = Logger.getInstance(OpenFileInCursorAction::class.java) 16 | 17 | override fun getActionUpdateThread(): ActionUpdateThread { 18 | return ActionUpdateThread.BGT 19 | } 20 | 21 | override fun actionPerformed(e: AnActionEvent) { 22 | val project: Project = e.project ?: return 23 | val virtualFile: VirtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE) ?: return 24 | 25 | val editor: Editor? = e.getData(CommonDataKeys.EDITOR) 26 | 27 | val line = editor?.caretModel?.logicalPosition?.line?.plus(1) ?: 1 28 | val column = editor?.caretModel?.logicalPosition?.column?.plus(1) ?: 1 29 | 30 | val filePath = virtualFile.path 31 | val settings = AppSettingsState.getInstance() 32 | val cursorPath = settings.cursorPath 33 | 34 | val command = when { 35 | System.getProperty("os.name").lowercase().contains("mac") -> { 36 | arrayOf("open", "-a", "$cursorPath", "cursor://file$filePath:$line:$column") 37 | } 38 | System.getProperty("os.name").lowercase().contains("windows") -> { 39 | arrayOf("cmd", "/c", "$cursorPath", "--goto", "$filePath:$line:$column") 40 | } 41 | else -> { 42 | arrayOf(cursorPath, "--goto", "$filePath:$line:$column") 43 | } 44 | } 45 | 46 | try { 47 | logger.info("Executing command: ${command.joinToString(" ")}") 48 | ProcessBuilder(*command).start() 49 | } catch (ex: Exception) { 50 | logger.error("Failed to execute cursor command: ${ex.message}", ex) 51 | com.intellij.openapi.ui.Messages.showErrorDialog( 52 | project, 53 | """ 54 | ${ex.message} 55 | 56 | Please check: 57 | 1. Cursor path is correctly configured in Settings > Tools > Switch2Cursor 58 | 2. Cursor is properly installed on your system 59 | 3. The configured path points to a valid Cursor executable 60 | """.trimIndent(), 61 | "Error" 62 | ) 63 | } 64 | 65 | WindowUtils.activeWindow() 66 | } 67 | 68 | override fun update(e: AnActionEvent) { 69 | val project = e.project 70 | val virtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE) 71 | 72 | e.presentation.isEnabledAndVisible = project != null && 73 | virtualFile != null && 74 | !virtualFile.isDirectory 75 | } 76 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/qczone/switch2cursor/actions/OpenProjectInCursorAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.qczone.switch2cursor.actions 2 | 3 | import com.github.qczone.switch2cursor.settings.AppSettingsState 4 | import com.github.qczone.switch2cursor.utils.WindowUtils 5 | import com.intellij.openapi.actionSystem.AnAction 6 | import com.intellij.openapi.actionSystem.AnActionEvent 7 | import com.intellij.openapi.diagnostic.Logger 8 | import com.intellij.openapi.project.Project 9 | import com.intellij.openapi.actionSystem.ActionUpdateThread 10 | 11 | class OpenProjectInCursorAction : AnAction() { 12 | private val logger = Logger.getInstance(OpenProjectInCursorAction::class.java) 13 | 14 | override fun getActionUpdateThread(): ActionUpdateThread { 15 | return ActionUpdateThread.BGT 16 | } 17 | 18 | override fun actionPerformed(e: AnActionEvent) { 19 | val project: Project = e.project ?: return 20 | val projectPath = project.basePath ?: return 21 | 22 | val settings = AppSettingsState.getInstance() 23 | val cursorPath = settings.cursorPath 24 | 25 | val command = when { 26 | System.getProperty("os.name").lowercase().contains("mac") -> { 27 | arrayOf("open", "-a", "$cursorPath", projectPath) 28 | } 29 | 30 | System.getProperty("os.name").lowercase().contains("windows") -> { 31 | arrayOf("cmd", "/c", "$cursorPath", projectPath) 32 | } 33 | 34 | else -> { 35 | arrayOf(cursorPath, projectPath) 36 | } 37 | } 38 | try { 39 | logger.info("Executing command: ${command.joinToString(" ")}") 40 | ProcessBuilder(*command).start() 41 | } catch (ex: Exception) { 42 | logger.error("Failed to execute cursor command: ${ex.message}", ex) 43 | com.intellij.openapi.ui.Messages.showErrorDialog( 44 | project, 45 | """ 46 | ${ex.message} 47 | 48 | Please check: 49 | 1. Cursor path is correctly configured in Settings > Tools > Switch2Cursor 50 | 2. Cursor is properly installed on your system 51 | 3. The configured path points to a valid Cursor executable 52 | """.trimIndent(), 53 | "Error" 54 | ) 55 | } 56 | 57 | WindowUtils.activeWindow() 58 | } 59 | 60 | override fun update(e: AnActionEvent) { 61 | val project = e.project 62 | e.presentation.isEnabledAndVisible = project != null 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/qczone/switch2cursor/settings/AppSettingsConfigurable.kt: -------------------------------------------------------------------------------- 1 | package com.github.qczone.switch2cursor.settings 2 | 3 | import com.intellij.openapi.options.Configurable 4 | import javax.swing.JComponent 5 | import javax.swing.JPanel 6 | import javax.swing.JTextField 7 | import com.intellij.ui.components.JBLabel 8 | import com.intellij.util.ui.FormBuilder 9 | 10 | class AppSettingsConfigurable : Configurable { 11 | private var mySettingsComponent: AppSettingsComponent? = null 12 | 13 | override fun getDisplayName(): String = "Open In Cursor" 14 | 15 | override fun createComponent(): JComponent { 16 | mySettingsComponent = AppSettingsComponent() 17 | return mySettingsComponent!!.panel 18 | } 19 | 20 | override fun isModified(): Boolean { 21 | val settings = AppSettingsState.getInstance() 22 | return mySettingsComponent!!.cursorPath != settings.cursorPath 23 | } 24 | 25 | override fun apply() { 26 | val settings = AppSettingsState.getInstance() 27 | settings.cursorPath = mySettingsComponent!!.cursorPath 28 | } 29 | 30 | override fun reset() { 31 | val settings = AppSettingsState.getInstance() 32 | mySettingsComponent!!.cursorPath = settings.cursorPath 33 | } 34 | 35 | override fun disposeUIResources() { 36 | mySettingsComponent = null 37 | } 38 | } 39 | 40 | class AppSettingsComponent { 41 | val panel: JPanel 42 | private val cursorPathText = JTextField() 43 | 44 | init { 45 | panel = FormBuilder.createFormBuilder() 46 | .addLabeledComponent(JBLabel("Cursor Path: "), cursorPathText, 1, false) 47 | .addComponentFillVertically(JPanel(), 0) 48 | .panel 49 | } 50 | 51 | var cursorPath: String 52 | get() = cursorPathText.text 53 | set(value) { 54 | cursorPathText.text = value 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/qczone/switch2cursor/settings/AppSettingsState.kt: -------------------------------------------------------------------------------- 1 | package com.github.qczone.switch2cursor.settings 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.components.PersistentStateComponent 5 | import com.intellij.openapi.components.State 6 | import com.intellij.openapi.components.Storage 7 | import com.intellij.util.xmlb.XmlSerializerUtil 8 | 9 | @State( 10 | name = "com.github.qczone.switch2cursor.settings.AppSettingsState", 11 | storages = [Storage("Switch2CursorSettings.xml")] 12 | ) 13 | class AppSettingsState : PersistentStateComponent { 14 | var cursorPath: String = "cursor" 15 | 16 | override fun getState(): AppSettingsState = this 17 | 18 | override fun loadState(state: AppSettingsState) { 19 | XmlSerializerUtil.copyBean(state, this) 20 | } 21 | 22 | companion object { 23 | fun getInstance(): AppSettingsState = ApplicationManager.getApplication().getService(AppSettingsState::class.java) 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/qczone/switch2cursor/utils/WindowUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.qczone.switch2cursor.utils 2 | 3 | import com.intellij.openapi.util.SystemInfo 4 | import com.intellij.openapi.diagnostic.Logger 5 | 6 | object WindowUtils { 7 | 8 | private val logger = Logger.getInstance(WindowUtils::class.java) 9 | 10 | fun activeWindow() { 11 | if (!SystemInfo.isWindows) { 12 | return 13 | } 14 | try { 15 | val command = """Get-Process | Where-Object { ${'$'}_.ProcessName -eq '"Cursor"' -and ${'$'}_.MainWindowTitle -match '"Cursor"' } | Sort-Object { ${'$'}_.StartTime } -Descending | Select-Object -First 1 | ForEach-Object { (New-Object -ComObject WScript.Shell).AppActivate(${'$'}_.Id) }""" 16 | logger.info("Executing PowerShell command: $command") 17 | 18 | val processBuilder = ProcessBuilder("powershell", "-command", command) 19 | processBuilder.redirectErrorStream(true) 20 | 21 | val process = processBuilder.start() 22 | val output = process.inputStream.bufferedReader().use { it.readText() } 23 | logger.info("Command output: $output") 24 | 25 | val exitCode = process.waitFor() 26 | logger.info("Command completed with exit code: $exitCode") 27 | 28 | if (exitCode != 0) { 29 | logger.error("Command failed with exit code: $exitCode") 30 | } 31 | } catch (e: Exception) { 32 | logger.error("Failed to activate Cursor window", e) 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 14 | 15 | 17 | 18 | 23 | 24 | 25 | 30 | 31 | 34 | 35 | 38 | 39 | ]> 40 | 41 | 42 | com.github.qczone.switch2cursor 43 | 44 | 46 | Switch2Cursor 47 | 48 | 49 | qczone 50 | 51 | 54 | Switch2Cursor is a JetBrains IDE plugin that enables seamless switching between IDE and Cursor while maintaining precise cursor position.

56 | 57 |

Key Features

58 |
    59 |
  • Open files in Cursor with one click
  • 60 |
  • Maintain exact cursor position (line and column)
  • 61 |
  • Support opening entire projects in Cursor
  • 62 |
  • Convenient shortcuts (customizable): 63 |
      64 |
    • Alt+Shift+O - Open current file
    • 65 |
    • Alt+Shift+P - Open current project
    • 66 |
    67 |
  • 68 |
  • Multiple access methods: shortcuts, context menu, tools menu
  • 69 |
70 | 71 |

Usage

72 |
    73 |
  • Open current file: 74 |
      75 |
    • Press Alt+Shift+O
    • 76 |
    • Right-click in editor → Open File In Cursor
    • 77 |
    • Tools menu → Open File In Cursor
    • 78 |
    79 |
  • 80 |
  • Open project: 81 |
      82 |
    • Press Alt+Shift+P
    • 83 |
    • Right-click in project view → Open Project In Cursor
    • 84 |
    • Tools menu → Open Project In Cursor
    • 85 |
    86 |
  • 87 |
88 | 89 |

Configuration

90 |
    91 |
  • Go to Settings/Preferences → Tools → Switch2Cursor
  • 92 |
  • Set Cursor executable path (default is "cursor")
  • 93 |
  • Customize shortcuts in Keymap settings
  • 94 |
95 | 96 |

Requirements

97 |
    98 |
  • Cursor Editor installed (https://cursor.sh)
  • 99 |
  • Compatible with all JetBrains IDEs
  • 100 |
  • Supported IDE versions: 2022.3 and above
  • 101 |
102 | 103 |
104 | 105 |

Switch2Cursor 是一个 JetBrains IDE 插件,可以让你在 IDE 和 Cursor 之间无缝切换,并保持精确的光标位置。

106 | 107 |

主要特性

108 |
    109 |
  • 一键在 Cursor 中打开文件
  • 110 |
  • 精确保持光标位置(行号和列号)
  • 111 |
  • 支持在 Cursor 中打开整个项目
  • 112 |
  • 便捷的快捷键(可自定义): 113 |
      114 |
    • Alt+Shift+O - 打开当前文件
    • 115 |
    • Alt+Shift+P - 打开当前项目
    • 116 |
    117 |
  • 118 |
  • 多种访问方式:快捷键、右键菜单、工具菜单
  • 119 |
120 | 121 |

使用方法

122 |
    123 |
  • 打开当前文件: 124 |
      125 |
    • 按下 Alt+Shift+O
    • 126 |
    • 在编辑器中右键 → Open File In Cursor
    • 127 |
    • 工具菜单 → Open File In Cursor
    • 128 |
    129 |
  • 130 |
  • 打开项目: 131 |
      132 |
    • 按下 Alt+Shift+P
    • 133 |
    • 在项目视图中右键 → Open Project In Cursor
    • 134 |
    • 工具菜单 → Open Project In Cursor
    • 135 |
    136 |
  • 137 |
138 | 139 |

配置说明

140 |
    141 |
  • 进入 Settings/Preferences → Tools → Switch2Cursor
  • 142 |
  • 设置 Cursor 可执行文件路径(默认为 "cursor")
  • 143 |
  • 通过 Keymap 设置自定义快捷键
  • 144 |
145 | 146 |

系统要求

147 |
    148 |
  • 已安装 Cursor Editor (https://cursor.sh)
  • 149 |
  • 兼容所有 JetBrains IDE
  • 150 |
  • 支持的 IDE 版本:2022.3 及以上
  • 151 |
152 | ]]>
153 | 154 | 156 | com.intellij.modules.platform 157 | 158 | 160 | 161 | 162 | 167 | 168 | 169 | 170 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 1.0.3 188 |
    189 |
  • ⚡️ Update action update thread to background thread for better performance
  • 190 |
191 | 192 |
193 | 194 |
    195 |
  • ⚡️ 将动作更新线程改为后台线程以提升性能
  • 196 |
197 | 198 |

1.0.2

199 |
    200 |
  • ⚡️ Support JetBrains IDEs from version 2022.3
  • 201 |
  • 🐛 Fix DirectoryChooserPopupMenu registration issue
  • 202 |
203 | 204 |
205 | 206 |
    207 |
  • ⚡️ 支持 JetBrains IDEs 2022.3 开始以及以后的版本
  • 208 |
  • 🐛 修复 DirectoryChooserPopupMenu 注册问题
  • 209 |
210 |

1.0.1

211 |
    212 |
  • 🐛 Fix the issue of not being able to activate the Cursor window on Windows
  • 213 |
214 | 215 |
216 | 217 |
    218 |
  • 🐛 修复在 Windows 上无法激活 Cursor 窗口的问题
  • 219 |
220 | 221 |

1.0.0

222 |
    223 |
  • ✨ Support opening files and projects in Cursor
  • 224 |
  • 🔧 Configure Cursor executable path
  • 225 |
  • ⌨️ Access via shortcuts, context menu and toolbar
  • 226 |
227 | 228 |
229 | 230 |
    231 |
  • ✨ 支持在 Cursor 中打开文件和项目
  • 232 |
  • 🔧 支持配置 Cursor 可执行文件路径
  • 233 |
  • ⌨️ 支持快捷键、右键菜单和工具栏访问
  • 234 |
235 | ]]>
236 |
-------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 8 | --------------------------------------------------------------------------------