├── .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 | [![MIT License](img/mit_license.svg)](https://opensource.org/licenses/MIT) 8 | ![ZSH version 4.3.11 and higher](img/zsh_4.3.11_plus.svg) 9 | [![GitHub stars](https://img.shields.io/github/stars/agkozak/zcomet.svg)](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 | ![Latencies in Milliseconds](https://raw.githubusercontent.com/agkozak/zcomet-media/main/latencies.png) 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 | licenselicenseMITMIT -------------------------------------------------------------------------------- /img/social_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agkozak/zcomet/3dfe6f837479c6731e74400a233739500ec6d648/img/social_preview.png -------------------------------------------------------------------------------- /img/zsh_4.3.11_plus.svg: -------------------------------------------------------------------------------- 1 | zshzsh4.3.11+4.3.11+ -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------