├── .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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------