├── .github
└── FUNDING.yml
├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── benchmarks
└── benchmarks.txt
├── functions
├── _zcomet
├── zcomet_help
├── zcomet_list
├── zcomet_self-update
├── zcomet_unload
└── zcomet_update
├── img
├── logo.png
├── mit_license.svg
├── social_preview.png
└── zsh_4.3.11_plus.svg
├── tests
└── tests
└── zcomet.zsh
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: agkozak
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: [ "https://www.paypal.me/agkozak" ]
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.zwc
2 | _site/
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "zcomet-media"]
2 | path = zcomet-media
3 | url = https://github.com/agkozak/zcomet-media
4 | ignore = dirty
5 | branch = main
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021-2024 Alexandros Kozak
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # `zcomet` - Fast, Simple Zsh Plugin Manager
2 |
3 |
4 |
5 |
6 |
7 | [](https://opensource.org/licenses/MIT)
8 | 
9 | [](https://github.com/agkozak/zcomet/stargazers)
10 |
11 | `zcomet` is a Zsh plugin manager that gets you to the prompt quickly. Its goal is to be simple and convenient without slowing you down. It succeeds in keeping latencies down to the level you would expect if you were not even using a plugin manager:
12 |
13 | 
14 |
15 | *Many thanks to Roman Perepelitsa for sharing his [`zsh-bench`](https://github.com/romkatv/zsh-bench) benchmarking utility (see ["Notes on Benchmarks"](#notes-on-benchmarks)).*
16 |
17 | The speed difference can be undetectable, but the improved convenience is noteworthy. A `zcomet` configuration can be as simple as:
18 |
19 | ```sh
20 | source /path/to/zcomet.zsh
21 |
22 | zcomet load author1/plugin1
23 | zcomet load author2/plugin2
24 | zcomet load author3/plugin3
25 |
26 | zcomet compinit
27 | ```
28 |
29 | Those lines will clone repositories, source scripts, update your `FPATH` and `PATH`, and load the Zsh completion system.
30 |
31 | ## Table of Contents
32 |
33 | - [News](#news)
34 | - [Sample `.zshrc`](#sample-zshrc)
35 | - [Commands and Arguments](#commands-and-arguments)
36 | + [`load`](#load-repository-name-subdirectory-file1-file2-)
37 | + [`fpath`](#fpath-repository-name-subdirectory)
38 | + [`trigger`](#trigger-trigger-name-arguments)
39 | + [`snippet`](#snippet-snippet)
40 | + [`update`](#update)
41 | + [`list`](#list)
42 | + [`compinit`](#compinit)
43 | + [`compile`](#compile)
44 | + [`help`](#help)
45 | + [`self-update`](#self-update)
46 | + [`unload`](#unload-repository-name)
47 | - [Options](#options)
48 | + [`--no-submodules`](#--no-submodules)
49 | - [Directory Customization](#directory-customization)
50 | - [Dynamic Named Directories](#dynamic-named-directories)
51 | - [FAQ](#faq)
52 | + [How do I install `fzf`?](#how-do-i-install-fzf)
53 | - [Standards Compliance](#standards-compliance)
54 | - [Notes on Benchmarks](#notes-on-benchmarks)
55 | - [TODO](#todo)
56 |
57 | ## News
58 |
59 | - December 7, 2024
60 | + `zcomet load` now first chooses a branch and then initializes and updates submodules.
61 | - August 24, 2023
62 | + `zcomet compile` no longer expands aliases when compiling scripts.
63 |
64 |
65 | Older news
66 |
67 | - November 17, 2021
68 | + `zcomet update` no longer re-sources loaded plugins and snippets, as doing so can have undesired consequences. Instead, it reminds the user to `exec zsh` to refresh the system.
69 | - November 16, 2021
70 | + You can now refer to a GitHub repository by the full URL, if you prefer, e.g. `https://github.com/zsh-users/zsh-syntax-highlighting.git` instead of `zsh-users/zsh-syntax-highlighting`. Support for Git servers other than GitHub is coming soon.
71 | + `zcomet list` now displays triggers in a more abbreviated fashion.
72 | - October 21, 2021
73 | + `zcomet` now supports local plugins and snippets.
74 | - October 13, 2021
75 | + I have adopted [@romkatv](https://github.com/romkatv)'s [zsh-bench](https://github.com/romkatv/zsh-bench) benchmarks as a standard for measuring performance.
76 | + `zcomet` no longer `zcompile`s rc files, and the default behavior of `zcomet compinit` is merely to run `compinit` while specifying a sensibly named cache file (again, props to **@romkatv** for suggesting these changes).
77 | - October 4, 2021
78 | + `zcomet` now fetches Git submodules by default. If you do not need them, be sure to save yourself time by using the [`--no-submodules`](#--no-submodules) option with `load`, `fpath`, or `trigger`.
79 | - September 30, 2021
80 | + `zcomet` now defers running `compdef` calls until after `zcomet compinit` has been run.
81 | - September 28, 2021
82 | + `zcomet` now autoloads functions in a `functions/` directory before sourcing a Prezto-style module.
83 | - September 27, 2021
84 | + `zcomet` now looks for the `bin/` subdirectory in the root directory of the repository, not in the directory where the sources plugin files reside.
85 | - September 21, 2021
86 | + I have opted to have named directories assigned only at the repository level. Also, if there is more than one repository with the same name (e.g., `author1/zsh-tool` and `author2/zsh-tool`), neither directory is given a name (to prevent mistakes from happening).
87 | - September 20, 2021
88 | + `zcomet` plugins are now assigned [dynamic named directories](#dynamic-named-directories). This feature was inspired by Marlon Richert's [Znap](https://github.com/marlonrichert/zsh-snap).
89 | - September 18, 2021
90 | + `zcomet` directories are now specified using `zstyle`; [see below](#directory-customization).
91 | + The `load` command will now add a plugin's `bin/` subdirectory, if it has one, to the `PATH`.
92 | - September 17, 2021
93 | + `zcommet trigger` now always makes sure that the repository it needs has already been cloned, meaning that you will never have to wait for files to be downloaded when you use a defined trigger.
94 | - September 16, 2021
95 | + `zcomet list` now reflects `FPATH` elements added using the `fpath` command.
96 | + New command: `zcomet compinit` runs `compinit` and compiles its cache for you.
97 | - September 15, 2021
98 | + `zcomet` will store your plugins and snippets in `${ZDOTDIR}`, if you have set that variable and if `${HOME}/.zcomet` does not already exist. Props to @mattjamesdev.
99 | - September 13, 2021
100 | + The `snippet` command now supports any URL that points to raw Zsh code (not HTML) via HTTP or HTTPS. It will translate `github.com` addresses into their `raw.githubusercontent.com` equivalents. You may still use the `OMZ::` shorthand for Oh-My-Zsh code.
101 |
102 |
103 | ## Sample `.zshrc`
104 |
105 | ```sh
106 | # Clone zcomet if necessary
107 | if [[ ! -f ${ZDOTDIR:-${HOME}}/.zcomet/bin/zcomet.zsh ]]; then
108 | command git clone https://github.com/agkozak/zcomet.git ${ZDOTDIR:-${HOME}}/.zcomet/bin
109 | fi
110 |
111 | source ${ZDOTDIR:-${HOME}}/.zcomet/bin/zcomet.zsh
112 |
113 | # Load a prompt
114 | zcomet load agkozak/agkozak-zsh-prompt
115 |
116 | # Load some plugins
117 | zcomet load agkozak/zsh-z
118 | zcomet load ohmyzsh plugins/gitfast
119 |
120 | # Load a code snippet - no need to download an entire repository
121 | zcomet snippet https://github.com/jreese/zsh-titles/blob/master/titles.plugin.zsh
122 |
123 | # Lazy-load some plugins
124 | zcomet trigger zhooks agkozak/zhooks
125 | zcomet trigger zsh-prompt-benchmark romkatv/zsh-prompt-benchmark
126 |
127 | # Lazy-load Prezto's archive module without downloading all of Prezto's
128 | # submodules
129 | zcomet trigger --no-submodules archive unarchive lsarchive \
130 | sorin-ionescu/prezto modules/archive
131 |
132 | # It is good to load these popular plugins last, and in this order:
133 | zcomet load zsh-users/zsh-syntax-highlighting
134 | zcomet load zsh-users/zsh-autosuggestions
135 |
136 | # Run compinit and compile its cache
137 | zcomet compinit
138 | ```
139 |
140 | ## Commands and Arguments
141 |
142 | ### `load` repository-name \[subdirectory\] \[file1\] \[file2\] ...
143 |
144 | `load` is the most commonly used command; it clones a GitHub repository (if it has not already been downloaded), adds its root directory (or `functions/` subdirectory, if it exists) to `FPATH`, adds any `bin/` subdirectory to `PATH`, and sources a file or files. The simplest example is:
145 |
146 | zcomet load agkozak/zsh-z
147 |
148 | The common repositories `ohmyzsh/ohmyzsh` and `sorin-ionescu/prezto` can be abbreviated as `ohmyzsh` and `prezto`, respectively. `zcomet` uses simple principles to choose which init file to source (in this case, `/path/to/agkozak/zsh-z/zsh-z.plugin.zsh` is the obvious choice).
149 |
150 | A subdirectory of a repository can be specified:
151 |
152 | zcomet load ohmyzsh plugins/gitfast
153 |
154 | loads Oh-My-Zsh's useful `gitfast` plugin. If a specific file or files in a subdirectory should be sourced, they can be specified:
155 |
156 | zcomet load ohmyzsh lib git.zsh
157 | zcomet load sindresorhus/pure async.zsh pure.zsh
158 |
159 | If there are autoloadable functions in a Prezto-style `functions/` directory, they will be automatically autoloaded.
160 |
161 | A specific branch, tag, or commit of a repository can be checked out using the following syntax:
162 |
163 | zcomet load author/repo@branch
164 |
165 | (`@tag` and `@commit` are equally valid.)
166 |
167 | `load` is the command used for loading prompts.
168 |
169 | `load` also supports local plugins that do not need to be cloned. Just make sure that the plugin name starts with a slash or something that will expand to a slash, e.g.
170 |
171 | zcomet load /path/to/plugin1
172 | zcomet load ~/path/to/plugin2
173 | zcomet load ${HOME}/path/to/plugin3
174 |
175 | Relative directories cannot be used.
176 |
177 | *NOTE: If the repository that `load` is cloning has submodules, consider whether or not you really need them. Using the [`--no-submodules`](#--no-submodules) option after `load` can save a lot of time during installation and updating.*
178 |
179 | ### `fpath` repository-name \[subdirectory\]
180 |
181 | `fpath` will clone a repository and add one of its directories to `FPATH`. Unlike `load`, it does not source any files. Also, you must be very specific about which subdirectory is to be added to `FPATH`; `zcomet fpath` does not try to guess. If you wanted to use the agkozak-zsh-prompt with `promptinit`, you could run
182 |
183 | zcomet fpath agkozak/agkozak-zsh-prompt
184 | autoload promptinit; promptinit
185 | prompt agkozak-zsh-prompt
186 |
187 | (But if you are not intending to switch prompts, it is much easier just to use `zcomet load agkozak/agkozak-zsh-prompt`.)
188 |
189 | *NOTE: If the repository that `fpath` is cloning has submodules, consider whether or not you really need them. Using the [`--no-submodules`](#--no-submodules) option after `fpath` can save a lot of time during installation and updating.*
190 |
191 | ### `trigger` trigger-name \[arguments\]
192 |
193 | `trigger` lazy-loads plugins, saving time when you start the shell. If you specify a command name, a Git repository, and other optional arguments (the same arguments that get used for `load`), the plugin will be loaded and the command run only when the command is first used:
194 |
195 | zcomet trigger zhooks agkozak/zhooks
196 |
197 | for example, creates a function called `zhooks` that loads the `zhooks` plugin and runs the command `zhooks`. It takes next to no time to create the initial function, so this is perfect for commands that you do not instantly and constantly use. If there is more than one command that should trigger the loading of the plugin, you can specify each separately:
198 |
199 | zcomet trigger extract ohmyzsh plugins/extract
200 | zcomet trigger x ohmyzsh plugins/extract
201 |
202 | or save time by listing a number of triggers before the repository name:
203 |
204 | zcomet trigger extract x ohmyzsh plugins/extract
205 |
206 | `trigger` always checks to make sure that the repository it needs has been already cloned; if not, it clones it. The goal is for triggers to take almost no time to load when they are actually run.
207 |
208 | *NOTE: If the repository that `trigger` is cloning has submodules, consider whether or not you really need them. Using the [`--no-submodules`](#--no-submodules) option after `trigger` can save a lot of time during installation and updating.*
209 |
210 | This feature was inspired by [Zinit](https://github.com/zdharma-continuum/zinit)'s `trigger-load` command.
211 |
212 | ### `snippet` snippet
213 |
214 | `snippet` downloads a script (when necessary) and sources it:
215 |
216 | zcomet snippet OMZ::plugins/git/git.plugins.zsh
217 |
218 | This example will download Oh-My-Zsh's `git` aliases without cloning the whole Oh-My-Zsh repository -- a great time-saver.
219 |
220 | `zcomet` will translate `github.com` URLs into their raw code `raw.githubusercontent.com` equivalents. For example,
221 |
222 | zcomet snippet https://github.com/jreese/zsh-titles/blob/master/titles.plugin.zsh
223 |
224 | really executes
225 |
226 | zcomet snippet https://raw.githubusercontent.com/jreese/zsh-titles/master/titles.plugin.zsh
227 |
228 | For snippets that are not hosted by GitHub, you will want to make sure that the URL you use points towards raw code, not a pretty HTML display of it.
229 |
230 | `zcomet` will also allow you to load local snippets that do not need to be downloaded, e.g.
231 |
232 | zcomet snippet /path/to/my/code.zsh
233 |
234 | ### `update`
235 |
236 | `zcomet update` downloads updates for any plugins or snippets that have been downloaded in the past.
237 |
238 | ### `list`
239 |
240 | `zcomet list` displays any active plugins, added `FPATH` elements, snippets, and triggers. As you use the triggers, you will see them disappear as triggers and reappear as loaded plugins.
241 |
242 | ### `compinit`
243 |
244 | Runs Zsh's `compinit` command, which is necessary if you want to use command line completions. `compinit`'s cache is then stored in a file in the `$HOME` directory (or in `$ZDOTDIR`, if you have defined it) starting with `.zcompdump_`, followed by the effective user ID (`EUID`), the operating system type (`OSTYPE`), and ending with the version number of the `zsh` you are using, e.g., `.zcompdump_1000_linux-gnu_5.8`. `zcomet` compiles the cache for you.
245 |
246 | Like other plugin managers and frameworks, `zcomet` defers running `compdef` calls until `zcomet compinit` runs, which means that you can load a plugin full of `compdefs` (e.g., `zcomet load ohmyzsh plugins/git`) even before `zcomet compinit` and its completions will still work.
247 |
248 | A simple `zcomet compinit` should always get the job done, but if you need to rename the cache file ("dump file"), you can do so thus:
249 |
250 | zstyle ':zcomet:compinit' dump-file /path/to/dump_file
251 |
252 | If you need to specify other options to `compinit`, you can do it this way:
253 |
254 | zstyle ':zcomet:compinit' arguments -i # I.e., run `compinit -i'
255 |
256 | But it is safest to stick to the default behavior. An incorrectly configured `compinit` can lead to your completions being broken or unsafe code being loaded.
257 |
258 | ### `compile`
259 |
260 | Compiles a script or scripts if there is no corresponding wordcode (`.zwc`) file or if a script is newer than its `.zwc`. Note that `zcomet` always compiles scripts after cloning repositories or running `update`, so you should generally never need to invoke `zcomet compile` yourself.
261 |
262 | ### `help`
263 |
264 | Displays a help screen.
265 |
266 | ### `self-update`
267 |
268 | Updates `zcomet` itself. Note that `zcomet` must have been installed as a cloned Git repository for this to work.
269 |
270 | ### `unload` \[repository-name\]
271 |
272 | Unloads a plugin that has an [unload function](https://github.com/agkozak/Zsh-100-Commits-Club/blob/master/Zsh-Plugin-Standard.adoc#4-unload-function). The implementation is still very basic.
273 |
274 | ## Options
275 |
276 | ### `--no-submodules`
277 |
278 | By default, if a repository has submodules, `zcomet` will fetch them whenever the `load`, `fpath`, `trigger`, or `update` commands are issued. For example, I use [Prezto's `archive` module](https://github.com/sorin-ionescu/prezto/tree/master/modules/archive), but I do not need all of the external prompts in the `prompt` module, so I use `zcomet`'s `--no-submodules` option:
279 |
280 | zcomet load --no-submodules sorin-ionescu/prezto modules/archive
281 |
282 | Not fetching the submodules saves a good deal of time when cloning the repository.
283 |
284 | ## Directory Customization
285 |
286 | `zcomet` will store plugins, snippets, and the like in `~/.zcomet` by default. If you have set `$ZDOTDIR`, then `zcomet` will use `${ZDOTDIR}/.zcomet` instead. You can also specify a custom home directory for `zcomet` thus:
287 |
288 | zstyle ':zcomet:*' home-dir ~/path/to/home_dir
289 |
290 | Make sure to do that before you start loading code.
291 |
292 | In the home directory there will usually be a `/repos` subdirectory for plugins and a `/snippets` subdirectory for snippets, but you may name your own locations:
293 |
294 | zstyle ':zcomet:*' repos-dir ~/path/to/repos_dir
295 | zstyle ':zcomet:*' snippets-dir ~/path/to/snippets_dir
296 |
297 | I recommend cloning the `agkozak/zcomet` repository to a `/bin` subdirectory in your `zcomet` home directory (e.g., `~/.zcomet/bin`), as in the [example `.zshrc`](#example-zshrc) above.
298 |
299 | ## Dynamic Named Directories
300 |
301 | If you `load`, `fpath`, or `trigger` a number of plugins, `zcomet` will give them dynamic directory names. For the [example `.zshrc`](https://github.com/agkozak/zcomet/tree/develop#example-zshrc) above, the following named directories would be created:
302 |
303 | ~[agkozak-zsh-prompt]
304 | ~[ohmyzsh]
305 | ~[zhooks]
306 | ~[zsh-prompt-benchmark]
307 | ~[zsh-z]
308 |
309 | You will also have `~[zcomet-bin]`, the directory in which the `zcomet.zsh` script resides.
310 |
311 | Try typing `cd ~[` and press `` to see a list of dynamic directories. This new feature should be particularly useful to people who write plugins and prompts -- it makes it very easy to get to the code.
312 |
313 | This feature is based on Marlon Richert's [Znap](https://github.com/marlonrichert/zsh-snap).
314 |
315 | ## FAQ
316 |
317 | ### How do I install `fzf`?
318 |
319 | `fzf` is not structured like a normal Zsh plugin, but you can install it like this:
320 |
321 | zcomet load junegunn/fzf shell completion.zsh key-bindings.zsh
322 | (( ${+commands[fzf]} )) || ~[fzf]/install --bin
323 |
324 | The first line makes sure the `fzf` repository gets cloned, its `bin/` subdirectory is added to `PATH`, and the relevant scripts get sourced. The second line checks to make sure that the `fzf` binary is actually available and installs it if it is not (note that `fzf` does not work on all systems and that its install script relies on `bash`'s being installed).
325 |
326 | ## Standards Compliance
327 |
328 | I am a great admirer of [Sebastian Gniazdowski's principles for plugin development](https://github.com/agkozak/Zsh-100-Commits-Club/blob/master/Zsh-Plugin-Standard.adoc), and I have incorporated most of his suggestions into `zcomet`:
329 |
330 | * Standardized `$0` handling
331 | * Support for `functions/` directories
332 | * Support for `bin/` directories
333 | * Support for `unload` functions
334 | * `zsh_loaded_plugins`: a plugin manager activity indicator
335 | * `ZPFX`: global parameter with PREFIX for `make`, `configure`, etc.
336 | * `PMSPEC`: global parameter holding the plugin manager’s capabilities
337 |
338 | ## Notes on Benchmarks
339 |
340 | When I started this project, I was happy to discover that `zcomet` scored rather well on benchmarks that measure `zsh -lic "exit"`. Roman Perepelitsa [has argued eloquently](https://github.com/romkatv/zsh-bench), however, that such benchmarks are misleading, and that we should instead pay attention to comparative latencies that affect user experience. The graph above compares the performance of [a well constructed `.zshrc` with no plugin manager](https://github.com/romkatv/zsh-bench/blob/3bdd47bece687ec532f19f79c4e4b996e22b2226/configs/diy%2B%2B/skel/.zshrc) to that of a comparable configuration using [`zcomet`](https://github.com/romkatv/zsh-bench/blob/3bdd47bece687ec532f19f79c4e4b996e22b2226/configs/zcomet/skel/.zshrc).
341 |
342 | ## TODO
343 |
344 | * Supply prettier output
345 | * Provide more helpful error messages
346 | * Allow users to update just one repository or snippet
347 | * Improve the `unload` command
348 | * Allow the loading of repositories not on GitHub
349 | * Support for `ssh://` and `git://`
350 |
351 | *Copyright (C) 2021-2024 Alexandros Kozak*
352 |
--------------------------------------------------------------------------------
/benchmarks/benchmarks.txt:
--------------------------------------------------------------------------------
1 | ==> benchmarking zplug ...
2 | creates_tty=1
3 | has_compsys=1
4 | has_syntax_highlighting=1
5 | has_autosuggestions=1
6 | has_git_prompt=1
7 | first_prompt_lag_ms=103.316
8 | first_command_lag_ms=289.683
9 | command_lag_ms=4.768
10 | input_lag_ms=31.034
11 | exit_time_ms=199.023
12 | ==> benchmarking zcomet ...
13 | creates_tty=1
14 | has_compsys=1
15 | has_syntax_highlighting=1
16 | has_autosuggestions=1
17 | has_git_prompt=1
18 | first_prompt_lag_ms=15.212
19 | first_command_lag_ms=133.719
20 | command_lag_ms=4.856
21 | input_lag_ms=30.286
22 | exit_time_ms=43.418
23 | ==> benchmarking diy++ ...
24 | creates_tty=1
25 | has_compsys=1
26 | has_syntax_highlighting=1
27 | has_autosuggestions=1
28 | has_git_prompt=1
29 | first_prompt_lag_ms=15.298
30 | first_command_lag_ms=127.654
31 | command_lag_ms=4.699
32 | input_lag_ms=28.487
33 | exit_time_ms=38.749
34 |
--------------------------------------------------------------------------------
/functions/_zcomet:
--------------------------------------------------------------------------------
1 | #compdef zcomet
2 |
3 | local -a commands
4 | commands=(
5 | 'load:clone and load a plugin'
6 | 'trigger:create a shortcut for loading and running a plugin'
7 | 'snippet:load a snippet of code'
8 | 'unload:unload a prompt or plugin'
9 | 'update:update all plugins and snippets'
10 | 'list:list all loaded plugins and snippets'
11 | 'compile:(re)compile script(s) if necessary'
12 | 'self-update:update zcomet itself'
13 | 'help:print this help text'
14 | 'fpath:clone a repository and add one of its directories to FPATH'
15 | 'compinit:run compinit and compile its cache'
16 | )
17 |
18 | if (( CURRENT == 2 )); then
19 | _describe -t commands 'commands' commands
20 | fi
21 |
22 | return 0
23 |
24 | # vim: ft=zsh
25 |
--------------------------------------------------------------------------------
/functions/zcomet_help:
--------------------------------------------------------------------------------
1 | #autoload
2 |
3 | ############################################################
4 | # Displays help
5 | ############################################################
6 |
7 | print "usage: zcomet command [...]
8 |
9 | compile (re)compile script(s) (only when necessary)
10 | compinit run compinit and compile its cache
11 | fpath clone a plugin and add one of its directories to FPATH
12 | help print this help text
13 | list list all loaded plugins and snippets
14 | load clone and load a plugin
15 | self-update update zcomet itself
16 | snippet load a snippet of code
17 | trigger create a shortcut for loading and running a plugin
18 | unload unload a plugin
19 | update update all plugins and snippets" | fold -s -w $COLUMNS
20 |
21 | # vim: ft=zsh
22 |
--------------------------------------------------------------------------------
/functions/zcomet_list:
--------------------------------------------------------------------------------
1 | #autoload
2 |
3 | ############################################################
4 | # The `list' command
5 | #
6 | # Displays loaded plugins, added FPATH elements, sourced
7 | # snippets, and defined triggers
8 | #
9 | # Arguments:
10 | # None
11 | # Returns:
12 | # 0 if there is anything to report, otherwise 1
13 | ############################################################
14 |
15 | local success plugin abbr
16 |
17 | if (( ${#zsh_loaded_plugins} )); then
18 | success=1
19 | print -P '%B%F{yellow}Plugins:%f%b'
20 | for plugin in ${(o)zsh_loaded_plugins[@]}; do
21 | if [[ $plugin == /* ]]; then
22 | print -n -- " ${plugin}"
23 | else
24 | print -n ' '
25 | print -n -- "${plugin%%/*}/${${plugin#*/}%%/*}"
26 | abbr=${plugin#${plugin%%/*}/${${plugin#*/}%%/*}}
27 | [[ -n $abbr ]] && print -n -- " ${abbr#/}"
28 | fi
29 | print -n -- "${ZCOMET_PLUGINS[$plugin]}"
30 | print
31 | done
32 | fi
33 | (( ${#ZCOMET_FPATH} )) &&
34 | success=1 &&
35 | print -P '%B%F{yellow}FPATH elements:%f%b' &&
36 | print -l -f ' %s\n' "${(o)ZCOMET_FPATH[@]}"
37 | (( ${#ZCOMET_SNIPPETS} )) &&
38 | success=1 &&
39 | print -P '%B%F{yellow}Snippets:%f%b' &&
40 | print -l -f ' %s\n' "${(o)ZCOMET_SNIPPETS[@]}"
41 | (( ${#ZCOMET_TRIGGERS} )) &&
42 | success=1 &&
43 | print -P '%B%F{yellow}Triggers:%f%b' &&
44 | print -n ' '
45 | print -z -f '%s, ' "${(o)ZCOMET_TRIGGERS[@]}"
46 | read -z && print "${REPLY%,}" | fold -s -w $COLUMNS
47 |
48 | (( success )) && return 0 || return 1
49 |
50 | # vim: ft=zsh:ts=2:sts=2:sw=2
51 |
--------------------------------------------------------------------------------
/functions/zcomet_self-update:
--------------------------------------------------------------------------------
1 | #autoload
2 |
3 | ############################################################
4 | # The `self-update' command
5 | #
6 | # If zcomet.zsh is in a Git repository, this command will
7 | # update it and source it afresh.
8 | #
9 | # Outputs:
10 | # Git output; error message
11 | ############################################################
12 |
13 | print -P '%B%F{yellow}zcomet:%f%b '
14 |
15 | if ! command git --git-dir="${${ZCOMET[SCRIPT]}:A:h}/.git" \
16 | --work-tree="${ZCOMET[SCRIPT]:A:h}" pull; then
17 | >&2 print 'Could not self-update.'
18 | return 1
19 | fi
20 |
21 | >&2 print
22 | >&2 print -P '%B%F{yellow}zcomet: Self-update finished.'
23 | >&2 print "You may now run \`exec zsh' to refresh the system."
24 | >&2 print -P '%f%b'
25 |
26 | # vim: ft=zsh
27 |
--------------------------------------------------------------------------------
/functions/zcomet_unload:
--------------------------------------------------------------------------------
1 | #autoload
2 |
3 | ############################################################
4 | # The `unload' command - an attempt at implementing part of
5 | # Sebastian Gniazdowski's Zsh Plugin Standard
6 | # (https://github.com/agkozak/Zsh-100-Commits-Club/blob/master/Zsh-Plugin-Standard.adoc#4-unload-function)
7 | # Globals:
8 | # zsh_loaded_plugins
9 | # Arguments:
10 | # $1 The plugin Repo name
11 | # Outputs:
12 | # Error messages
13 | #
14 | # TODO: This routine needs work.
15 | ############################################################
16 |
17 | [[ $1 != ?*/?* ]] &&
18 | >&2 print 'Specify a plugin to unload.' &&
19 | return 1
20 |
21 | local base_dir
22 | [[ $1 == /* ]] || base_dir="${ZCOMET[REPOS_DIR]}/"
23 |
24 | if (( ${+functions[${1##*/}_plugin_unload]} )) &&
25 | ${1##*/}_plugin_unload; then
26 | typeset -gUa zsh_loaded_plugins
27 | zsh_loaded_plugins=( "${zsh_loaded_plugins[@]:#${1}}" )
28 | fpath=( "${fpath[@]:#${base_dir}${1}}" )
29 | fpath=( "${fpath[@]:#${base_dir}/${1}/functions}" )
30 | path=( "${path[@]:#${base_dir}/${1}/bin}" )
31 | else
32 | >&2 print "${1} cannot be unloaded."
33 | return 1
34 | fi
35 |
36 | # vim: ft=zsh
37 |
--------------------------------------------------------------------------------
/functions/zcomet_update:
--------------------------------------------------------------------------------
1 | #autoload
2 |
3 | ############################################################
4 | # The `update' command
5 | #
6 | # For the time being, updates all plugins and snippets that
7 | # have been downloaded and recompiles their scripts (if
8 | # necessary). Eventually this command will be able to update
9 | # individual plugins and snippets.
10 | #
11 | # Arguments:
12 | # None
13 | # Outputs:
14 | # Informative messages and raw Git, curl, and wget output;
15 | # error message
16 | ############################################################
17 |
18 | setopt EQUALS
19 |
20 | local git_dir file plugin snippet
21 | local -a snippets
22 |
23 | # TODO: Consider having zcomet check to see if the branch has been changed
24 | # in .zshrc or the equivalent.
25 |
26 | for git_dir in ${ZCOMET[REPOS_DIR]}/**/.git(N/); do
27 | print -P "%B%F{yellow}${${git_dir:h}#${ZCOMET[REPOS_DIR]}/}:%f%b "
28 | command git --git-dir=${git_dir} --work-tree=${git_dir:h} pull
29 | if [[ -f ${git_dir:h}/.gitmodules ]]; then
30 | (
31 | cd ${git_dir:h} || exit 1
32 | command git submodule update --recursive
33 | )
34 | fi
35 | for file in "${git_dir:h}"/**/*.zsh(|-theme)(N.) \
36 | "${git_dir:h}"/**/prompt_*_setup(N.); do
37 | _zcomet_compile "$file"
38 | done
39 | done
40 |
41 | snippets=( "${ZCOMET[SNIPPETS_DIR]}"/**/*(N.) )
42 | for file in "${snippets[@]}"; do
43 | snippet=${file#${ZCOMET[SNIPPETS_DIR]}/}
44 | if [[ $snippet == *.zwc ]]; then
45 | continue
46 | elif [[ $snippet == OMZ::* ]]; then
47 | :
48 | elif [[ $snippet == https/* ]]; then
49 | snippet="https:/${snippet#https}"
50 | elif [[ $snippet == http/* ]]; then
51 | snippet="http:/${snippet#http}"
52 | else
53 | >&2 print "Snippet ${file} not supported."
54 | fi
55 | zcomet snippet --update "${snippet}"
56 | _zcomet_compile "$file"
57 | done
58 |
59 | >&2 print
60 | >&2 print -P '%B%F{yellow}zcomet: Update finished.'
61 | >&2 print "You may now run \`exec zsh' to refresh the system."
62 | >&2 print -P '%f%b'
63 |
64 | # vim: ft=zsh:ts=2:sts=2:sw=2
65 |
--------------------------------------------------------------------------------
/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agkozak/zcomet/3dfe6f837479c6731e74400a233739500ec6d648/img/logo.png
--------------------------------------------------------------------------------
/img/mit_license.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/img/social_preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agkozak/zcomet/3dfe6f837479c6731e74400a233739500ec6d648/img/social_preview.png
--------------------------------------------------------------------------------
/img/zsh_4.3.11_plus.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/tests:
--------------------------------------------------------------------------------
1 | zcomet snippet https://github.com/willghatch/zsh-saneopt/blob/master/saneopt.plugin.zsh
2 |
3 | zcomet unload agkozak/zsh-z
4 | zcomet snippet https://github.com/rupa/z/blob/master/z.sh
5 |
6 | [[ ! -o ksharrays && ! -o shwordsplit ]] && {
7 | zcomet unload agkozak/agkozak-zsh-prompt
8 | zcomet load sindresorhus/pure async.zsh pure.zsh
9 | }
10 |
11 | zcomet load http://github.com/oldratlee/hacker-quotes.git
12 |
13 | zcomet trigger hist marlonrichert/zsh-hist
14 |
15 | zcomet fpath https://github.com/zsh-users/zsh-completions
16 |
17 | zcomet load ohmyzsh lib git.zsh
18 |
19 | zcomet load prezto modules/archive
20 |
--------------------------------------------------------------------------------
/zcomet.zsh:
--------------------------------------------------------------------------------
1 | # zcomet Zsh Plugin Manager
2 | #
3 | # https://github.com/agkozak/zcomet
4 | #
5 | # MIT License / Copyright (c) 2021-2024 Alexandros Kozak
6 |
7 | typeset -gA ZCOMET
8 |
9 | ZCOMET[SCRIPT]=$0
10 |
11 | autoload -Uz is-at-least
12 | if ! is-at-least 4.3.11; then
13 | zcomet() {
14 | >&2 print 'zcomet only supports Zsh v4.3.11+.'
15 | return 1
16 | }
17 | zcomet; return 1
18 | fi
19 |
20 | # Add zcomet functions to FPATH and autoload some things
21 | fpath=( "${ZCOMET[SCRIPT]:A:h}/functions" "${fpath[@]}" )
22 | autoload -Uz add-zsh-hook \
23 | zcomet_{unload,update,list,self-update,help}
24 |
25 | # Global Parameter holding the plugin-manager’s capabilities
26 | # https://github.com/agkozak/Zsh-100-Commits-Club/blob/master/Zsh-Plugin-Standard.adoc#9-global-parameter-holding-the-plugin-managers-capabilities
27 | typeset -g PMSPEC
28 | PMSPEC='0fbuiPs'
29 |
30 | ############################################################
31 | # Compile scripts to wordcode or recompile them when they
32 | # have changed.
33 | # Arguments:
34 | # Files to compile or recompile
35 | #
36 | # Adapted from Zim's old zcompare function. Still appears to
37 | # be faster than using zrecompile.
38 | ############################################################
39 | _zcomet_compile() {
40 | while (( $# )); do
41 | # Only zcompile if there isn't already a .zwc file or the .zwc is outdated,
42 | # and never compile zsh-syntax-highlighting's test data
43 | if [[ -s $1 && $1 != *.zwc &&
44 | ( ! -s ${1}.zwc || $1 -nt ${1}.zwc ) &&
45 | $1 != *.zwc &&
46 | $1 != */test-data/* ]]; then
47 | # Autoloadable functions
48 | if [[ $1 == ${ZCOMET[SCRIPT]:A:h}/functions/zcomet_* ||
49 | ${1:t} == prompt_*_setup ||
50 | ${1:t} == _* ]]; then
51 | builtin zcompile -Uz "$1"
52 | # Scripts to be sourced
53 | else
54 | builtin zcompile -UR "$1"
55 | fi
56 | fi
57 | shift
58 | done
59 | }
60 |
61 | ############################################################
62 | # Allows the user to employ the shorthand `ohmyzsh' for the
63 | # ohmyzsh/ohmyzsh repo and `prezto' for
64 | # sorin-ionescu/prezto
65 | # Globals:
66 | # REPLY
67 | # Arguments:
68 | # $1 A repo or its shorthand
69 | # Outputs:
70 | # The repo
71 | ############################################################
72 | _zcomet_repo_shorthand() {
73 | if [[ $1 == http(|s)://${ZCOMET[GITSERVER]}/* ]]; then
74 | typeset -g REPLY=${1#http(|s):\/\/${ZCOMET[GITSERVER]}/}
75 | if [[ $REPLY == *.git ]]; then
76 | typeset -g REPLY=${REPLY%.git}
77 | elif [[ $REPLY == *.git@* ]]; then
78 | typeset -g REPLY=${REPLY/.git@/@}
79 | fi
80 | elif [[ $1 == 'ohmyzsh' ]]; then
81 | typeset -g REPLY='ohmyzsh/ohmyzsh'
82 | elif [[ $1 == 'prezto' ]]; then
83 | typeset -g REPLY='sorin-ionescu/prezto'
84 | else
85 | typeset -g REPLY=$1
86 | fi
87 | }
88 |
89 | ############################################################
90 | # Allows the user to use the shorthand OMZ:: for Oh-My-Zsh
91 | # snippets or an https://github.com address that gets
92 | # translated into https://raw.githubuser.com; otherwise, a
93 | # simple URL of raw shell code.
94 | # Globals:
95 | # REPLY
96 | # Arguments:
97 | # $1 A URL to raw code, a normative github.com URL, or
98 | # shorthand
99 | # Outputs:
100 | # A URL to raw code
101 | ############################################################
102 | _zcomet_snippet_shorthand() {
103 | if [[ $1 == OMZ::* ]]; then
104 | typeset -g REPLY="https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/${1#OMZ::}"
105 | elif [[ $1 == https://github.com/* ]]; then
106 | typeset -g REPLY=${${1/github/raw.githubusercontent}/\/blob/}
107 | else
108 | typeset -g REPLY=$1
109 | fi
110 | }
111 |
112 | ############################################################
113 | # Captures `compdef' calls that will actually be run
114 | # after `zcomet compinit' is run.
115 | # Globals:
116 | # ZCOMET_COMPDEFS
117 | ############################################################
118 | compdef() {
119 | setopt NO_WARN_NESTED_VAR 2> /dev/null
120 |
121 | typeset -gUa ZCOMET_COMPDEFS
122 | ZCOMET_COMPDEFS=( "${ZCOMET_COMPDEFS[@]}" "$*" )
123 | }
124 |
125 | ############################################################
126 | # This function loads plugins that have already been
127 | # cloned. Loading consists of sourcing a main file or
128 | # adding the root directory or a /functions/ subdirectory
129 | # to FPATH or both.
130 | # Globals:
131 | # ZCOMET
132 | # Arguments:
133 | # A repo
134 | # A subdirectory [Optional]
135 | # A specific file to be sourced [Optional]
136 | # Returns:
137 | # 0 if a file is successfully sourced or an element is
138 | # added to FPATH; otherwise 1
139 | # Outputs:
140 | # Error messages
141 | ############################################################
142 | _zcomet_load() {
143 | typeset repo base_path subdir file plugin_path plugin_name plugin_loaded
144 | typeset -a files
145 | _zcomet_repo_shorthand "$1"
146 | repo=$REPLY
147 | shift
148 |
149 | if [[ $repo == /* ]]; then
150 | base_path=$repo
151 | else
152 | base_path="${ZCOMET[REPOS_DIR]}/${repo}"
153 | fi
154 |
155 | if [[ -n $1 ]]; then
156 | if [[ -f ${base_path}/$1 ]]; then
157 | files=( "$@" )
158 | set --
159 | elif [[ -d ${base_path}/$1 ]]; then
160 | subdir=$1 && shift
161 | (( $# )) && files=( "$@" )
162 | set --
163 | else
164 | >&2 print "zcomet: ${repo}: invalid arguments." && return 1
165 | fi
166 | fi
167 | plugin_path=${base_path}${subdir:+/${subdir}}
168 |
169 | # Add repo dir or the functions/ subdirectory to FPATH
170 | local dir fpath_added prezto_style
171 | if [[ -d ${plugin_path}/functions ]]; then
172 | dir="${plugin_path}/functions"
173 | prezto_style=1
174 | elif [[ -d $plugin_path ]]; then
175 | dir=$plugin_path
176 | else
177 | >&2 print "zcomet: ${plugin_path} does not appear to be a directory."
178 | return 1
179 | fi
180 |
181 | if (( ! ${fpath[(Ie)${dir}]} )); then
182 | fpath=( "$dir" "${fpath[@]}" )
183 | if (( ! ${#files} )); then
184 | _zcomet_add_list load "${repo}${subdir:+/${subdir}}" && fpath_added=1
185 | fi
186 | fi
187 |
188 | # Autoload prezto-style functions
189 | if (( prezto_style )); then
190 | () {
191 | setopt LOCAL_OPTIONS EXTENDED_GLOB
192 |
193 | local zfunction
194 |
195 | for zfunction in "${dir}"/^(*~|*.zwc(|.old)|_*|prompt_*_setup)(N-.:t); do
196 | autoload -Uz -- $zfunction
197 | done
198 | }
199 | fi
200 |
201 | if (( ${#files} )); then
202 | for file in "${files[@]}"; do
203 | if zsh_loaded_plugins+=( ${repo}${subdir:+/${subdir}} ) \
204 | ZERO="${plugin_path}/${file}" \
205 | source "${plugin_path}/${file}"; then
206 | (( ZCOMET[DEBUG] )) && >&2 print "Sourced ${file}."
207 | _zcomet_add_list load "${repo}${subdir:+/${subdir}}" "$file" &&
208 | plugin_loaded=1
209 | else
210 | return $?
211 | fi
212 | done
213 | else
214 | plugin_name=${${subdir:+${subdir##*/}}:-${repo##*/}}
215 | files=(
216 | "${plugin_path}/${plugin_name}.zsh-theme"(N.)
217 | "${plugin_path}/${plugin_name}.plugin.zsh"(N.)
218 | "${plugin_path}/${plugin_name}.zsh"(N.)
219 | )
220 | if ! (( ${#files} )); then
221 | files+=(
222 | "${plugin_path}"/*.zsh-theme(N.)
223 | "${plugin_path}"/*.plugin.zsh(N.)
224 | "${plugin_path}"/init.zsh(N.)
225 | "${plugin_path}"/*.zsh(N.)
226 | "${plugin_path}"/*.sh(N.)
227 | )
228 | fi
229 | file=${files[@]:0:1}
230 |
231 | if [[ -n $file ]]; then
232 | if zsh_loaded_plugins+=( ${repo}${subdir:+/${subdir}} )\
233 | ZERO=$file \
234 | source "$file"; then
235 | (( ZCOMET[DEBUG] )) && >&2 print "Sourced ${file:t}."
236 | _zcomet_add_list load "${repo}${subdir:+/${subdir}}" && plugin_loaded=1
237 | else
238 | >&2 print "Cannot source ${file}."
239 | return 1
240 | fi
241 | fi
242 | fi
243 |
244 | # Add the bin/ subdirectory, if it exists, to PATH
245 | if [[ -d ${base_path}/bin ]]; then
246 | if (( ! ${path[(Ie)${base_path}/bin]} )); then
247 | path=( "${base_path}/bin" "${path[@]}" )
248 | (( ! plugin_added && ! fpath_added )) &&
249 | _zcomet_add_list load "${repo}${subdir:+ ${subdir}}"
250 | fi
251 | fi
252 | }
253 |
254 | ############################################################
255 | # Manage the arrays used when running `zcomet list'
256 | # Globals:
257 | # zsh_loaded_plugins
258 | # ZCOMET_FPATH
259 | # ZCOMET_SNIPPETS
260 | # ZCOMET_TRIGGERS
261 | # Arguments:
262 | # $1 The command being run (load/snippet/trigger)
263 | # $2 Repository and optional subpackage, e.g.,
264 | # ohmyzsh/ohmyzsh plugins/extract
265 | ############################################################
266 | _zcomet_add_list() {
267 | setopt NO_WARN_NESTED_VAR 2> /dev/null
268 |
269 | if [[ $1 == 'load' ]]; then
270 | zsh_loaded_plugins=( "${zsh_loaded_plugins[@]}" "$2" )
271 | [[ -n $3 ]] && ZCOMET_PLUGINS[$2]+=" $3"
272 | elif [[ $1 == 'fpath' ]]; then
273 | ZCOMET_FPATH=( "${ZCOMET_FPATH[@]}" "$2" )
274 | elif [[ $1 == 'snippet' ]]; then
275 | ZCOMET_SNIPPETS=( "${ZCOMET_SNIPPETS[@]}" "$2" )
276 | elif [[ $1 == 'trigger' ]]; then
277 | ZCOMET_TRIGGERS=( "${ZCOMET_TRIGGERS[@]}" "$2" )
278 | fi
279 | }
280 |
281 | ############################################################
282 | # Checks to make sure that the user has provided a valid
283 | # plugin name
284 | #
285 | # Arguments:
286 | # The supposed plugin
287 | ############################################################
288 | _zcomet_is_valid_plugin() {
289 | [[ $1 == ?*/?* ||
290 | $1 == 'ohmyzsh' ||
291 | $1 == 'prezto' ||
292 | $1 == /* ]]
293 | }
294 |
295 | ############################################################
296 | # Clone a repository, switch to a branch/tag/commit if
297 | # requested, and compile the scripts
298 | # Globals:
299 | # ZCOMET
300 | # Arguments:
301 | # --no-submodules [Optional] Do not clone submodules
302 | # The repository and branch/tag/commit
303 | ############################################################
304 | _zcomet_clone_repo() {
305 | local submodules=1
306 | if [[ $1 == '--no-submodules' ]]; then
307 | submodules=0
308 | shift
309 | fi
310 |
311 | [[ $1 == ?*/?* || $1 == 'ohmyzsh' || $1 == 'prezto' ]] || return 1
312 | local repo branch repo_dir ret file
313 | _zcomet_repo_shorthand "${1%@*}"
314 | repo=$REPLY
315 | repo_dir="${ZCOMET[REPOS_DIR]}/${repo}"
316 | [[ $1 == *@* ]] && branch=${1#*@}
317 |
318 | [[ -d $repo_dir ]] && return
319 |
320 | print -P "%B%F{yellow}Cloning ${repo}:%f%b"
321 | if ! command git clone "https://${ZCOMET[GITSERVER]}/${repo}" "$repo_dir"; then
322 | ret=$?
323 | >&2 print "Could not clone repository ${repo}."
324 | return $ret
325 | fi
326 | if [[ -n $branch ]] && ! command git --git-dir="${repo_dir}/.git" \
327 | --work-tree=$repo_dir checkout -q "$branch"; then
328 | ret=$?
329 | >&2 print "Could not checkout \`${branch}'."
330 | return $ret
331 | fi
332 | if (( submodules )) && [[ -e ${repo_dir}/.gitmodules ]]; then
333 | (
334 | if ! cd $repo_dir ||
335 | ! git submodule update --init --recursive; then
336 | ret=$?
337 | >&2 print 'Could not initialize and update submodule(s).'
338 | return $ret
339 | fi
340 | )
341 | fi
342 | for file in "${repo_dir}"/**/*.zsh(|-theme)(N.) \
343 | "${repo_dir}"/**/prompt_*_setup(N.); do
344 | _zcomet_compile "$file"
345 | done
346 | }
347 |
348 | ############################################################
349 | # The `load' command
350 | #
351 | # Clones a repo (if necessary). Sources init file(s) or adds
352 | # one of its directories to FPATH or both.
353 | #
354 | # Arguments:
355 | # --no-submodules [Optional] Do not clone submodules
356 | # The repo A GitHub repository, in the format
357 | # user/repo@branch, where @branch could
358 | # also be a tag or a commit.
359 | # Subdirectory [Optional] A subdirectory of a larger
360 | # repo
361 | # Script(s) [Optional] A list of specific scripts to
362 | # source
363 | # Outputs:
364 | # Confirmation and error messages, plus raw Git output
365 | # (for the time being)
366 | ############################################################
367 | _zcomet_load_command() {
368 | local clone_options
369 | [[ $1 == '--no-submodules' ]] && clone_options=$1 && shift
370 |
371 | if ! _zcomet_is_valid_plugin "$1"; then
372 | >&2 print 'You need to specify a valid plugin name.' && return 1
373 | fi
374 |
375 | local repo_branch
376 | repo_branch=$1
377 | shift
378 |
379 | # Don't try to clone local plugins
380 | if [[ $repo_branch != /* ]]; then
381 | _zcomet_clone_repo ${clone_options} "$repo_branch" || return $?
382 | # Do keep local plugins zcompiled
383 | else
384 | for file in "${repo_branch}"/**/*.zsh(|-theme)(N.) \
385 | "${repo_branch}"/**/prompt_*_setup(N.); do
386 | _zcomet_compile "$file"
387 | done
388 | fi
389 | _zcomet_load "${repo_branch%@*}" "$@"
390 | }
391 |
392 | ############################################################
393 | # The `fpath' command
394 | #
395 | # Clones a repo (if necessary). Adds one of its
396 | # subdirectories to FPATH. This command does not try to
397 | # guess which directory to add; it must be made explicit.
398 | #
399 | # Arguments:
400 | # --no-submodules Do not clone submodules
401 | # The repo A Git repository
402 | # (username/repo@branch/tag/commit), as
403 | # with `load'.
404 | # A subdirectory [Optional] A subdirectory within the
405 | # repo to add to FPATH
406 | # Output:
407 | # Raw Git output and error messages
408 | ############################################################
409 | _zcomet_fpath_command() {
410 | local clone_options
411 | [[ $1 == '--no-submodules' ]] && clone_options=$1 && shift
412 |
413 | if ! _zcomet_is_valid_plugin "$1"; then
414 | >&2 print 'You need to specify a valid plugin name.' && return 1
415 | fi
416 |
417 | local repo_branch plugin_path
418 | repo_branch=$1 && shift
419 |
420 | # Don't clone local plugins
421 | if [[ $repo_branch != /* ]]; then
422 | _zcomet_clone_repo ${clone_options} "$repo_branch" || return $?
423 | fi
424 |
425 | _zcomet_repo_shorthand "${repo_branch%@*}"
426 | repo_branch=$REPLY
427 |
428 | if [[ $repo_branch == /* ]]; then
429 | plugin_path="${repo_branch}${1:+/${1}}"
430 | else
431 | plugin_path="${ZCOMET[REPOS_DIR]}/${repo_branch}${1:+/${1}}"
432 | fi
433 |
434 | [[ ! -d $plugin_path ]] && local ret=$? && >&2 print 'Invalid directory.' &&
435 | return $ret
436 | if (( ! ${fpath[(Ie)${plugin_path}]} )); then
437 | fpath=( "${plugin_path}" "${fpath[@]}" )
438 | _zcomet_add_list "$cmd" "$repo_branch${@:+ $@}"
439 | fi
440 | }
441 |
442 | ############################################################
443 | # The `snippet' command
444 | #
445 | # Downloads a script (if it has not already been
446 | # downloaded) using either curl or wget. Sources it. This
447 | # command understands how to translate normative Github URLs
448 | # into raw code (and includes the OMZ:: shorthand for
449 | # Oh-My-Zsh scripts); otherwise you must make sure that the
450 | # snippet you direct it to is genuinely Zsh code and not a
451 | # pretty HTML representation of it.
452 | #
453 | # Arguments:
454 | # --update The script is being updated; don't source it
455 | # for now
456 | # $1 The snippet
457 | # Outputs:
458 | # Informative messages, raw curl or wget output, error
459 | # messages
460 | ############################################################
461 | _zcomet_snippet_command() {
462 | [[ -z $1 ]] && print 'You need to specify a snippet.' && return 1
463 |
464 | local update snippet url method snippet_file snippet_dir ret temp_dir
465 |
466 | [[ $1 == '--update' ]] && update=1 && shift
467 | snippet=$1 && shift
468 |
469 | # Local snippets
470 | if [[ $snippet != http(|s)://* && $snippet != OMZ::* ]]; then
471 | snippet=${snippet/\~/${HOME}}
472 | _zcomet_compile "$snippet"
473 | if [[ -f $snippet ]] && ZERO=$snippet source $snippet; then
474 | _zcomet_add_list "$cmd" "${${snippet:a}/${HOME}/~}"
475 | return
476 | fi
477 | >&2 print "Could not source snippet ${snippet}."
478 | return 1
479 | fi
480 |
481 | # Remote snippets
482 | _zcomet_snippet_shorthand "$snippet"
483 | url=$REPLY
484 | snippet_file=${snippet##*/}
485 | snippet_dir=${snippet%/*}
486 | snippet_dir="${ZCOMET[SNIPPETS_DIR]}/${snippet_dir/:\//}"
487 | temp_dir="/tmp/zcomet/$$/${snippet_dir}"
488 |
489 | if [[ ! -f ${snippet_dir}/${snippet_file} ]] ||
490 | (( update )); then
491 | if [[ ! -d ${temp_dir} ]]; then
492 | command mkdir -p "${temp_dir}"
493 | fi
494 | print -P "%B%F{yellow}Downloading snippet ${snippet}:%f%b"
495 | if (( ${+commands[curl]} )); then
496 | method='curl'
497 | curl "$url" > "${temp_dir}/${snippet_file}"
498 | ret=$?
499 | elif (( ${+commands[wget]} )); then
500 | method='wget'
501 | wget "$url" -O "${temp_dir}/${snippet_file}"
502 | ret=$?
503 | else
504 | >&2 print "You need \`curl' or \`wget' to download snippets."
505 | return 1
506 | fi
507 | if (( ret == 0 )); then
508 | if [[ ! -d ${snippet_dir} ]]; then
509 | command mkdir -p "${snippet_dir}"
510 | fi
511 | command mv "${temp_dir}/${snippet_file}" "${snippet_dir}" &&
512 | _zcomet_compile "${snippet_dir}/${snippet_file}"
513 | command rm -rf "/tmp/zcomet/$$"
514 | else
515 | >&2 print "Could not ${method} snippet ${snippet}."
516 | fi
517 | fi
518 |
519 | (( update )) && return
520 |
521 | if ZERO="${snippet_dir}/${snippet_file}" \
522 | source "${snippet_dir}/${snippet_file}"; then
523 | _zcomet_add_list "$cmd" "$snippet"
524 | else
525 | ret=$?
526 | >&2 print "Could not source snippet ${snippet}."
527 | return $ret
528 | fi
529 | }
530 |
531 | ############################################################
532 | # The `trigger' command - lazy-loading plugins
533 | #
534 | # This command creates one or more functions that will, at
535 | # some point in the future, `load` a plugin and the run a
536 | # command from that plugin. When any of the triggers that
537 | # have been defined is pulled, all of the triggers for that
538 | # plugin are unfunctioned to make way for their namesake
539 | # commands. By putting off loading a plugin until it is
540 | # is needed, precious shell startup time can be conserved.
541 | #
542 | # Arguments:
543 | # --no-submodules [Optional] Do not clone submodules
544 | # Trigger(s) Commands that will load a plugin and
545 | # then run plugin commands of the same
546 | # name
547 | # Repo A GitHub repository; uses the same
548 | # format as `load` and `fpath`
549 | #
550 | # TODO: Add some way to pre-clone repos to be triggered in
551 | # the future so that the cloning process doesn't slow the
552 | # user down.
553 | ############################################################
554 | _zcomet_trigger_command() {
555 | local clone_options
556 | [[ $1 == '--no-submodules' ]] && clone_options=$1 && shift
557 |
558 | [[ -z $1 ]] && >&2 print 'You need to name a trigger.' && return 1
559 |
560 | local -Ua triggers
561 | local trigger
562 |
563 | while [[ -n $1 ]] && ! _zcomet_is_valid_plugin "$1"; do
564 | triggers+=( "$1" )
565 | shift
566 | done
567 |
568 | # Don't clone local plugins
569 | # TODO: Check to make sure local plugins exist?
570 | if [[ $1 != /* ]]; then
571 | _zcomet_clone_repo ${clone_options} "$@"
572 | fi
573 |
574 | for trigger in "${triggers[@]}"; do
575 | functions[$trigger]="local trigger;
576 | for trigger in ${triggers[@]};
577 | do
578 | ZCOMET_TRIGGERS=( "\${ZCOMET_TRIGGERS[@]:#\${trigger}}" );
579 | done
580 | unfunction ${triggers[@]};
581 | zcomet load $clone_options $@;
582 | eval $trigger \$@" && _zcomet_add_list "$cmd" "$trigger"
583 | done
584 |
585 | _zcomet_repo_shorthand $1
586 | 1=$REPLY
587 |
588 | local base_dir
589 | if [[ $1 == /* ]]; then
590 | base_dir=$1
591 | else
592 | base_dir="${ZCOMET[REPOS_DIR]}/${1%@*}"
593 | fi
594 | }
595 |
596 | ############################################################
597 | # The main command
598 | # Globals:
599 | # ZCOMET
600 | # zsh_loaded_plugins
601 | # ZCOMET_PLUGINS
602 | # ZCOMET_SNIPPETS
603 | # ZCOMET_TRIGGERS
604 | # ZCOMET_NAMED_DIRS
605 | # Arguments:
606 | # load [...]
607 | # fpath [...]
608 | # trigger
610 | # update
611 | # unload
612 | # list
613 | # compile
614 | # compinit
615 | # help
616 | # Outputs:
617 | # Status updates
618 | ############################################################
619 | zcomet() {
620 | typeset -gUa zsh_loaded_plugins ZCOMET_FPATH ZCOMET_SNIPPETS ZCOMET_TRIGGERS
621 | typeset -gUA ZCOMET_PLUGINS
622 |
623 | typeset -g REPLY
624 |
625 | # Allow the user to specify custom directories
626 | local home_dir repos_dir snippets_dir git_server
627 |
628 | # E.g., zstyle ':zcomet:*' home-dir ~/.my_dir
629 | if zstyle -s :zcomet: home-dir home_dir; then
630 | ZCOMET[HOME_DIR]=$home_dir
631 | else
632 | : ${ZCOMET[HOME_DIR]:=${ZDOTDIR:-${HOME}}/.zcomet}
633 | fi
634 |
635 | if zstyle -s :zcomet: repos-dir repos_dir; then
636 | ZCOMET[REPOS_DIR]=$repos_dir
637 | else
638 | : ${ZCOMET[REPOS_DIR]:=${ZCOMET[HOME_DIR]}/repos}
639 | fi
640 |
641 | if zstyle -s :zcomet: snippets-dir snippets_dir; then
642 | ZCOMET[SNIPPETS_DIR]=$snippets_dir
643 | else
644 | : ${ZCOMET[SNIPPETS_DIR]:=${ZCOMET[HOME_DIR]}/snippets}
645 | fi
646 |
647 | if zstyle -s :zcomet: gitserver git_server; then
648 | ZCOMET[GITSERVER]=$gitserver
649 | else
650 | ZCOMET[GITSERVER]='github.com'
651 | fi
652 |
653 | # Global parameter with PREFIX for make, configure, etc.
654 | # https://github.com/agkozak/Zsh-100-Commits-Club/blob/master/Zsh-Plugin-Standard.adoc#8-global-parameter-with-prefix-for-make-configure-etc
655 | [[ -z $ZPFX ]] && {
656 | typeset -gx ZPFX
657 | : ${ZPFX:=${ZCOMET[HOME_DIR]}/polaris}
658 | [[ -z ${path[(re)${ZPFX}/bin]} ]] &&
659 | [[ -d "${ZPFX}/bin" ]] &&
660 | path=( "${ZPFX}/bin" "${path[@]}" )
661 | [[ -z ${path[(re)${ZPFX}/sbin]} ]] &&
662 | [[ -d "${ZPFX}/sbin" ]] &&
663 | path=( "${ZPFX}/sbin" "${path[@]}" )
664 | }
665 |
666 | local cmd
667 | [[ -n $1 ]] && cmd=$1 && shift
668 |
669 | case $cmd in
670 | load|fpath|snippet|trigger)
671 | _zcomet_${cmd}_command "$@"
672 | ;;
673 | unload|update|list|self-update) zcomet_$cmd "$@" ;;
674 | compinit)
675 | autoload -Uz compinit
676 |
677 | if [[ $TERM != 'dumb' ]]; then
678 | () {
679 | setopt LOCAL_OPTIONS EQUALS EXTENDED_GLOB
680 |
681 | local dump_file
682 | zstyle -s ':zcomet:compinit' dump-file dump_file
683 | if [[ -n $dump_file ]]; then
684 | typeset -g _comp_dumpfile=$dump_file
685 | else
686 | typeset -g _comp_dumpfile="${ZDOTDIR:-${HOME}}/.zcompdump_${EUID}_${OSTYPE}_${ZSH_VERSION}"
687 | fi
688 |
689 | local -a compinit_opts
690 | zstyle -a ':zcomet:compinit' arguments compinit_opts
691 | compinit -d "$_comp_dumpfile" ${compinit_opts[@]}
692 |
693 | # Run compdef calls that were deferred earlier
694 | local def
695 | for def in "${ZCOMET_COMPDEFS[@]}"; do
696 | [[ -n $def ]] && compdef ${=def}
697 | done
698 | (( ${+ZCOMET_COMPDEFS} )) && unset ZCOMET_COMPDEFS
699 |
700 | # Compile the dumpfile
701 | ( _zcomet_compile "$_comp_dumpfile" ) &!
702 | }
703 | fi
704 | ;;
705 | compile)
706 | if [[ -z $1 ]]; then
707 | >&2 print 'Which script(s) would you like to zcompile?'
708 | return 1
709 | fi
710 | _zcomet_compile "$@"
711 | ;;
712 | -h|--help|help) zcomet_help "$@" ;;
713 | *)
714 | zcomet_help
715 | return 1
716 | ;;
717 | esac
718 | }
719 |
720 | () {
721 | setopt LOCAL_OPTIONS EXTENDED_GLOB
722 |
723 | _zcomet_compile "${ZCOMET[SCRIPT]}" \
724 | "${ZCOMET[SCRIPT]:A:h}"/functions/zcomet_*~*.zwc(N.)
725 | }
726 |
727 | ############################################################
728 | # zcomet's plugin directories are dynamic named
729 | # directories - an idea inspired by Marlon Richert's Znap.
730 | #
731 | # Note that if two repos of the same name appear under the
732 | # ${ZCOMET[REPOS_DIR]} directory, neither will be assigned a
733 | # name -- the idea being to prevent terrible mistakes.
734 | ############################################################
735 | _zcomet_named_dirs() {
736 | emulate -L zsh
737 |
738 | typeset -ga reply
739 | local -a dirs names
740 | local expl
741 |
742 | if [[ $1 == 'n' ]]; then
743 | [[ $2 == 'zcomet-bin' ]] && reply=( ${ZCOMET[SCRIPT]:A:h} ) && return 0
744 | dirs=( ${ZCOMET[REPOS_DIR]}/*/$2(N/) )
745 | (( ${#dirs} != 1 )) && return 1
746 | reply=( ${dirs[1]} ) && return 0
747 | elif [[ $1 == 'd' ]]; then
748 | if [[ $2 == ${ZCOMET[SCRIPT]:A:h} ]]; then
749 | reply=( 'zcomet-bin' ${#2} )
750 | return 0
751 | elif [[ ${${2:h}:h} == ${ZCOMET[REPOS_DIR]} ]]; then
752 | dirs=( ${ZCOMET[REPOS_DIR]}/*/${2:t}(N/) )
753 | (( ${#dirs} != 1 )) && return 1
754 | reply=( ${2:t} ${#2} )
755 | return 0
756 | fi
757 | return 1
758 | elif [[ $1 == 'c' ]]; then
759 | dirs=( ${ZCOMET[REPOS_DIR]}/*/*(N/) )
760 | names=( ${dirs:t} )
761 | local -A names_tally
762 | local name
763 | for name in $names; do
764 | (( names_tally[$name]++ ))
765 | done
766 | name=''
767 | names=()
768 | for name in ${(k)names_tally}; do
769 | (( names_tally[$name] == 1 )) && names+=( $name )
770 | done
771 | _tags named-directories
772 | _tags && _requested named-directories expl 'dynamic named directories' &&
773 | compadd $expl -S\] -- $names 'zcomet-bin'
774 | return 1
775 | fi
776 | }
777 |
778 | # The zsh_directory_name hook did not appear till Zsh v4.3.12, so for v4.3.11
779 | # we'll just have to use the zsh_directory_name function directly
780 | if is-at-least 4.3.12; then
781 | add-zsh-hook zsh_directory_name _zcomet_named_dirs
782 | else
783 | zsh_directory_name() {
784 | _zcomet_named_dirs $@
785 | }
786 | fi
787 |
--------------------------------------------------------------------------------