Repository: andreyorst/plug.kak Branch: master Commit: aa31e3bd89aa Files: 4 Total size: 48.2 KB Directory structure: gitextract_em9e3vjj/ ├── LICENSE ├── README.md └── rc/ ├── plug.kak └── plug.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Andrey Orst Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Project status Because I have stopped using Kakoune, this project will not receive active maintenance. The issues section has been closed, as I do not plan to address any issues myself. However, I welcome pull requests from anyone who encounters problems with this plugin and knows how to resolve them. # plug.kak [![GitHub issues][1]][2] ![license][3] ![plug.kak][4] **plug.kak** is a plugin manager for Kakoune, which was inspired by [vim-plug][5] and [use-package][6]. It can install and update plugins, run post-update actions, and help to encapsulate the configuration within itself. ## Installation **plug.kak** can be installed anywhere in your system, but to update itself, it is required to install **plug.kak** in the plugin installation directory. By default, **plug.kak** installs plugins to the `%val{config}/plugins`, which is usually at `$HOME/.config/kak/plugins`: ``` sh mkdir -p $HOME/.config/kak/plugins git clone https://github.com/andreyorst/plug.kak.git $HOME/.config/kak/plugins/plug.kak ``` Now, when **plug.kak** is installed, we need to tell Kakoune about it. Add this to the `kakrc` file: ``` kak source "%val{config}/plugins/plug.kak/rc/plug.kak" plug "andreyorst/plug.kak" noload ``` Alternatively, this process can be automated by adding the following snippet to the `kakrc`: ``` sh evaluate-commands %sh{ plugins="$kak_config/plugins" mkdir -p "$plugins" [ ! -e "$plugins/plug.kak" ] && \ git clone -q https://github.com/andreyorst/plug.kak.git "$plugins/plug.kak" printf "%s\n" "source '$plugins/plug.kak/rc/plug.kak'" } plug "andreyorst/plug.kak" noload ``` This will create all needed directories on Kakoune launch, and download **plug.kak** if it is not installed already. **Note**: `plug "andreyorst/plug.kak" noload` is needed to register **plug.kak** as a manually loaded plugin, so `plug-clean` will not delete **plug.kak**. ## Usage All plugins are installed and loaded with the `plug` command. This command accepts one or more arguments, which are keywords and attributes, that change how **plug.kak** behaves. The first strict rule of the `plug` command is that the first argument is always the plugin name formatted as in GitHub URL: `"author/repository"`. ``` kak plug "author/repository" ``` By default **plug.kak** will look for the plugin at GitHub.com and download it. When the plugin is hosted on a different service, a URL can be used as the first argument. So, in most cases, it is enough to add this to the `kakrc` to use a plugin: ```kak plug "delapouite/kakoune-text-objects" ``` Or with URL: ```kak plug "https://gitlab.com/Screwtapello/kakoune-inc-dec" ``` After adding this, `kakrc` needs to be re-sourced to let **plug.kak** know that the configuration was changed. Alternatively, Kakoune can be restarted. After that, newly added plugins can be installed with the `plug-install` command. More information about other commands available in the [Commands](#Commands) section. ### Keywords and attributes The `plug` command accepts optional attributes that change how **plug.kak** works, or add additional steps for `plug` to perform. These keywords are supported: - [branch, tag, commit](#branch-tag-or-commit) - [load-path](#loading-plugin-from-a-different-path) - [noload](#skipping-loading-of-a-plugin) - [do](#automatically-do-certain-tasks-on-install-or-update) - [theme](#installing-color-schemes) - [config](#handling-user-configurations) - [defer](#deferring-plugin-configuration) - [demand](#demanding-plugin-module-configuration) - [ensure](#ensuring-that-plugins-are-installed) #### Branch, Tag, or Commit `plug` can checkout a plugin to the desired branch, commit, or tag before loading it. It can be done by adding the following keywords with parameters: `branch "branch_name"`, `tag "tag_name"` or `commit "commit_hash"`. #### Loading plugin from a different path Plugins can be loaded from an arbitrary path by specifying the `load-path` keyword and providing the path as an argument: ``` kak plug "plugin_name" load-path "~/Development/plugin_dir" ``` However, all `plug` related commands, like `plug-update` or `plug-clean`, will not work for plugins that aren't installed to `plug_install_dir`. #### Skipping loading of a plugin If a plugin needs to be loaded manually, the `noload` keyword can be used. This can also be used to avoid loading the plugin a second time, like in the example with **plug.kak** from the [installation](#installation) section: ```kak source "%val{config}/plugins/plug.kak/rc/plug.kak" plug "andreyorst/plug.kak" noload ``` Note that plugins with the `noload` keyword are still configured and managed. See [handling-user-configuration](#handling-user-configurations) for more details. #### Automatically do certain tasks on install or update When the plugin requires some additional steps to perform after installation or update, the `do` keyword can be used. This keyword expects the body, which will be executed in the shell. Thus, it can only contain shell commands, not Kakoune commands. ```kak plug "ul/kak-lsp" do %{ cargo build --release --locked cargo install --force --path . } ``` In the example above **plug.kak** will run these `cargo` commands after `kak-lsp` was installed or updated. **Note** that even though this is technically a shell expansion, the `%sh{}` expansion can't be used with `do`, as it will be evaluated immediately each time `kakrc` is loaded. Use `%{}` instead. #### Installing color schemes To register the plugin as a color scheme, use `theme` keyword. Such plugins will be copied to the `%val{config}/colors` directory. ```kak plug "andreyorst/base16-gruvbox.kak" theme config %{ colorscheme base16-gruvbox-dark-soft } ``` #### Ensuring that plugins are installed `plug` command can be explicitly told to install the plugin automatically with the `ensure` keyword. The `plug_always_ensure` option can be set to `true` to perform this for each and every plugin specified in the `kakrc`. Note that `ensure` plugins are installed (if missing) in a background job; they are then only loaded when the install finishes. Thus, subsequent `kakrc` commands should not depend on functionality provided by such plugins. Only use `ensure` with non-essential plugins, which are not required for `kakrc` to complete loading. #### Handling user configurations The configuration of the plugin is performed only when the plugin is installed. There's a second strict rule of `plug` command: every parameter that doesn't have a keyword before it, is treated as plugin configuration. For example: ```kak plug "andreyorst/fzf.kak" config %{ map -docstring 'fzf mode' global normal '' ': fzf-mode' } ``` Here, `plug` will map Ctrl+p key only if the plugin is installed. Everything within the `config %{}` block is an ordinary kakscript. The `config` keyword is optional, and can be skipped. Multiple `config` blocks are also supported. #### Commenting out `plug` options It may be tricky to "toggle" `plug` options, for debugging or testing purposes, because it is impossible to continue a command past a `#...` comment (also, `config` blocks usually span multiple lines). To solve this, `plug` supports a `comment` keyword that ignores its next argument. For example, to toggle a `load-path` option, wrap it in `comment %{}`; then remove the "wrapper" to turn it back on (without having to re-type the full path): ```kak plug "andreyorst/fzf.kak" comment %{load-path /usr/local/src/fzf} config %{ # ... } ``` ### Deferring plugin configuration With the introduction of the module system, some configurations have to be preformed after loading the module. The `defer` keyword is a shorthand to register a `ModuleLoaded` hook for given `module`. You need to **`require` the module explicitly** elsewhere. Below is the configuration of [fzf.kak](https://github.com/andreyorst/fzf.kak) plugin, which provides the `fzf` module: ```kak plug "andreyorst/fzf.kak" config %{ map -docstring 'fzf mode' global normal '' ': fzf-mode' } defer fzf %{ set-option global fzf_preview_width '65%' set-option global fzf_project_use_tilda true } ``` **Note**: the `ModuleLoaded` hook is defined as early as possible - before sourcing any of plugin files. ### Demanding plugin module configuration Works the same as `defer` except requires the module immediately: ```kak plug "andreyorst/fzf.kak" config %{ # config1 (evaluated before demanding the module) } demand fzf %{ # demand block (will generate `require-modlue fzf` call, and a respective hook) set-option global fzf_project_use_tilda true } config %{ # config2 (evaluated after demanding the module) } ``` The above snippet is a shorthand for this code: ``` kak plug "andreyorst/fzf.kak" defer fzf %{ # the body of demand block set-option global fzf_project_use_tilda true # demand block } config %{ # config1 (evaluated before demanding the module) require-module fzf # the demand hook # config2 (evaluated after demanding the module) } ``` **Note**: the `ModuleLoaded` hook is defined as early as possible - before sourcing any of plugin files. The place where `require-module` call will be placed depends on the order of config blocks in the `plug` command. As soon as the module is required, the `ModuleLoaded` hook will execute. ## **plug.kak** Configuration Several configuration options are available: - Changing the [plugin installation directory](#plugin-installation-directory), - Limiting the [maximum amount of active downloads](#maximum-downloads), - Specifying the [default git domain](#default-git-domain), - And [ensuring that plugins are installed](#ensuring-that-plugins-are-installed). Proper way to configure **plug.kak** is to load it with the `plug` command, and providing both `noload` and `config` blocks: This should be done before loading other plugins. ```kak plug "andreyorst/plug.kak" noload config %{ # configure plug.kak here } ``` ### Plugin installation directory By default **plug.kak** automatically detects its installation path and installs plugins to the same directory. To change this, use the `plug_install_dir` option: ```kak plug "andreyorst/plug.kak" noload config %{ set-option global plug_install_dir %sh{ echo $HOME/.cache/kakoune_plugins } } ``` ### Maximum downloads **plug.kak** downloads plugins from github.com asynchronously via `git`. By default it allows only `10` simultaneously active `git` processes. To change this, use the `plug_max_simultaneous_downloads` option. ### Default git domain If majority of plugins is installed from the service other than GitHub, default git domain can be changed to avoid specifying the `domain` keyword for each plugin, or using URLs. ### Notify on configuration error By default, **plug.kak** will display an `info` box when any plugin's `config` block has errors while being evaluated. To change this, use the `plug_report_conf_errors` option: ```kak set-option global plug_report_conf_errors false ``` ## Commands **plug.kak** adds five new commands to Kakoune. ### `plug-install` This command installs all plugins that were specified in any of the configuration files sourced after Kakoune launch. It accepts optional argument, which can be the plugin name or the URL, so it could be used to install a plugin from command prompt without restarting Kakoune. This plugin will be enabled automatically, but you still need to add `plug` command to your configuration files in order to use that plugin after the restart. ### `plug-list` Display the buffer with all installed plugins, and check for updates. The Enter key is remapped to execute `plug-update` or `plug-install` command for selected plugin, depending on its state. This command accepts an optional argument `noupdate`, and if it is specified, check for updates will not be performed. ### `plug-update` This command updates all installed plugins. It accepts one optional argument, which is a plugin name, so it could be used to update single plugin. When called from prompt, it shows all installed plugins in the completion menu. ### `plug-clean` Remove plugins, that are installed, but disabled or missing in configuration files. This command also accepts optional argument, which is a plugin name, and can be used to remove any installed plugin. ### `plug` Load plugin from plugin installation directory by its name. ### `plug-chain` This command can collapse separate `plug` invocations and thus saves startup time by reducing multiple shell calls; it may come in handy if you're invoking `kak` frequently (e.g. as the `$EDITOR`). Replace the first `plug` command in your `kakrc` with `plug-chain`, then append subsequent `plug` calls and their parameters, as in the following: ``` plug-chain https://github.com/Delapouite/kakoune-select-view config %{ map global view s ': select-view' -docstring 'select view' } plug https://github.com/occivink/kakoune-vertical-selection %{ } plug https://github.com/jbomanson/search-doc.kak demand search-doc %{ alias global doc-search search-doc } ``` Backslashes can also be used to separate individual `plug` "clauses" (which avoids the "visual hack" of empty config blocks, as above, serving as newlines). An initial `plug` redundant argument is also supported for symmetry. Either way, `plug-chain` simply figures out the parameters intended for each individual `plug` clause (using "`plug`" as a delimiter), and executes all implied `plug`s in a single shell call. All regular `plug` features are supported. Mix and match `plug` / `plug-chain` invocations in any order, any number of times. Note, that if plug.kak own variables are altered in the `plug-chain` body, the chained `plug` commands won't get updated values. This happens because Kakoune reads its variables only once per shell invocation, and calling `set-option` won't update the value of a variable for current shell. ### Alternative plugin managers Here are some other plugin managers to consider as alternatives to plug.kak: - [kak-bundle][7] - [cork.kak][8] [1]: https://img.shields.io/github/issues/andreyorst/plug.kak.svg [2]: https://github.com/andreyorst/plug.kak/issues [3]: https://img.shields.io/github/license/andreyorst/plug.kak.svg [4]: https://user-images.githubusercontent.com/19470159/51197223-f2c26a80-1901-11e9-9494-b79ce823a364.png [5]: https://github.com/junegunn/vim-plug [6]: https://github.com/jwiegley/use-package [7]: https://github.com/jdugan6240/kak-bundle [8]: https://github.com/topisani/cork.kak ================================================ FILE: rc/plug.kak ================================================ # Author: Andrey Listopadov # plug.kak is a plugin manager for Kakoune. It can install plugins, keep them updated, configure and build dependencies # https://github.com/andreyorst/plug.kak # Public options declare-option -docstring \ "Path where plugins should be installed. Defaults to the plug.kak installation directory" \ str plug_install_dir %sh{ echo "${kak_source%%/rc*}/../" } declare-option -docstring \ "Default domain to access git repositories. Can be changed to any preferred domain, like gitlab, bitbucket, gitea, etc. Default value: 'https://github.com'" \ str plug_git_domain 'https://github.com' declare-option -docstring \ "Profile plugin loading." \ bool plug_profile false declare-option -docstring \ "Maximum amount of simultaneously active downloads when installing or updating all plugins Default value: 10 " \ int plug_max_active_downloads 10 declare-option -docstring \ "Always ensure that all plugins are installed. If this option specified, all uninstalled plugins are being installed when Kakoune starts." \ bool plug_always_ensure false declare-option -docstring "name of the client in which utilities display information" \ str toolsclient declare-option -docstring \ "Block UI until operation completes." \ bool plug_block_ui false # Private options declare-option -hidden -docstring \ "Path to plug.sh script." \ str plug_sh_source %sh{ echo "${kak_source%%.kak}.sh" } declare-option -hidden -docstring \ "Array of all plugins, mentioned in any configuration file. Empty by default, and erased on reload of main Kakoune configuration, to track if some plugins were disabled Should not be modified by user." \ str plug_plugins "" declare-option -hidden -docstring \ "List of loaded plugins. Has no default value. Should not be cleared during update of configuration files. Should not be modified by user." \ str plug_loaded_plugins "" declare-option -docstring \ "Whether or not to report errors in config blocks. Defaults to true." \ bool plug_report_conf_errors true declare-option -hidden -docstring \ "This will be set if there are any errors with a plugin's config block. Has no default value. Should not be cleared during update of configuration files. Should not be modified by user." \ str plug_conf_errors "" # since we want to add highlighters to kak filetype we need to require kak module # using `try' here since kakrc module may not be available in rare cases try %@ require-module kak try %$ add-highlighter shared/kakrc/code/plug_keywords regex '\b(plug|plug-chain|do|config|domain|defer|demand|load-path|branch|tag|commit|comment)(?=[ \t])' 0:keyword add-highlighter shared/kakrc/code/plug_attributes regex '(?<=[ \t])(noload|ensure|theme)\b' 0:attribute add-highlighter shared/kakrc/plug_post_hooks1 region -recurse '\{' '\bdo\K\h+%\{' '\}' ref sh add-highlighter shared/kakrc/plug_post_hooks2 region -recurse '\[' '\bdo\K\h+%\[' '\]' ref sh add-highlighter shared/kakrc/plug_post_hooks3 region -recurse '\(' '\bdo\K\h+%\(' '\)' ref sh add-highlighter shared/kakrc/plug_post_hooks4 region -recurse '<' '\bdo\K\h+%<' '>' ref sh $ catch %$ echo -debug "Error: plug.kak: can't declare highlighters for 'kak' filetype: %val{error}" $ @ catch %{ echo -debug "Error: plug.kak: can't require 'kak' module to declare highlighters for plug.kak. Check if kakrc.kak is available in your autoload." } # *plug* highlighters try %{ add-highlighter shared/plug_buffer group add-highlighter shared/plug_buffer/done regex [^:]+:\h+(Up\h+to\h+date|Done|Installed)$ 1:string add-highlighter shared/plug_buffer/update regex [^:]+:\h+(Update\h+available|Deleted)$ 1:keyword add-highlighter shared/plug_buffer/not_installed regex [^:]+:\h+(Not\h+(installed|loaded)|(\w+\h+)?Error([^\n]+)?)$ 1:red+b add-highlighter shared/plug_buffer/updating regex [^:]+:\h+(Installing|Updating|Local\h+changes)$ 1:type add-highlighter shared/plug_buffer/working regex [^:]+:\h+(Running\h+post-update\h+hooks|Waiting[^\n]+)$ 1:attribute } catch %{ echo -debug "Error: plug.kak: Can't declare highlighters for *plug* buffer: %val{error}" } hook -group plug-syntax global WinSetOption filetype=plug %{ add-highlighter buffer/plug_buffer ref plug_buffer hook -always -once window WinSetOption filetype=.* %{ remove-highlighter buffer/plug_buffer } } define-command -override -docstring \ "plug []: manage from ""%opt{plug_install_dir}"" Switches: branch (tag, commit) checkout to before loading plugin noload do not source plugin files subset source only of plugin files load-path path for loading plugin from foreign location defer load plugin only when is loaded config plugin " \ plug -params 1.. -shell-script-candidates %{ ls -1 ${kak_opt_plug_install_dir} } %{ try %{ evaluate-commands %sh{ # $kak_client # $kak_config # $kak_opt_plug_always_ensure # $kak_opt_plug_git_domain # $kak_opt_plug_install_dir # $kak_opt_plug_loaded_plugins # $kak_opt_plug_max_active_downloads # $kak_opt_plug_plugin # $kak_opt_plug_plugins # $kak_opt_plug_profile # $kak_opt_plug_block_ui # $kak_opt_plug_report_conf_errors # $kak_opt_plug_conf_errors # $kak_session . "${kak_opt_plug_sh_source}" plug "$@" } }} define-command -override plug-chain -params 0.. -docstring %{ Chain plug commands (see docs, saves startup time by reducing sh calls) } %{ try %{ evaluate-commands %sh{ # $kak_client # $kak_config # $kak_opt_plug_always_ensure # $kak_opt_plug_git_domain # $kak_opt_plug_install_dir # $kak_opt_plug_loaded_plugins # $kak_opt_plug_max_active_downloads # $kak_opt_plug_plugin # $kak_opt_plug_plugins # $kak_opt_plug_profile # $kak_opt_plug_block_ui # $kak_opt_plug_report_conf_errors # $kak_opt_plug_conf_errors # $kak_session set -u . "${kak_opt_plug_sh_source}" plug1() { for _plug_param; do # reset "$@" on 1st iteration; args still in 'for' [ "$_plug_processed_args" != 0 ] || set -- _plug_processed_args=$((_plug_processed_args + 1)) [ plug != "$_plug_param" ] || break set -- "$@" "$_plug_param" done [ $# = 0 ] || plug "$@" # subshell would be safer, but slower } while [ "$#" != 0 ]; do _plug_processed_args=0 plug1 "$@" shift "$_plug_processed_args" done } }} define-command -override -docstring \ "plug-install [] []: install . If omitted installs all plugins mentioned in configuration files. If is supplied skip loading the plugin." \ plug-install -params ..2 %{ nop %sh{ # $kak_client # $kak_config # $kak_opt_plug_always_ensure # $kak_opt_plug_git_domain # $kak_opt_plug_install_dir # $kak_opt_plug_loaded_plugins # $kak_opt_plug_max_active_downloads # $kak_opt_plug_plugin # $kak_opt_plug_plugins # $kak_opt_plug_profile # $kak_opt_plug_block_ui # $kak_opt_plug_report_conf_errors # $kak_opt_plug_conf_errors # $kak_session . "${kak_opt_plug_sh_source}" plug_install "$@" }} define-command -override -docstring \ "plug-update []: Update plugin. If omitted all installed plugins are updated" \ plug-update -params ..1 -shell-script-candidates %{ printf "%s\n" ${kak_opt_plug_plugins} | tr ' ' '\n' } %{ evaluate-commands %sh{ # $kak_client # $kak_config # $kak_opt_plug_always_ensure # $kak_opt_plug_git_domain # $kak_opt_plug_install_dir # $kak_opt_plug_loaded_plugins # $kak_opt_plug_max_active_downloads # $kak_opt_plug_plugin # $kak_opt_plug_plugins # $kak_opt_plug_profile # $kak_opt_plug_block_ui # $kak_opt_plug_report_conf_errors # $kak_opt_plug_conf_errors # $kak_session . "${kak_opt_plug_sh_source}" plug_update "$@" }} define-command -override -docstring \ "plug-clean []: delete . If omitted deletes all plugins that are installed but not presented in configuration files" \ plug-clean -params ..1 -shell-script-candidates %{ ls -1 ${kak_opt_plug_install_dir} } %{ nop %sh{ # $kak_client # $kak_config # $kak_opt_plug_always_ensure # $kak_opt_plug_git_domain # $kak_opt_plug_install_dir # $kak_opt_plug_loaded_plugins # $kak_opt_plug_max_active_downloads # $kak_opt_plug_plugin # $kak_opt_plug_plugins # $kak_opt_plug_profile # $kak_opt_plug_block_ui # $kak_opt_plug_report_conf_errors # $kak_opt_plug_conf_errors # $kak_session . "${kak_opt_plug_sh_source}" plug_clean "$@" }} define-command -override -hidden \ -docstring "plug-eval-hooks: wrapper for post update/install hooks" \ plug-eval-hooks -params 1 %{ nop %sh{ # $kak_client # $kak_config # $kak_opt_plug_always_ensure # $kak_opt_plug_git_domain # $kak_opt_plug_install_dir # $kak_opt_plug_loaded_plugins # $kak_opt_plug_max_active_downloads # $kak_opt_plug_plugin # $kak_opt_plug_plugins # $kak_opt_plug_profile # $kak_opt_plug_block_ui # $kak_opt_plug_report_conf_errors # $kak_opt_plug_conf_errors # $kak_session . "${kak_opt_plug_sh_source}" plug_eval_hooks "$@" }} define-command -override \ -docstring "plug-list []: list all installed plugins in *plug* buffer. Checks updates by default unless is specified." \ plug-list -params ..1 %{ evaluate-commands -try-client %opt{toolsclient} %sh{ # $kak_client # $kak_config # $kak_opt_plug_always_ensure # $kak_opt_plug_git_domain # $kak_opt_plug_install_dir # $kak_opt_plug_loaded_plugins # $kak_opt_plug_max_active_downloads # $kak_opt_plug_plugin # $kak_opt_plug_plugins # $kak_opt_plug_profile # $kak_opt_plug_block_ui # $kak_opt_plug_report_conf_errors # $kak_opt_plug_conf_errors # $kak_session . "${kak_opt_plug_sh_source}" plug_list "$@" }} define-command -hidden -override \ -docstring "operate on *plug* buffer contents based on current cursor position" \ plug-fifo-operate -params 1 %{ evaluate-commands -save-regs t %{ execute-keys -save-regs '' "" set-register t %val{selection} evaluate-commands %sh{ # $kak_reg_t # $kak_client # $kak_config # $kak_opt_plug_always_ensure # $kak_opt_plug_git_domain # $kak_opt_plug_install_dir # $kak_opt_plug_loaded_plugins # $kak_opt_plug_max_active_downloads # $kak_opt_plug_plugin # $kak_opt_plug_plugins # $kak_opt_plug_profile # $kak_opt_plug_block_ui # $kak_opt_plug_report_conf_errors # $kak_opt_plug_conf_errors # $kak_session . "${kak_opt_plug_sh_source}" plug_fifo_operate "$@" }}} define-command -hidden -override \ plug-display-log -params 1 %{ evaluate-commands %sh{ plugin_log="${TMPDIR:-/tmp}/${1##*/}-log" [ -s "${plugin_log}" ] && printf "%s\n" "edit! -existing -debug -readonly -scroll %{${plugin_log}}" }} define-command -override \ -docstring "displays help message" \ plug-show-help %{ info -title "plug.kak Help" "h,j,k,l: Move : Update or Install plugin I: Install plugin U: Update plugin D: clean (Delete) plugin L: show Log, if any R: Run post-update hooks manually H show Help message" } ================================================ FILE: rc/plug.sh ================================================ #!/usr/bin/env sh # Author: Andrey Listopadov # https://github.com/andreyorst/plug.kak # # plug.kak is a plugin manager for Kakoune. It can install plugins, # keep them updated, configure and build dependencies. # # plug.sh contains a set of functions plug.kak calls via shell # expansions. plug_code_append () { eval "$1=\"\$$1 \$2\"" } plug () { [ "${kak_opt_plug_profile:-}" = "true" ] && plug_save_timestamp profile_start plugin_arg=$1 plugin="${1%%.git}"; plugin=${plugin%%/} shift plugin_name="${plugin##*/}" path_to_plugin="${kak_opt_plug_install_dir:?}/$plugin_name" build_dir="${kak_opt_plug_install_dir:?}/.build/$plugin_name" conf_file="$build_dir/config" hook_file="$build_dir/hooks" domain_file="$build_dir/domain" configurations= hooks= domain= checkout= checkout_type= noload= ensure= case "${kak_opt_plug_loaded_plugins:-}" in (*"$plugin"*) printf "%s\n" "echo -markup %{{Information}$plugin_name already loaded}" exit ;; (*) printf "%s\n" "set-option -add global plug_plugins %{$plugin }" ;; esac while [ $# -gt 0 ]; do case $1 in (branch|tag|commit) checkout_type=$1; shift; checkout=${1?} ;; (noload) noload=1 ;; (load-path) shift; eval "path_to_plugin=${1?}" ;; (comment) shift ;; (defer|demand) demand=$1 shift; module=${1?} if [ $# -ge 2 ]; then case "$2" in (branch|tag|commit|noload|load-path|ensure|theme|domain|depth-sort|subset|no-depth-sort|config|defer|demand|comment) ;; (*) shift deferred=$1 case "$deferred" in (*[![:space:]]*) case "$deferred" in (*'@'*) deferred=$(printf "%s\n" "$deferred" | sed "s/@/@@/g") ;; esac printf "%s\n" "hook global ModuleLoaded '$module' %@ $deferred @" esac [ "$demand" = demand ] && plug_code_append configurations "require-module $module" ;; esac fi ;; ('do') shift; plug_code_append hooks "set -e ${1?}" ;; (ensure) ensure=1 ;; (theme) noload=1 plug_code_append hooks "[ -d \"${kak_config:?}/colors\" ] || mkdir -p \"${kak_config}/colors\"; ln -sf \"\$PWD\" \"$kak_config/colors\"" ;; (domain) shift; domain=${1?} ;; (depth-sort|subset) printf "%s\n" "echo -debug %{Error: plug.kak: '$plugin_name': keyword '$1' is no longer supported. Use the module system instead}" exit 1 ;; (no-depth-sort) printf "%s\n" "echo -debug %{Warning: plug.kak: '$plugin_name': use of deprecated '$1' keyword which has no effect}" ;; (config) shift; plug_code_append configurations "${1?}" ;; (*) plug_code_append configurations "$1" ;; esac shift done [ -d "$build_dir" ] || mkdir -p "$build_dir" rm -rf "$build_dir"/* "$build_dir"/.[!.]* "$build_dir"/..?* [ -n "$hooks" ] && printf "%s" "$hooks" > "$hook_file" [ -n "$domain" ] && printf "%s" "$domain" > "$domain_file" if [ -n "$configurations" ]; then if [ "${kak_opt_plug_report_conf_errors:-}" = "true" ]; then cat > "$conf_file" < "$conf_file" fi fi if [ -d "$path_to_plugin" ]; then if [ -n "$checkout" ]; then ( cd "$path_to_plugin" || exit # shellcheck disable=SC2030,SC2031 [ -z "${GIT_TERMINAL_PROMPT:-}" ] && export GIT_TERMINAL_PROMPT=0 if [ "$checkout_type" = "branch" ]; then [ "$(git branch --show-current)" != "$checkout" ] && git fetch >/dev/null 2>&1 fi git checkout "$checkout" >/dev/null 2>&1 ) fi plug_load "$plugin" "$path_to_plugin" "$noload" if [ "$kak_opt_plug_profile" = "true" ]; then plug_save_timestamp profile_end profile_time=$(echo "scale=3; x=($profile_end-$profile_start)/1000; if(x<1) print 0; x" | bc -l) printf "%s\n" "echo -debug %{'$plugin_name' loaded in $profile_time sec}" fi else if [ -n "$ensure" ] || [ "${kak_opt_plug_always_ensure:-}" = "true" ]; then ( plug_install "$plugin_arg" "$noload" wait if [ "$kak_opt_plug_profile" = "true" ]; then plug_save_timestamp profile_end profile_time=$(echo "scale=3; x=($profile_end-$profile_start)/1000; if(x<1) print 0; x" | bc -l) printf "%s\n" "echo -debug %{'$plugin_name' loaded in $profile_time sec}" | kak -p "${kak_session:?}" fi ) > /dev/null 2>&1 < /dev/null & fi fi } plug_install () { ( plugin="${1%%.git}"; plugin=${plugin%%/} noload=$2 plugin_name="${plugin##*/}" build_dir="${kak_opt_plug_install_dir:?}/.build/$plugin_name" domain_file="$build_dir/domain" # shellcheck disable=SC2030,SC2031 [ -z "${GIT_TERMINAL_PROMPT:-}" ] && export GIT_TERMINAL_PROMPT=0 if [ ! -d "${kak_opt_plug_install_dir}" ]; then if ! mkdir -p "${kak_opt_plug_install_dir}" >/dev/null 2>&1; then printf "%s\n" "evaluate-commands -client ${kak_client:-client0} echo -debug 'Error: plug.kak: unable to create directory for plugins'" | kak -p "${kak_session:?}" exit fi fi printf "%s\n" "evaluate-commands -client ${kak_client:-client0} %{ try %{ buffer *plug* } catch %{ plug-list noupdate } }" | kak -p "${kak_session}" sleep 0.3 lockfile="${kak_opt_plug_install_dir}/.${plugin_name:-global}.plug.kak.lock" if [ -d "${lockfile}" ]; then plug_fifo_update "${plugin_name}" "Waiting for .plug.kak.lock" fi # this creates the lock file for a plugin, if specified to # prevent several processes of installation of the same # plugin, but will allow install different plugins without # waiting for each other. Should be fine, since different # plugins doesn't interfere with each other. while ! mkdir "${lockfile}" 2>/dev/null; do sleep 1; done # shellcheck disable=SC2064 trap "rmdir '${lockfile}'" EXIT # if plugin specified as an argument add it to the *plug* # buffer, if it isn't there already otherwise update all # plugins if [ -n "${plugin}" ]; then plugin_list=${plugin} printf "%s\n" " evaluate-commands -buffer *plug* %{ try %{ execute-keys /${plugin} } catch %{ execute-keys gjO${plugin}:Notinstalled }}" | kak -p "${kak_session}" sleep 0.2 else plugin_list=${kak_opt_plug_plugins} fi for plugin in ${plugin_list}; do plugin_name="${plugin##*/}" [ -e "$domain_file" ] && git_domain="https://$(cat "$domain_file")" || git_domain=${kak_opt_plug_git_domain:?} if [ ! -d "${kak_opt_plug_install_dir}/${plugin_name}" ]; then ( plugin_log="${TMPDIR:-/tmp}/${plugin_name}-log" printf "%s\n" "hook global -always KakEnd .* %{ nop %sh{rm -rf \"$plugin_log\"}} " | kak -p "${kak_session}" plug_fifo_update "${plugin_name}" "Installing" cd "${kak_opt_plug_install_dir}" || exit case ${plugin} in (https://*|http://*|*@*|file://*|ext::*) git clone --recurse-submodules "${plugin}" "$plugin_name" >> "$plugin_log" 2>&1 ;; (*) git clone --recurse-submodules "$git_domain/$plugin" "$plugin_name" >> "$plugin_log" 2>&1 ;; esac status=$? if [ ${status} -ne 0 ]; then plug_fifo_update "$plugin_name" "Download Error ($status)" else plug_eval_hooks "$plugin_name" wait plug_load "$plugin" "${kak_opt_plug_install_dir:?}/$plugin_name" "$noload" | kak -p "${kak_session:?}" fi ) > /dev/null 2>&1 < /dev/null & fi # this is a hacky way to measure amount of active # processes. We need this because dash shell has this long # term bug: # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=482999 jobs=$(mktemp "${TMPDIR:-/tmp}"/plug.kak.jobs.XXXXXX) jobs > "${jobs}"; active=$(wc -l < "${jobs}") while [ "${active}" -ge "${kak_opt_plug_max_active_downloads:?}" ]; do sleep 1 jobs > "${jobs}"; active=$(wc -l < "${jobs}") done rm -rf "${jobs}" done wait ) > /dev/null 2>&1 < /dev/null & } plug_load() { plugin="${1%%.git}" path_to_plugin=$2 noload=$3 plugin_name="${plugin##*/}" build_dir="${kak_opt_plug_install_dir:?}/.build/$plugin_name" conf_file="$build_dir/config" if [ -z "${noload}" ]; then find -L "${path_to_plugin}" -path '*/.git' -prune -o -type f -name '*.kak' -exec printf 'source "%s"\n' {} + fi [ -e "$conf_file" ] && printf "%s\n" "source $conf_file" printf "%s\n" "set-option -add global plug_loaded_plugins %{${plugin} }" } plug_update () { ( plugin="${1%%.git}" plugin_name="${plugin##*/}" # shellcheck disable=SC2030,SC2031 [ -z "${GIT_TERMINAL_PROMPT:-}" ] && export GIT_TERMINAL_PROMPT=0 printf "%s\n" "evaluate-commands -client ${kak_client:-client0} %{ try %{ buffer *plug* } catch %{ plug-list noupdate } }" | kak -p "${kak_session}" lockfile="${kak_opt_plug_install_dir}/.${plugin_name:-global}.plug.kak.lock" if [ -d "${lockfile}" ]; then plug_fifo_update "${plugin##*/}" "Waiting for .plug.kak.lock" fi while ! mkdir "${lockfile}" 2>/dev/null; do sleep 1; done # shellcheck disable=SC2064 trap "rmdir '${lockfile}'" EXIT [ -n "${plugin}" ] && plugin_list=${plugin} || plugin_list=${kak_opt_plug_plugins} for plugin in ${plugin_list}; do plugin_name="${plugin##*/}" if [ -d "${kak_opt_plug_install_dir}/${plugin_name}" ]; then ( plugin_log="${TMPDIR:-/tmp}/${plugin_name}-log" printf "%s\n" "hook global -always KakEnd .* %{ nop %sh{rm -rf ${plugin_log}}} " | kak -p "${kak_session}" plug_fifo_update "${plugin_name}" "Updating" cd "${kak_opt_plug_install_dir}/${plugin_name}" && rev=$(git rev-parse HEAD) && git pull --recurse-submodules >> "${plugin_log}" 2>&1 status=$? if [ ${status} -ne 0 ]; then plug_fifo_update "${plugin_name}" "Update Error (${status})" else if [ "${rev}" != "$(git rev-parse HEAD)" ]; then printf "%s\n" "evaluate-commands -client ${kak_client:-client0} plug-eval-hooks ${plugin_name}" | kak -p "${kak_session}" else plug_fifo_update "${plugin_name}" "Done" fi fi ) > /dev/null 2>&1 < /dev/null & fi jobs=$(mktemp "${TMPDIR:-/tmp}"/jobs.XXXXXX) jobs > "${jobs}"; active=$(wc -l < "${jobs}") # TODO: re-check this # For some reason I need to multiply the amount of jobs by five here. while [ "${active}" -ge $((kak_opt_plug_max_active_downloads * 5)) ]; do sleep 1 jobs > "${jobs}"; active=$(wc -l < "${jobs}") done rm -rf "${jobs}" done wait ) > /dev/null 2>&1 < /dev/null & if [ "${kak_opt_plug_block_ui:-}" = "true" ]; then wait fi } plug_clean () { ( plugin="${1%%.git}" plugin_name="${plugin##*/}" printf "%s\n" "evaluate-commands -client ${kak_client:-client0} %{ try %{ buffer *plug* } catch %{ plug-list noupdate } }" | kak -p "${kak_session}" lockfile="${kak_opt_plug_install_dir}/.${plugin_name:-global}.plug.kak.lock" if [ -d "${lockfile}" ]; then plug_fifo_update "${plugin_name}" "Waiting for .plug.kak.lock" fi while ! mkdir "${lockfile}" 2>/dev/null; do sleep 1; done # shellcheck disable=SC2064 trap "rmdir '${lockfile}'" EXIT if [ -n "${plugin}" ]; then if [ -d "${kak_opt_plug_install_dir}/${plugin_name}" ]; then ( cd "${kak_opt_plug_install_dir}" && rm -rf "${plugin_name}" plug_fifo_update "${plugin_name}" "Deleted" ) else printf "%s\n" "evaluate-commands -client ${kak_client:-client0} echo -markup %{{Error}No such plugin '${plugin}'}" | kak -p "${kak_session}" exit fi else for installed_plugin in $(printf "%s\n" "${kak_opt_plug_install_dir}"/*); do skip= for enabled_plugin in ${kak_opt_plug_plugins}; do [ "${installed_plugin##*/}" = "${enabled_plugin##*/}" ] && { skip=1; break; } done [ "${skip}" = "1" ] || plugins_to_remove=${plugins_to_remove}" ${installed_plugin}" done for plugin in ${plugins_to_remove}; do plug_fifo_update "${plugin##*/}" "Deleted" rm -rf "${plugin}" done fi ) > /dev/null 2>&1 < /dev/null & if [ "$kak_opt_plug_block_ui" = "true" ]; then wait fi } plug_eval_hooks () { ( plugin="${1%%.git}" plugin_name="${plugin##*/}" path_to_plugin="${kak_opt_plug_install_dir:?}/$plugin_name" build_dir="${kak_opt_plug_install_dir:?}/.build/$plugin_name" hook_file="$build_dir/hooks" plugin_log="${TMPDIR:-/tmp}/${plugin_name}-log" cd "$path_to_plugin" || exit printf "%s\n" "hook global -always KakEnd .* %{ nop %sh{rm -rf ${plugin_log}}}" | kak -p "${kak_session}" plug_fifo_update "${plugin_name}" "Running post-update hooks" status=0 if [ -e "$hook_file" ]; then # shellcheck disable=SC1090 (. "$hook_file" >> "$plugin_log" 2>&1) status=$? fi [ ${status} -ne 0 ] && message="Error (${status})" || message="Done" plug_fifo_update "${plugin_name}" "${message}" ) > /dev/null 2>&1 < /dev/null & if [ "$kak_opt_plug_block_ui" = "true" ]; then wait fi } plug_list () { noupdate=$1 tmp=$(mktemp -d "${TMPDIR:-/tmp}/plug-kak.XXXXXXXX") fifo="${tmp}/fifo" plug_buffer="${tmp}/plug-buffer" mkfifo "${fifo}" printf "%s\n" "edit! -fifo ${fifo} *plug* set-option buffer filetype plug plug-show-help hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -rf ${tmp} } } map buffer normal '' ':plug-fifo-operate install-update' map buffer normal 'H' ':plug-show-help' map buffer normal 'U' ':plug-fifo-operate update' map buffer normal 'I' ':plug-fifo-operate install' map buffer normal 'L' ':plug-fifo-operate log' map buffer normal 'D' ':plug-fifo-operate clean' map buffer normal 'R' ':plug-fifo-operate hooks'" # get those plugins which were loaded by plug.kak eval "set -- ${kak_opt_plug_plugins}" while [ $# -gt 0 ]; do if [ -d "${kak_opt_plug_install_dir}/${1##*/}" ]; then printf "%s: Installed\n" "$1" >> "${plug_buffer}" else printf "%s: Not installed\n" "$1" >> "${plug_buffer}" fi shift done # get those plugins which have a directory at installation path, # but wasn't mentioned in any config file for existing_plugin in "${kak_opt_plug_install_dir}"/*; do case "${kak_opt_plug_plugins}" in (*"${existing_plugin##*/}"*) ;; (*) printf "%s: Not loaded\n" "${existing_plugin##*/}" >> "${plug_buffer}" ;; esac done ( sort "${plug_buffer}" > "${fifo}" ) > /dev/null 2>&1 < /dev/null & if [ -z "${noupdate}" ]; then ( # shellcheck disable=SC2030,SC2031 [ -z "${GIT_TERMINAL_PROMPT:-}" ] && export GIT_TERMINAL_PROMPT=0 eval "set -- ${kak_opt_plug_plugins}" while [ $# -gt 0 ]; do plugin_dir="${1##*/}" if [ -d "${kak_opt_plug_install_dir}/${plugin_dir}" ]; then ( cd "${kak_opt_plug_install_dir}/${plugin_dir}" || exit git fetch > /dev/null 2>&1 status=$? if [ ${status} -eq 0 ]; then { IFS= read -r LOCAL; IFS= read -r REMOTE; IFS= read -r BASE; } < /dev/null 2>&1 < /dev/null & fi shift done ) > /dev/null 2>&1 < /dev/null & fi } plug_fifo_operate() { plugin="${kak_reg_t%:*}" case $1 in (install-update) if [ -d "${kak_opt_plug_install_dir}/${plugin##*/}" ]; then plug_update "${plugin}" else plug_install "${plugin}" true fi ;; (update) if [ -d "${kak_opt_plug_install_dir}/${plugin##*/}" ]; then plug_update "${plugin}" else printf "%s\n" "echo -markup %{{Information}'${plugin}' is not installed}" fi ;; (install) if [ ! -d "${kak_opt_plug_install_dir}/${plugin##*/}" ]; then plug_install "${plugin}" else printf "%s\n" "echo -markup %{{Information}'${plugin}' already installed}" fi ;; (clean) plug_clean "${plugin}" ;; (log) printf "%s\n" "plug-display-log $plugin" ;; (hooks) plug_eval_hooks "${plugin##*/}" ;; (*) ;; esac } plug_fifo_update() { printf "%s\n" " evaluate-commands -draft -buffer *plug* -save-regs \"/\"\"\" %{ try %{ set-register / \"$1: \" set-register dquote %{$2} execute-keys -draft /lGlR }}" | kak -p "$kak_session" } plug_save_timestamp() { plug_tstamp=${EPOCHREALTIME:-} if [ -n "$plug_tstamp" ]; then plug_tstamp_ms=${plug_tstamp#*.} case "$plug_tstamp_ms" in (????*) plug_tstamp_ms=${plug_tstamp_ms%"${plug_tstamp_ms#???}"} ;; (???) ;; (*) plug_tstamp= ;; # redo with date esac if [ -n "$plug_tstamp" ]; then plug_tstamp=${plug_tstamp%.*}${plug_tstamp_ms} fi fi : "${plug_tstamp:=$(date +%s%3N)}" if [ -n "$1" ]; then eval "$1=\$plug_tstamp"; fi } # Spell-checker local dictionary # LocalWords: Andrey Listopadov github kak usr config dir Kakoune # LocalWords: expr ModuleLoaded mkdir ln PWD conf shellcheck noload # LocalWords: TMPDIR tmp noupdate lockfile rmdir ret gjO esc KakEnd # LocalWords: nop rf hacky eval fifo filetype BufCloseFifo regs # LocalWords: dquote lGlR