├── .gitignore ├── LICENSE ├── README.en.md ├── README.md ├── assets ├── assets.go ├── ico │ ├── clion.ico │ ├── datagrip.ico │ ├── dataspell.ico │ ├── fleet.ico │ ├── goland.ico │ ├── idea.ico │ ├── phpstorm.ico │ ├── pycharm.ico │ ├── rider.ico │ ├── rubymine.ico │ ├── rustrover.ico │ ├── studio.ico │ ├── toolbox.ico │ ├── webstorm.ico │ └── writerside.ico └── template │ ├── toolboxAdd.reg.tmp │ └── toolboxRemove.reg.tmp ├── cmd └── tbm │ ├── add.go │ ├── clear.go │ ├── list.go │ ├── main.go │ ├── rm.go │ ├── set.go │ └── version.go ├── go.mod ├── go.sum ├── image ├── preview.png └── shellpath.png ├── makefile └── toolbox ├── helper.go └── toolbox.go /.gitignore: -------------------------------------------------------------------------------- 1 | ### Go template 2 | # If you prefer the allow list template instead of the deny list, see community template: 3 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 4 | # 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | 12 | # Test binary, built with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | # vendor/ 20 | 21 | # Go workspace file 22 | go.work 23 | 24 | # jetbrain 25 | /.idea 26 | /build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 寒江蓑笠翁 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # AutoToolBox 2 | 3 | **English**|[简体中文](README.md) 4 | 5 | > If you are using the old version, please go to [v2.2.0 · 246859/AutoToolBox (github.com)](https://github.com/246859/AutoToolBox/tree/v2.2.0) to view information, or [update to the new version](#Upgrade) 6 | 7 | Update: After six years, JetBrains finally started to try to solve the problem of context menu, but the menu item is hidden in the open method, which only works for files, not for directories and directory backgrounds. This is obviously just a very simple function, but it has not been supported for a long time, so this project still needs to exist. 8 | 9 | Original issue link: [TBX-2540 (jetbrains.com)](https://youtrack.jetbrains.com/issue/TBX-2540/Associate-file-extenstions-with-correct-Toolbox-app-or-with-the-Toolbox-itself-so-that-files-can-be-launched-from-Windows) [ TBX-2478 (jetbrains.com)](https://youtrack.jetbrains.com/issue/TBX-2478/Windows-Open-Directory-With-Editor) 10 | 11 | ## Introduction 12 | 13 | This is a very simple command line tool for adding a windows right-click menu to the Toolbox App. It has the following features: 14 | 15 | - Updating or rolling back the version will not cause the menu to become invalid (when there are multiple versions of the IDE at the same time, only the latest version will be directed) 16 | - You can set the IDE to be opened with administrator privileges 17 | - No need to manually maintain the registry, 18 | - The display order of the menu is synchronized with that in the Toolbox 19 | - Easy to delete 20 | 21 | Here is the effect diagram 22 | 23 | Effect diagram 24 | 25 | ## Installation 26 | 27 | If you have a go environment and the version is greater than go1.16, you can use the `go install` method to install it, as shown below 28 | 29 | ```bash 30 | $ go install github.com/246859/AutoToolBox/v3/cmd/tbm@latest 31 | ``` 32 | 33 | Or download the latest binary file directly in Release. 34 | 35 | ## Use 36 | 37 | Version 3.0 is much easier to use. Although there are a few more commands, they are not used in most cases. The only required path parameter is the installation path of the Toolbox. Generally, the Toolbox is installed in the following path by default. 38 | 39 | ``` 40 | $HOME/AppData/Local/Jetbrains/Toolbox/ 41 | ``` 42 | 43 | The tool uses the above path by default and does not require additional parameters. If the installation path is modified, you need to use `-d` to specify it (it is best not to modify the installation path of the Toolbox). 44 | 45 | Please make sure that **Generate Shell Script** in the settings is turned on, otherwise the tool will not work properly. 46 | 47 | shellpath 48 | 49 | 50 | ### Upgrade 51 | If you are an old tool user and want to upgrade to a new version, you can use the old generated 'toolboxRemove.reg' to remove the old registry, and then use the new version as follows. 52 | 53 | ### Start 54 | 55 | > **The tool requires administrator privileges to run properly** 56 | 57 | After installation, execute the following command 58 | 59 | ```bash 60 | $ tbm set -a 61 | ``` 62 | 63 | You can add all locally installed IDEs to the right-click menu. This is the simplest way to use it. In most cases, only this command will be used. 64 | 65 | ### Commands 66 | 67 | ``` 68 | add Add ToolBox IDE to existing context menu 69 | clear clear all the context menu of Toolbox 70 | list List installed ToolBox IDEs 71 | remove Remove ToolBox IDEs from context menu 72 | set Register ToolBox IDEs to context menu 73 | version Print ToolBox version 74 | ``` 75 | 76 | The following is a brief description of the general function of each command 77 | 78 | #### list 79 | 80 | ```bash 81 | $ tbm list -h 82 | Examples: 83 | tbm list -c 84 | tbm list --menu 85 | tbm list -c --menu 86 | 87 | Usage: 88 | tbm list [flags] 89 | 90 | Flags: 91 | -c, --count count the number of installed tools 92 | -h, --help help for list 93 | --menu list the tools shown in the context menu 94 | ``` 95 | 96 | `list` command is used to view all locally installed IDEs, for example 97 | 98 | ```bash 99 | $ tbm list 100 | Android Studio Koala 2024.1.1 Patch 1 101 | Aqua 2024.1.2 102 | CLion 2024.1.4 103 | DataGrip 2024.1.4 104 | GoLand 2024.1.4 105 | GoLand 2023.3.7 106 | IntelliJ IDEA Community Edition 2024.1.4 107 | IntelliJ IDEA Ultimate 2024.1.4 108 | MPS 2023.3.1 109 | PhpStorm 2024.1.4 110 | PyCharm Community 2024.1.4 111 | PyCharm Professional 2024.1.4 112 | ``` 113 | 114 | Check the number 115 | 116 | ```bash 117 | $ tbm list -c 118 | 25 119 | ``` 120 | 121 | Check all items added to the menu 122 | 123 | ```bash 124 | $ tbm list --menu 125 | Aqua 2024.1.2 126 | CLion 2024.1.4 127 | DataGrip 2024.1.4 128 | DataSpell 2024.1.3 129 | Fleet 1.37.84 Public Preview 130 | GoLand 2024.1.4 131 | IntelliJ IDEA Ultimate 2024.1.4 132 | MPS 2023.3.1 133 | PhpStorm 2024.1.4 134 | PyCharm Professional 2024.1.4 135 | Rider 2024.1.4 136 | RubyMine 2024.1.4 137 | ``` 138 | 139 | View the number of items added to the menu 140 | 141 | ```bash 142 | $ tbm list --menu -c 143 | 16 144 | ``` 145 | 146 | 147 | 148 | #### set 149 | 150 | ```bash 151 | $ tbm set -h 152 | Usage: 153 | tbm set [flags] 154 | 155 | Flags: 156 | --admin run as admin 157 | -a, --all select all 158 | -h, --help help for set 159 | -s, --silence silence output 160 | --top place toolbox menu at top of context menu 161 | -u, --update only select current menu items 162 | ``` 163 | 164 | The `set` command indicates which IDEs are set as menu items. It will directly overwrite the existing menus. The display order of the menus is the same as in the Toolbox interface. 165 | 166 | The simplest way to use it is to directly set all IDEs as menu items. If the number of local IDEs exceeds 16, only the first 16 will be added. This is because the maximum limit of Windows menu items is 16. 167 | 168 | ```bash 169 | $ tbm set -a 170 | Warning: too many tools, only first 16 will be added to the context menu 171 | GoLand 172 | IntelliJ IDEA Ultimate 173 | PyCharm Professional 174 | WebStorm 175 | RustRover 176 | Aqua 177 | Writerside 178 | Fleet 179 | DataSpell 180 | CLion 181 | PhpStorm 182 | DataGrip 183 | Rider 184 | RubyMine 185 | Space Desktop 186 | MPS 187 | ``` 188 | 189 | Or specify separately 190 | 191 | ```bash 192 | $ tbm set GoLand WebStorm 193 | ``` 194 | 195 | If you need to run the IDE with administrator privileges, you can add `--admin`, as shown below, 196 | 197 | ```bash 198 | $ tbm set -a --admin 199 | ``` 200 | 201 | Using `--update` will only update existing menu items, not add new ones. If there are multiple versions of the same IDE, this command can guide it to the latest version. 202 | 203 | ```bash 204 | $ tbm set --update 205 | ``` 206 | 207 | When registering the menu, you can use `--top` to make the Toolbox menu at the top position 208 | 209 | ```bash 210 | $ tbm set -a --admin --top 211 | ``` 212 | 213 |
214 | 215 | It should be noted that some products do not provide a stable shell script path or the location of the `exe` file. The following are some of them 216 | 217 | ``` 218 | dotMemory Portable 219 | dotPeek Portable 220 | dotTrace Portable 221 | ReSharper Tools 222 | ``` 223 | 224 | Although they can be added to the menu at this stage, their file structure is not as organized as other IDEs. The `list` command will show which tools are not supported yet, as follows 225 | 226 | ```bash 227 | $ tbm list 228 | Android Studio Koala 2024.1.1 Patch 1 229 | Aqua 2024.1.2 230 | CLion 2024.1.4 231 | DataGrip 2024.1.4 232 | DataSpell 2024.1.3 233 | dotMemory Portable 2024.1.4 unavailable 234 | dotPeek Portable 2024.1.4 unavailable 235 | dotTrace Portable 2024.1.4 unavailable 236 | Fleet 1.37.84 Public Preview 237 | ``` 238 | 239 | For them, they will not be added to the menu for the time being. 240 | 241 | #### add 242 | 243 | ```bash 244 | $ tbm add -h 245 | Usage: 246 | tbm add [flags] 247 | 248 | Flags: 249 | --admin run as admin 250 | -h, --help help for add 251 | -s, --silence silence output 252 | --top place toolbox menu at top of context menu 253 | ``` 254 | 255 | The difference between the `add` command is that it will add new menu items to the existing menu instead of directly overwriting like `set`, and the usage is generally the same. 256 | 257 | ```bash 258 | $ tbm add GoLand WebStorm 259 | ``` 260 | 261 | However, it does not support `-a`, so all IDEs cannot be added at once. 262 | 263 | #### rmove 264 | 265 | ```bash 266 | $ tbm remove -h 267 | Command "remove" will remove the specified IDEs from the context menu, use "tbm remove -a" to remove all IDEs. 268 | 269 | Usage: 270 | tbm remove [flags] 271 | 272 | Aliases: 273 | remove, rm 274 | 275 | Flags: 276 | -a, --all remove all 277 | -h, --help help for remove 278 | -s, --silence silence output 279 | ``` 280 | 281 | The `remove` command is used to remove menu items 282 | 283 | ```bash 284 | $ tbm rm GoLand WebStorm 285 | ``` 286 | 287 | Use `-a` to remove all 288 | 289 | ```bash 290 | $ tbm rm -a 291 | ``` 292 | 293 | 294 | 295 | #### clear 296 | 297 | ```bash 298 | $ tbm clear 299 | clear all the context menu of Toolbox 300 | 301 | Usage: 302 | tbm clear [flags] 303 | 304 | Flags: 305 | -h, --help help for clear 306 | ``` 307 | 308 | The command `clear` will directly clear all menu items related to Toolbox, including the top-level menu, and will not produce any output. If you do not want to use this tool anymore, you can use this command to clear all registry entries. 309 | 310 | 311 | 312 | ## Contribution 313 | 314 | 1. Fork this repository to your account 315 | 2. Create a new branch in the forked repository 316 | 3. Submit code changes in the new branch 317 | 4. Then initiate a Pull Request to this repository 318 | 5. Waiting for Pull Request 319 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AutoToolBox 2 | 3 | [English](README.en.md)|**简体中文** 4 | 5 | > 如果你是旧版使用者,请前往[v2.2.0 · 246859/AutoToolBox (github.com)](https://github.com/246859/AutoToolBox/tree/v2.2.0)查看信息,或者查看[迁移到新版](#迁移) 6 | 7 | 更新:时隔六年JetBrains终于开始尝试解决上下文菜单的问题,但菜单项是隐藏在打开方式里面的,仅对文件起作用,对于目录和目录背景并不生效,这明明只是一个很简单的功能,却迟迟不支持,所以本项目依旧有存在的必要性。 8 | 9 | 原问题链接:[TBX-2540 (jetbrains.com)](https://youtrack.jetbrains.com/issue/TBX-2540/Associate-file-extenstions-with-correct-Toolbox-app-or-with-the-Toolbox-itself-so-that-files-can-be-launched-from-Windows) [ TBX-2478 (jetbrains.com)](https://youtrack.jetbrains.com/issue/TBX-2478/Windows-Open-Directory-With-Editor) 10 | 11 | 12 | 13 | ## 简介 14 | 15 | 这是一个很简单的命令行工具,用于给Toolbox App添加windows右键菜单,它具有以下特性: 16 | 17 | - 更新或回退版本不会导致菜单失效(同时存在多个版本的IDE时,只会导向最新版) 18 | - 可设置通过管理员权限打开IDE 19 | - 无需手动维护注册表, 20 | - 菜单项的排列顺序与Toolbox中的同步 21 | - 能很轻易的删除,不会保留任何注册表项的残留 22 | 23 | 下面是效果图 24 | 25 | 效果图 26 | 27 | ## 安装 28 | 29 | 如果你拥有go环境,并且版本大于go1.16,可以采用`go install`的方式来安装,如下所示 30 | 31 | ```bash 32 | $ go install github.com/246859/AutoToolBox/v3/cmd/tbm@latest 33 | ``` 34 | 35 | 或者直接在Release里面下载最新版的二进制文件。 36 | 37 | 38 | 39 | ## 使用 40 | 41 | 3.0版本的工具使用起来简单了很多,虽然多了几个命令但大多数情况下都用不上,唯一需要的路径参数就是Toolbox的安装路径,一般情况下Toolbox被默认安装在如下路径。 42 | 43 | ``` 44 | $HOME/AppData/Local/Jetbrains/Toolbox/ 45 | ``` 46 | 47 | 工具在默认情况下使用上述路径,不需要额外指定参数,如果安装路径被修改了则需要用`-d`来指定(最好不要修改Toolbox的安装路径)。 48 | 49 | 请确保设置中的**生成Shell脚本**处于打开状态,否则工具无法正常工具。 50 | 51 | shellpath 52 | 53 | ### 迁移 54 | 如果你是旧版工具的使用者,且想要升级到新版,可以使用旧版生成的`toolboxRemove.reg`将旧版注册表删除,然后再按照下面的方法使用新版即可。 55 | 56 | ### 开始 57 | 58 | > **工具需要管理员权限才能正常运行** 59 | 60 | 安装好后执行如下命令 61 | 62 | ```bash 63 | $ tbm set -a 64 | ``` 65 | 66 | 就可以将所有本地安装的IDE添加到右键菜单中,这是最简单的使用方法,大多数情况下只会用到这一个命令。 67 | 68 | 69 | 70 | ### 命令 71 | 72 | ``` 73 | add Add ToolBox IDE to existing context menu 74 | clear clear all the context menu of Toolbox 75 | list List installed ToolBox IDEs 76 | remove Remove ToolBox IDEs from context menu 77 | set Register ToolBox IDEs to context menu 78 | version Print ToolBox version 79 | ``` 80 | 81 | 下面简单讲一下每个命令的大概作用 82 | 83 | 84 | 85 | #### list 86 | 87 | ```bash 88 | $ tbm list -h 89 | Examples: 90 | tbm list -c 91 | tbm list --menu 92 | tbm list -c --menu 93 | 94 | Usage: 95 | tbm list [flags] 96 | 97 | Flags: 98 | -c, --count count the number of installed tools 99 | -h, --help help for list 100 | --menu list the tools shown in the context menu 101 | ``` 102 | 103 | `list`命令用于查看本地所有已安装的IDE,例如 104 | 105 | ```bash 106 | $ tbm list 107 | Android Studio Koala 2024.1.1 Patch 1 108 | Aqua 2024.1.2 109 | CLion 2024.1.4 110 | DataGrip 2024.1.4 111 | GoLand 2024.1.4 112 | GoLand 2023.3.7 113 | IntelliJ IDEA Community Edition 2024.1.4 114 | IntelliJ IDEA Ultimate 2024.1.4 115 | MPS 2023.3.1 116 | PhpStorm 2024.1.4 117 | PyCharm Community 2024.1.4 118 | PyCharm Professional 2024.1.4 119 | ``` 120 | 121 | 查看数量 122 | 123 | ```bash 124 | $ tbm list -c 125 | 25 126 | ``` 127 | 128 | 查看所有已添加到菜单中的项 129 | 130 | ```bash 131 | $ tbm list --menu 132 | Aqua 2024.1.2 133 | CLion 2024.1.4 134 | DataGrip 2024.1.4 135 | DataSpell 2024.1.3 136 | Fleet 1.37.84 Public Preview 137 | GoLand 2024.1.4 138 | IntelliJ IDEA Ultimate 2024.1.4 139 | MPS 2023.3.1 140 | PhpStorm 2024.1.4 141 | PyCharm Professional 2024.1.4 142 | Rider 2024.1.4 143 | RubyMine 2024.1.4 144 | ``` 145 | 146 | 查看已添加到菜单中的项的数量 147 | 148 | ```bash 149 | $ tbm list --menu -c 150 | 16 151 | ``` 152 | 153 | 154 | 155 | #### set 156 | 157 | ```bash 158 | $ tbm set -h 159 | Usage: 160 | tbm set [flags] 161 | 162 | Flags: 163 | --admin run as admin 164 | -a, --all select all 165 | -h, --help help for set 166 | -s, --silence silence output 167 | --top place toolbox menu at top of context menu 168 | -u, --update only select current menu items 169 | ``` 170 | 171 | `set`命令表示将哪些IDE设置为菜单项,会直接覆盖现有的菜单,菜单的展示顺序与Toolbox界面中的相同。 172 | 173 | 最简单的使用方法就是直接将全部IDE设置为菜单项,如果本地的IDE数量超过了16个,那么只会添加前16个,这是因为windows菜单项的最大限制就是16个。 174 | 175 | ```bash 176 | $ tbm set -a 177 | Warning: too many tools, only first 16 will be added to the context menu 178 | GoLand 179 | IntelliJ IDEA Ultimate 180 | PyCharm Professional 181 | WebStorm 182 | RustRover 183 | Aqua 184 | Writerside 185 | Fleet 186 | DataSpell 187 | CLion 188 | PhpStorm 189 | DataGrip 190 | Rider 191 | RubyMine 192 | Space Desktop 193 | MPS 194 | ``` 195 | 196 | 或者单独指定 197 | 198 | ```bash 199 | $ tbm set GoLand WebStorm 200 | ``` 201 | 202 | 如果你需要以管理员权限来运行IDE,那么可以加上`--admin`,就像下面这样, 203 | 204 | ```bash 205 | $ tbm set -a --admin 206 | ``` 207 | 208 | 使用`--update`时只会更新现有的菜单项,不会添加新的菜单。如果同一个IDE存在多个版本,该命令可以将其导向最新版。 209 | 210 | ```bash 211 | $ tbm set --update 212 | ``` 213 | 214 | 在注册菜单时可以使用`--top`来让Toolbox菜单位于置顶的位置 215 | 216 | ```bash 217 | $ tbm set -a --admin --top 218 | ``` 219 | 220 |
221 | 222 | 需要注意的是,有一些产品既没有提供稳定的shell脚本路径,也没有提供`exe`文件的位置,下面几个就是 223 | 224 | ``` 225 | dotMemory Portable 226 | dotPeek Portable 227 | dotTrace Portable 228 | ReSharper Tools 229 | ``` 230 | 231 | 虽然现阶段可以将其添加到菜单中,但它们的文件结构并不像其他IDE一样具有条理,`list`命令会展示出现哪些工具暂不支持,如下 232 | 233 | ```bash 234 | $ tbm list 235 | Android Studio Koala 2024.1.1 Patch 1 236 | Aqua 2024.1.2 237 | CLion 2024.1.4 238 | DataGrip 2024.1.4 239 | DataSpell 2024.1.3 240 | dotMemory Portable 2024.1.4 unavailable 241 | dotPeek Portable 2024.1.4 unavailable 242 | dotTrace Portable 2024.1.4 unavailable 243 | Fleet 1.37.84 Public Preview 244 | ``` 245 | 246 | 对于它们而言,暂时不会被添加进菜单中。 247 | 248 | 249 | 250 | #### add 251 | 252 | ```bash 253 | $ tbm add -h 254 | Usage: 255 | tbm add [flags] 256 | 257 | Flags: 258 | --admin run as admin 259 | -h, --help help for add 260 | -s, --silence silence output 261 | --top place toolbox menu at top of context menu 262 | ``` 263 | 264 | `add`命令的区别在于它会向已有的菜单中添加新的菜单项,而不是像`set`一样直接覆盖,用法大体上一致。 265 | 266 | ```bash 267 | $ tbm add GoLand WebStorm 268 | ``` 269 | 270 | 不过它并不支持`-a`,不能一次性添加所有IDE。 271 | 272 | 273 | 274 | #### rmove 275 | 276 | ```bash 277 | $ tbm remove -h 278 | Command "remove" will remove the specified IDEs from the context menu, use "tbm remove -a" to remove all IDEs. 279 | 280 | Usage: 281 | tbm remove [flags] 282 | 283 | Aliases: 284 | remove, rm 285 | 286 | Flags: 287 | -a, --all remove all 288 | -h, --help help for remove 289 | -s, --silence silence output 290 | ``` 291 | 292 | `remove`命令用于删除菜单项 293 | 294 | ```bash 295 | $ tbm rm GoLand WebStorm 296 | ``` 297 | 298 | 使用`-a`来删除所有 299 | 300 | ```bash 301 | $ tbm rm -a 302 | ``` 303 | 304 | 305 | 306 | #### clear 307 | 308 | ```bash 309 | $ tbm clear 310 | clear all the context menu of Toolbox 311 | 312 | Usage: 313 | tbm clear [flags] 314 | 315 | Flags: 316 | -h, --help help for clear 317 | ``` 318 | 319 | 命令`clear`会直接清空所有与Toolbox有关的菜单项,包括顶级菜单,且不会有任何输出。如果你不想再使用本工具,可以用该命令将所有注册表项清理干净。 320 | 321 | 322 | 323 | ## 贡献 324 | 325 | 1. Fork本仓库到你的账号 326 | 2. 在Fork的仓库中创建一个新的分支 327 | 3. 在新分支中提交代码修改 328 | 4. 然后向本仓库发起Pull Request 329 | 5. 等待Pull Request 330 | -------------------------------------------------------------------------------- /assets/assets.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import "embed" 4 | 5 | //go:embed * 6 | var Fs embed.FS 7 | -------------------------------------------------------------------------------- /assets/ico/clion.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/clion.ico -------------------------------------------------------------------------------- /assets/ico/datagrip.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/datagrip.ico -------------------------------------------------------------------------------- /assets/ico/dataspell.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/dataspell.ico -------------------------------------------------------------------------------- /assets/ico/fleet.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/fleet.ico -------------------------------------------------------------------------------- /assets/ico/goland.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/goland.ico -------------------------------------------------------------------------------- /assets/ico/idea.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/idea.ico -------------------------------------------------------------------------------- /assets/ico/phpstorm.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/phpstorm.ico -------------------------------------------------------------------------------- /assets/ico/pycharm.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/pycharm.ico -------------------------------------------------------------------------------- /assets/ico/rider.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/rider.ico -------------------------------------------------------------------------------- /assets/ico/rubymine.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/rubymine.ico -------------------------------------------------------------------------------- /assets/ico/rustrover.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/rustrover.ico -------------------------------------------------------------------------------- /assets/ico/studio.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/studio.ico -------------------------------------------------------------------------------- /assets/ico/toolbox.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/toolbox.ico -------------------------------------------------------------------------------- /assets/ico/webstorm.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/webstorm.ico -------------------------------------------------------------------------------- /assets/ico/writerside.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/assets/ico/writerside.ico -------------------------------------------------------------------------------- /assets/template/toolboxAdd.reg.tmp: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [HKEY_CLASSES_ROOT\Directory\shell\ToolBoxProject] 4 | "SubCommands"="{{.SubCommands}}" 5 | "Icon"="{{.Icon}}" {{ if .Top }} 6 | "Position"="Top" {{ end }} 7 | "MUIVerb"="Open As ToolBox" 8 | 9 | [HKEY_CLASSES_ROOT\Directory\Background\shell\ToolBoxBackground] 10 | "SubCommands"="{{.SubCommands}}" 11 | "Icon"="{{.Icon}}" {{ if .Top }} 12 | "Position"="Top" {{ end }} 13 | "MUIVerb"="Open ToolBox Here" 14 | 15 | {{range $name,$ide := .IdeGroup}} 16 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{{$ide.HKey}}] 17 | @="Open {{$ide.Display}} Here" 18 | "Icon"="{{$ide.IconPath}}" 19 | 20 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{{$ide.HKey}}\command] 21 | @="\"{{$ide.ShellPath}}\" \"%v\"" 22 | {{end}} -------------------------------------------------------------------------------- /assets/template/toolboxRemove.reg.tmp: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [-HKEY_CLASSES_ROOT\Directory\shell\ToolBoxProject] 4 | "SubCommands"="{{.SubCommands}}" 5 | "Icon"="{{.Icon}}" 6 | "Position"="Top" 7 | "MUIVerb"="Open As ToolBox" 8 | 9 | [-HKEY_CLASSES_ROOT\Directory\Background\shell\ToolBoxBackground] 10 | "SubCommands"="{{.SubCommands}}" 11 | "Icon"="{{.Icon}}" 12 | "Position"="Top" 13 | "MUIVerb"="Open ToolBox Here" 14 | 15 | {{range $name,$ide := .IdeGroup}} 16 | [-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{{$ide.HKey}}] 17 | @=-"Open {{$ide.Display}} Here" 18 | "Icon"=-"{{$ide.IconPath}}" 19 | 20 | [-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{{$ide.HKey}}\command] 21 | @=-"\"{{$ide.ShellPath}}\" \"%v\"" 22 | {{end}} -------------------------------------------------------------------------------- /cmd/tbm/add.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/246859/AutoToolBox/v3/toolbox" 6 | "github.com/spf13/cobra" 7 | "slices" 8 | ) 9 | 10 | var addCmd = &cobra.Command{ 11 | Use: "add", 12 | Short: "Add ToolBox IDE to existing context menu", 13 | Long: `Command "add" will append items to the existing context menu instead of overwrite them. 14 | 15 | Use "tbm set -h" for more information. 16 | 17 | Examples: 18 | tbm add GoLand --admin 19 | tbm add GoLand WebStorm 20 | `, 21 | RunE: func(cmd *cobra.Command, args []string) error { 22 | if len(args) == 0 { 23 | fmt.Println(`no tools specified, use "tbm list" to show all installed tools, use "tbm add -h" to get help.`) 24 | return nil 25 | } 26 | tools, err := RunAdd(ToolBoxDir, args, top, admin) 27 | if err != nil { 28 | return err 29 | } 30 | if !silence { 31 | for _, tool := range tools { 32 | fmt.Println(tool.Name) 33 | } 34 | } 35 | return nil 36 | }, 37 | } 38 | 39 | func init() { 40 | addCmd.Flags().BoolVar(&top, "top", false, "place toolbox menu at top of context menu") 41 | addCmd.Flags().BoolVar(&admin, "admin", false, "run as admin") 42 | addCmd.Flags().BoolVarP(&silence, "silence", "s", false, "silence output") 43 | } 44 | 45 | func RunAdd(dir string, targets []string, admin, top bool) ([]*toolbox.Tool, error) { 46 | // get all latest tools 47 | toolboxState, err := toolbox.GetLatestTools(dir, toolbox.SortOrder) 48 | if err != nil { 49 | return nil, err 50 | } 51 | // collect tools 52 | appendTools := toolbox.FindTargetTools(toolboxState.Tools, targets, false) 53 | 54 | if len(appendTools) > toolbox.EntryLimit { 55 | fmt.Println("Warning: too many tools, only first 16 will be added to the context menu") 56 | appendTools = appendTools[:toolbox.EntryLimit] 57 | } 58 | 59 | // read subCommands 60 | items, _, err := toolbox.ReadSubCommands() 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | // collect items to be saved 66 | for _, tool := range appendTools { 67 | if !slices.ContainsFunc(items, func(item string) bool { return item == tool.Id }) { 68 | items = append(items, tool.Id) 69 | } 70 | } 71 | 72 | // create new subcommands 73 | for _, tool := range appendTools { 74 | err := toolbox.SetItem(tool, all) 75 | if err != nil { 76 | return nil, err 77 | } 78 | } 79 | 80 | // update menu 81 | if err := toolbox.SetMenu(dir, items, top); err != nil { 82 | return nil, err 83 | } 84 | return appendTools, nil 85 | } 86 | -------------------------------------------------------------------------------- /cmd/tbm/clear.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/246859/AutoToolBox/v3/toolbox" 5 | "github.com/spf13/cobra" 6 | "golang.org/x/sys/windows/registry" 7 | ) 8 | 9 | var clearCmd = &cobra.Command{ 10 | Use: "clear", 11 | Short: "clear all the context menu of Toolbox", 12 | RunE: func(cmd *cobra.Command, args []string) error { 13 | return RunClear(ToolBoxDir) 14 | }, 15 | } 16 | 17 | func RunClear(dir string) error { 18 | toolboxState, err := toolbox.GetAllTools(dir) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | // delete all sub-keys whatever can be deleted 24 | for _, tool := range toolboxState.Tools { 25 | err := toolbox.DeleteKey(registry.LOCAL_MACHINE, toolbox.CommandStoreShell+tool.Id) 26 | if err != nil { 27 | return err 28 | } 29 | } 30 | 31 | if err := toolbox.DeleteKey(registry.CLASSES_ROOT, toolbox.DirectoryBackgroundShell+toolbox.AppName); err != nil { 32 | return err 33 | } 34 | if err := toolbox.DeleteKey(registry.CLASSES_ROOT, toolbox.DirectoryShell+toolbox.AppName); err != nil { 35 | return err 36 | } 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /cmd/tbm/list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/246859/AutoToolBox/v3/toolbox" 6 | "github.com/spf13/cobra" 7 | "slices" 8 | ) 9 | 10 | var ( 11 | showCount bool 12 | showInMenu bool 13 | ) 14 | 15 | var listCmd = &cobra.Command{ 16 | Use: "list", 17 | Short: "List installed ToolBox IDEs", 18 | Long: `Command "list" will list all installed ToolBox IDEs. 19 | 20 | Examples: 21 | tbm list -c 22 | tbm list --menu 23 | tbm list -c --menu 24 | `, 25 | RunE: func(cmd *cobra.Command, args []string) error { 26 | tools, err := ListToolboxTools(ToolBoxDir, showInMenu) 27 | if err != nil { 28 | return err 29 | } 30 | if showCount { 31 | fmt.Println(len(tools)) 32 | } else { // show list 33 | for _, tool := range tools { 34 | var tips string 35 | if tool.Availability > 0 { 36 | tips = tool.Availability.String() 37 | } 38 | fmt.Printf("%-30s\t%-20s\t%-10s\n", tool.Name, tool.Version, tips) 39 | } 40 | } 41 | return nil 42 | }, 43 | } 44 | 45 | func init() { 46 | listCmd.Flags().BoolVarP(&showCount, "count", "c", false, "count the number of installed tools") 47 | listCmd.Flags().BoolVar(&showInMenu, "menu", false, "list the tools shown in the context menu") 48 | } 49 | 50 | // ListToolboxTools list local tools 51 | func ListToolboxTools(dir string, showInMenu bool) ([]*toolbox.Tool, error) { 52 | if !showInMenu { 53 | toolBox, err := toolbox.GetAllTools(dir) 54 | if err != nil { 55 | return nil, err 56 | } 57 | return toolBox.Tools, err 58 | } 59 | 60 | toolboxState, err := toolbox.GetLatestTools(dir, toolbox.SortNames) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | items, exist, err := toolbox.ReadSubCommands() 66 | if err != nil { 67 | return nil, err 68 | } else if !exist { 69 | return nil, nil 70 | } 71 | 72 | var tools []*toolbox.Tool 73 | for _, tool := range toolboxState.Tools { 74 | if slices.ContainsFunc(items, func(id string) bool { return tool.Id == id }) { 75 | tools = append(tools, tool) 76 | } 77 | } 78 | toolbox.SortTools(tools, toolbox.SortNames) 79 | return tools, nil 80 | } 81 | -------------------------------------------------------------------------------- /cmd/tbm/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/246859/AutoToolBox/v3/toolbox" 6 | "github.com/spf13/cobra" 7 | "os" 8 | ) 9 | 10 | var Version string 11 | 12 | var ToolBoxDir string 13 | 14 | var rootCmd = &cobra.Command{ 15 | Use: "tbm", 16 | Version: Version, 17 | SilenceUsage: true, 18 | Short: `ToolBox Menu helper`, 19 | Long: `tbm is a helper tool to manage ToolBox IDEs context menu on Windows. 20 | 21 | Toolbox App is located at $HOME/AppData/Local/JetBrains/ by default, in most cases 22 | you do not need to specify the path unless you have moved this location. If you did 23 | do that, use -d to specify the directory. 24 | 25 | see more information at https://github.com/246859/AutoToolBox 26 | `, 27 | RunE: func(cmd *cobra.Command, args []string) error { 28 | return nil 29 | }, 30 | } 31 | 32 | func init() { 33 | defaultTbDIr, err := toolbox.DefaultToolboxDir() 34 | if err != nil { 35 | fmt.Println(err.Error()) 36 | os.Exit(1) 37 | } 38 | rootCmd.SetVersionTemplate("{{ .Version }}") 39 | rootCmd.PersistentFlags().StringVar(&ToolBoxDir, "dir", defaultTbDIr, "specify the directory where ToolBox installed") 40 | rootCmd.AddCommand(versionCmd) 41 | rootCmd.AddCommand(listCmd) 42 | rootCmd.AddCommand(setCmd) 43 | rootCmd.AddCommand(addCmd) 44 | rootCmd.AddCommand(removeCmd) 45 | rootCmd.AddCommand(clearCmd) 46 | } 47 | 48 | func main() { 49 | rootCmd.Execute() 50 | } 51 | -------------------------------------------------------------------------------- /cmd/tbm/rm.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/246859/AutoToolBox/v3/toolbox" 6 | "github.com/spf13/cobra" 7 | "golang.org/x/sys/windows/registry" 8 | "slices" 9 | ) 10 | 11 | var removeCmd = &cobra.Command{ 12 | Use: "remove", 13 | Aliases: []string{"rm"}, 14 | Short: "Remove ToolBox IDEs from context menu", 15 | Long: `Command "remove" will remove the specified IDEs from the context menu, use "tbm remove -a" to remove all IDEs. 16 | 17 | Example: 18 | tbm rm GoLand 19 | tbm rm GoLand WebStorm 20 | tbm rm -a 21 | `, 22 | RunE: func(cmd *cobra.Command, args []string) error { 23 | if !all && len(args) == 0 { 24 | fmt.Println(`no tools specified, use "tbm list" to show all installed tools, use "tbm rm -h" to get help.`) 25 | return nil 26 | } 27 | tools, err := RemoveTools(ToolBoxDir, args, all) 28 | if err != nil { 29 | return err 30 | } 31 | if !silence { 32 | for _, tool := range tools { 33 | fmt.Println(tool.Name) 34 | } 35 | } 36 | return nil 37 | }, 38 | } 39 | 40 | func init() { 41 | removeCmd.Flags().BoolVarP(&silence, "silence", "s", false, "silence output") 42 | removeCmd.Flags().BoolVarP(&all, "all", "a", false, "remove all") 43 | } 44 | 45 | func RemoveTools(dir string, targets []string, all bool) ([]*toolbox.Tool, error) { 46 | toolboxState, err := toolbox.GetLatestTools(dir, toolbox.SortOrder) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | // get local tools 52 | preparedTools := toolbox.FindTargetTools(toolboxState.Tools, targets, all) 53 | 54 | // read subcommands 55 | items, exist, err := toolbox.ReadSubCommands() 56 | if err != nil { 57 | return nil, err 58 | } else if !exist { 59 | return nil, nil 60 | } 61 | 62 | // find union set between preparedTools and items 63 | var temp []*toolbox.Tool 64 | for _, tool := range preparedTools { 65 | if slices.Contains(items, tool.Id) { 66 | temp = append(temp, tool) 67 | } 68 | } 69 | preparedTools = temp 70 | 71 | for _, tool := range preparedTools { 72 | items = slices.DeleteFunc(items, func(s string) bool { 73 | return tool.Id == s 74 | }) 75 | } 76 | 77 | // update menu subCommands 78 | if err := toolbox.SetMenu(dir, items, false); err != nil { 79 | return nil, err 80 | } 81 | 82 | // remove menu item 83 | var removedTools []*toolbox.Tool 84 | for _, tool := range preparedTools { 85 | err := toolbox.DeleteKey(registry.LOCAL_MACHINE, toolbox.CommandStoreShell+tool.Id) 86 | if err != nil { 87 | return nil, fmt.Errorf("Error deleting registry key %s: %v\n", toolbox.CommandStoreShell+tool.Id, err) 88 | } 89 | removedTools = append(removedTools, tool) 90 | } 91 | 92 | // remove top level menu if remove all 93 | if all { 94 | if err := toolbox.DeleteKey(registry.CLASSES_ROOT, toolbox.DirectoryBackgroundShell+toolbox.AppName); err != nil { 95 | return nil, err 96 | } 97 | if err := toolbox.DeleteKey(registry.CLASSES_ROOT, toolbox.DirectoryShell+toolbox.AppName); err != nil { 98 | return nil, err 99 | } 100 | } 101 | return removedTools, nil 102 | } 103 | -------------------------------------------------------------------------------- /cmd/tbm/set.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/246859/AutoToolBox/v3/toolbox" 6 | "github.com/spf13/cobra" 7 | "slices" 8 | ) 9 | 10 | var ( 11 | // whether to set ToolBox at top of context menu 12 | top bool 13 | // Run ToolBox IDE as admin mode 14 | admin bool 15 | // slice output 16 | silence bool 17 | // update subCommands 18 | update bool 19 | // select all 20 | all bool 21 | ) 22 | 23 | var setCmd = &cobra.Command{ 24 | Use: "set", 25 | Short: "Register ToolBox IDEs to context menu", 26 | Long: `Command "set" will create a new menu to overwrite existing menu, it will set all by default. 27 | If you want to append items to the existing menu, use "tbm add". 28 | 29 | Microsoft limits the number of items in the context menu to no more than 16, so only the first 16 tools 30 | will be set to the context menu. If there has different versions of the same IDE, only the latest 31 | will be set. 32 | 33 | The default order of IDEs in the menu is determined by ToolBox/state.json, that is, 34 | the download time of the installation tool is sorted. In other case, the order of the 35 | menus depends on the order of args. 36 | 37 | Examples: 38 | tbm set -a 39 | tbm set Goland 40 | tbm set Goland CLion Webstorm 41 | `, 42 | RunE: func(cmd *cobra.Command, args []string) error { 43 | if !all && !update && len(args) == 0 { 44 | fmt.Println(`no tools specified, use "tbm list" to show all installed tools, use "tbm rm -h" to get help.`) 45 | return nil 46 | } 47 | tools, err := RunSet(ToolBoxDir, args, top, admin, all, update) 48 | if err != nil { 49 | return err 50 | } 51 | if !silence { 52 | for _, tool := range tools { 53 | fmt.Println(tool.Name) 54 | } 55 | } 56 | return nil 57 | }, 58 | } 59 | 60 | func init() { 61 | setCmd.Flags().BoolVar(&top, "top", false, "place toolbox menu at top of context menu") 62 | setCmd.Flags().BoolVar(&admin, "admin", false, "run as admin") 63 | setCmd.Flags().BoolVarP(&silence, "silence", "s", false, "silence output") 64 | setCmd.Flags().BoolVarP(&update, "update", "u", false, "only select current menu items") 65 | setCmd.Flags().BoolVarP(&all, "all", "a", false, "select all") 66 | } 67 | 68 | // RunSet register the specified IDEs to the context menu and return which tools are added successfully. 69 | 70 | func RunSet(dir string, targets []string, top, admin, all, update bool) ([]*toolbox.Tool, error) { 71 | 72 | // get all latest tools 73 | toolboxState, err := toolbox.GetLatestTools(dir, toolbox.SortOrder) 74 | if err != nil { 75 | return nil, err 76 | } 77 | // collect tools 78 | tools := toolbox.FindTargetTools(toolboxState.Tools, targets, all || update) 79 | 80 | // select items from menu 81 | itemsInMenu, _, err := toolbox.ReadSubCommands() 82 | if err != nil { 83 | return nil, err 84 | } 85 | var temp []*toolbox.Tool 86 | if update { 87 | for _, tool := range tools { 88 | if slices.ContainsFunc(itemsInMenu, func(id string) bool { return tool.Id == id }) { 89 | temp = append(temp, tool) 90 | } 91 | } 92 | tools = temp 93 | } 94 | 95 | if len(tools) > toolbox.EntryLimit { 96 | fmt.Println("Warning: too many tools, only first 16 will be added to the context menu") 97 | tools = tools[:toolbox.EntryLimit] 98 | } 99 | 100 | var items []string 101 | // add menu item 102 | for _, tool := range tools { 103 | err := toolbox.SetItem(tool, admin) 104 | if err != nil { 105 | return nil, err 106 | } 107 | items = append(items, tool.Id) 108 | } 109 | 110 | // set menu 111 | if err := toolbox.SetMenu(dir, items, top); err != nil { 112 | return nil, err 113 | } 114 | 115 | return tools, nil 116 | } 117 | -------------------------------------------------------------------------------- /cmd/tbm/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/246859/AutoToolBox/v3/toolbox" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var versionCmd = &cobra.Command{ 10 | Use: "version", 11 | Short: "Print ToolBox version", 12 | RunE: func(cmd *cobra.Command, args []string) error { 13 | version, err := GetToolBoxVersion(ToolBoxDir) 14 | if err != nil { 15 | return err 16 | } 17 | fmt.Println("ToolBox", version) 18 | return nil 19 | }, 20 | } 21 | 22 | func GetToolBoxVersion(dir string) (string, error) { 23 | toolBoxState, err := toolbox.GetToolBoxState(dir) 24 | if err != nil { 25 | return "", err 26 | } 27 | return toolBoxState.Version, nil 28 | } 29 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/246859/AutoToolBox/v3 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/spf13/cobra v1.8.0 7 | github.com/tidwall/gjson v1.17.1 8 | golang.org/x/sys v0.22.0 9 | ) 10 | 11 | require ( 12 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 13 | github.com/spf13/pflag v1.0.5 // indirect 14 | github.com/tidwall/match v1.1.1 // indirect 15 | github.com/tidwall/pretty v1.2.1 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 3 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 4 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 5 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 6 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 7 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 8 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 9 | github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= 10 | github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 11 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 12 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 13 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 14 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 15 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 16 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 17 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 19 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | -------------------------------------------------------------------------------- /image/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/image/preview.png -------------------------------------------------------------------------------- /image/shellpath.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/246859/AutoToolBox/1849e4a9ecfe293574456c34f4e7bcc37ab9520f/image/shellpath.png -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # basic info 2 | app := tbm 3 | module := github.com/246859/AutoToolBox/v3/cmd/tbm 4 | output := $(shell pwd)/build 5 | # meta info 6 | git_version := $(shell git tag --sort=-version:refname | sed -n 1p) 7 | # build info 8 | host_os := $(shell go env GOHOSTOS) 9 | host_arch := $(shell go env GOHOSTARCH) 10 | os := $(host_os) 11 | arch := $(host_arch) 12 | 13 | ifeq ($(os), windows) 14 | exe := .exe 15 | endif 16 | 17 | 18 | .PHONY: build 19 | build: 20 | # go lint 21 | go vet ./... 22 | 23 | # prepare target environment $(os)/$(arch) 24 | go env -w GOOS=$(os) 25 | go env -w GOARCH=$(arch) 26 | 27 | # build go module 28 | go build -trimpath \ 29 | -ldflags="-X main.AppName=$(app) -X main.Version=$(git_version)" \ 30 | -o $(output)/$(app)-$(os)-$(arch)/$(app)$(exe) \ 31 | $(module) 32 | 33 | # resume host environment $(host_os)/$(host_arch) 34 | go env -w GOOS=$(host_os) 35 | go env -w GOARCH=$(host_arch) 36 | 37 | # support platforms 38 | windows := 386 amd64 arm64 arm 39 | platforms := windows 40 | 41 | .PHONY: build_all 42 | build_all: 43 | @$(foreach os_i, $(platforms), \ 44 | $(foreach arch_j, $(call $(os_i)), \ 45 | $(shell $(MAKE) build os=$(os_i) arch=$(arch_j) mode=$(mode)))) -------------------------------------------------------------------------------- /toolbox/helper.go: -------------------------------------------------------------------------------- 1 | package toolbox 2 | 3 | import ( 4 | "cmp" 5 | "errors" 6 | "fmt" 7 | "golang.org/x/sys/windows/registry" 8 | "slices" 9 | "strings" 10 | "unsafe" 11 | ) 12 | 13 | // convert a byte slice to a string without allocating new memory. 14 | func bytes2string(bytes []byte) string { 15 | return unsafe.String(unsafe.SliceData(bytes), len(bytes)) 16 | } 17 | 18 | // convert a string to a byte slice without allocating new memory. 19 | func string2bytes(s string) []byte { 20 | return unsafe.Slice(unsafe.StringData(s), len(s)) 21 | } 22 | 23 | // compare two version strings. 24 | func compareVersion(version1 string, version2 string) int { 25 | var i, j int 26 | for i < len(version1) || j < len(version2) { 27 | var a, b int 28 | for ; i < len(version1) && version1[i] != '.'; i++ { 29 | a = a*10 + int(version1[i]-'0') 30 | } 31 | for ; j < len(version2) && version2[j] != '.'; j++ { 32 | b = b*10 + int(version2[j]-'0') 33 | } 34 | if a > b { 35 | return 1 36 | } else if a < b { 37 | return -1 38 | } 39 | 40 | i++ 41 | j++ 42 | } 43 | return 0 44 | } 45 | 46 | // compare tool name. 47 | func compareName(a string, b string) int { 48 | // safe check 49 | nameA, nameB := strings.ToLower(a), strings.ToLower(b) 50 | if nameA[0] != nameB[0] { 51 | return cmp.Compare(nameA[0], nameB[0]) 52 | } else if nameA[0] == nameB[0] && nameA != nameB { 53 | return strings.Compare(nameA, nameB) 54 | } 55 | return strings.Compare(nameA, nameB) 56 | } 57 | 58 | // OpenOrCreateKey open a registry key, create it if it doesn't exist. 59 | func OpenOrCreateKey(key registry.Key, path string, access uint32) (registry.Key, error) { 60 | newk, existing, err := registry.CreateKey(key, path, access) 61 | if err != nil { 62 | return 0, err 63 | } 64 | if existing { 65 | return registry.OpenKey(key, path, access) 66 | } 67 | return newk, nil 68 | } 69 | 70 | // DeleteKey delete a registry key and its sub keys. 71 | func DeleteKey(key registry.Key, path string) error { 72 | parentKey, err := registry.OpenKey(key, path, registry.READ) 73 | if errors.Is(err, registry.ErrNotExist) { 74 | return nil 75 | } else if err != nil { 76 | return err 77 | } 78 | defer parentKey.Close() 79 | 80 | // join path 81 | if len(path) > 0 && path[len(path)-1] != '\\' { 82 | path = path + `\` 83 | } 84 | 85 | subKeyNames, err := parentKey.ReadSubKeyNames(-1) 86 | if err != nil { 87 | return err 88 | } 89 | 90 | for _, name := range subKeyNames { 91 | subKeyPath := path + name 92 | err := DeleteKey(key, subKeyPath) 93 | if err != nil { 94 | return err 95 | } 96 | } 97 | 98 | return registry.DeleteKey(key, path) 99 | } 100 | 101 | // return command script for the given cmd 102 | func commandScript(cmd string, admin bool) string { 103 | if admin { 104 | return fmt.Sprintf(_VBScript, cmd) 105 | } else { 106 | return fmt.Sprintf(_CmdScript, cmd) 107 | } 108 | } 109 | 110 | const ( 111 | SortNames = 0 112 | SortOrder = 1 113 | ) 114 | 115 | func SortTools(tools []*Tool, sortType int) { 116 | switch sortType { 117 | case SortNames: 118 | slices.SortFunc(tools, func(a, b *Tool) int { 119 | return compareName(a.Name, b.Name) 120 | }) 121 | case SortOrder: 122 | slices.SortFunc(tools, func(a, b *Tool) int { 123 | return cmp.Compare(a.order, b.order) 124 | }) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /toolbox/toolbox.go: -------------------------------------------------------------------------------- 1 | package toolbox 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/tidwall/gjson" 8 | "golang.org/x/sys/windows/registry" 9 | "os" 10 | "path/filepath" 11 | "slices" 12 | "strings" 13 | ) 14 | 15 | const ( 16 | AppName = "Toolbox" 17 | _StateFile = "state.json" 18 | _SettingFile = ".settings.json" 19 | _ToolBoxPath = "/AppData/Local/JetBrains/Toolbox" 20 | _ToolBoxCommand = "/bin/jetbrains-toolbox.exe" 21 | _ScriptExt = "cmd" 22 | 23 | // registry keys 24 | DirectoryBackgroundShell = `Directory\Background\shell\` 25 | DirectoryShell = `Directory\shell\` 26 | CommandStoreShell = `SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\` 27 | 28 | // EntryLimit max limit for cascade menu 29 | EntryLimit = 16 30 | ) 31 | 32 | const ( 33 | // using the VBScript to open IDE as admin 34 | _VBScript = `mshta vbscript:createobject("shell.application").shellexecute("%s","%%V","","runas",1)(close)` 35 | _CmdScript = `"%s" "%%V"` 36 | ) 37 | 38 | // DefaultToolboxDir returns default toolbox installation directory. 39 | func DefaultToolboxDir() (string, error) { 40 | dir, err := os.UserHomeDir() 41 | if err != nil { 42 | return "", err 43 | } 44 | return filepath.Join(dir, _ToolBoxPath), nil 45 | } 46 | 47 | // ToolBox is a struct to hold the toolbox state. 48 | type ToolBox struct { 49 | Version string `json:"AppVersion"` 50 | Tools []*Tool `json:"tools"` 51 | 52 | ShellPath string 53 | } 54 | 55 | type Availability int 56 | 57 | const ( 58 | available Availability = iota 59 | legacy 60 | unavailable 61 | ) 62 | 63 | func (a Availability) String() string { 64 | switch a { 65 | case available: 66 | return "available" 67 | case legacy: 68 | return "legacy" 69 | case unavailable: 70 | return "unavailable" 71 | default: 72 | return "available" 73 | } 74 | } 75 | 76 | func toolFilter(tools []*Tool, maxAvl Availability) []*Tool { 77 | var filtered []*Tool 78 | for _, tool := range tools { 79 | if tool.Availability <= maxAvl { 80 | filtered = append(filtered, tool) 81 | } 82 | } 83 | return filtered 84 | } 85 | 86 | // Tool represents an IDE in ToolBox. 87 | type Tool struct { 88 | Id string `json:"toolId"` 89 | Tag string `json:"tag"` 90 | Name string `json:"displayName"` 91 | Version string `json:"displayVersion"` 92 | BuildNumber string `json:"buildNumber"` 93 | Channel string `json:"channelId"` 94 | Location string `json:"installLocation"` 95 | // exe 96 | Command string `json:"launchCommand"` 97 | // script file 98 | Script string 99 | Availability Availability 100 | 101 | order int 102 | } 103 | 104 | // GetToolBoxState returns content of ToolBox/state.json 105 | func GetToolBoxState(dir string) (*ToolBox, error) { 106 | 107 | // get toolbox tools information from state.json 108 | stateFilepath := filepath.Join(dir, _StateFile) 109 | stateFile, err := os.Open(stateFilepath) 110 | if err != nil { 111 | return nil, err 112 | } 113 | var toolbox ToolBox 114 | if err := json.NewDecoder(stateFile).Decode(&toolbox); err != nil { 115 | return nil, err 116 | } 117 | 118 | // read shell path 119 | settingBytes, err := os.ReadFile(filepath.Join(dir, _SettingFile)) 120 | if err != nil { 121 | return nil, err 122 | } 123 | toolbox.ShellPath = gjson.GetBytes(settingBytes, "shell_scripts.location").String() 124 | if toolbox.ShellPath == "" { 125 | defaultDir, err := DefaultToolboxDir() 126 | if err != nil { 127 | return nil, err 128 | } 129 | toolbox.ShellPath = filepath.Join(defaultDir, "script") 130 | } 131 | 132 | // get shell script name for per tool from channel file 133 | for i, tool := range toolbox.Tools { 134 | channelBytes, err := os.ReadFile(filepath.Join(dir, "channels", fmt.Sprintf("%s.json", tool.Channel))) 135 | if err != nil { 136 | return nil, err 137 | } 138 | 139 | // find shel script from extension 140 | extensions := gjson.GetBytes(channelBytes, "tool.extensions") 141 | if extensions.Exists() { 142 | for _, ext := range extensions.Array() { 143 | if ext.Get("type").String() == "shell" { 144 | scriptName := ext.Get("name").String() 145 | tool.Script = filepath.Join(toolbox.ShellPath, fmt.Sprintf("%s.%s", scriptName, _ScriptExt)) 146 | } 147 | } 148 | } 149 | 150 | // judge availability 151 | if tool.Script == "" && tool.Command == "" { 152 | tool.Availability = unavailable 153 | } else if tool.Script == "" { 154 | tool.Availability = legacy 155 | } 156 | 157 | // by default, the ordering of tools is depending on state.json that is maintained by Toolbox. 158 | // Toolbox app will update state.json after you change the order of tools. 159 | tool.order = i 160 | } 161 | 162 | return &toolbox, err 163 | } 164 | 165 | // GetAllTools return local tool list description 166 | func GetAllTools(dir string) (*ToolBox, error) { 167 | toolbox, err := GetToolBoxState(dir) 168 | if err != nil { 169 | return nil, err 170 | } 171 | // sort tools 172 | slices.SortFunc(toolbox.Tools, func(a, b *Tool) int { 173 | if a.Name == b.Name { 174 | return -compareVersion(a.BuildNumber, b.BuildNumber) 175 | } 176 | return compareName(a.Name, b.Name) 177 | }) 178 | 179 | // find which tools have different versions 180 | tools := make(map[string][]*Tool) 181 | for _, tool := range toolbox.Tools { 182 | tools[tool.Name] = append(tools[tool.Name], tool) 183 | } 184 | return toolbox, nil 185 | } 186 | 187 | // GetLatestTools returns latest tool list 188 | func GetLatestTools(dir string, sortType int) (*ToolBox, error) { 189 | 190 | tools := make(map[string][]*Tool) 191 | toolbox, err := GetToolBoxState(dir) 192 | if err != nil { 193 | return nil, err 194 | } 195 | 196 | // collect tools 197 | for _, tool := range toolbox.Tools { 198 | tools[tool.Name] = append(tools[tool.Name], tool) 199 | } 200 | 201 | // collect latest tools 202 | var latestTools []*Tool 203 | for _, list := range tools { 204 | switch len(list) { 205 | case 0: 206 | continue 207 | case 1: 208 | latestTools = append(latestTools, list[0]) 209 | default: 210 | latestTools = append(latestTools, FindLatestTool(list)) 211 | } 212 | } 213 | SortTools(latestTools, sortType) 214 | toolbox.Tools = latestTools 215 | return toolbox, nil 216 | } 217 | 218 | // FindLatestTool find the latest tool in a list of tools. 219 | func FindLatestTool(tools []*Tool) *Tool { 220 | maxTool := &Tool{Version: "0"} 221 | for _, tool := range tools { 222 | if compareVersion(tool.Version, maxTool.Version) == 1 { 223 | maxTool = tool 224 | } 225 | } 226 | return maxTool 227 | } 228 | 229 | // FindTargetTools returns tools with specific names 230 | func FindTargetTools(tools []*Tool, targets []string, all bool) []*Tool { 231 | var targetTools []*Tool 232 | if all { 233 | targetTools = tools 234 | } else { 235 | for _, target := range targets { 236 | for _, tool := range tools { 237 | if tool.Name == target { 238 | targetTools = append(targetTools, tool) 239 | break 240 | } 241 | } 242 | } 243 | } 244 | return toolFilter(targetTools, legacy) 245 | } 246 | 247 | // ReadSubCommands returns current menu items 248 | func ReadSubCommands() ([]string, bool, error) { 249 | bgShellKey, err := registry.OpenKey(registry.CLASSES_ROOT, DirectoryBackgroundShell+AppName, registry.READ) 250 | if errors.Is(err, registry.ErrNotExist) { 251 | return nil, false, nil 252 | } else if err != nil { 253 | return nil, false, err 254 | } 255 | defer bgShellKey.Close() 256 | 257 | value, _, err := bgShellKey.GetStringValue("SubCommands") 258 | if err != nil { 259 | return nil, true, err 260 | } 261 | if value == "" { 262 | return nil, true, nil 263 | } 264 | return strings.Split(value, ";"), true, err 265 | } 266 | 267 | func SetMenu(dir string, items []string, top bool) error { 268 | toolboxDisplay := fmt.Sprintf("Open %s Here", AppName) 269 | toolboxCmd := filepath.Join(dir, _ToolBoxCommand) 270 | subCommands := strings.Join(items, ";") 271 | 272 | // add directory background shell 273 | err := SetMenuItem(DirectoryBackgroundShell+AppName, toolboxDisplay, toolboxCmd, subCommands, top) 274 | if err != nil { 275 | return err 276 | } 277 | 278 | // add directory shell 279 | err = SetMenuItem(DirectoryShell+AppName, toolboxDisplay, toolboxCmd, subCommands, top) 280 | if err != nil { 281 | return err 282 | } 283 | return nil 284 | } 285 | 286 | // SetMenuItem add menu to registry 287 | func SetMenuItem(path, display, command, subCommands string, top bool) error { 288 | key, err := OpenOrCreateKey(registry.CLASSES_ROOT, path, registry.WRITE) 289 | if err != nil { 290 | return err 291 | } 292 | defer key.Close() 293 | 294 | // set display content 295 | if err := key.SetStringValue("MUIVerb", display); err != nil { 296 | return err 297 | } 298 | 299 | // set icon 300 | if err := key.SetStringValue("Icon", command); err != nil { 301 | return err 302 | } 303 | 304 | // set sub commands 305 | if err := key.SetStringValue("SubCommands", subCommands); err != nil { 306 | return err 307 | } 308 | 309 | // position 310 | if top { 311 | err := key.SetStringValue("Position", "Top") 312 | if err != nil { 313 | return err 314 | } 315 | } 316 | 317 | return nil 318 | } 319 | 320 | // SetItem setItem add items to commandStore shell 321 | func SetItem(tool *Tool, admin bool) error { 322 | 323 | regPath := CommandStoreShell + tool.Id 324 | ico := filepath.Join(tool.Location, tool.Command) 325 | script := tool.Script 326 | // special case for MPS 327 | if tool.Id == "MPS" { 328 | ico = filepath.Join(tool.Location, "bin/mps.ico") 329 | } 330 | if tool.Availability == legacy { 331 | script = ico 332 | } else if tool.Availability == unavailable { 333 | return nil 334 | } 335 | // create or open registry key 336 | key, err := OpenOrCreateKey(registry.LOCAL_MACHINE, regPath, registry.WRITE) 337 | if err != nil { 338 | return err 339 | } 340 | 341 | // default value 342 | if err := key.SetStringValue("", fmt.Sprintf("Open %s Here", tool.Name)); err != nil { 343 | return err 344 | } 345 | // set icon 346 | if err := key.SetStringValue("Icon", ico); err != nil { 347 | return err 348 | } 349 | 350 | // command sub key 351 | cmdKey, err := OpenOrCreateKey(registry.LOCAL_MACHINE, regPath+`\command`, registry.WRITE) 352 | if err != nil { 353 | return err 354 | } 355 | 356 | // set command 357 | if err := cmdKey.SetStringValue("", commandScript(script, admin)); err != nil { 358 | return err 359 | } 360 | 361 | return nil 362 | } 363 | --------------------------------------------------------------------------------