├── image ├── JumpAndBack.gif ├── ConfigurationPanel.png └── SelectTargetEditor.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle.kts ├── gradle.properties ├── .gitignore ├── src └── main │ ├── kotlin │ └── com │ │ └── github │ │ └── wanniwa │ │ └── editorjumper │ │ ├── editors │ │ ├── TraeHandler.kt │ │ ├── VoidHandler.kt │ │ ├── KiroHandler.kt │ │ ├── QoderHandler.kt │ │ ├── CursorHandler.kt │ │ ├── TraeCNHandler.kt │ │ ├── VSCodeHandler.kt │ │ ├── WindsurfHandler.kt │ │ ├── CatPawAIHandler.kt │ │ ├── AntigravityHandler.kt │ │ ├── EditorHandlerFactory.kt │ │ └── EditorHandler.kt │ │ ├── utils │ │ ├── EditorTargetUtils.kt │ │ └── I18nUtils.kt │ │ ├── startup │ │ └── EditorJumperStartupActivity.kt │ │ ├── statusbar │ │ ├── EditorJumperStatusBarWidgetFactory.kt │ │ └── EditorJumperStatusBarWidget.kt │ │ ├── settings │ │ ├── EditorJumperProjectSettings.kt │ │ ├── EditorJumperSettings.kt │ │ ├── EditorJumperProjectSettingsConfigurable.kt │ │ ├── EditorJumperSettingsConfigurable.kt │ │ └── EditorJumperSettingsComponent.kt │ │ └── actions │ │ ├── OpenInExternalEditorAction.kt │ │ ├── FastOpenInExternalEditorAction.kt │ │ └── BaseAction.kt │ └── resources │ ├── bundle_zh.properties │ ├── bundle.properties │ └── META-INF │ ├── pluginIcon.svg │ └── plugin.xml ├── LICENSE ├── gradlew.bat ├── README_CN.md ├── README.md └── gradlew /image/JumpAndBack.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanniwa/EditorJumper/HEAD/image/JumpAndBack.gif -------------------------------------------------------------------------------- /image/ConfigurationPanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanniwa/EditorJumper/HEAD/image/ConfigurationPanel.png -------------------------------------------------------------------------------- /image/SelectTargetEditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanniwa/EditorJumper/HEAD/image/SelectTargetEditor.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanniwa/EditorJumper/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | } 7 | 8 | rootProject.name = "EditorJumper" -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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=true 5 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html 6 | org.gradle.caching=true 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | .idea 13 | *.iws 14 | *.iml 15 | *.ipr 16 | out/ 17 | !**/src/main/**/out/ 18 | !**/src/test/**/out/ 19 | 20 | ### Eclipse ### 21 | .apt_generated 22 | .classpath 23 | .factorypath 24 | .project 25 | .settings 26 | .springBeans 27 | .sts4-cache 28 | bin/ 29 | !**/src/main/**/bin/ 30 | !**/src/test/**/bin/ 31 | 32 | ### NetBeans ### 33 | /nbproject/private/ 34 | /nbbuild/ 35 | /dist/ 36 | /nbdist/ 37 | /.nb-gradle/ 38 | 39 | ### VS Code ### 40 | .vscode/ 41 | 42 | ### Mac OS ### 43 | .DS_Store 44 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/editors/TraeHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.editors 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.util.SystemInfo 5 | 6 | class TraeHandler(customPath: String, project: Project?) : BaseVscodeEditorHandler(customPath, project) { 7 | 8 | override fun getName(): String = "Trae" 9 | 10 | override fun getDefaultPath(): String { 11 | return when { 12 | SystemInfo.isMac -> "/Applications/Trae.app/Contents/MacOS/Electron" 13 | SystemInfo.isWindows -> "Trae" 14 | else -> "" // 其他平台不支持 15 | } 16 | } 17 | 18 | override fun getMacOpenName(): String? { 19 | return "trae" 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/editors/VoidHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.editors 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.util.SystemInfo 5 | 6 | class VoidHandler(customPath: String, project: Project?) : BaseVscodeEditorHandler(customPath,project) { 7 | override fun getName(): String = "Void" 8 | 9 | override fun getDefaultPath(): String { 10 | return when { 11 | SystemInfo.isMac -> "/Applications/Void.app/Contents/Resources/app/bin/void" 12 | SystemInfo.isWindows -> "void" 13 | else -> "void" // 其他平台不支持 14 | } 15 | } 16 | 17 | override fun getMacOpenName(): String? { 18 | return "void" 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/editors/KiroHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.editors 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.util.SystemInfo 5 | 6 | class KiroHandler(customPath: String?, private val project: Project?) : BaseEditorHandler(customPath) { 7 | override fun getName(): String = "Kiro" 8 | 9 | override fun getDefaultPath(): String { 10 | return when { 11 | SystemInfo.isMac -> "/Applications/Kiro.app/Contents/Resources/app/bin/code" 12 | SystemInfo.isWindows -> "kiro" 13 | else -> "kiro" // Linux 等其他平台 14 | } 15 | } 16 | 17 | override fun getMacOpenName(): String? { 18 | return "kiro" 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/editors/QoderHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.editors 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.util.SystemInfo 5 | 6 | class QoderHandler(customPath: String, project: Project?) : BaseVscodeEditorHandler(customPath, project) { 7 | override fun getName(): String = "Qoder" 8 | 9 | override fun getDefaultPath(): String { 10 | return when { 11 | SystemInfo.isMac -> "/Applications/Qoder.app/Contents/Resources/app/bin/code" 12 | SystemInfo.isWindows -> "qoder" 13 | else -> "qoder" // Linux 和其他平台 14 | } 15 | } 16 | 17 | override fun getMacOpenName(): String? { 18 | return "qoder" 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/editors/CursorHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.editors 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.util.SystemInfo 5 | 6 | class CursorHandler(customPath: String, project: Project?) : BaseVscodeEditorHandler(customPath, project) { 7 | override fun getName(): String = "Cursor" 8 | 9 | override fun getDefaultPath(): String { 10 | return when { 11 | SystemInfo.isMac -> "/Applications/Cursor.app/Contents/Resources/app/bin/code" 12 | SystemInfo.isWindows -> "Cursor" 13 | else -> "Cursor" // 其他平台不支持 14 | } 15 | } 16 | 17 | override fun getMacOpenName(): String? { 18 | return "cursor" 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/editors/TraeCNHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.editors 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.util.SystemInfo 5 | 6 | class TraeCNHandler(customPath: String, project: Project?) : BaseVscodeEditorHandler(customPath, project) { 7 | 8 | override fun getName(): String = "Trae CN" 9 | 10 | override fun getDefaultPath(): String { 11 | return when { 12 | SystemInfo.isMac -> "/Applications/Trae CN.app/Contents/MacOS/Electron" 13 | SystemInfo.isWindows -> "Trae" 14 | else -> "" // 其他平台不支持 15 | } 16 | } 17 | 18 | override fun getMacOpenName(): String { 19 | return "trae-cn" 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/editors/VSCodeHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.editors 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.util.SystemInfo 5 | 6 | class VscodeHandler(customPath: String, project: Project?) : BaseVscodeEditorHandler(customPath, project) { 7 | override fun getName(): String = "Visual Studio Code" 8 | 9 | override fun getDefaultPath(): String { 10 | return when { 11 | SystemInfo.isMac -> "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" 12 | SystemInfo.isWindows -> "Code" 13 | else -> "Code" // 其他平台不支持 14 | } 15 | } 16 | 17 | override fun getMacOpenName(): String { 18 | return "vscode" 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/editors/WindsurfHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.editors 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.util.SystemInfo 5 | 6 | class WindsurfHandler(customPath: String, project: Project?) : BaseVscodeEditorHandler(customPath, project) { 7 | override fun getName(): String = "Windsurf" 8 | 9 | override fun getDefaultPath(): String { 10 | return when { 11 | SystemInfo.isMac -> "/Applications/Windsurf.app/Contents/Resources/app/bin/windsurf" 12 | SystemInfo.isWindows -> "Windsurf" 13 | else -> "Windsurf" // 其他平台不支持 14 | } 15 | } 16 | 17 | override fun getMacOpenName(): String? { 18 | return "windsurf" 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/editors/CatPawAIHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.editors 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.util.SystemInfo 5 | 6 | class CatPawAIHandler(customPath: String, project: Project?) : BaseEditorHandler(customPath) { 7 | override fun getName(): String = "CatPawAI" 8 | 9 | override fun getDefaultPath(): String { 10 | return when { 11 | SystemInfo.isMac -> "/Applications/CatPawAI.app/Contents/Resources/app/bin/code" // Mac implementation to be done 12 | SystemInfo.isWindows -> "CatPawAI" 13 | else -> "CatPawAI" 14 | } 15 | } 16 | 17 | override fun getMacOpenName(): String { 18 | return "catpaw" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/editors/AntigravityHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.editors 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.util.SystemInfo 5 | 6 | class AntigravityHandler(customPath: String?, private val project: Project?) : BaseEditorHandler(customPath) { 7 | override fun getName(): String = "Antigravity" 8 | 9 | override fun getDefaultPath(): String { 10 | return when { 11 | SystemInfo.isMac -> "/Applications/Antigravity.app/Contents/Resources/app/bin/antigravity" 12 | SystemInfo.isWindows -> "antigravity" 13 | else -> "antigravity" 14 | } 15 | } 16 | 17 | override fun getMacOpenName(): String { 18 | return "antigravity" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/utils/EditorTargetUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.utils 2 | 3 | import com.github.wanniwa.editorjumper.settings.EditorJumperProjectSettings 4 | import com.github.wanniwa.editorjumper.settings.EditorJumperSettings 5 | import com.intellij.openapi.project.Project 6 | 7 | object EditorTargetUtils { 8 | /** 9 | * 获取目标编辑器类型,优先使用项目级设置,如果项目级设置为空则使用全局设置 10 | */ 11 | fun getTargetEditor(project: Project?): String { 12 | val projectSettings = project?.let { EditorJumperProjectSettings.getInstance(it) } 13 | return if (projectSettings?.projectEditorType.isNullOrBlank()) { 14 | EditorJumperSettings.getInstance().selectedEditorType 15 | } else { 16 | projectSettings!!.projectEditorType 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/startup/EditorJumperStartupActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.startup 2 | 3 | import com.github.wanniwa.editorjumper.settings.EditorJumperProjectSettings 4 | import com.github.wanniwa.editorjumper.settings.EditorJumperSettings 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.openapi.startup.ProjectActivity 7 | 8 | class EditorJumperStartupActivity : ProjectActivity { 9 | override suspend fun execute(project: Project) { 10 | // 获取全局设置和项目设置 11 | val globalSettings = EditorJumperSettings.getInstance() 12 | val projectSettings = EditorJumperProjectSettings.getInstance(project) 13 | 14 | // 如果项目设置为空,使用全局设置 15 | if (projectSettings.projectEditorType.isBlank()) { 16 | projectSettings.projectEditorType = globalSettings.selectedEditorType 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/statusbar/EditorJumperStatusBarWidgetFactory.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.statusbar 2 | 3 | import com.intellij.openapi.Disposable 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.openapi.util.Disposer 6 | import com.intellij.openapi.wm.StatusBar 7 | import com.intellij.openapi.wm.StatusBarWidget 8 | import com.intellij.openapi.wm.StatusBarWidgetFactory 9 | 10 | class EditorJumperStatusBarWidgetFactory : StatusBarWidgetFactory, Disposable { 11 | override fun getId(): String = "EditorJumperWidget" 12 | 13 | override fun getDisplayName(): String = "Editor Jumper" 14 | 15 | override fun isAvailable(project: Project): Boolean = true 16 | 17 | override fun createWidget(project: Project): StatusBarWidget { 18 | return EditorJumperStatusBarWidget(project) 19 | } 20 | 21 | override fun disposeWidget(widget: StatusBarWidget) { 22 | Disposer.dispose(widget) 23 | } 24 | 25 | override fun canBeEnabledOn(statusBar: StatusBar): Boolean = true 26 | 27 | override fun dispose() { 28 | // 清理工厂类资源 29 | } 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 wanniwa 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 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/settings/EditorJumperProjectSettings.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.settings 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent 4 | import com.intellij.openapi.components.Service 5 | import com.intellij.openapi.components.State 6 | import com.intellij.openapi.components.Storage 7 | import com.intellij.openapi.project.Project 8 | import com.intellij.util.xmlb.XmlSerializerUtil 9 | 10 | @Service(Service.Level.PROJECT) 11 | @State( 12 | name = "com.github.wanniwa.editorjumper.settings.EditorJumperProjectSettings", 13 | storages = [Storage("editorJumperProjectSettings.xml")] 14 | ) 15 | class EditorJumperProjectSettings : PersistentStateComponent { 16 | var vsCodeWorkspacePath: String = "" 17 | var projectEditorType: String = "" // 删除默认值,完全继承全局设置 18 | 19 | companion object { 20 | fun getInstance(project: Project): EditorJumperProjectSettings { 21 | return project.getService(EditorJumperProjectSettings::class.java) 22 | } 23 | } 24 | 25 | override fun getState(): EditorJumperProjectSettings { 26 | return this 27 | } 28 | 29 | override fun loadState(state: EditorJumperProjectSettings) { 30 | XmlSerializerUtil.copyBean(state, this) 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/resources/bundle_zh.properties: -------------------------------------------------------------------------------- 1 | # Actions 2 | action.openInExternalEditor.text=在 {0} 中打开 3 | 4 | 5 | # Dialog Messages 6 | dialog.editorPathNotConfigured.title=编辑器路径未配置 7 | dialog.editorPathNotConfigured.message={0} 的路径未配置。是否现在进行配置? 8 | dialog.editorPathNotConfigured.openSettings=打开设置 9 | dialog.editorPathNotConfigured.cancel=取消 10 | 11 | dialog.updateCurrentProject.title=更新当前项目目标 12 | dialog.updateCurrentProject.message=默认编辑器已更改为 {0}。是否也要将当前项目的跳转目标更新为 {0}? 13 | dialog.updateCurrentProject.yes=是 14 | dialog.updateCurrentProject.no=否 15 | 16 | # Settings UI 17 | settings.displayName=EditorJumper 18 | settings.projectSettings.displayName=项目设置 19 | settings.defaultEditor.label=新项目的默认编辑器: 20 | settings.hint.macOS=macOS:所有路径都会自动检测,无需手动配置 21 | settings.hint.windows=Windows:Cursor 和 Qoder 会自动检测,其他编辑器需要 .exe 文件路径 22 | settings.hint.example=示例:C:\\Users\\username\\AppData\\Local\\Programs\\VSCode\\Code.exe 23 | settings.hint.defaultEditor=默认编辑器:仅适用于新打开的项目,
要更改当前项目的目标 IDE,请从底部状态栏中选择 24 | 25 | settings.path.label={0} 路径: 26 | settings.traeCN.label=使用Trae国内版 27 | 28 | settings.projectSettings.workspacePath=工作区文件路径: 29 | 30 | # Statusbar 31 | statusbar.tooltip=点击切换目标编辑器 32 | statusbar.jumpTo=跳转到:{0} 33 | statusbar.popup.title=选择跳转编辑器 34 | 35 | # File Chooser 36 | fileChooser.title=选择 {0} 可执行文件 37 | 38 | # Editor Names 39 | editor.settings=设置 -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/settings/EditorJumperSettings.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.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.wanniwa.editorjumper.settings.EditorJumperSettings", 11 | storages = [Storage("EditorJumperSettings.xml")] 12 | ) 13 | class EditorJumperSettings : PersistentStateComponent { 14 | var vscodePath: String = "" 15 | var cursorPath: String = "" 16 | var traePath: String = "" 17 | var windsurfPath: String = "" 18 | var voidPath: String = "" 19 | var kiroPath: String = "" 20 | var qoderPath: String = "" 21 | var catPawAIPath: String = "" 22 | var antigravityPath: String = "" 23 | var selectedEditorType: String = "Cursor" 24 | var traeCN: Boolean = false 25 | 26 | companion object { 27 | fun getInstance(): EditorJumperSettings { 28 | return ApplicationManager.getApplication().getService(EditorJumperSettings::class.java) 29 | } 30 | } 31 | 32 | override fun getState(): EditorJumperSettings { 33 | return this 34 | } 35 | 36 | override fun loadState(state: EditorJumperSettings) { 37 | XmlSerializerUtil.copyBean(state, this) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/bundle.properties: -------------------------------------------------------------------------------- 1 | # Actions 2 | action.openInExternalEditor.text=Open in {0} 3 | 4 | # Dialog Messages 5 | dialog.editorPathNotConfigured.title=Editor Path Not Configured 6 | dialog.editorPathNotConfigured.message=The path to {0} is not configured. Would you like to configure it now? 7 | dialog.editorPathNotConfigured.openSettings=Open Settings 8 | dialog.editorPathNotConfigured.cancel=Cancel 9 | 10 | dialog.updateCurrentProject.title=Update Current Project Target 11 | dialog.updateCurrentProject.message=Default editor has been changed to {0}. Would you like to also update the current project's jump target to {0}? 12 | dialog.updateCurrentProject.yes=Yes 13 | dialog.updateCurrentProject.no=No 14 | 15 | # Settings UI 16 | settings.displayName=EditorJumper 17 | settings.projectSettings.displayName=Project Settings 18 | settings.defaultEditor.label=Default Editor for new projects: 19 | settings.hint.macOS=macOS: All paths are auto-detected, no manual configuration needed 20 | settings.hint.windows=Windows: Cursor and Qoder are auto-detected, other editors need .exe file path 21 | settings.hint.example=Example: C:\\Users\\username\\AppData\\Local\\Programs\\VSCode\\Code.exe 22 | settings.hint.defaultEditor=Default Editor: Only for newly opened projects,
To change the target IDE for current project, please select from the status bar at the bottom 23 | 24 | settings.path.label={0} path: 25 | settings.traeCN.label=Use Trae CN 26 | 27 | settings.projectSettings.workspacePath=Workspace File Path: 28 | 29 | # Statusbar 30 | statusbar.tooltip=Click to switch target editor 31 | statusbar.jumpTo=Jump to: {0} 32 | statusbar.popup.title=Select Jump Editor 33 | 34 | # File Chooser 35 | fileChooser.title=Select {0} Executable 36 | 37 | # Editor Names 38 | editor.settings=Settings -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/actions/OpenInExternalEditorAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.actions 2 | 3 | import com.github.wanniwa.editorjumper.editors.EditorHandler 4 | import com.github.wanniwa.editorjumper.utils.EditorTargetUtils 5 | import com.github.wanniwa.editorjumper.utils.I18nUtils 6 | import com.intellij.openapi.actionSystem.ActionUpdateThread 7 | import com.intellij.openapi.actionSystem.AnActionEvent 8 | import com.intellij.openapi.actionSystem.CommonDataKeys 9 | import com.intellij.openapi.project.Project 10 | import com.intellij.openapi.ui.Messages 11 | import com.intellij.openapi.util.SystemInfo 12 | import com.intellij.openapi.vfs.VirtualFile 13 | import java.io.IOException 14 | 15 | /** 16 | * 在外部编辑器中打开文件或文件夹的动作 17 | */ 18 | class OpenInExternalEditorAction : BaseAction() { 19 | 20 | override fun actionPerformed(e: AnActionEvent) { 21 | val project = e.project ?: return 22 | 23 | // 获取编辑器处理器 24 | val handler = getEditorHandler(project) 25 | 26 | // 检查编辑器路径是否存在 27 | if (!checkEditorPathExists(project, handler)) { 28 | return 29 | } 30 | 31 | val selectedFile = e.getData(CommonDataKeys.VIRTUAL_FILE) 32 | val editor = e.getData(CommonDataKeys.EDITOR) 33 | 34 | // 获取光标位置 35 | var lineNumber = editor?.caretModel?.currentCaret?.logicalPosition?.line?.plus(1) 36 | var columnNumber = editor?.caretModel?.currentCaret?.logicalPosition?.column?.plus(1) 37 | 38 | // 在外部编辑器中打开 39 | openInExternalEditor(project, handler, selectedFile, lineNumber, columnNumber) 40 | } 41 | 42 | 43 | /** 44 | * 更新动作的可见性和文本 45 | */ 46 | override fun update(e: AnActionEvent) { 47 | super.update(e) 48 | 49 | // 更新菜单项文本 50 | val project = e.project 51 | val editorType = EditorTargetUtils.getTargetEditor(project) 52 | e.presentation.text = I18nUtils.message("action.openInExternalEditor.text", editorType) 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/utils/I18nUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.utils 2 | 3 | import com.intellij.openapi.diagnostic.Logger 4 | import java.text.MessageFormat 5 | import java.util.* 6 | 7 | /** 8 | * 国际化工具类 9 | * 根据 IDEA 的语言设置自动切换中英文 10 | */ 11 | object I18nUtils { 12 | private val logger = Logger.getInstance(I18nUtils::class.java) 13 | 14 | private val bundle: ResourceBundle by lazy { 15 | ResourceBundle.getBundle("bundle") 16 | } 17 | 18 | /** 19 | * 获取国际化文本 20 | * @param key 资源键 21 | * @return 对应语言的文本,如果找不到则返回键值本身 22 | */ 23 | fun message(key: String): String { 24 | return try { 25 | bundle.getString(key) 26 | } catch (e: MissingResourceException) { 27 | logger.warn("Missing translation key: $key", e) 28 | // 返回友好的fallback文本而不是原始key 29 | "[Missing: $key]" 30 | } 31 | } 32 | 33 | /** 34 | * 获取带参数的国际化文本 35 | * @param key 资源键 36 | * @param args 参数 37 | * @return 格式化后的文本 38 | */ 39 | fun message(key: String, vararg args: Any): String { 40 | return try { 41 | val pattern = bundle.getString(key) 42 | MessageFormat.format(pattern, *args) 43 | } catch (e: MissingResourceException) { 44 | logger.warn("Missing translation key: $key", e) 45 | "[Missing: $key]" 46 | } catch (e: IllegalArgumentException) { 47 | logger.warn("Invalid message format for key: $key, args: ${args.contentToString()}", e) 48 | // 尝试返回原始模板 49 | try { 50 | bundle.getString(key) 51 | } catch (ex: MissingResourceException) { 52 | "[Format Error: $key]" 53 | } 54 | } 55 | } 56 | 57 | 58 | /** 59 | * 获取设置路径标签文本 60 | */ 61 | fun getPathLabel(editorName: String): String { 62 | return message("settings.path.label", editorName) 63 | } 64 | 65 | /** 66 | * 获取文件选择器标题 67 | */ 68 | fun getFileChooserTitle(editorType: String): String { 69 | return message("fileChooser.title", editorType) 70 | } 71 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/editors/EditorHandlerFactory.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.editors 2 | 3 | import com.github.wanniwa.editorjumper.settings.EditorJumperSettings 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.openapi.util.SystemInfo 6 | 7 | class EditorHandlerFactory { 8 | companion object { 9 | fun getHandler(editorType: String, customPath: String, project: Project?): EditorHandler { 10 | return when (editorType) { 11 | "Visual Studio Code" -> VscodeHandler(customPath, project) 12 | "Cursor" -> CursorHandler(customPath, project) 13 | "Trae" -> { 14 | val settings = EditorJumperSettings.getInstance() 15 | // 只在 Mac 平台上检查 Trae CN 设置 16 | if (SystemInfo.isMac && settings.traeCN) { 17 | TraeCNHandler(customPath, project) 18 | } else { 19 | TraeHandler(customPath, project) 20 | } 21 | } 22 | "Windsurf" -> WindsurfHandler(customPath, project) 23 | "Void" -> VoidHandler(customPath, project) 24 | "Kiro" -> KiroHandler(customPath, project) 25 | "Qoder" -> QoderHandler(customPath, project) 26 | "CatPawAI" -> CatPawAIHandler(customPath, project) 27 | "Antigravity" -> AntigravityHandler(customPath, project) 28 | else -> CursorHandler(customPath, project) 29 | } 30 | } 31 | 32 | /** 33 | * 从设置中获取自定义路径并创建对应的编辑器处理器 34 | */ 35 | fun getHandler(editorType: String, project: Project?): EditorHandler { 36 | val settings = EditorJumperSettings.getInstance() 37 | val customPath = when (editorType) { 38 | "Visual Studio Code" -> settings.vscodePath 39 | "Cursor" -> settings.cursorPath 40 | "Trae" -> settings.traePath 41 | "Windsurf" -> settings.windsurfPath 42 | "Void" -> settings.voidPath 43 | "Kiro" -> settings.kiroPath 44 | "Qoder" -> settings.qoderPath 45 | "CatPawAI" -> settings.catPawAIPath 46 | "Antigravity" -> settings.antigravityPath 47 | else -> "" 48 | } 49 | return getHandler(editorType, customPath, project) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/actions/FastOpenInExternalEditorAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.actions 2 | 3 | import com.github.wanniwa.editorjumper.editors.EditorHandler 4 | import com.intellij.openapi.actionSystem.ActionUpdateThread 5 | import com.intellij.openapi.actionSystem.AnActionEvent 6 | import com.intellij.openapi.actionSystem.CommonDataKeys 7 | import com.intellij.openapi.project.Project 8 | import com.intellij.openapi.ui.Messages 9 | import com.intellij.openapi.util.SystemInfo 10 | import com.intellij.openapi.vfs.VirtualFile 11 | 12 | /** 13 | * Action for opening files in external editor using Mac URL scheme 14 | * Triggered by Option+Shift+P shortcut on Mac systems 15 | */ 16 | class FastOpenInExternalEditorAction : BaseAction() { 17 | 18 | override fun getActionUpdateThread(): ActionUpdateThread { 19 | return ActionUpdateThread.BGT 20 | } 21 | 22 | override fun actionPerformed(e: AnActionEvent) { 23 | val project = e.project ?: return 24 | 25 | // 获取编辑器处理器 26 | val handler = getEditorHandler(project) 27 | 28 | // 检查编辑器路径是否存在 29 | if (!checkEditorPathExists(project, handler)) { 30 | return 31 | } 32 | 33 | val selectedFile = e.getData(CommonDataKeys.VIRTUAL_FILE) 34 | val editor = e.getData(CommonDataKeys.EDITOR) 35 | 36 | // 获取光标位置 37 | var lineNumber = editor?.caretModel?.currentCaret?.logicalPosition?.line?.plus(1) 38 | var columnNumber = editor?.caretModel?.currentCaret?.logicalPosition?.column?.plus(1) 39 | if (lineNumber == null) { 40 | lineNumber = 1 41 | } 42 | if (columnNumber == null) { 43 | columnNumber = 1 44 | } 45 | // 在外部编辑器中打开 46 | if (SystemInfo.isMac) { 47 | openInExternalEditor(project, handler, selectedFile, lineNumber, columnNumber) 48 | } else { 49 | super.openInExternalEditor(project, handler, selectedFile, lineNumber, columnNumber) 50 | } 51 | } 52 | 53 | @Override 54 | override fun openInExternalEditor( 55 | project: Project, 56 | handler: EditorHandler, 57 | file: VirtualFile?, 58 | lineNumber: Int?, 59 | columnNumber: Int? 60 | ) { 61 | val projectPath = getProjectPath(project) ?: return 62 | val filePath = getFilePath(file) 63 | 64 | try { 65 | val command = handler.getFastOpenCommand(projectPath, filePath, lineNumber, columnNumber) 66 | 67 | ProcessBuilder(command.toList()) 68 | .redirectOutput(ProcessBuilder.Redirect.DISCARD) 69 | .redirectError(ProcessBuilder.Redirect.DISCARD) 70 | .start() 71 | .outputStream.close() 72 | 73 | } catch (e: Exception) { 74 | Messages.showErrorDialog( 75 | project, 76 | "Failed to fast open editor. Error: ${e.message}", 77 | "Editor Launch Failed" 78 | ) 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/settings/EditorJumperProjectSettingsConfigurable.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.settings 2 | 3 | import com.intellij.openapi.extensions.BaseExtensionPointName 4 | import com.intellij.openapi.fileChooser.FileChooserDescriptor 5 | import com.intellij.openapi.options.Configurable 6 | import com.intellij.openapi.options.Configurable.WithEpDependencies 7 | import com.intellij.openapi.project.Project 8 | import com.intellij.openapi.ui.TextBrowseFolderListener 9 | import com.intellij.openapi.ui.TextFieldWithBrowseButton 10 | import com.intellij.ui.components.JBLabel 11 | import com.intellij.util.ui.FormBuilder 12 | import com.github.wanniwa.editorjumper.utils.I18nUtils 13 | import javax.swing.JComponent 14 | import javax.swing.JPanel 15 | 16 | class EditorJumperProjectSettingsConfigurable(private val project: Project) : Configurable, WithEpDependencies { 17 | private var mySettingsComponent: EditorJumperProjectSettingsComponent? = null 18 | 19 | override fun getDisplayName(): String { 20 | return I18nUtils.message("settings.projectSettings.displayName") 21 | } 22 | 23 | override fun getPreferredFocusedComponent(): JComponent { 24 | return mySettingsComponent!!.getPreferredFocusedComponent() 25 | } 26 | 27 | override fun createComponent(): JComponent { 28 | mySettingsComponent = EditorJumperProjectSettingsComponent() 29 | return mySettingsComponent!!.getPanel() 30 | } 31 | 32 | override fun isModified(): Boolean { 33 | val settings = EditorJumperProjectSettings.getInstance(project) 34 | return mySettingsComponent!!.getWorkspacePath() != settings.vsCodeWorkspacePath 35 | } 36 | 37 | override fun apply() { 38 | val settings = EditorJumperProjectSettings.getInstance(project) 39 | settings.vsCodeWorkspacePath = mySettingsComponent!!.getWorkspacePath() 40 | } 41 | 42 | override fun reset() { 43 | val settings = EditorJumperProjectSettings.getInstance(project) 44 | mySettingsComponent!!.setWorkspacePath(settings.vsCodeWorkspacePath) 45 | } 46 | 47 | override fun disposeUIResources() { 48 | mySettingsComponent = null 49 | } 50 | 51 | override fun getDependencies(): Collection> { 52 | return ArrayList() 53 | } 54 | } 55 | 56 | class EditorJumperProjectSettingsComponent { 57 | private val myMainPanel: JPanel 58 | private val workspacePathField = TextFieldWithBrowseButton() 59 | 60 | init { 61 | val workspaceDescriptor = FileChooserDescriptor(true, false, false, false, false, false) 62 | workspaceDescriptor.title = "Select Workspace File" 63 | workspaceDescriptor.withFileFilter { file -> file.name.endsWith(".code-workspace") } 64 | 65 | workspacePathField.addBrowseFolderListener(TextBrowseFolderListener(workspaceDescriptor)) 66 | 67 | myMainPanel = FormBuilder.createFormBuilder() 68 | .addLabeledComponent(JBLabel(I18nUtils.message("settings.projectSettings.workspacePath")), workspacePathField, 1, false) 69 | .addComponentFillVertically(JPanel(), 0) 70 | .panel 71 | } 72 | 73 | fun getPanel(): JPanel { 74 | return myMainPanel 75 | } 76 | 77 | fun getPreferredFocusedComponent(): JComponent { 78 | return workspacePathField 79 | } 80 | 81 | fun getWorkspacePath(): String { 82 | return workspacePathField.text 83 | } 84 | 85 | fun setWorkspacePath(path: String) { 86 | workspacePathField.text = path 87 | } 88 | } -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # Editor Jumper 2 | 3 |
4 | Editor Jumper图标 5 |
6 | 7 |
8 | JetBrains Plugin 9 | License 10 | English Doc 11 |
12 | 13 | ## 🔍 简介 14 | 15 | EditorJumper 是一个 JetBrains IDE 插件,允许您在 JetBrains IDE 和其他流行的代码编辑器(如 VS Code、Cursor、Trae、Windsurf、Void、Kiro 和 Qoder)之间无缝跳转。它能够保持光标位置和编辑上下文,大大提高了多编辑器环境中的开发效率。 16 | 17 |
18 | Jump and Back Demo 19 |
20 |
21 | Configuration Panel 22 |
23 | ## 🌟 功能特点 24 | 25 | - 🚀 **无缝编辑器切换** 26 | - 快速在 JetBrains IDE 和 VS Code、Cursor、Trae、Windsurf、Void、Kiro、Qoder 之间跳转 27 | - 自动定位到相同的光标位置(行和列) 28 | - 完美保持编辑上下文,不中断工作流 29 | - 工作空间文件支持 - 所有编辑器现在都支持多根项目的工作空间文件 (.code-workspace) 30 | 31 | - 🎯 **智能跳转行为** 32 | - 有文件打开时:打开相同的项目和文件,保持光标位置 33 | - 无文件打开时:直接在目标编辑器中打开项目 34 | - 兼容编辑器:支持多根项目的工作空间文件 (.code-workspace) 35 | 36 | - ⚡ **多种触发方式** 37 | - 在编辑器中右击 - 选择"在 [编辑器名称] 中打开"(名称会根据选择的编辑器更新) 38 | - 工具菜单 - 选择"在 [编辑器名称] 中打开"(名称会根据选择的编辑器更新) 39 | - 快捷键 - Alt+Shift+O / Option+Shift+O 40 | - 极速跳转(mac)- Alt+Shift+P / Option+Shift+P 41 | 42 | - 🎚️ **便捷的目标编辑器选择** 43 | - 状态栏小部件 - 点击编辑器图标选择要跳转到哪个编辑器 44 | 45 | - 🍎 **Mac 专属功能** 46 | - Trae CN(国内版)勾选选项,专为中国用户提供 47 | 48 | 49 | ## 💻 系统要求 50 | 51 | - 适用于任何 JetBrains IDE(IntelliJ IDEA、WebStorm、PyCharm 等) 52 | - 需要 IDE 版本 2023 或更新版本 53 | 54 | ## 📥 安装 55 | 56 | 1. 在 JetBrains IDE 中打开设置/首选项 57 | 2. 导航到插件 > 市场 58 | 3. 搜索 "EditorJumper" 59 | 4. 点击安装按钮 60 | 5. 重启 IDE 以激活插件 61 | 62 | ## ⚙️ 配置 63 | 64 | 1. 在 IDE 中打开设置/首选项 65 | 2. 导航到工具 > EditorJumper 设置 66 | 3. 配置每个编辑器的可执行文件路径: 67 | - VS Code 路径 68 | - Cursor 路径 69 | - Trae 路径(Mac 上有可选的 CN 版本勾选框) 70 | - Windsurf 路径 71 | - Void 路径 72 | - Kiro 路径 73 | - Qoder 路径 74 | 4. 选择默认编辑器 75 | 5. 点击应用保存设置 76 | 77 | > **配置说明:** 78 | > - macOS:所有编辑器无需额外配置 79 | > - 特殊:为中国用户提供 Trae CN 勾选选项 80 | > - Windows: 81 | > - Cursor/Qoder:无需配置(使用系统 PATH) 82 | > - 其他编辑器:需在设置中配置 .exe 文件路径 83 | > 84 | > **配置界面:** 85 | > - 默认编辑器:选择使用快捷键时要使用的编辑器 86 | > - 编辑器路径: 87 | > - macOS:所有路径都自动检测,无需手动配置 88 | > - Windows: 89 | > - Cursor/Qoder:通过系统 PATH 自动检测 90 | > - VS Code/Trae/Windsurf/Void/Kiro:浏览并选择 .exe 文件位置 91 | > - 示例:`C:\Users\username\AppData\Local\Programs\VSCode\Code.exe` 92 | 93 | ## 🚀 使用方法 94 | 95 | ### 通过快捷键 96 | 97 | | 使用场景 | Alt+Shift+O / Option+Shift+O | Alt+Shift+P / Option+Shift+P | 98 | |----------|------------------------------|---------------------------------------| 99 | | **在项目文件夹上** | 极速打开项目 | 极速打开项目 | 100 | | **在具体文件上** | 自动打开项目+文件 | mac更快(但需要先打开项目,否则只会打开单个文件),windows无差别 | 101 | 102 | **使用建议:** 103 | - **Windows 用户**:使用 Alt+Shift+O 即可(满足所有需求) 104 | - **Mac 用户**:使用 Option+Shift+O,熟悉之后想要更快速的体验用 Option+Shift+P 105 | 106 | ### 通过上下文菜单 107 | 108 | 1. 在编辑器中右击 109 | 2. 选择"在外部编辑器中打开" 110 | 111 | ### 在状态栏更换跳转目标 112 | 113 | 1. 点击 IDE 底部状态栏中的跳转图标 114 | 2. 从下拉菜单中选择要跳转到的编辑器 115 | ![选择目标编辑器](image/SelectTargetEditor.png) 116 | 3. 使用上述任一触发方式(快捷键、右击或工具菜单)执行跳转 117 | 118 | ## 🔄 配套使用 119 | 120 | > 推荐与 [EditorJumper-V](https://github.com/wanniwa/EditorJumper-V) 配合使用,以便从 Cursor、VS Code、Trae、Windsurf、Void、Kiro、Qoder 快速返回 JetBrains IDE 121 | 122 | ## 🤝 贡献 123 | 124 | 欢迎提交 Pull Requests 和 Issues 来帮助改进这个插件! 125 | 126 | ## 📄 许可证 127 | 128 | 本项目采用 MIT 许可证 - 详情请参阅 [LICENSE](LICENSE) 文件 129 | 130 | ## 收藏历史 131 | 132 | [![Star History Chart](https://api.star-history.com/svg?repos=wanniwa/EditorJumper&type=Date)](https://www.star-history.com/#wanniwa/EditorJumper&Date) 133 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/statusbar/EditorJumperStatusBarWidget.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.statusbar 2 | 3 | import com.github.wanniwa.editorjumper.settings.EditorJumperProjectSettings 4 | import com.github.wanniwa.editorjumper.settings.EditorJumperSettings 5 | import com.github.wanniwa.editorjumper.settings.EditorJumperSettingsConfigurable 6 | import com.github.wanniwa.editorjumper.utils.I18nUtils 7 | import com.intellij.openapi.Disposable 8 | import com.intellij.openapi.options.ShowSettingsUtil 9 | import com.intellij.openapi.project.Project 10 | import com.intellij.openapi.ui.popup.JBPopupFactory 11 | import com.intellij.openapi.ui.popup.ListPopup 12 | import com.intellij.openapi.ui.popup.PopupStep 13 | import com.intellij.openapi.ui.popup.util.BaseListPopupStep 14 | import com.intellij.openapi.wm.StatusBar 15 | import com.intellij.openapi.wm.StatusBarWidget 16 | import com.intellij.ui.awt.RelativePoint 17 | import com.intellij.util.Consumer 18 | import java.awt.Point 19 | import java.awt.event.MouseEvent 20 | 21 | class EditorJumperStatusBarWidget(private val project: Project) : StatusBarWidget, 22 | StatusBarWidget.MultipleTextValuesPresentation, 23 | Disposable { 24 | companion object { 25 | const val ID = "EditorJumperWidget" 26 | } 27 | 28 | private var statusBar: StatusBar? = null 29 | private val supportedEditors = arrayOf( 30 | "Visual Studio Code", 31 | "Cursor", 32 | "Trae", 33 | "Windsurf", 34 | "Void", 35 | "Kiro", 36 | "Qoder", 37 | "CatPawAI", 38 | "Antigravity", 39 | I18nUtils.message("editor.settings") 40 | ) 41 | 42 | override fun ID(): String = ID 43 | 44 | override fun getTooltipText(): String = I18nUtils.message("statusbar.tooltip") 45 | 46 | override fun getSelectedValue(): String { 47 | val projectSettings = EditorJumperProjectSettings.getInstance(project) 48 | val editorType = if (projectSettings.projectEditorType.isBlank()) { 49 | EditorJumperSettings.getInstance().selectedEditorType 50 | } else { 51 | projectSettings.projectEditorType 52 | } 53 | return I18nUtils.message("statusbar.jumpTo", editorType) 54 | } 55 | 56 | override fun getPresentation(): StatusBarWidget.WidgetPresentation { 57 | return this 58 | } 59 | 60 | override fun getPopup(): ListPopup { 61 | val factory = JBPopupFactory.getInstance() 62 | var selectedValue: String? = null 63 | 64 | val step = 65 | object : BaseListPopupStep(I18nUtils.message("statusbar.popup.title"), supportedEditors.toList()) { 66 | override fun onChosen(value: String, finalChoice: Boolean): PopupStep<*>? { 67 | selectedValue = value 68 | if (value == I18nUtils.message("editor.settings")) { 69 | return PopupStep.FINAL_CHOICE 70 | } else { 71 | // 只更新项目级设置 72 | val projectSettings = EditorJumperProjectSettings.getInstance(project) 73 | projectSettings.projectEditorType = value 74 | // 更新状态栏显示 75 | statusBar?.updateWidget(ID()) 76 | } 77 | return PopupStep.FINAL_CHOICE 78 | } 79 | 80 | override fun getFinalRunnable(): Runnable? { 81 | return if (selectedValue == I18nUtils.message("editor.settings")) { 82 | Runnable { 83 | ShowSettingsUtil.getInstance().showSettingsDialog( 84 | project, 85 | EditorJumperSettingsConfigurable::class.java 86 | ) 87 | } 88 | } else null 89 | } 90 | } 91 | 92 | return factory.createListPopup(step) 93 | } 94 | 95 | override fun getClickConsumer(): Consumer = Consumer { event -> 96 | val popup = getPopup() 97 | val component = event.component 98 | 99 | // 将弹出菜单显示在组件的正上方,并与组件左对齐 100 | // 使用固定的偏移量,确保菜单贴合状态栏 101 | val point = Point(0, 0) 102 | 103 | popup.show(RelativePoint(component, point)) 104 | } 105 | 106 | override fun install(statusBar: StatusBar) { 107 | this.statusBar = statusBar 108 | } 109 | 110 | override fun dispose() { 111 | statusBar = null 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 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 | 1 41 | 2 42 | 3 43 | 4 44 | 45 | 46 | 47 | 1 48 | 2 49 | 3 50 | 4 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 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/editors/EditorHandler.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.editors 2 | 3 | import com.github.wanniwa.editorjumper.settings.EditorJumperProjectSettings 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.openapi.util.SystemInfo 6 | import java.io.File 7 | 8 | interface EditorHandler { 9 | fun getName(): String 10 | fun getPath(): String 11 | fun getDefaultPath(): String 12 | 13 | /** 14 | * 获取打开命令 15 | * @param projectPath 项目路径 16 | * @param filePath 文件路径,可能为null 17 | * @param lineNumber 行号,可能为null 18 | * @param columnNumber 列号,可能为null 19 | * @return 命令数组 20 | */ 21 | fun getOpenCommand( 22 | projectPath: String, 23 | filePath: String?, 24 | lineNumber: Int?, 25 | columnNumber: Int? 26 | ): Array 27 | 28 | fun getFastOpenCommand( 29 | projectPath: String, 30 | filePath: String?, 31 | lineNumber: Int?, 32 | columnNumber: Int? 33 | ): Array 34 | 35 | /** 36 | * 获取Mac URL方案名称(如 "cursor", "vscode" 等) 37 | * @return URL方案名称,如果不支持则返回null 38 | */ 39 | fun getMacOpenName(): String? = null 40 | } 41 | 42 | abstract class BaseEditorHandler(private val customPath: String?) : EditorHandler { 43 | override fun getPath(): String { 44 | return if (customPath.isNullOrEmpty()) getDefaultPath() else customPath 45 | } 46 | 47 | override fun getOpenCommand( 48 | projectPath: String, 49 | filePath: String?, 50 | lineNumber: Int?, 51 | columnNumber: Int? 52 | ): Array { 53 | val macAppName = getName() 54 | return when { 55 | filePath != null -> { 56 | val actualLineNumber = lineNumber ?: 1 57 | val actualColumnNumber = columnNumber ?: 1 58 | // 如果有文件路径和光标位置,则打开项目并定位到文件的具体行列 59 | val fileWithPosition = "$filePath:$actualLineNumber:$actualColumnNumber" 60 | if (SystemInfo.isWindows && getPath() == getDefaultPath()) { 61 | arrayOf("cmd", "/c", getPath(), projectPath, "--goto", fileWithPosition) 62 | } else { 63 | arrayOf(getPath(), projectPath, "--goto", fileWithPosition) 64 | } 65 | } 66 | 67 | else -> { 68 | // 只打开项目 69 | if (SystemInfo.isWindows && getPath() == getDefaultPath()) { 70 | arrayOf("cmd", "/c", getPath(), projectPath) 71 | } else if (SystemInfo.isMac) { 72 | arrayOf("open", "-a", "$macAppName", projectPath) 73 | } else { 74 | arrayOf(getPath(), projectPath) 75 | } 76 | } 77 | } 78 | } 79 | 80 | override fun getFastOpenCommand( 81 | projectPath: String, 82 | filePath: String?, 83 | lineNumber: Int?, 84 | columnNumber: Int? 85 | ): Array { 86 | val macOpenName = getMacOpenName() 87 | val macAppName = getName() 88 | if (SystemInfo.isWindows) { 89 | return getOpenCommand(projectPath, filePath, lineNumber, columnNumber) 90 | } 91 | return when { 92 | filePath.isNullOrBlank() -> { 93 | arrayOf("open", "-a", "$macAppName", projectPath) 94 | } 95 | else -> { 96 | arrayOf("open", "-a", "$macAppName", "$macOpenName://file$filePath:$lineNumber:$columnNumber") 97 | } 98 | } 99 | } 100 | } 101 | 102 | abstract class BaseVscodeEditorHandler(customPath: String?, val project: Project?) : 103 | BaseEditorHandler(customPath) { 104 | override fun getOpenCommand( 105 | projectPath: String, 106 | filePath: String?, 107 | lineNumber: Int?, 108 | columnNumber: Int? 109 | ): Array { 110 | 111 | // 如果配置了工作空间文件,优先使用工作空间文件 112 | val projectSettings = project?.let { EditorJumperProjectSettings.getInstance(it) } 113 | val workspacePath = projectSettings?.vsCodeWorkspacePath 114 | 115 | // 如果配置了工作空间文件且文件存在,使用工作空间文件 116 | if (!workspacePath.isNullOrBlank() && File(workspacePath).exists()) { 117 | return super.getOpenCommand(workspacePath, filePath, lineNumber, columnNumber) 118 | } else { 119 | return super.getOpenCommand(projectPath, filePath, lineNumber, columnNumber) 120 | } 121 | } 122 | 123 | override fun getFastOpenCommand( 124 | projectPath: String, 125 | filePath: String?, 126 | lineNumber: Int?, 127 | columnNumber: Int? 128 | ): Array { 129 | // 如果配置了工作空间文件,优先使用工作空间文件 130 | val projectSettings = project?.let { EditorJumperProjectSettings.getInstance(it) } 131 | val workspacePath = projectSettings?.vsCodeWorkspacePath 132 | 133 | // 如果配置了工作空间文件且文件存在,使用工作空间文件 134 | if (!workspacePath.isNullOrBlank() && File(workspacePath).exists()) { 135 | return super.getFastOpenCommand(workspacePath, filePath, lineNumber, columnNumber) 136 | } else { 137 | return super.getFastOpenCommand(projectPath, filePath, lineNumber, columnNumber) 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/actions/BaseAction.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.actions 2 | 3 | import com.github.wanniwa.editorjumper.editors.EditorHandler 4 | import com.github.wanniwa.editorjumper.editors.EditorHandlerFactory 5 | import com.github.wanniwa.editorjumper.settings.EditorJumperSettings 6 | import com.github.wanniwa.editorjumper.settings.EditorJumperSettingsConfigurable 7 | import com.github.wanniwa.editorjumper.utils.EditorTargetUtils 8 | import com.github.wanniwa.editorjumper.utils.I18nUtils 9 | import com.intellij.openapi.actionSystem.ActionUpdateThread 10 | import com.intellij.openapi.actionSystem.AnAction 11 | import com.intellij.openapi.actionSystem.AnActionEvent 12 | import com.intellij.openapi.options.ShowSettingsUtil 13 | import com.intellij.openapi.project.Project 14 | import com.intellij.openapi.ui.Messages 15 | import com.intellij.openapi.util.SystemInfo 16 | import com.intellij.openapi.vfs.VirtualFile 17 | 18 | /** 19 | * 基础动作类,提供通用方法 20 | */ 21 | abstract class BaseAction : AnAction() { 22 | 23 | override fun getActionUpdateThread() = ActionUpdateThread.BGT 24 | 25 | /** 26 | * 获取项目路径 27 | */ 28 | protected open fun getProjectPath(project: Project): String? { 29 | return project.basePath 30 | } 31 | 32 | /** 33 | * 获取文件路径,子类可以覆盖此方法以提供不同的文件路径获取逻辑 34 | */ 35 | protected open fun getFilePath(virtualFile: VirtualFile?): String? { 36 | if (virtualFile == null) { 37 | return null 38 | } 39 | return if (virtualFile.isDirectory) null else virtualFile.path 40 | } 41 | 42 | /** 43 | * 获取编辑器处理器 44 | */ 45 | protected open fun getEditorHandler(project: Project): EditorHandler { 46 | val editorType = EditorTargetUtils.getTargetEditor(project) 47 | return EditorHandlerFactory.getHandler(editorType, project) 48 | } 49 | 50 | /** 51 | * 检查编辑器路径是否存在 52 | * @return 如果路径有效,则返回true;否则返回false 53 | */ 54 | protected fun checkEditorPathExists(project: Project, handler: EditorHandler): Boolean { 55 | val settings = EditorJumperSettings.getInstance() 56 | // 获取目标编辑器类型,如果为空则使用默认设置s 57 | val targetEditor = EditorTargetUtils.getTargetEditor(project) 58 | val editorType = targetEditor.ifBlank { settings.selectedEditorType } 59 | val customPath = when (editorType) { 60 | "Visual Studio Code" -> settings.vscodePath 61 | "Cursor" -> settings.cursorPath 62 | "Trae" -> settings.traePath 63 | "Windsurf" -> settings.windsurfPath 64 | "Void" -> settings.voidPath 65 | "Kiro" -> settings.kiroPath 66 | "Qoder" -> settings.qoderPath 67 | "CatPawAI" -> settings.catPawAIPath 68 | "Antigravity" -> settings.antigravityPath 69 | else -> "" 70 | } 71 | 72 | // macOS: 不需要检查路径,所有编辑器都自动检测 73 | if (SystemInfo.isMac) { 74 | return true 75 | } 76 | 77 | // Windows: 只检查非 Cursor 编辑器的路径 78 | // Antigravity支持这种方式 79 | if (SystemInfo.isWindows && listOf("Cursor", "Qoder", "Antigravity").contains(editorType)) { 80 | return true 81 | } 82 | 83 | // 其他情况: 只检查用户自定义的路径是否为空 84 | if (customPath.isBlank()) { 85 | // 路径为空,提示用户配置 86 | val result = Messages.showYesNoDialog( 87 | project, 88 | I18nUtils.message("dialog.editorPathNotConfigured.message", editorType), 89 | I18nUtils.message("dialog.editorPathNotConfigured.title"), 90 | I18nUtils.message("dialog.editorPathNotConfigured.openSettings"), 91 | I18nUtils.message("dialog.editorPathNotConfigured.cancel"), 92 | Messages.getWarningIcon() 93 | ) 94 | 95 | if (result == Messages.YES) { 96 | // 打开设置对话框 97 | ShowSettingsUtil.getInstance().showSettingsDialog( 98 | project, 99 | EditorJumperSettingsConfigurable::class.java 100 | ) 101 | } 102 | return false 103 | } 104 | return true 105 | } 106 | 107 | /** 108 | * 更新动作的可见性 109 | */ 110 | override fun update(e: AnActionEvent) { 111 | val project = e.project 112 | 113 | // 只要有项目就启用该操作,不需要选择文件 114 | e.presentation.isEnabledAndVisible = project != null 115 | } 116 | 117 | protected open fun openInExternalEditor( 118 | project: Project, 119 | handler: EditorHandler, 120 | file: VirtualFile?, 121 | lineNumber: Int? = null, 122 | columnNumber: Int? = null 123 | ) { 124 | val projectPath = getProjectPath(project) ?: return 125 | val filePath = getFilePath(file) 126 | 127 | // 使用 getOpenCommand 方法 128 | val command = handler.getOpenCommand( 129 | projectPath, 130 | filePath, 131 | lineNumber, 132 | columnNumber 133 | ) 134 | 135 | try { 136 | ProcessBuilder(command.toList()) 137 | .redirectOutput(ProcessBuilder.Redirect.DISCARD) // 丢弃 stdout 138 | .redirectError(ProcessBuilder.Redirect.DISCARD) // 丢弃 stderr 139 | .start() 140 | .outputStream.close() // 明确告诉子进程我不会输入任何数据 141 | } catch (e: Exception) { 142 | Messages.showErrorDialog( 143 | project, 144 | "Failed to open editor. Error: ${e.message}", 145 | "Editor Launch Failed" 146 | ) 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Editor Jumper 2 | 3 |
4 | Editor Jumper Icon 5 |
6 | 7 |
8 | JetBrains Plugin 9 | License 10 | Chinese Doc 11 |
12 | 13 | ## 🔍 Introduction 14 | 15 | EditorJumper is a JetBrains IDE plugin that allows you to seamlessly jump between JetBrains IDE and other popular code editors (such as VS Code, Cursor, Trae, Windsurf, Void, Kiro, and Qoder). It maintains your cursor position and editing context, greatly improving development efficiency in multi-editor environments. 16 | 17 |
18 | Jump and Back Demo 19 |
20 |
21 | Configuration Panel 22 |
23 | ## 🌟 Features 24 | 25 | - 🚀 **Seamless Editor Switching** 26 | - Quickly jump between JetBrains IDE and VS Code, Cursor, Trae, Windsurf, Void, Kiro, Qoder 27 | - Automatically positions to the same cursor location (line and column) 28 | - Perfectly maintains editing context without interrupting workflow 29 | - Workspace file support - all editors now support workspace files (.code-workspace) for multi-root projects 30 | 31 | 32 | - ⚡ **Multiple Trigger Methods** 33 | - Right-click in editor - select "Open in [Editor Name]" (name updates based on selected editor) 34 | - Tools menu - select "Open in [Editor Name]" (name updates based on selected editor) 35 | - Standard mode jump- Alt+Shift+O / Option+Shift+O () 36 | - Ultra-fast jump(mac)- Alt+Shift+P / Option+Shift+P 37 | 38 | - 🎚️ **Easy Target Editor Selection** 39 | - Status bar widget - click the editor icon to select which editor to jump to 40 | 41 | - 🍎 **Mac Exclusive Features** 42 | - Trae CN (国内版) checkbox option for Chinese users 43 | 44 | 45 | ## 💻 System Requirements 46 | 47 | - Works with any JetBrains IDE (IntelliJ IDEA, WebStorm, PyCharm, etc.) 48 | - Requires IDE version 2023 or newer 49 | 50 | ## 📥 Installation 51 | 52 | 1. Open Settings/Preferences in JetBrains IDE 53 | 2. Navigate to Plugins > Marketplace 54 | 3. Search for "EditorJumper" 55 | 4. Click the Install button 56 | 5. Restart the IDE to activate the plugin 57 | 58 | ## ⚙️ Configuration 59 | 60 | 1. Open Settings/Preferences in the IDE 61 | 2. Navigate to Tools > EditorJumper Settings 62 | 3. Configure the executable paths for each editor: 63 | - VS Code path 64 | - Cursor path 65 | - Trae path (with optional CN version checkbox on Mac) 66 | - Windsurf path 67 | - Void path 68 | - Kiro path 69 | - Qoder path 70 | 4. Select the default editor 71 | 5. Click Apply to save settings 72 | 73 | > **Configuration Notes:** 74 | > - macOS: No additional configuration needed for any editor 75 | > - Special: Trae CN checkbox option available for Chinese users 76 | > - Windows: 77 | > - Cursor/Qoder: No configuration needed (uses system PATH) 78 | > - Other editors: Configure the .exe file path in Settings 79 | > 80 | > **Configuration Interface:** 81 | > - Default Editor: Select which editor to use when using keyboard shortcuts 82 | > - Editor Paths: 83 | > - macOS: All paths are auto-detected, no manual configuration needed 84 | > - Windows: 85 | > - Cursor/Qoder: Auto-detected through system PATH 86 | > - VS Code/Trae/Windsurf/Void/Kiro: Browse and select the .exe file location 87 | > - Example: `C:\Users\username\AppData\Local\Programs\VSCode\Code.exe` 88 | 89 | ## 🚀 Usage 90 | 91 | ### Via Keyboard Shortcut 92 | 93 | | Usage Scenario | Alt+Shift+O / Option+Shift+O | Alt+Shift+P / Option+Shift+P | 94 | |-----------------|------------------------------|---------------------------------------| 95 | | **On Project Folder** | Ultra-fast project opening | Ultra-fast project opening | 96 | | **On Specific File** | Automatically opens project + file | Faster on Mac (requires project opened first, otherwise opens single file), no difference on Windows | 97 | 98 | **Recommendations:** 99 | - **Windows users**: Use Alt+Shift+O (sufficient for all needs) 100 | - **Mac users**: Use Option+Shift+O, experienced users can use Option+Shift+P for faster experience 101 | 102 | ### Via Context Menu 103 | 104 | 1. Right-click in the editor 105 | 2. Select "Open in External Editor" 106 | 107 | ### Via Tools Menu 108 | 109 | 1. Click on Tools in the top menu bar 110 | 2. Select "Open in External Editor" 111 | 112 | ### Switch Target via Status Bar 113 | 114 | 1. Click on the jump icon in the IDE's bottom status bar 115 | 2. Select the editor you want to jump to from the dropdown menu 116 | ![Select Target Editor](image/SelectTargetEditor.png) 117 | 3. Use any of the trigger methods above (keyboard shortcut, right-click, or Tools menu) to perform the jump 118 | 119 | ## 🔄 Complementary Use 120 | 121 | > Recommended to use with [EditorJumper-V](https://github.com/wanniwa/EditorJumper-V) to quickly return to JetBrains IDE from Cursor, VS Code, Trae, Windsurf, Void, Kiro, Qoder 122 | 123 | ## 🤝 Contribution 124 | 125 | Pull Requests and Issues are welcome to help improve this plugin! 126 | 127 | ## 📄 License 128 | 129 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 130 | 131 | ## Star History 132 | 133 | [![Star History Chart](https://api.star-history.com/svg?repos=wanniwa/EditorJumper&type=Date)](https://www.star-history.com/#wanniwa/EditorJumper&Date) 134 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/settings/EditorJumperSettingsConfigurable.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.settings 2 | 3 | import com.intellij.openapi.options.Configurable 4 | import com.intellij.openapi.options.Configurable.WithEpDependencies 5 | import com.intellij.openapi.extensions.BaseExtensionPointName 6 | import com.intellij.openapi.project.ProjectManager 7 | import com.intellij.openapi.util.SystemInfo 8 | import com.github.wanniwa.editorjumper.utils.I18nUtils 9 | import javax.swing.JComponent 10 | import java.util.ArrayList 11 | 12 | class EditorJumperSettingsConfigurable : Configurable, WithEpDependencies { 13 | private var mySettingsComponent: EditorJumperSettingsComponent? = null 14 | 15 | override fun getDisplayName(): String { 16 | return I18nUtils.message("settings.displayName") 17 | } 18 | 19 | override fun getPreferredFocusedComponent(): JComponent { 20 | return mySettingsComponent!!.getPreferredFocusedComponent() 21 | } 22 | 23 | override fun createComponent(): JComponent { 24 | mySettingsComponent = EditorJumperSettingsComponent() 25 | return mySettingsComponent!!.getPanel() 26 | } 27 | 28 | override fun isModified(): Boolean { 29 | val settings = EditorJumperSettings.getInstance() 30 | val baseModified = mySettingsComponent!!.getVscodePath() != settings.vscodePath || 31 | mySettingsComponent!!.getCursorPath() != settings.cursorPath || 32 | mySettingsComponent!!.getTraePath() != settings.traePath || 33 | mySettingsComponent!!.getWindsurfPath() != settings.windsurfPath || 34 | mySettingsComponent!!.getVoidPath() != settings.voidPath || 35 | mySettingsComponent!!.getKiroPath() != settings.kiroPath || 36 | mySettingsComponent!!.getQoderPath() != settings.qoderPath || 37 | mySettingsComponent!!.getcatPawAIPath() != settings.catPawAIPath || 38 | mySettingsComponent!!.getAntigravityPath() != settings.antigravityPath || 39 | mySettingsComponent!!.getSelectedEditorType() != settings.selectedEditorType 40 | 41 | // 只在 Mac 平台上检查 Trae CN 设置 42 | val traeCNModified = if (SystemInfo.isMac) { 43 | mySettingsComponent!!.getTraeCN() != settings.traeCN 44 | } else { 45 | false 46 | } 47 | 48 | return baseModified || traeCNModified 49 | } 50 | 51 | override fun apply() { 52 | val settings = EditorJumperSettings.getInstance() 53 | val oldEditorType = settings.selectedEditorType 54 | val newEditorType = mySettingsComponent!!.getSelectedEditorType() 55 | 56 | settings.vscodePath = mySettingsComponent!!.getVscodePath() 57 | settings.cursorPath = mySettingsComponent!!.getCursorPath() 58 | settings.traePath = mySettingsComponent!!.getTraePath() 59 | settings.windsurfPath = mySettingsComponent!!.getWindsurfPath() 60 | settings.voidPath = mySettingsComponent!!.getVoidPath() 61 | settings.kiroPath = mySettingsComponent!!.getKiroPath() 62 | settings.qoderPath = mySettingsComponent!!.getQoderPath() 63 | settings.catPawAIPath = mySettingsComponent!!.getcatPawAIPath() 64 | settings.antigravityPath = mySettingsComponent!!.getAntigravityPath() 65 | settings.selectedEditorType = newEditorType 66 | 67 | // 只在 Mac 平台上设置 Trae CN 68 | if (SystemInfo.isMac) { 69 | settings.traeCN = mySettingsComponent!!.getTraeCN() 70 | } 71 | 72 | // 如果默认编辑器类型改变了,询问是否同时更新当前项目的编辑器类型设置 73 | if (oldEditorType != newEditorType) { 74 | askToUpdateCurrentProject(newEditorType) 75 | } 76 | } 77 | 78 | /** 79 | * 询问是否更新当前项目的编辑器类型设置 80 | */ 81 | private fun askToUpdateCurrentProject(newEditorType: String) { 82 | // 获取当前打开的项目(非默认项目) 83 | val openProjects = com.intellij.openapi.project.ProjectManager.getInstance().openProjects 84 | val currentProject = openProjects.firstOrNull { !it.isDefault } 85 | 86 | currentProject?.let { project -> 87 | val result = com.intellij.openapi.ui.Messages.showYesNoDialog( 88 | project, 89 | I18nUtils.message("dialog.updateCurrentProject.message", newEditorType, newEditorType), 90 | I18nUtils.message("dialog.updateCurrentProject.title"), 91 | I18nUtils.message("dialog.updateCurrentProject.yes"), 92 | I18nUtils.message("dialog.updateCurrentProject.no"), 93 | com.intellij.openapi.ui.Messages.getQuestionIcon() 94 | ) 95 | 96 | if (result == com.intellij.openapi.ui.Messages.YES) { 97 | val projectSettings = EditorJumperProjectSettings.getInstance(project) 98 | projectSettings.projectEditorType = newEditorType 99 | 100 | // 更新状态栏显示 101 | com.intellij.openapi.wm.WindowManager.getInstance().getStatusBar(project)?.updateWidget("EditorJumperWidget") 102 | } 103 | } 104 | } 105 | 106 | override fun reset() { 107 | val settings = EditorJumperSettings.getInstance() 108 | mySettingsComponent!!.setVscodePath(settings.vscodePath) 109 | mySettingsComponent!!.setCursorPath(settings.cursorPath) 110 | mySettingsComponent!!.setTraePath(settings.traePath) 111 | mySettingsComponent!!.setWindsurfPath(settings.windsurfPath) 112 | mySettingsComponent!!.setVoidPath(settings.voidPath) 113 | mySettingsComponent!!.setKiroPath(settings.kiroPath) 114 | mySettingsComponent!!.setQoderPath(settings.qoderPath) 115 | mySettingsComponent!!.setcatPawAIPath(settings.catPawAIPath) 116 | mySettingsComponent!!.setAntigravityPath(settings.antigravityPath) 117 | mySettingsComponent!!.setSelectedEditorType(settings.selectedEditorType) 118 | 119 | // 只在 Mac 平台上设置 Trae CN 120 | if (SystemInfo.isMac) { 121 | mySettingsComponent!!.setTraeCN(settings.traeCN) 122 | } 123 | } 124 | 125 | override fun disposeUIResources() { 126 | mySettingsComponent = null 127 | } 128 | 129 | override fun getDependencies(): Collection> { 130 | return ArrayList() 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/kotlin/com/github/wanniwa/editorjumper/settings/EditorJumperSettingsComponent.kt: -------------------------------------------------------------------------------- 1 | package com.github.wanniwa.editorjumper.settings 2 | 3 | import com.intellij.openapi.fileChooser.FileChooserDescriptor 4 | import com.intellij.openapi.ui.TextBrowseFolderListener 5 | import com.intellij.openapi.ui.TextFieldWithBrowseButton 6 | import com.intellij.openapi.ui.ComboBox 7 | import com.intellij.ui.components.JBLabel 8 | import com.intellij.ui.components.JBCheckBox 9 | import com.intellij.util.ui.FormBuilder 10 | import com.github.wanniwa.editorjumper.utils.I18nUtils 11 | import com.intellij.openapi.util.SystemInfo 12 | import javax.swing.JComponent 13 | import javax.swing.JPanel 14 | import javax.swing.DefaultComboBoxModel 15 | 16 | class EditorJumperSettingsComponent { 17 | private val myMainPanel: JPanel 18 | private val editorTypeComboBox = ComboBox() 19 | private val vscodePathField = TextFieldWithBrowseButton() 20 | private val cursorPathField = TextFieldWithBrowseButton() 21 | private val traePathField = TextFieldWithBrowseButton() 22 | private val traeCNCheckBox = JBCheckBox() 23 | private val voidPathField = TextFieldWithBrowseButton() 24 | private val windsurfPathField = TextFieldWithBrowseButton() 25 | private val kiroPathField = TextFieldWithBrowseButton() 26 | private val qoderPathField = TextFieldWithBrowseButton() 27 | private val catPawAIPathField = TextFieldWithBrowseButton() 28 | private val antigravityPathField = TextFieldWithBrowseButton() 29 | 30 | init { 31 | // 为每个编辑器创建单独的描述符 32 | val vscodeDescriptor = FileChooserDescriptor(true, false, false, false, false, false) 33 | vscodeDescriptor.title = I18nUtils.getFileChooserTitle("Visual Studio Code") 34 | 35 | val cursorDescriptor = FileChooserDescriptor(true, false, false, false, false, false) 36 | cursorDescriptor.title = I18nUtils.getFileChooserTitle("Cursor") 37 | 38 | val traeDescriptor = FileChooserDescriptor(true, false, false, false, false, false) 39 | traeDescriptor.title = I18nUtils.getFileChooserTitle("Trae") 40 | 41 | val windsurfDescriptor = FileChooserDescriptor(true, false, false, false, false, false) 42 | windsurfDescriptor.title = I18nUtils.getFileChooserTitle("Windsurf") 43 | 44 | val voidDescriptor = FileChooserDescriptor(true, false, false, false, false, false) 45 | voidDescriptor.title = I18nUtils.getFileChooserTitle("Void") 46 | 47 | val kiroDescriptor = FileChooserDescriptor(true, false, false, false, false, false) 48 | kiroDescriptor.title = I18nUtils.getFileChooserTitle("Kiro") 49 | 50 | val qoderDescriptor = FileChooserDescriptor(true, false, false, false, false, false) 51 | qoderDescriptor.title = I18nUtils.getFileChooserTitle("Qoder") 52 | 53 | val catPawAIDescriptor = FileChooserDescriptor(true, false, false, false, false, false) 54 | catPawAIDescriptor.title = I18nUtils.getFileChooserTitle("CatPawAI") 55 | 56 | val antigravityDescriptor = FileChooserDescriptor(true, false, false, false, false, false) 57 | antigravityDescriptor.title = I18nUtils.getFileChooserTitle("Antigravity") 58 | 59 | vscodePathField.addBrowseFolderListener(TextBrowseFolderListener(vscodeDescriptor)) 60 | cursorPathField.addBrowseFolderListener(TextBrowseFolderListener(cursorDescriptor)) 61 | traePathField.addBrowseFolderListener(TextBrowseFolderListener(traeDescriptor)) 62 | windsurfPathField.addBrowseFolderListener(TextBrowseFolderListener(windsurfDescriptor)) 63 | voidPathField.addBrowseFolderListener(TextBrowseFolderListener(voidDescriptor)) 64 | kiroPathField.addBrowseFolderListener(TextBrowseFolderListener(kiroDescriptor)) 65 | qoderPathField.addBrowseFolderListener(TextBrowseFolderListener(qoderDescriptor)) 66 | catPawAIPathField.addBrowseFolderListener(TextBrowseFolderListener(catPawAIDescriptor)) 67 | antigravityPathField.addBrowseFolderListener(TextBrowseFolderListener(antigravityDescriptor)) 68 | 69 | // 添加编辑器类型选项 70 | val editorTypes = arrayOf("Visual Studio Code", "Cursor", "Trae", "Windsurf", "Void", "Kiro", "Qoder", "CatPawAI", "Antigravity") 71 | editorTypeComboBox.model = DefaultComboBoxModel(editorTypes) 72 | 73 | val macHintLabel = JBLabel("${I18nUtils.message("settings.hint.macOS")}") 74 | val windowsHintLabel = JBLabel("${I18nUtils.message("settings.hint.windows")}") 75 | val exampleLabel = JBLabel("${I18nUtils.message("settings.hint.example")}") 76 | val defaultEditorHintLabel = JBLabel("${I18nUtils.message("settings.hint.defaultEditor")}") 77 | 78 | val formBuilder = FormBuilder.createFormBuilder() 79 | .addComponent(macHintLabel) 80 | .addComponent(windowsHintLabel) 81 | .addComponent(exampleLabel) 82 | .addSeparator() 83 | .addComponent(defaultEditorHintLabel) 84 | .addLabeledComponent(JBLabel(I18nUtils.message("settings.defaultEditor.label")), editorTypeComboBox, 1, false) 85 | .addSeparator() 86 | .addLabeledComponent(JBLabel("Visual Studio Code"), vscodePathField, 1, false) 87 | .addLabeledComponent(JBLabel("Cursor"), cursorPathField, 1, false) 88 | .addLabeledComponent(JBLabel("Trae"), traePathField, 1, false) 89 | 90 | // 只在 Mac 平台上显示 Trae CN 选项 91 | if (SystemInfo.isMac) { 92 | formBuilder.addLabeledComponent(JBLabel(I18nUtils.message("settings.traeCN.label")), traeCNCheckBox, 1, false) 93 | } 94 | 95 | myMainPanel = formBuilder 96 | .addLabeledComponent(JBLabel("Windsurf"), windsurfPathField, 1, false) 97 | .addLabeledComponent(JBLabel("Void"), voidPathField, 1, false) 98 | .addLabeledComponent(JBLabel("Kiro"), kiroPathField, 1, false) 99 | .addLabeledComponent(JBLabel("Qoder"), qoderPathField, 1, false) 100 | .addLabeledComponent(JBLabel("CatPawAI"), catPawAIPathField, 1, false) 101 | .addLabeledComponent(JBLabel("Antigravity"), antigravityPathField, 1, false) 102 | .addComponentFillVertically(JPanel(), 0) 103 | .panel 104 | } 105 | 106 | fun getPanel(): JPanel { 107 | return myMainPanel 108 | } 109 | 110 | fun getPreferredFocusedComponent(): JComponent { 111 | return editorTypeComboBox 112 | } 113 | 114 | fun getVscodePath(): String { 115 | return vscodePathField.text 116 | } 117 | 118 | fun setVscodePath(path: String) { 119 | vscodePathField.text = path 120 | } 121 | 122 | fun getCursorPath(): String { 123 | return cursorPathField.text 124 | } 125 | 126 | fun setCursorPath(path: String) { 127 | cursorPathField.text = path 128 | } 129 | 130 | fun getTraePath(): String { 131 | return traePathField.text 132 | } 133 | 134 | fun setTraePath(path: String) { 135 | traePathField.text = path 136 | } 137 | 138 | fun getWindsurfPath(): String { 139 | return windsurfPathField.text 140 | } 141 | 142 | fun setWindsurfPath(path: String) { 143 | windsurfPathField.text = path 144 | } 145 | 146 | fun getVoidPath(): String { 147 | return voidPathField.text 148 | } 149 | 150 | fun setVoidPath(path: String) { 151 | voidPathField.text = path 152 | } 153 | 154 | fun getKiroPath(): String { 155 | return kiroPathField.text 156 | } 157 | 158 | fun setKiroPath(path: String) { 159 | kiroPathField.text = path 160 | } 161 | 162 | fun getQoderPath(): String { 163 | return qoderPathField.text 164 | } 165 | 166 | fun setQoderPath(path: String) { 167 | qoderPathField.text = path 168 | } 169 | 170 | fun getcatPawAIPath(): String { 171 | return catPawAIPathField.text 172 | } 173 | 174 | fun setcatPawAIPath(path: String) { 175 | catPawAIPathField.text = path 176 | } 177 | 178 | fun getAntigravityPath(): String { 179 | return antigravityPathField.text 180 | } 181 | 182 | fun setAntigravityPath(path: String) { 183 | antigravityPathField.text = path 184 | } 185 | 186 | fun getSelectedEditorType(): String { 187 | return editorTypeComboBox.selectedItem as String 188 | } 189 | 190 | fun setSelectedEditorType(editorType: String) { 191 | editorTypeComboBox.selectedItem = editorType 192 | } 193 | 194 | fun getTraeCN(): Boolean { 195 | return if (SystemInfo.isMac) traeCNCheckBox.isSelected else false 196 | } 197 | 198 | fun setTraeCN(selected: Boolean) { 199 | if (SystemInfo.isMac) { 200 | traeCNCheckBox.isSelected = selected 201 | } 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.github.wanniwa.EditorJumper 5 | 6 | 8 | EditorJumper 9 | 10 | 11 | wanniwa 12 | 13 | 16 | 1.6.0 18 |

EditorJumper

19 |

EditorJumper is a plugin that enables seamless switching between different editors, automatically opening the same project and file while preserving cursor position. This allows for instant context-aware transitions between editors:

20 |
    21 |
  • Jump from JetBrains IDE to VSCode
  • 22 |
  • Jump from JetBrains IDE to Trae
  • 23 |
  • Jump from JetBrains IDE to Cursor
  • 24 |
  • Jump from JetBrains IDE to Windsurf
  • 25 |
  • Jump from JetBrains IDE to Void
  • 26 |
  • Jump from JetBrains IDE to Kiro
  • 27 |
  • Jump from JetBrains IDE to Qoder
  • 28 |
29 |

Smart Jump Behavior:

30 |
    31 |
  • With file open: Opens the same project and file, preserving cursor position
  • 32 |
  • Without file open: Opens the project directly in target editor
  • 33 |
  • Supports workspace files (.code-workspace) for multi-root projects
  • 34 |
35 |

Multiple ways to trigger jump:

36 |
    37 |
  • Right-click in editor - select "Open in [Editor Name]" (name updates based on selected editor)
  • 38 |
  • Tools menu - select "Open in [Editor Name]" (name updates based on selected editor)
  • 39 |
  • Keyboard shortcut - use Alt+Shift+O / Option+Shift+O (macOS)
  • 40 |
  • Ultra-fast jump - use Alt+Shift+P / Option+Shift+P (all platforms supported)
  • 41 |
42 |

Keyboard Shortcuts Comparison:

43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
Usage ScenarioAlt+Shift+O / Option+Shift+OAlt+Shift+P / Option+Shift+P
On Project FolderUltra-fast project openingUltra-fast project opening
On Specific FileAutomatically opens project + fileFaster on Mac (requires project opened first, otherwise opens single file), no difference on Windows
60 | 61 |

Recommendations:

62 |
    63 |
  • Windows users: Use Alt+Shift+O (sufficient for all needs)
  • 64 |
  • Mac users: Use Option+Shift+O, experienced users can use Option+Shift+P for faster experience
  • 65 |
66 |

Easy target editor selection:

67 |
    68 |
  • Status bar widget - click the jump icon to select which editor to jump to
  • 69 |
70 |

Configuration Notes:

71 |
    72 |
  • macOS: No additional configuration needed for any editor
  • 73 |
  • Windows: 74 |
      75 |
    • Cursor/Qoder: No configuration needed (uses system PATH)
    • 76 |
    • Other editors: Configure the .exe file path in Settings
    • 77 |
    78 |
  • 79 |
80 |

Configuration Interface:

81 |
    82 |
  • Default Editor: Select which editor to use when using keyboard shortcuts
  • 83 |
  • Editor Paths: 84 |
      85 |
    • macOS: All paths are auto-detected, no manual configuration needed
    • 86 |
    • Windows: 87 |
        88 |
      • Cursor/Qoder: Auto-detected through system PATH
      • 89 |
      • VS Code/Trae/Windsurf/Void/Kiro: Browse and select the .exe file location
      • 90 |
      • Example: C:\Users\username\AppData\Local\Programs\VSCode\Code.exe
      • 91 |
      92 |
    • 93 |
    94 |
  • 95 |
  • Project Settings: 96 |
      97 |
    • VSCode Workspace: Configure project-specific VSCode workspace file (.code-workspace)
    • 98 |
    99 |
  • 100 |
101 |

System Requirements:

102 |
    103 |
  • Works with any JetBrains IDE (IntelliJ IDEA, WebStorm, PyCharm, etc.)
  • 104 |
  • Requires IDE version 2023 or newer
  • 105 |
106 |

Recommended Plugin:

107 |
    108 |
  • Recommended to use with EditorJumper-V for quick return to JetBrains IDE from other editors
  • 109 |
110 | 111 |
112 | 113 |

中文说明

114 |

EditorJumper 是一个能够在不同编辑器之间无缝切换的插件,自动打开相同的项目和文件,同时保持光标位置。这使得在编辑器之间的上下文感知转换变得即时且高效:

115 |
    116 |
  • 从 JetBrains IDE 跳转到 VSCode
  • 117 |
  • 从 JetBrains IDE 跳转到 Trae
  • 118 |
  • 从 JetBrains IDE 跳转到 Cursor
  • 119 |
  • 从 JetBrains IDE 跳转到 Windsurf
  • 120 |
  • 从 JetBrains IDE 跳转到 Void
  • 121 |
  • 从 JetBrains IDE 跳转到 AWS Kiro
  • 122 |
  • 从 JetBrains IDE 跳转到 Qoder
  • 123 |
124 |

智能跳转行为:

125 |
    126 |
  • 有文件打开时:打开相同的项目和文件,保持光标位置
  • 127 |
  • 无文件打开时:直接在目标编辑器中打开项目
  • 128 |
  • VSCode:支持多根项目的工作空间文件 (.code-workspace)
  • 129 |
130 |

多种触发跳转方式:

131 |
    132 |
  • 在编辑器中右击 - 选择"在 [编辑器名称] 中打开"(名称会根据选择的编辑器更新)
  • 133 |
  • 工具菜单 - 选择"在 [编辑器名称] 中打开"(名称会根据选择的编辑器更新)
  • 134 |
  • 快捷键 - 使用 Alt+Shift+O / Option+Shift+O
  • 135 |
  • 极速跳转 - 使用 Alt+Shift+P / Option+Shift+P
  • 136 |
137 |

快捷键对比说明:

138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 |
使用场景Alt+Shift+O / Option+Shift+OAlt+Shift+P / Option+Shift+P
在项目文件夹上极速打开项目极速打开项目
在具体文件上自动打开项目+文件mac更快(但需要先打开项目,否则只会打开单个文件),windows无差别
155 | 156 |

使用建议:

157 |
    158 |
  • Windows 用户:使用 Alt+Shift+O 即可(满足所有需求)
  • 159 |
  • Mac 用户:使用 Option+Shift+O,熟悉之后想要更快速的体验用 Option+Shift+P
  • 160 |
161 |

便捷的目标编辑器选择:

162 |
    163 |
  • 状态栏小部件 - 点击跳转图标选择要跳转到哪个编辑器
  • 164 |
165 |

配置说明:

166 |
    167 |
  • macOS:所有编辑器无需额外配置
  • 168 |
  • Windows: 169 |
      170 |
    • Cursor/Qoder:无需配置(使用系统 PATH)
    • 171 |
    • 其他编辑器:需在设置中配置 .exe 文件路径
    • 172 |
    173 |
  • 174 |
175 |

系统要求:

176 |
    177 |
  • 适用于任何 JetBrains IDE(IntelliJ IDEA、WebStorm、PyCharm 等)
  • 178 |
  • 需要 IDE 版本 2023 或更新版本
  • 179 |
180 |

推荐插件:

181 |
    182 |
  • 建议与 EditorJumper-V 配合使用,以便从其他编辑器快速返回 JetBrains IDE
  • 183 |
184 | ]]> 185 |
186 | 187 | 189 | com.intellij.modules.platform 190 | 191 | 193 | 194 | 195 | 199 | 203 | 206 | 207 | 208 | 209 | 212 | 213 | 214 | 215 | 216 | 218 | 219 | 220 | 221 | 222 | 1.6.0 224 |
    225 |
  • support CatPawAI、Antigravity
  • 226 |
227 |

1.5.0

228 |
    229 |
  • Added Alt+Shift+P / Option+Shift+P shortcut - faster jumping on Mac, same as Alt+Shift+O on Windows
  • 230 |
  • Mac exclusive: New Trae CN checkbox option
  • 231 |
  • All editors now support workspace files (.code-workspace)
  • 232 |
233 |
    234 |
  • 新增 Alt+Shift+P / Option+Shift+P 快捷键 - Mac 上更快速跳转,Windows 上与 Alt+Shift+O 相同
  • 235 |
  • Mac:新增 Trae CN 勾选选项
  • 236 |
  • 所有编辑器现支持工作空间文件 (.code-workspace)
  • 237 |
238 |

1.4.1

239 |
    240 |
  • Fixed Empty menu item text for EditorJumperSettingsAction
  • 241 |
242 |

1.4.0

243 |
    244 |
  • Added support for Qoder IDE - seamlessly jump from JetBrains IDE to Qoder
  • 245 |
  • Qoder integration supports the same features as other editors: workspace files, cursor position preservation, and cross-platform compatibility
  • 246 |
  • macOS/Windows: Auto-detected Qoder installation path, no manual configuration needed
  • 247 |
  • Added internationalization (i18n) support - automatically switches between Chinese and English based on IDEA language settings
  • 248 |
  • UI elements including dialogs, settings, status bar, and menus now support multiple languages
  • 249 |
250 |
    251 |
  • 新增对 Qoder IDE 的支持 - 可从 JetBrains IDE 无缝跳转到 Qoder
  • 252 |
  • Qoder 集成支持与其他编辑器相同的功能:工作空间文件、光标位置保持和跨平台兼容性
  • 253 |
  • macOS/Windows:自动检测 Qoder 安装路径,无需手动配置
  • 254 |
  • 新增国际化(i18n)支持 - 根据 IDEA 语言设置自动在中文和英文之间切换
  • 255 |
  • 包括对话框、设置、状态栏和菜单在内的 UI 元素现在都支持多语言
  • 256 |
257 |

1.3.2

258 |
    259 |
  • Fixed EPIPE: broken pipe error on Windows when jumping to Trae or other IDEs.
  • 260 |
  • 修复 Windows 平台,跳转 Trae或者其他IDE 出现的EPIPE :broken pipe 报错的问题
  • 261 |
262 |

1.3.1

263 |
    264 |
  • Fix the file path check to use project-level editor settings and support Kiro IDE.
  • 265 |
  • 修复文件路径检测使用项目级的目标IDE做检查。支持Kiro IDE。
  • 266 |
267 |

1.3.0

268 |
    269 |
  • Support for Kiro IDE
  • 270 |
  • UI: The workspace file path label is now "Workspace File Path" instead of "VSCode Workspace File Path".
  • 271 |
  • Enhanced: When changing default editor type in global settings, option to update current project's jump target.
  • 272 |
273 |
    274 |
  • 支持 Kiro IDE
  • 275 |
  • 设置界面 "VSCode Workspace File Path" 统一为 "Workspace File Path"。
  • 276 |
  • 增强:在全局设置中修改默认编辑器类型时,可选择同时更新当前项目的跳转目标。
  • 277 |
278 | 279 |

1.2.0

280 |
    281 |
  • Support custom workspace file (.code-workspace) for Cursor, Trae, Windsurf, and Void editors. The plugin will prioritize the configured workspace file if it exists.
  • 282 |
  • UI: The workspace file path label is now "Workspace File Path" instead of "VSCode Workspace File Path".
  • 283 |
  • Compatibility: Updated to support (build 252.*).
  • 284 |
285 | 286 |

1.1.1

287 |
    288 |
  • Support Void https://github.com/voideditor/void
  • 289 |
290 |

1.1.0

291 |
    292 |
  • Support for project-level target editor configuration
  • 293 |
  • Add "Settings" option to supported editors and show settings dialog
  • 294 |
295 | 296 |

1.0.5

297 |
    298 |
  • Add VSCode workspace file support in settings and UI
  • 299 |
300 | ]]>
301 | bundle 302 | 303 |
--------------------------------------------------------------------------------