1 unstable release
new 0.1.0 | Sep 12, 2024 |
---|
#28 in Configuration
98 downloads per month
73KB
1.5K
SLoC
FZS
Fuzzy selector Everything at your fingertips
Description
This is a program to help you manage your binaries. The idea is this: Put all of them in a root directory, categorized in folders For example:
- vcs
- _libs
- insert-under-heading
- _bins
- add-commit-all
- verson-control_vcs_select
- _libs
- python
- _libs
- __ignored
- some-dependency
- python_py_select
- colors
- fzf
- fzf_ff_main_select
- kube
- docker_dk
- imagesearch
- containers
- compose
- kube_kb
- docker_dk
- text
- bat_select
- follow
- journal
- log
- bat_select
fzs will scan FZS_ROOT_DIR
(~/.fzs
) for folders whose names match PLUGIN_REGEX
. By default, this follows the format of name_alias?_description?_select
(Literally it is, ^([a-zA-Z0-9]+)(?:_([a-zA-Z0-9-]+))?(?:_([a-zA-Z0-9-]+))?_select$
).
Any such folders will get parsed as a Plugin. (alias
defaults to name
, description
is empty by default, other fields are described in Types).
Firstly, (the name of) every executable under the plugin (non-recursive) is parsed into a Fn (by default via the regex ^([a-zA-Z0-9-]*)(?:_([a-zA-Z0-9-]+))?(?:_([a-zA-Z0-9-]+))?(?:\.[a-zA-Z0-9]+)?$
which essentially translates to name_alias?_description?
), belonging to the Plugin associated to the containing folder. Altogether, a Vec<Plugin> is built. Finally, every such executable is symlinked to FZS_PATH
(Defaults to $HOME/.local/fzs
), with the name plugin-alias.fn-name
, from where it will be accessible from the command-line.
We also scan for folders whose names match the
LINKEDBIN_REGEX
(Default:_name_alias?_description?
) . These are not parsed into objects, this functionality is only to help you namespace your executables. Mainly, the executables within these folders are also symlinked toFZS_PATH
in the same way.i.e.
vcs/_vcs__bins/add-commit-all -> ${FZS_PATH}/vcs.add-commit-all
.Also, any errors at this stage such as duplicate final names will cause an error and early exit, affecting nothing.
Note on name vs alias alias is just a mechanism for namespacing your executables, we don't check that it is unique across plugins. On the one hand, this allows you to put different plugins under the same namespace. But on the other hand, since we don't check for uniqueness, you have to check yourself, otherwise you get strange behavior. The purpose of the plugin name is just to serve as an identifier for the plugin folder, which itself serves as a grouping mechanism for a set of binaries + associated script(s). FZS generates a script whose (main) purpose is to create a selector for this plugin for you, and also an env file which provides variables you can use to define your own associated scripts.
Next, config.toml
is parsed for user defined plugins and plugin settings, which are merged into the scanned Vec<Plugin>.
- Declared functions whose names match a scanned function in the same plugin will override the scanned one.
- You can also further customize your functions here, i.e. by adding keybinds.
- You can also append additional functions and widgets (including other selectors) for your plugin here too.
[[plugins]]
name = "bat"
functions = [
{ name = "read", description = "read epubs and pdf", cmd = "rustic-reader" }
{ name = "follow", description = "Override the description of the scanned follow command"}
{ name = "example", description = "The command bat.example will be aliased to ex, and bound to ^x^x", bind = "^x^x", alias = "ex"}
]
widgets = [
{ name = "monitor", description = "monitor selector", cmd = "mn._select_widget" }
]
This combined Vec<Plugin> structure is then used to build a widget plugin_alias._select_widget
within a demarcated section in an initialization script plugin_folder/plugin_name.zshrc
for each plugin. This widget is compiled and included in your .zshrc, allowing you to easily access the functions and widgets described for that plugin through just a couple keystrokes.
Todo
- In
main.rs
: Parseconfig.toml
file intoglobal_config: GlobalConfig
andconfig_plugins: Vec<RawPlugins>
- Scan for all directories and filter for those matching
global_config.plugin_regex
. The capturing groups of the regex will be used to form aInitialPlugin
. Also filter for those matchinglinkedbin_regex
, and do the same to form alinkedbins: Vec<InitialPlugin>
.- An
InitialPlugin
has the fields{"path", "name", "alias", "description"}
. The fields are filled from the regex capturing groups. Thelinkedbin_regex
only has two capturing groups, and produces anInitialPlugin
with emptyname
field.
- An
- Check for no duplicate names in the resulting plugin list
scanned_plugins
, also, no duplicate function names in a plugin - Merge
config_plugins
intoscanned_plugins
:- For each
cplugin
inconfig_plugins
:- If its name doesn't appear in
config_plugins
: set thedisabled
field totrue
and add it toscanned_plugins
- Otherwise, let
splugin
be the corresponding plugin inconfig_plugins
. - For each field that is not
fns
orname
with valueSome
, override the pre-existing field insplugin
with the value fromcplugin
.- When overriding
alias
, check that it is not present into_symlink
- When overriding
- For each
fn
incplugins.fns
:- If its name doesn't appear in the
fns
ofsplugin
, append it. - Otherwise, for each field of the
fns
with valueSome
, override the pre-existing field insplugin
.
- If its name doesn't appear in the
- If its name doesn't appear in
- For each
- For each
lb
oflinkedbins
:- As above, scan the folder for executables matching
fn_regex
- Use the regex capturing groups to form the
{ path, pl_alias, name }: InitialFn
directly and append it toto_symlink: Vec<InitialFn>
.- If there is already another entry in
to_symlink
with the sameplugin_alias
andalias
, throw an error and exit.
- If there is already another entry in
- As above, scan the folder for executables matching
- For each
pl
inscanned_plugins
:- Let
select_widget_script
be the result of substitutingpl.name, pl.alias, pl.description, pl.function_template, pl.widget_template
intoTEMPLATE_FILE
. - For each
fn
ofpl.fns
for whichfn.cmd
is notNone
: Runfn_cond_block(fn, select_widget_script)
to insert some text below the line{{ fns }}
insideselect_widget_script
. - For each
wg
ofpl.widgets
: Runwg_cond_block(wg, select_widget_script)
to insert some text below the line{{ wgs }}
insideselect_widget_script
. - Run
cleanup_widget_script(select_widget_script)
to remove extra template variables like{{ text }}
fromselect_widget_script
- Look for a
{plugin.name}.zshrc
inplugin.path
- If it doesn't exist, create a new file containing just the two lines
### BEGIN SELECT_WIDGET
and### END SELECT_WIDGET
- Look for the section between
### BEGIN SELECT_WIDGET
and### END SELECT_WIDGET
.- If it doesn't exist, throw an error.
- Otherwise, replace that section with the contents of
select_widget_script
.
- If it doesn't exist, create a new file containing just the two lines
- Let
- Improve output format
- Use env_logging
- Custom error types
- Convert existing setup
- Delete all symlinks in
FZS_PATH
. For eachts
ofto_symlink
, symlinkts.path
to{FZS_PATH}/{fn_template(pl_alias, name)}
- Compile all
FZS_PATH/**/*.zshrc
- Print
Execution Complete
- Release
- Github actions (Upload release)
- CHANGELOG
- Refactor
- Tests on all platforms
- Extra features
- Alt-names and default exports? For now, we're just using pgname_pgname
- More options to autoconfigure widgets from file
- Unsure whether to use eval $cmd or eval "$cmd" in template, reading a var seems to disable word splitting?. Also, maybe can support custom run flags.
- Precompile soucrs
- Frequency sort
- Make one-accept an option over default
- fuzzy search over all commands
- AL flag to handle aliases
- Widget modes (Puts command onto BUFFER, puts output onto BUFFER)
- template out fzf_help_cmd
- Add an extra field to fzf to store id, and add cli option to pretty print from id
- .zshrc and other formats: .c, rust, etc?
- ! flag to register the following command, and use flags to set the name
- Convert flags to Hashset
Types
Extras
- should template_file handle cmd_template
- No, lets prefer to keep templating in rust
- BUFFER options?
- exit code options?
Case study
# manually replicate widget structure
$this._rg.wg() {
command fzf.rg "$@"
}
zle -N $this.$_rg.wg
vs
# Parse pg_name.zshrc, specify to not include in selector
# : flags=wg,na binds=""
$_rg() {
command fzf.rg "$@"
}
zle -N $this.$_rg.wg
vs
# .pw specifies a ProvisionWidget flag on the binary rg
rg.pw
# which autocreates the following code:
FAQ
- Do not make sourced files executable (You can use
fd -g "*.zshrc" -t x -x chmod -x
) - When parsing fns from files, the available function characters are limited to A-Za-z0-9_ due to shell syntax. You can use the config.toml to override if necessary. It is also recommended to use camelCase if your name or alias consists of multiple words.
- Use the base plugin to gather binaries without namespace
- end and begin your scripts with newline
- If your binds don't register, make sure to load your fzs_plugins file(s) after other zsh plugins
- How to always properly fold fzf output?
- I don't know ._.
fold -w \${FZF_PREVIEW_COLUMNS}
works okay sometimes.
- I don't know ._.
- The variables injected by fzs do not work inside of functions. This is a downside of using variables compared to retemplating. The upside is more portable, faster, leaner and syntax adherence. Instead, just refer to it by the generated name, it's not too difficult to change your mind, a find and replace works.
Dependencies
~4–14MB
~198K SLoC