Compare commits
No commits in common. "a55657f4d0d99c3a0371d875341ed33b3d74eee3" and "ffaf21b484c80412af6baba3b8efd6d6e7e0a8dd" have entirely different histories.
a55657f4d0
...
ffaf21b484
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/.deployed_cache
|
||||
**/cache
|
||||
**/*.log
|
||||
**/logs
|
||||
**/tmp/
|
||||
/.git
|
||||
/.yolk_git
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,6 +1,3 @@
|
||||
[submodule "eggs/vim/pack/vim-wayland-clipboard"]
|
||||
path = eggs/vim/pack/vim-wayland-clipboard
|
||||
url = https://github.com/jasonccox/vim-wayland-clipboard.git
|
||||
[submodule "eggs/zsh/config/fzf-tab"]
|
||||
path = eggs/zsh/config/fzf-tab
|
||||
url = https://github.com/Aloxaf/fzf-tab.git
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0"?>
|
||||
<conf:configuration-backend-db xmlns:conf="http://openoffice.org/extensionmanager/configuration-registry/2010"/>
|
||||
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0"?>
|
||||
<help:help-backend-db xmlns:help="http://openoffice.org/extensionmanager/help-registry/2010"/>
|
||||
1
eggs/vim/pack/vim-wayland-clipboard
Submodule
1
eggs/vim/pack/vim-wayland-clipboard
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 2fa6178d39925eab6a33dd13583d1bd9b67d3f65
|
||||
@ -1,21 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.2.1] - 2022-02-18
|
||||
### Fixed
|
||||
- Don't print control characters on screen from system calls to `wl-copy`/`wl-paste`.
|
||||
|
||||
## [0.2.0] - 2021-04-30
|
||||
### Added
|
||||
- Make `ctrl-r+` and friends work in insert mode.
|
||||
### Fixed
|
||||
- Corrected check for `clipboard` feature.
|
||||
|
||||
## [0.1.0] - 2021-04-27
|
||||
### Added
|
||||
- Start maintaining versions and a changelog.
|
||||
@ -1,55 +0,0 @@
|
||||
# vim-wayland-clipboard
|
||||
|
||||
**[jasoncarloscox.com/creations/vim-wayland-clipboard](https://jasoncarloscox.com/creations/vim-wayland-clipboard/)**
|
||||
|
||||
This plugin allows Vim to integrate with the Wayland clipboard when using the `+` register. This means you can yank text into the `+` register and paste it in other Wayland programs, or copy text in other Wayland programs and paste it in Vim from the `+` register. Operators and counts work, too!
|
||||
|
||||
When running Vim outside of Wayland, the `+` register continues to work as normal.
|
||||
|
||||
## Requirements
|
||||
|
||||
For this plugin to work, you need [wl-clipboard](https://github.com/bugaevc/wl-clipboard) installed, and Vim must be compiled with the `+eval` feature.
|
||||
|
||||
## Installation
|
||||
|
||||
Use Vim8's built-in packages:
|
||||
|
||||
1. `mkdir -p ~/.vim/pack/vim-wayland-clipboard/start/`
|
||||
2. `git clone https://github.com/jasonccox/vim-wayland-clipboard.git ~/.vim/pack/vim-wayland-clipboard/start/vim-wayland-clipboard`
|
||||
|
||||
## Usage and Features
|
||||
|
||||
Just use `"+y`, `"+p`, `<C-R>+`, and friends as you always do. Specifically, here's what's supported:
|
||||
|
||||
- Any yank command that starts with `"+` (e.g. `"+yy` or `"+yiw`) in insert and visual modes.
|
||||
- Pasting in normal and visual modes with `"+p` or `"+P`.
|
||||
- Pasting in insert mode with `<C-R>+`, `<C-R><C-R>+`, `<C-R><C-O>+`, or `<C-R><C-P>+`.
|
||||
- Yanking and pasting (`p` and `P` in normal and visual modes) with `clipboard=unnamedplus` or `g:wayland_clipboard_unnamedplus = 1`.
|
||||
|
||||
If you need more functionality, consider checking out [vim-fakeclip](https://github.com/kana/vim-fakeclip).
|
||||
|
||||
## Notes
|
||||
|
||||
### Passing extra arguments to `wl-copy` or `wl-paste`
|
||||
|
||||
If you want to pass extra arguments to `wl-copy`, set `g:wayland_clipboard_copy_args` to a list of strings, one per argument. For example, to copy to the primary clipboard and only allow the contents to be pasted once, you could do the following:
|
||||
|
||||
```vimscript
|
||||
let g:wayland_clipboard_copy_args = ['--primary', '--paste-once']
|
||||
```
|
||||
|
||||
To pass extra arguments to `wl-paste`, use `g:wayland_clipboard_paste_args` in the same way.
|
||||
|
||||
### Clobbering the `w` Register
|
||||
|
||||
On Vim builds without `clipboard`, or if Xwayland isn't running, the `+` register doesn't work for yanking. My solution is to map `"+` to `"w` and send the `w` register to the Wayland clipboard as well. (This only occurs when the `clipboard` feature is missing or the X `$DISPLAY` environment vairable is empty.) If you use the `w` register for other things and don't want it to clobber your system clipboard, put `let g:wayland_clipboard_no_plus_to_w = 1` in your `vimrc` to disable this feature.
|
||||
|
||||
### Non-recursive Mappings
|
||||
|
||||
This plugin uses mappings of `"+p`, `<C-R>+`, `"+`, etc. to do its job. As a result, it won't work with existing *non-recursive* mappings that run these commands, e.g. `nnoremap <Leader>p "+p`. If you have mappings like these, you'll need to use their recursive counterparts instead for the plugin to work.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! You can send questions, bug reports, patches, etc. by email to [~jcc/public-inbox@lists.sr.ht](https://lists.sr.ht/~jcc/public-inbox). (Don't know how to contribute via email? Check out the interactive tutorial at [git-send-email.io](https://git-send-email.io), or [email me](mailto:me@jasoncarloscox.com) for help.)
|
||||
|
||||
GitHub issues and pull requests are fine, too.
|
||||
@ -1,116 +0,0 @@
|
||||
" wayland-clipboard.vim - Integrate with Wayland's clipboard when using the '+'
|
||||
" register. Requires wl-clipboard and the +eval and +clipboard Vim features.
|
||||
"
|
||||
" This script was inspired by
|
||||
" https://www.reddit.com/r/Fedora/comments/ax9p9t/vim_and_system_clipboard_under_wayland/
|
||||
" but uses an autocmd to allow yanking with operators to work.
|
||||
|
||||
" Early exit checks {{{
|
||||
|
||||
" only load this script once
|
||||
if exists('g:loaded_wayland_clipboard')
|
||||
finish
|
||||
endif
|
||||
let g:loaded_wayland_clipboard = 1
|
||||
|
||||
" only run this in Vim on Wayland -- Vim on X has native clipboard support,
|
||||
" and Neovim already works with wl-copy by default
|
||||
if has('nvim') || empty($WAYLAND_DISPLAY)
|
||||
finish
|
||||
endif
|
||||
|
||||
" }}}
|
||||
|
||||
" Yanking {{{
|
||||
|
||||
" The '+' register doesn't work for yanking if:
|
||||
" - vim was built without 'clipboard'.
|
||||
" - x11 / xwayland is unavailable.
|
||||
" (https://github.com/vim/vim/blob/93197fde0f1db09b1e495cf3eb14a8f42c318b80/src/register.c#L247)
|
||||
"
|
||||
" My solution is to map '"+' to '"w' and send the 'w' register to the
|
||||
" Wayland clipboard as well.
|
||||
"
|
||||
" This variable controls whether '"+' gets mapped to '"w'. It's on by default
|
||||
" if the 'clipboard' feature isn't available, or if $DISPLAY isn't set,
|
||||
" but the user can turn it off always if desired.
|
||||
let s:plus_to_w = (!has('clipboard') || empty($DISPLAY)) && !exists('g:wayland_clipboard_no_plus_to_w')
|
||||
|
||||
" remap '"+' to '"w' -- this will only apply to yanking since '"+p' and '"+P'
|
||||
" are also remapped below
|
||||
if s:plus_to_w
|
||||
nnoremap "+ "w
|
||||
vnoremap "+ "w
|
||||
endif
|
||||
|
||||
let s:copy_args = exists('g:wayland_clipboard_copy_args') ? g:wayland_clipboard_copy_args : []
|
||||
|
||||
function! s:unnamedplus()
|
||||
return &clipboard =~ 'unnamedplus'
|
||||
\ || (exists('g:wayland_clipboard_unnamedplus') && g:wayland_clipboard_unnamedplus)
|
||||
endfunction
|
||||
|
||||
" pass register contents to wl-copy if the '+' (or 'w') register was used
|
||||
function! s:WaylandYank()
|
||||
if v:event['regname'] == '+' ||
|
||||
\ (v:event['regname'] == 'w' && s:plus_to_w) ||
|
||||
\ (v:event['regname'] == '' && s:unnamedplus())
|
||||
let job = job_start(['wl-copy'] + s:copy_args, {
|
||||
\ "in_io": "pipe", "out_io": "null", "err_io": "null",
|
||||
\ "stoponexit": "",
|
||||
\ })
|
||||
call ch_sendraw(job, getreg(v:event['regname']))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" run s:WaylandYank() after every time text is yanked
|
||||
augroup waylandyank
|
||||
autocmd!
|
||||
autocmd TextYankPost * call s:WaylandYank()
|
||||
augroup END
|
||||
|
||||
" }}}
|
||||
|
||||
" Pasting {{{
|
||||
|
||||
" remap paste commands to first pull in clipboard contents with wl-paste
|
||||
|
||||
let s:paste_args = exists('g:wayland_clipboard_paste_args') ? g:wayland_clipboard_paste_args : []
|
||||
let s:paste_args_str = empty(s:paste_args) ? '' : ' ' . join(s:paste_args)
|
||||
|
||||
function! s:clipboard_to_unnamed()
|
||||
silent let @"=substitute(system('wl-paste --no-newline' . s:paste_args_str), "\r", '', 'g')
|
||||
endfunction
|
||||
|
||||
function! s:put(p, fallback)
|
||||
if a:fallback
|
||||
return a:p
|
||||
endif
|
||||
|
||||
call s:clipboard_to_unnamed()
|
||||
return '""' . a:p
|
||||
endfunction
|
||||
|
||||
function! s:ctrl_r(cr)
|
||||
call s:clipboard_to_unnamed()
|
||||
return a:cr . '"'
|
||||
endfunction
|
||||
|
||||
nnoremap <expr> <silent> "+p <SID>put('p', v:false)
|
||||
nnoremap <expr> <silent> "+P <SID>put('P', v:false)
|
||||
nnoremap <expr> <silent> p <SID>put('p', !<SID>unnamedplus())
|
||||
nnoremap <expr> <silent> P <SID>put('P', !<SID>unnamedplus())
|
||||
|
||||
vnoremap <expr> <silent> "+p <SID>put('p', v:false)
|
||||
vnoremap <expr> <silent> "+P <SID>put('P', v:false)
|
||||
vnoremap <expr> <silent> p <SID>put('p', !<SID>unnamedplus())
|
||||
vnoremap <expr> <silent> P <SID>put('P', !<SID>unnamedplus())
|
||||
|
||||
inoremap <expr> <silent> <C-R>+ <SID>ctrl_r("\<C-R>")
|
||||
inoremap <expr> <silent> <C-R><C-R>+ <SID>ctrl_r("\<C-R>\<C-R>")
|
||||
inoremap <expr> <silent> <C-R><C-O>+ <SID>ctrl_r("\<C-R>\<C-O>")
|
||||
inoremap <expr> <silent> <C-R><C-P>+ <SID>ctrl_r("\<C-R>\<C-P>")
|
||||
|
||||
" }}}
|
||||
|
||||
" vim:foldmethod=marker:foldlevel=0
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 2abe1f2f1cbcb3d3c6b879d849d683de5688111f
|
||||
42
eggs/zsh/config/fzf-tab/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
42
eggs/zsh/config/fzf-tab/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
#### Describe the bug
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
I can make sure:
|
||||
- [ ] I am using the latest version of fzf-tab
|
||||
- [ ] this is the minimal zshrc which can reproduce this bug
|
||||
- [ ] fzf-tab is loaded after `compinit`
|
||||
- [ ] fzf-tab is loaded after plugins which will wrap <kbd>Tab</kbd>, like [junegunn/fzf/completion.zsh](https://github.com/junegunn/fzf/blob/master/shell/completion.zsh)
|
||||
- [ ] fzf-tab is loaded before zsh-autosuggestions, zsh-syntax-highlighting and fast-syntax-highlighting.
|
||||
|
||||
#### To Reproduce
|
||||
Steps to reproduce the behavior:
|
||||
1. Type '...'
|
||||
2. Press <kbd>Tab</kbd>
|
||||
4. See error
|
||||
|
||||
#### Expected behavior
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
#### Screenshots
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
#### Environment:
|
||||
- OS: [e.g. Arch Linux]
|
||||
- zsh version: [e.g. 5.8.1]
|
||||
|
||||
#### Minimal zshrc
|
||||
If applicable, add a minimal zshrc to help us analyze.
|
||||
|
||||
#### Log
|
||||
If applicable, use `C-x .` to trigger completion and provide the log.
|
||||
|
||||
If there are only three lines in your log, please make sure your fzf-tab is loaded with the correct order (see the checklist above).
|
||||
20
eggs/zsh/config/fzf-tab/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
eggs/zsh/config/fzf-tab/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FR]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
11
eggs/zsh/config/fzf-tab/.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
11
eggs/zsh/config/fzf-tab/.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about fzf-tab
|
||||
title: "[Q]"
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe your question**
|
||||
A clear and concise description of your question.
|
||||
44
eggs/zsh/config/fzf-tab/.github/workflows/linux.yaml
vendored
Normal file
44
eggs/zsh/config/fzf-tab/.github/workflows/linux.yaml
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
name: Linux compability
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: ["ubuntu:latest", "archlinux:latest"]
|
||||
container:
|
||||
image: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
if [ "${{ matrix.os }}" = "ubuntu:latest" ]; then
|
||||
apt-get update
|
||||
apt-get install -y zsh git curl build-essential autoconf libncurses-dev
|
||||
elif [ "${{ matrix.os }}" = "archlinux:latest" ]; then
|
||||
pacman -Syu --noconfirm
|
||||
pacman -S --noconfirm zsh base-devel git
|
||||
fi
|
||||
|
||||
- name: test completion
|
||||
run: cd test && zsh -f runtests.zsh fzftab.ztst
|
||||
|
||||
- name: build binary module
|
||||
run: zsh -fc 'source ./fzf-tab.zsh && build-fzf-tab-module'
|
||||
|
||||
- name: test binary module
|
||||
run: cd test && zsh -f runtests.zsh fzftab.ztst
|
||||
|
||||
30
eggs/zsh/config/fzf-tab/.github/workflows/macos.yaml
vendored
Normal file
30
eggs/zsh/config/fzf-tab/.github/workflows/macos.yaml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: macOS compability
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- run: brew install autoconf automake libtool
|
||||
|
||||
# FIXME: test on macOS
|
||||
#- name: test completion
|
||||
# run: cd test && zsh -f runtests.zsh fzftab.ztst
|
||||
|
||||
- name: build binary module
|
||||
run: zsh -fc 'source ./fzf-tab.zsh && build-fzf-tab-module'
|
||||
|
||||
#- name: test binary module
|
||||
# run: cd test && zsh -f runtests.zsh fzftab.ztst
|
||||
45
eggs/zsh/config/fzf-tab/.github/workflows/zsh.yaml
vendored
Normal file
45
eggs/zsh/config/fzf-tab/.github/workflows/zsh.yaml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: Zsh compability
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# TODO: test fzf version?
|
||||
zsh_version:
|
||||
- 5.3.1
|
||||
- 5.4.2
|
||||
- 5.5.1
|
||||
- 5.6.2
|
||||
- 5.7.1
|
||||
- 5.8
|
||||
- 5.9
|
||||
container:
|
||||
image: zshusers/zsh:${{ matrix.zsh_version }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: install dependencies
|
||||
run: apt update && apt-get install -y git curl build-essential autoconf libncurses-dev
|
||||
|
||||
- name: test completion
|
||||
run: cd test && zsh -f runtests.zsh fzftab.ztst
|
||||
|
||||
- name: build binary module
|
||||
run: zsh -fc 'source ./fzf-tab.zsh && build-fzf-tab-module'
|
||||
|
||||
- name: test binary module
|
||||
run: cd test && zsh -f runtests.zsh fzftab.ztst
|
||||
|
||||
1
eggs/zsh/config/fzf-tab/.gitignore
vendored
Normal file
1
eggs/zsh/config/fzf-tab/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.zwc
|
||||
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Jason Cox
|
||||
Copyright (c) 2019-2024 Aloxaf
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
169
eggs/zsh/config/fzf-tab/README.md
Normal file
169
eggs/zsh/config/fzf-tab/README.md
Normal file
@ -0,0 +1,169 @@
|
||||
# fzf-tab
|
||||
|
||||
[](https://github.com/Aloxaf/fzf-tab/actions?query=workflow%3Aci)
|
||||
[](https://github.com/Aloxaf/fzf-tab/blob/master/LICENSE)
|
||||
|
||||
Replace zsh's default completion selection menu with fzf!
|
||||
|
||||
[](https://asciinema.org/a/293849)
|
||||
|
||||
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
|
||||
**Table of Contents**
|
||||
|
||||
- [fzf-tab](#fzf-tab)
|
||||
- [Install](#install)
|
||||
- [Manual](#manual)
|
||||
- [Antigen](#antigen)
|
||||
- [Zinit](#zinit)
|
||||
- [Oh-My-Zsh](#oh-my-zsh)
|
||||
- [Prezto](#prezto)
|
||||
- [Usage](#usage)
|
||||
- [Configure](#configure)
|
||||
- [Tmux](#tmux)
|
||||
- [Binary module](#binary-module)
|
||||
- [Difference from other plugins](#difference-from-other-plugins)
|
||||
- [Compatibility with other plugins](#compatibility-with-other-plugins)
|
||||
- [Related projects](#related-projects)
|
||||
|
||||
<!-- markdown-toc end -->
|
||||
|
||||
# Install
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> 1. make sure [fzf](https://github.com/junegunn/fzf) is installed
|
||||
> 2. fzf-tab needs to be loaded after `compinit`, but before plugins which will wrap widgets, such as [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions) or [fast-syntax-highlighting](https://github.com/zdharma-continuum/fast-syntax-highlighting)
|
||||
> 3. Completions should be configured before `compinit`, as stated in the [zsh-completions manual installation guide](https://github.com/zsh-users/zsh-completions#manual-installation).
|
||||
|
||||
### Manual
|
||||
|
||||
First, clone this repository.
|
||||
|
||||
```zsh
|
||||
git clone https://github.com/Aloxaf/fzf-tab ~/somewhere
|
||||
```
|
||||
|
||||
Then add the following line to your `~/.zshrc`.
|
||||
|
||||
```zsh
|
||||
autoload -U compinit; compinit
|
||||
source ~/somewhere/fzf-tab.plugin.zsh
|
||||
```
|
||||
|
||||
### Antigen
|
||||
|
||||
```zsh
|
||||
antigen bundle Aloxaf/fzf-tab
|
||||
```
|
||||
|
||||
### Zinit
|
||||
|
||||
```zsh
|
||||
zinit light Aloxaf/fzf-tab
|
||||
```
|
||||
|
||||
### Oh-My-Zsh
|
||||
|
||||
Clone this repository to your custom directory and then add `fzf-tab` to your plugin list.
|
||||
|
||||
```zsh
|
||||
git clone https://github.com/Aloxaf/fzf-tab ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/fzf-tab
|
||||
```
|
||||
|
||||
### Prezto
|
||||
|
||||
Clone this repository to your contrib directory and then add `fzf-tab` to your module list in `.zpreztorc`.
|
||||
|
||||
```zsh
|
||||
git clone https://github.com/Aloxaf/fzf-tab $ZPREZTODIR/contrib/fzf-tab
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
Just press <kbd>Tab</kbd> as usual~
|
||||
|
||||
Available keybindings:
|
||||
|
||||
- <kbd>Ctrl</kdb>+<kdb>Space</kbd>: select multiple results, can be configured by `fzf-bindings` tag
|
||||
|
||||
- <kbd>F1</kbd>/<kbd>F2</kbd>: switch between groups, can be configured by `switch-group` tag
|
||||
|
||||
- <kbd>/</kbd>: trigger continuous completion (useful when completing a deep path), can be configured by `continuous-trigger` tag
|
||||
|
||||
Available commands:
|
||||
|
||||
- `disable-fzf-tab`: disable fzf-tab and fallback to compsys
|
||||
|
||||
- `enable-fzf-tab`: enable fzf-tab
|
||||
|
||||
- `toggle-fzf-tab`: toggle the state of fzf-tab. This is also a zle widget.
|
||||
|
||||
## Configure
|
||||
|
||||
A common configuration is:
|
||||
|
||||
```zsh
|
||||
# disable sort when completing `git checkout`
|
||||
zstyle ':completion:*:git-checkout:*' sort false
|
||||
# set descriptions format to enable group support
|
||||
# NOTE: don't use escape sequences (like '%F{red}%d%f') here, fzf-tab will ignore them
|
||||
zstyle ':completion:*:descriptions' format '[%d]'
|
||||
# set list-colors to enable filename colorizing
|
||||
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
|
||||
# force zsh not to show completion menu, which allows fzf-tab to capture the unambiguous prefix
|
||||
zstyle ':completion:*' menu no
|
||||
# preview directory's content with eza when completing cd
|
||||
zstyle ':fzf-tab:complete:cd:*' fzf-preview 'eza -1 --color=always $realpath'
|
||||
# custom fzf flags
|
||||
# NOTE: fzf-tab does not follow FZF_DEFAULT_OPTS by default
|
||||
zstyle ':fzf-tab:*' fzf-flags --color=fg:1,fg+:2 --bind=tab:accept
|
||||
# To make fzf-tab follow FZF_DEFAULT_OPTS.
|
||||
# NOTE: This may lead to unexpected behavior since some flags break this plugin. See Aloxaf/fzf-tab#455.
|
||||
zstyle ':fzf-tab:*' use-fzf-default-opts yes
|
||||
# switch group using `<` and `>`
|
||||
zstyle ':fzf-tab:*' switch-group '<' '>'
|
||||
```
|
||||
|
||||
## Tmux
|
||||
|
||||
If you're using tmux >= 3.2, we provide a script `ftb-tmux-popup` to make full use of it's "popup" feature.
|
||||
|
||||
```zsh
|
||||
zstyle ':fzf-tab:*' fzf-command ftb-tmux-popup
|
||||
```
|
||||
|
||||
BTW, you can also use this script outside the fzf-tab.
|
||||
|
||||
```zsh
|
||||
ls | ftb-tmux-popup
|
||||
```
|
||||
|
||||
[](https://asciinema.org/a/367471)
|
||||
|
||||
For more information, please see [Wiki#Configuration](https://github.com/Aloxaf/fzf-tab/wiki/Configuration).
|
||||
|
||||
## Binary module
|
||||
|
||||
By default, fzf-tab uses [zsh-ls-colors](https://github.com/xPMo/zsh-ls-colors) to parse and apply ZLS_COLORS if you have set the `list-colors` tag.
|
||||
|
||||
However, it is a pure zsh script and is slow if you have too many files to colorize.
|
||||
fzf-tab is shipped with a binary module to speed up this process. You can build it with `build-fzf-tab-module`, then it will be enabled automatically.
|
||||
|
||||
# Difference from other plugins
|
||||
|
||||
fzf-tab doesn't do "complete", it just shows you the results of the default completion system.
|
||||
|
||||
So it works EVERYWHERE (variables, function names, directory stack, in-word completion, etc.).
|
||||
And most of your configuration for default completion system is still valid.
|
||||
|
||||
# Compatibility with other plugins
|
||||
|
||||
Some plugins may also bind "^I" to their custom widget, like [fzf/shell/completion.zsh](https://github.com/junegunn/fzf/blob/master/shell/completion.zsh) or [ohmyzsh/lib/completion.zsh](https://github.com/ohmyzsh/ohmyzsh/blob/master/lib/completion.zsh#L61-L73).
|
||||
|
||||
By default, fzf-tab will call the widget previously bound to "^I" to get the completion list. So there is no problem in most cases, unless fzf-tab is initialized before a plugin which doesn't handle the previous binding properly.
|
||||
|
||||
So if you find your fzf-tab doesn't work properly, **please make sure it is the last plugin to bind "^I"** (If you don't know what I mean, just put it to the end of your plugin list).
|
||||
|
||||
# Related projects
|
||||
|
||||
- https://github.com/lincheney/fzf-tab-completion (fzf tab completion for zsh, bash and GNU readline apps)
|
||||
3
eggs/zsh/config/fzf-tab/fzf-tab.plugin.zsh
Normal file
3
eggs/zsh/config/fzf-tab/fzf-tab.plugin.zsh
Normal file
@ -0,0 +1,3 @@
|
||||
0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
|
||||
0="${${(M)0:#/*}:-$PWD/$0}"
|
||||
source "${0:A:h}/fzf-tab.zsh"
|
||||
440
eggs/zsh/config/fzf-tab/fzf-tab.zsh
Normal file
440
eggs/zsh/config/fzf-tab/fzf-tab.zsh
Normal file
@ -0,0 +1,440 @@
|
||||
# temporarily change options
|
||||
'builtin' 'local' '-a' '_ftb_opts'
|
||||
[[ ! -o 'aliases' ]] || _ftb_opts+=('aliases')
|
||||
[[ ! -o 'sh_glob' ]] || _ftb_opts+=('sh_glob')
|
||||
[[ ! -o 'no_brace_expand' ]] || _ftb_opts+=('no_brace_expand')
|
||||
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
|
||||
|
||||
# disable aliases
|
||||
typeset _ftb_aliases="$(builtin alias -Lm '[^+]*')"
|
||||
builtin unalias -m '[^+]*'
|
||||
|
||||
# thanks Valodim/zsh-capture-completion
|
||||
-ftb-compadd() {
|
||||
# parse all options
|
||||
local -A apre hpre dscrs _oad _mesg
|
||||
local -a isfile _opts __ expl
|
||||
zparseopts -a _opts P:=apre p:=hpre d:=dscrs X+:=expl O:=_oad A:=_oad D:=_oad f=isfile \
|
||||
i: S: s: I: x:=_mesg r: R: W: F: M+: E: q e Q n U C \
|
||||
J:=__ V:=__ a=__ l=__ k=__ o::=__ 1=__ 2=__
|
||||
|
||||
# store $curcontext for further usage
|
||||
_ftb_curcontext=${curcontext#:}
|
||||
|
||||
# just delegate and leave if any of -O, -A or -D are given or fzf-tab is not enabled
|
||||
# or fzf-tab is disabled in the current context
|
||||
if (( $#_oad != 0 || ! IN_FZF_TAB )) \
|
||||
|| { -ftb-zstyle -m disabled-on "any" } \
|
||||
|| { -ftb-zstyle -m disabled-on "files" && [[ -n $isfile ]] }; then
|
||||
builtin compadd "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
# store matches in $__hits and descriptions in $__dscr
|
||||
local -a __hits __dscr
|
||||
if (( $#dscrs == 1 )); then
|
||||
__dscr=( "${(@P)${(v)dscrs}}" )
|
||||
fi
|
||||
builtin compadd -A __hits -D __dscr "$@"
|
||||
local ret=$?
|
||||
if (( $#__hits == 0 )); then
|
||||
if is-at-least 5.9 && (( $#_mesg != 0 )); then
|
||||
builtin compadd -x $_mesg
|
||||
fi
|
||||
return $ret
|
||||
fi
|
||||
|
||||
# only store the fist `-X`
|
||||
expl=$expl[2]
|
||||
|
||||
# keep order of group description
|
||||
[[ -n $expl ]] && _ftb_groups+=$expl
|
||||
|
||||
# store these values in _ftb_compcap
|
||||
local -a keys=(apre hpre PREFIX SUFFIX IPREFIX ISUFFIX)
|
||||
local key expanded __tmp_value=$'<\0>' # placeholder
|
||||
for key in $keys; do
|
||||
expanded=${(P)key}
|
||||
if [[ -n $expanded ]]; then
|
||||
__tmp_value+=$'\0'$key$'\0'$expanded
|
||||
fi
|
||||
done
|
||||
if [[ -n $expl ]]; then
|
||||
# store group index
|
||||
__tmp_value+=$'\0group\0'$_ftb_groups[(ie)$expl]
|
||||
fi
|
||||
if [[ -n $isfile ]]; then
|
||||
# NOTE: need a extra ${} here or ~ expansion won't work
|
||||
__tmp_value+=$'\0realdir\0'${${(Qe)~${:-$IPREFIX$hpre}}}
|
||||
fi
|
||||
_opts+=("${(@kv)apre}" "${(@kv)hpre}" $isfile)
|
||||
__tmp_value+=$'\0args\0'${(pj:\1:)_opts}
|
||||
|
||||
if (( $+builtins[fzf-tab-compcap-generate] )); then
|
||||
fzf-tab-compcap-generate __hits __dscr __tmp_value
|
||||
else
|
||||
# dscr - the string to show to users
|
||||
# word - the string to be inserted
|
||||
local dscr word i
|
||||
for i in {1..$#__hits}; do
|
||||
word=$__hits[i] dscr=$__dscr[i]
|
||||
if [[ -n $dscr ]]; then
|
||||
dscr=${dscr//$'\n'}
|
||||
elif [[ -n $word ]]; then
|
||||
dscr=$word
|
||||
fi
|
||||
_ftb_compcap+=$dscr$'\2'$__tmp_value$'\0word\0'$word
|
||||
done
|
||||
fi
|
||||
|
||||
# tell zsh that the match is successful
|
||||
builtin compadd "$@"
|
||||
}
|
||||
|
||||
-ftb-zstyle() {
|
||||
zstyle $1 ":fzf-tab:$_ftb_curcontext" ${@:2}
|
||||
}
|
||||
|
||||
-ftb-complete() {
|
||||
local -Ua _ftb_groups
|
||||
local choice choices _ftb_curcontext continuous_trigger print_query accept_line bs=$'\2' nul=$'\0'
|
||||
local ret=0
|
||||
|
||||
# must run with user options; don't move `emulate -L zsh` above this line
|
||||
(( $+builtins[fzf-tab-compcap-generate] )) && fzf-tab-compcap-generate -i
|
||||
COLUMNS=500 _ftb__main_complete "$@" || ret=$?
|
||||
(( $+builtins[fzf-tab-compcap-generate] )) && fzf-tab-compcap-generate -o
|
||||
|
||||
emulate -L zsh -o extended_glob
|
||||
|
||||
local _ftb_query _ftb_complist=() _ftb_headers=() command opts
|
||||
-ftb-generate-complist # sets `_ftb_complist`
|
||||
|
||||
-ftb-zstyle -s continuous-trigger continuous_trigger || {
|
||||
[[ $OSTYPE == msys ]] && continuous_trigger=// || continuous_trigger=/
|
||||
}
|
||||
|
||||
case $#_ftb_complist in
|
||||
0) return 1;;
|
||||
1)
|
||||
choices=("EXPECT_KEY" "${_ftb_compcap[1]%$bs*}")
|
||||
if (( _ftb_continue_last )); then
|
||||
choices[1]=$continuous_trigger
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if (( ! _ftb_continue_last )) \
|
||||
&& [[ $compstate[insert] == *"unambiguous" ]] \
|
||||
&& [[ -n $compstate[unambiguous] ]] \
|
||||
&& [[ "$compstate[unambiguous]" != "$compstate[quote]$IPREFIX$PREFIX$compstate[quote]" ]]; then
|
||||
compstate[list]=
|
||||
compstate[insert]=unambiguous
|
||||
_ftb_finish=1
|
||||
return 0
|
||||
fi
|
||||
|
||||
-ftb-generate-query # sets `_ftb_query`
|
||||
-ftb-generate-header # sets `_ftb_headers`
|
||||
-ftb-zstyle -s print-query print_query || print_query=alt-enter
|
||||
-ftb-zstyle -s accept-line accept_line
|
||||
|
||||
choices=("${(@f)"$(builtin print -rl -- $_ftb_headers $_ftb_complist | -ftb-fzf)"}")
|
||||
ret=$?
|
||||
# choices=(query_string expect_key returned_word)
|
||||
|
||||
# insert query string directly
|
||||
if [[ $choices[2] == $print_query ]] || [[ -n $choices[1] && $#choices == 1 ]] ; then
|
||||
local -A v=("${(@0)${_ftb_compcap[1]}}")
|
||||
local -a args=("${(@ps:\1:)v[args]}")
|
||||
[[ -z $args[1] ]] && args=() # don't pass an empty string
|
||||
IPREFIX=$v[IPREFIX] PREFIX=$v[PREFIX] SUFFIX=$v[SUFFIX] ISUFFIX=$v[ISUFFIX]
|
||||
# NOTE: should I use `-U` here?, ../f\tabcd -> ../abcd
|
||||
builtin compadd "${args[@]:--Q}" -Q -- $choices[1]
|
||||
|
||||
compstate[list]=
|
||||
compstate[insert]=
|
||||
if (( $#choices[1] > 0 )); then
|
||||
compstate[insert]='1'
|
||||
[[ $RBUFFER == ' '* ]] || compstate[insert]+=' '
|
||||
fi
|
||||
_ftb_finish=1
|
||||
return $ret
|
||||
fi
|
||||
choices[1]=()
|
||||
|
||||
choices=("${(@)${(@)choices%$nul*}#*$nul}")
|
||||
|
||||
unset CTXT
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n $choices[1] && $choices[1] == $continuous_trigger ]]; then
|
||||
typeset -gi _ftb_continue=1
|
||||
typeset -gi _ftb_continue_last=1
|
||||
fi
|
||||
|
||||
if [[ -n $choices[1] && $choices[1] == $accept_line ]]; then
|
||||
typeset -gi _ftb_accept=1
|
||||
fi
|
||||
choices[1]=()
|
||||
|
||||
_ftb_choices=("${(@)choices}")
|
||||
|
||||
compstate[list]=
|
||||
compstate[insert]=
|
||||
|
||||
return $ret
|
||||
}
|
||||
|
||||
_fzf-tab-apply() {
|
||||
local choice bs=$'\2'
|
||||
for choice in "$_ftb_choices[@]"; do
|
||||
local -A v=("${(@0)${_ftb_compcap[(r)${(b)choice}$bs*]#*$bs}}")
|
||||
local -a args=("${(@ps:\1:)v[args]}")
|
||||
[[ -z $args[1] ]] && args=() # don't pass an empty string
|
||||
IPREFIX=${v[IPREFIX]-} PREFIX=${v[PREFIX]-} SUFFIX=${v[SUFFIX]-} ISUFFIX=${v[ISUFFIX]-}
|
||||
builtin compadd "${args[@]:--Q}" -Q -- "$v[word]"
|
||||
done
|
||||
|
||||
compstate[list]=
|
||||
if (( $#_ftb_choices == 1 )); then
|
||||
compstate[insert]='1'
|
||||
[[ $RBUFFER == ' '* ]] || compstate[insert]+=' '
|
||||
elif (( $#_ftb_choices > 1 )); then
|
||||
compstate[insert]='all'
|
||||
fi
|
||||
}
|
||||
|
||||
fzf-tab-debug() {
|
||||
(( $+_ftb_debug_cnt )) || typeset -gi _ftb_debug_cnt
|
||||
local tmp=${TMPPREFIX:-/tmp/zsh}-$$-fzf-tab-$(( ++_ftb_debug_cnt )).log
|
||||
local -i debug_fd=-1 IN_FZF_TAB=1
|
||||
{
|
||||
exec {debug_fd}>&2 2>| $tmp
|
||||
local -a debug_indent; debug_indent=( '%'{3..20}'(e. .)' )
|
||||
local PROMPT4 PS4="${(j::)debug_indent}+%N:%i> "
|
||||
functions -t -- -ftb-complete _fzf-tab-apply fzf-tab-complete
|
||||
{
|
||||
echo $ZSH_NAME $ZSH_VERSION
|
||||
echo fzf-tab: $(-ftb-version)
|
||||
typeset -p FZF_DEFAULT_OPTS
|
||||
echo $commands[fzf] $(fzf --version)
|
||||
} >&2
|
||||
zle fzf-tab-complete
|
||||
if (( debug_fd != -1 )); then
|
||||
zle -M "fzf-tab-debug: Trace output left in $tmp"
|
||||
fi
|
||||
} always {
|
||||
functions +t -- -ftb-complete _fzf-tab-apply fzf-tab-complete
|
||||
(( debug_fd != -1 )) && exec 2>&$debug_fd {debug_fd}>&-
|
||||
}
|
||||
}
|
||||
|
||||
fzf-tab-complete() {
|
||||
# this name must be ugly to avoid clashes
|
||||
local -i _ftb_continue=1 _ftb_continue_last=0 _ftb_accept=0 ret=0
|
||||
# hide the cursor until finishing completion, so that users won't see cursor up and down
|
||||
# NOTE: MacOS Terminal doesn't support civis & cnorm
|
||||
echoti civis >/dev/tty 2>/dev/null
|
||||
while (( _ftb_continue )); do
|
||||
local _ftb_choices=() _ftb_compcap=() _ftb_finish=0
|
||||
_ftb_continue=0
|
||||
local IN_FZF_TAB=1
|
||||
{
|
||||
zle .fzf-tab-orig-$_ftb_orig_widget || ret=$?
|
||||
if (( ! ret && ! _ftb_finish )); then
|
||||
zle _fzf-tab-apply || ret=$?
|
||||
fi
|
||||
} always {
|
||||
IN_FZF_TAB=0
|
||||
}
|
||||
if (( _ftb_continue )); then
|
||||
zle .split-undo
|
||||
zle .reset-prompt
|
||||
zle -R
|
||||
zle fzf-tab-dummy
|
||||
fi
|
||||
done
|
||||
echoti cnorm >/dev/tty 2>/dev/null
|
||||
zle .redisplay
|
||||
(( _ftb_accept )) && zle .accept-line
|
||||
return $ret
|
||||
}
|
||||
|
||||
# this function does nothing, it is used to be wrapped by other plugins like f-sy-h.
|
||||
# this make it possible to call the wrapper function without causing any other side effects.
|
||||
fzf-tab-dummy() { }
|
||||
|
||||
zle -N fzf-tab-debug
|
||||
zle -N fzf-tab-complete
|
||||
zle -N fzf-tab-dummy
|
||||
# this is registered as a completion widget
|
||||
# so that we can have a clean completion list to only insert the results user selected
|
||||
zle -C _fzf-tab-apply complete-word _fzf-tab-apply
|
||||
|
||||
disable-fzf-tab() {
|
||||
emulate -L zsh -o extended_glob
|
||||
(( $+_ftb_orig_widget )) || return 0
|
||||
|
||||
bindkey '^I' $_ftb_orig_widget
|
||||
case $_ftb_orig_list_grouped in
|
||||
0) zstyle ':completion:*' list-grouped false ;;
|
||||
1) zstyle ':completion:*' list-grouped true ;;
|
||||
2) zstyle -d ':completion:*' list-grouped ;;
|
||||
esac
|
||||
unset _ftb_orig_widget _ftb_orig_list_groupded
|
||||
|
||||
# unhook compadd so that _approximate can work properply
|
||||
unfunction compadd 2>/dev/null
|
||||
|
||||
functions[_main_complete]=$functions[_ftb__main_complete]
|
||||
functions[_approximate]=$functions[_ftb__approximate]
|
||||
|
||||
# Don't remove .fzf-tab-orig-$_ftb_orig_widget as we won't be able to reliably
|
||||
# create it if enable-fzf-tab is called again.
|
||||
}
|
||||
|
||||
enable-fzf-tab() {
|
||||
emulate -L zsh -o extended_glob
|
||||
(( ! $+_ftb_orig_widget )) || disable-fzf-tab
|
||||
|
||||
typeset -g _ftb_orig_widget="${${$(builtin bindkey '^I')##* }:-expand-or-complete}"
|
||||
if (( ! $+widgets[.fzf-tab-orig-$_ftb_orig_widget] )); then
|
||||
# Widgets that get replaced by compinit.
|
||||
local compinit_widgets=(
|
||||
complete-word
|
||||
delete-char-or-list
|
||||
expand-or-complete
|
||||
expand-or-complete-prefix
|
||||
list-choices
|
||||
menu-complete
|
||||
menu-expand-or-complete
|
||||
reverse-menu-complete
|
||||
)
|
||||
# Note: We prefix the name of the widget with '.' so that it doesn't get wrapped.
|
||||
if [[ $widgets[$_ftb_orig_widget] == builtin &&
|
||||
$compinit_widgets[(Ie)$_ftb_orig_widget] != 0 ]]; then
|
||||
# We are initializing before compinit and being asked to fall back to a completion
|
||||
# widget that isn't defined yet. Create our own copy of the widget ahead of time.
|
||||
zle -C .fzf-tab-orig-$_ftb_orig_widget .$_ftb_orig_widget _main_complete
|
||||
else
|
||||
# Copy the widget before it's wrapped by zsh-autosuggestions and zsh-syntax-highlighting.
|
||||
zle -A $_ftb_orig_widget .fzf-tab-orig-$_ftb_orig_widget
|
||||
fi
|
||||
fi
|
||||
|
||||
zstyle -t ':completion:*' list-grouped false
|
||||
typeset -g _ftb_orig_list_grouped=$?
|
||||
|
||||
zstyle ':completion:*' list-grouped false
|
||||
bindkey -M emacs '^I' fzf-tab-complete
|
||||
bindkey -M viins '^I' fzf-tab-complete
|
||||
bindkey -M emacs '^X.' fzf-tab-debug
|
||||
bindkey -M viins '^X.' fzf-tab-debug
|
||||
|
||||
# make sure we can copy them
|
||||
autoload +X -Uz _main_complete _approximate
|
||||
|
||||
# hook compadd
|
||||
functions[compadd]=$functions[-ftb-compadd]
|
||||
|
||||
# hook _main_complete to trigger fzf-tab
|
||||
functions[_ftb__main_complete]=$functions[_main_complete]
|
||||
function _main_complete() { -ftb-complete "$@" }
|
||||
|
||||
# TODO: This is not a full support, see #47
|
||||
# _approximate will also hook compadd
|
||||
# let it call -ftb-compadd instead of builtin compadd so that fzf-tab can capture result
|
||||
# make sure _approximate has been loaded.
|
||||
functions[_ftb__approximate]=$functions[_approximate]
|
||||
function _approximate() {
|
||||
# if not called by fzf-tab, don't do anything with compadd
|
||||
(( ! IN_FZF_TAB )) || unfunction compadd
|
||||
_ftb__approximate
|
||||
(( ! IN_FZF_TAB )) || functions[compadd]=$functions[-ftb-compadd]
|
||||
}
|
||||
}
|
||||
|
||||
toggle-fzf-tab() {
|
||||
emulate -L zsh -o extended_glob
|
||||
if (( $+_ftb_orig_widget )); then
|
||||
disable-fzf-tab
|
||||
else
|
||||
enable-fzf-tab
|
||||
fi
|
||||
}
|
||||
|
||||
build-fzf-tab-module() {
|
||||
{
|
||||
pushd -q $FZF_TAB_HOME/modules
|
||||
if -ftb-build-module $@; then
|
||||
print -P "%F{green}%BThe module has been built successfully. Please restart zsh to apply it.%f%b"
|
||||
else
|
||||
print -P -u2 "%F{red}%BThe module building has failed. See the output above for details.%f%b"
|
||||
return 1
|
||||
fi
|
||||
} always {
|
||||
popd -q
|
||||
}
|
||||
}
|
||||
|
||||
zmodload zsh/zutil
|
||||
zmodload zsh/mapfile
|
||||
zmodload -F zsh/stat b:zstat
|
||||
|
||||
0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
|
||||
0="${${(M)0:#/*}:-$PWD/$0}"
|
||||
FZF_TAB_HOME="${0:A:h}"
|
||||
|
||||
source "$FZF_TAB_HOME"/lib/zsh-ls-colors/ls-colors.zsh fzf-tab-lscolors
|
||||
|
||||
typeset -ga _ftb_group_colors=(
|
||||
$'\x1b[94m' $'\x1b[32m' $'\x1b[33m' $'\x1b[35m' $'\x1b[31m' $'\x1b[38;5;27m' $'\x1b[36m'
|
||||
$'\x1b[38;5;100m' $'\x1b[38;5;98m' $'\x1b[91m' $'\x1b[38;5;80m' $'\x1b[92m'
|
||||
$'\x1b[38;5;214m' $'\x1b[38;5;165m' $'\x1b[38;5;124m' $'\x1b[38;5;120m'
|
||||
)
|
||||
|
||||
# init
|
||||
() {
|
||||
emulate -L zsh -o extended_glob
|
||||
|
||||
if (( ! $fpath[(I)$FZF_TAB_HOME/lib] )); then
|
||||
fpath+=($FZF_TAB_HOME/lib)
|
||||
fi
|
||||
|
||||
autoload -Uz is-at-least -- $FZF_TAB_HOME/lib/-#ftb*(:t)
|
||||
|
||||
if (( $+FZF_TAB_COMMAND || $+FZF_TAB_OPTS || $+FZF_TAB_QUERY || $+FZF_TAB_SINGLE_GROUP || $+fzf_tab_preview_init )) \
|
||||
|| zstyle -m ":fzf-tab:*" command '*' \
|
||||
|| zstyle -m ":fzf-tab:*" extra-opts '*'; then
|
||||
print -P "%F{red}%B[fzf-tab] Sorry, your configuration is not supported anymore\n" \
|
||||
"See https://github.com/Aloxaf/fzf-tab/pull/132 for more information%f%b"
|
||||
fi
|
||||
|
||||
if [[ -n $FZF_TAB_HOME/modules/Src/aloxaf/fzftab.(so|bundle)(#qN) ]]; then
|
||||
module_path+=("$FZF_TAB_HOME/modules/Src")
|
||||
zmodload aloxaf/fzftab
|
||||
|
||||
if [[ $FZF_TAB_MODULE_VERSION != "0.2.2" ]]; then
|
||||
zmodload -u aloxaf/fzftab
|
||||
local rebuild
|
||||
print -Pn "%F{yellow}fzftab module needs to be rebuild, rebuild now?[Y/n]:%f"
|
||||
read -q rebuild
|
||||
if [[ $rebuild == y ]]; then
|
||||
build-fzf-tab-module
|
||||
zmodload aloxaf/fzftab
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
enable-fzf-tab
|
||||
zle -N toggle-fzf-tab
|
||||
|
||||
# restore aliases
|
||||
eval "$_ftb_aliases"
|
||||
builtin unset _ftb_aliases
|
||||
|
||||
# restore options
|
||||
(( ${#_ftb_opts} )) && setopt ${_ftb_opts[@]}
|
||||
'builtin' 'unset' '_ftb_opts'
|
||||
38
eggs/zsh/config/fzf-tab/lib/-ftb-build-module
Normal file
38
eggs/zsh/config/fzf-tab/lib/-ftb-build-module
Normal file
@ -0,0 +1,38 @@
|
||||
#!/hint/zsh
|
||||
emulate -LR zsh -o extended_glob -o err_return
|
||||
|
||||
local zsh_version=${1:-${FZF_TAB_ZSH_SRC_VERSION:-$ZSH_VERSION}}
|
||||
|
||||
# macos check
|
||||
local ret bundle nproc
|
||||
[[ $OSTYPE == darwin* ]] && {
|
||||
[[ -n ${module_path[1]}/**/*.bundle(#qN) ]] && bundle=true
|
||||
nproc=$(sysctl -n hw.logicalcpu)
|
||||
} || {
|
||||
nproc=$(nproc)
|
||||
}
|
||||
|
||||
# clone zsh source code if not exists
|
||||
[[ -d ./zsh/$zsh_version ]] || {
|
||||
git clone --depth=1 --branch zsh-$zsh_version https://github.com/zsh-users/zsh ./zsh/$zsh_version
|
||||
}
|
||||
|
||||
ln -sf $PWD/Src/fzftab.c ./zsh/$zsh_version/Src/Modules/
|
||||
ln -sf $PWD/Src/fzftab.mdd ./zsh/$zsh_version/Src/Modules/
|
||||
|
||||
# build zsh
|
||||
cd -q ./zsh/$zsh_version
|
||||
|
||||
git checkout -- .
|
||||
|
||||
[[ $zsh_version != "5.9" ]] || {
|
||||
curl -s https://github.com/zsh-users/zsh/commit/4c89849c98172c951a9def3690e8647dae76308f.patch | git apply --exclude=ChangeLog -
|
||||
curl -s https://github.com/zsh-users/zsh/commit/ab4d62eb975a4c4c51dd35822665050e2ddc6918.patch | git apply --exclude=ChangeLog -
|
||||
}
|
||||
|
||||
[[ -f ./configure ]] || ./Util/preconfig
|
||||
[[ -f ./Makefile ]] || ./configure --disable-gdbm --disable-pcre --without-tcsetpgrp --prefix=/tmp/zsh-fzf-tab-module ${bundle:+DL_EXT=bundle}
|
||||
make -j$nproc
|
||||
|
||||
# we only need aloxaf/fzftab.so
|
||||
mv ./Src/Modules/fzftab.(so|bundle) $FZF_TAB_HOME/modules/Src/aloxaf/
|
||||
34
eggs/zsh/config/fzf-tab/lib/-ftb-colorize
Normal file
34
eggs/zsh/config/fzf-tab/lib/-ftb-colorize
Normal file
@ -0,0 +1,34 @@
|
||||
#!/hint/zsh
|
||||
emulate -L zsh -o cbases -o octalzeroes
|
||||
|
||||
local REPLY
|
||||
local -a reply stat lstat
|
||||
|
||||
# fzf-tab-lscolors::match-by $1 lstat follow
|
||||
zstat -A lstat -L -- $1
|
||||
# follow symlink
|
||||
(( lstat[3] & 0170000 )) && zstat -A stat -- $1 2>/dev/null
|
||||
|
||||
fzf-tab-lscolors::from-mode "$1" "$lstat[3]" $stat[3]
|
||||
# fall back to name
|
||||
[[ -z $REPLY ]] && fzf-tab-lscolors::from-name $1
|
||||
|
||||
# If this is a symlink
|
||||
if [[ -n $lstat[14] ]]; then
|
||||
local sym_color=$REPLY
|
||||
local rsv_color=$REPLY
|
||||
local rsv=$lstat[14]
|
||||
# If this is not a broken symlink
|
||||
if [[ -e $rsv ]]; then
|
||||
# fzf-tab-lscolors::match-by $rsv stat
|
||||
zstat -A stat -- $rsv
|
||||
fzf-tab-lscolors::from-mode $rsv $stat[3]
|
||||
# fall back to name
|
||||
[[ -z $REPLY ]] && fzf-tab-lscolors::from-name $rsv
|
||||
rsv_color=$REPLY
|
||||
fi
|
||||
dpre=$'\033[0m\033['$sym_color'm'
|
||||
dsuf+=$'\033[0m -> \033['$rsv_color'm'$rsv
|
||||
else
|
||||
dpre=$'\033[0m\033['$REPLY'm'
|
||||
fi
|
||||
114
eggs/zsh/config/fzf-tab/lib/-ftb-fzf
Executable file
114
eggs/zsh/config/fzf-tab/lib/-ftb-fzf
Executable file
@ -0,0 +1,114 @@
|
||||
#!/hint/zsh
|
||||
|
||||
# import math functions
|
||||
autoload -Uz zmathfunc
|
||||
zmathfunc
|
||||
|
||||
local tmp_dir=${TMPPREFIX:-/tmp/zsh}-fzf-tab-$USER
|
||||
[[ -d $tmp_dir ]] || command mkdir $tmp_dir
|
||||
|
||||
local ftb_preview_init="
|
||||
zmodload zsh/mapfile
|
||||
local -a _ftb_compcap=(\"\${(@f)mapfile[$tmp_dir/compcap.$$]}\")
|
||||
local -a _ftb_groups=(\"\${(@f)mapfile[$tmp_dir/groups.$$]}\")
|
||||
local bs=\$'\2'
|
||||
# get description
|
||||
export desc=\${\${\"\$(<{f})\"%\$'\0'*}#*\$'\0'}
|
||||
# get ctxt for current completion
|
||||
local -A ctxt=(\"\${(@0)\${_ftb_compcap[(r)\${(b)desc}\$bs*]#*\$bs}}\")
|
||||
# get group
|
||||
if (( \$+ctxt[group] )); then
|
||||
export group=\$_ftb_groups[\$ctxt[group]]
|
||||
fi
|
||||
# get original word
|
||||
export word=\${(Q)ctxt[word]}
|
||||
# get real path if it is file
|
||||
if (( \$+ctxt[realdir] )); then
|
||||
export realpath=\${ctxt[realdir]}\$word
|
||||
fi
|
||||
$(typeset -p words)
|
||||
"
|
||||
local default_binds=tab:down,btab:up,change:top,ctrl-space:toggle,bspace:backward-delete-char/eof,ctrl-h:backward-delete-char/eof
|
||||
local fzf_command fzf_flags fzf_preview debug_command tmp switch_group fzf_pad fzf_min_height binds
|
||||
local ret=0
|
||||
|
||||
-ftb-zstyle -s fzf-command fzf_command || fzf_command=fzf
|
||||
-ftb-zstyle -a fzf-bindings-default tmp && binds=${(j:,:)tmp} || binds=$default_binds
|
||||
-ftb-zstyle -a fzf-bindings tmp && binds+=,${(j:,:)tmp}
|
||||
-ftb-zstyle -a fzf-flags fzf_flags
|
||||
-ftb-zstyle -s fzf-preview fzf_preview
|
||||
-ftb-zstyle -a switch-group switch_group || switch_group=(F1 F2)
|
||||
-ftb-zstyle -s fzf-pad fzf_pad || fzf_pad=2
|
||||
-ftb-zstyle -s fzf-min-height fzf_min_height || fzf_min_height=0
|
||||
-ftb-zstyle -b use-fzf-default-opts use_fzf_default_opts || use_fzf_default_opts="no"
|
||||
|
||||
-ftb-zstyle -a debug-command debug_command && {
|
||||
${(eX)debug_command} $fzf_flags
|
||||
return
|
||||
}
|
||||
|
||||
print -rl -- $_ftb_compcap > $tmp_dir/compcap.$$
|
||||
print -rl -- $_ftb_groups > $tmp_dir/groups.$$
|
||||
print -r -- ${ftb_preview_init/{f}/\$1} > $tmp_dir/ftb_preview_init.$$
|
||||
|
||||
binds=${binds//{_FTB_INIT_}/. $tmp_dir/ftb_preview_init.$$ {f} $'\n'}
|
||||
|
||||
local -i header_lines=$#_ftb_headers
|
||||
local -i lines=$(( $#_ftb_compcap + fzf_pad + header_lines ))
|
||||
local reload_command="$commands[zsh] -f $FZF_TAB_HOME/lib/ftb-switch-group $$ $header_lines $tmp_dir"
|
||||
|
||||
# detect if we will use tmux popup
|
||||
local use_tmux_popup=0
|
||||
if [[ $fzf_command == "ftb-tmux-popup" ]]; then
|
||||
use_tmux_popup=1
|
||||
fi
|
||||
|
||||
if (( ! use_tmux_popup )); then
|
||||
# fzf will cause the current line to refresh, so move the cursor down.
|
||||
echoti cud1 >/dev/tty
|
||||
# reset cursor before call fzf
|
||||
echoti cnorm >/dev/tty 2>/dev/null
|
||||
fi
|
||||
|
||||
cat > $tmp_dir/completions.$$
|
||||
|
||||
local dd='gdd'
|
||||
if (( ${+commands[$dd]} == 0 )) ; then
|
||||
dd='dd'
|
||||
fi
|
||||
if (( ${+commands[$dd]} == 0 )) ; then
|
||||
dd='true' # nop if dd is not installed
|
||||
fi
|
||||
|
||||
_ftb_query="${_ftb_query}$(command "$dd" bs=1G count=1 status=none iflag=nonblock < /dev/tty 2>/dev/null)" || true
|
||||
|
||||
local fzf_default_opts=''
|
||||
if [[ "$use_fzf_default_opts" == "yes" ]]; then
|
||||
fzf_default_opts=$FZF_DEFAULT_OPTS
|
||||
fi
|
||||
|
||||
FZF_DEFAULT_OPTS=$fzf_default_opts SHELL=$ZSH_NAME $fzf_command \
|
||||
--ansi \
|
||||
--bind=$binds \
|
||||
--bind="${switch_group[1]}:reload($reload_command -1),${switch_group[2]}:reload($reload_command 1)" \
|
||||
--cycle \
|
||||
--delimiter='\x00' \
|
||||
--expect=$continuous_trigger,$print_query,$accept_line \
|
||||
--header-lines=$header_lines \
|
||||
--height=${FZF_TMUX_HEIGHT:=$(( min(max(lines, fzf_min_height), LINES / 3 * 2) ))} \
|
||||
--layout=reverse \
|
||||
--multi \
|
||||
--nth=2,3 \
|
||||
--print-query \
|
||||
--query=$_ftb_query \
|
||||
--tiebreak=begin \
|
||||
${fzf_preview:+--preview=${ftb_preview_init/{f}/'{f}'}$fzf_preview} \
|
||||
$fzf_flags < $tmp_dir/completions.$$ || ret=$?
|
||||
|
||||
if (( ! use_tmux_popup )); then
|
||||
echoti civis >/dev/tty 2>/dev/null
|
||||
echoti cuu1 >/dev/tty
|
||||
fi
|
||||
|
||||
command rm $tmp_dir/*.$$ 2>/dev/null
|
||||
return $ret
|
||||
113
eggs/zsh/config/fzf-tab/lib/-ftb-generate-complist
Normal file
113
eggs/zsh/config/fzf-tab/lib/-ftb-generate-complist
Normal file
@ -0,0 +1,113 @@
|
||||
#!/hint/zsh
|
||||
|
||||
local dsuf dpre k _v filepath first_word show_group default_color prefix bs=$'\b'
|
||||
local -a list_colors group_colors tcandidates reply match mbegin mend
|
||||
local -i same_word=1 colorful=0
|
||||
local -Ua duplicate_groups=()
|
||||
local -A word_map=()
|
||||
|
||||
(( $#_ftb_compcap == 0 )) && return
|
||||
|
||||
-ftb-zstyle -s show-group show_group || show_group=full
|
||||
-ftb-zstyle -s default-color default_color || default_color=''
|
||||
-ftb-zstyle -s prefix prefix || {
|
||||
zstyle -m ':completion:*:descriptions' format '*' && prefix='·'
|
||||
}
|
||||
-ftb-zstyle -a group-colors group_colors || group_colors=($_ftb_group_colors)
|
||||
zstyle -a ":completion:$_ftb_curcontext" list-colors list_colors
|
||||
|
||||
# init colorize
|
||||
if (( $+builtins[fzf-tab-candidates-generate] )); then
|
||||
fzf-tab-candidates-generate -i list_colors
|
||||
else
|
||||
local -A namecolors=(${(@s:=:)${(@s.:.)list_colors}:#[[:alpha:]][[:alpha:]]=*})
|
||||
local -A modecolors=(${(@Ms:=:)${(@s.:.)list_colors}:#[[:alpha:]][[:alpha:]]=*})
|
||||
(( $#namecolors == 0 && $#modecolors == 0 )) && list_colors=()
|
||||
fi
|
||||
|
||||
if (( $#_ftb_groups == 1 )); then
|
||||
-ftb-zstyle -m single-group prefix || prefix=''
|
||||
-ftb-zstyle -m single-group color || group_colors=("$default_color")
|
||||
fi
|
||||
|
||||
if (( $+builtins[fzf-tab-candidates-generate] )); then
|
||||
fzf-tab-candidates-generate
|
||||
else
|
||||
for k _v in "${(@ps:\2:)_ftb_compcap}"; do
|
||||
local -A v=("${(@0)_v}")
|
||||
[[ $v[word] == ${first_word:=$v[word]} ]] || same_word=0
|
||||
|
||||
# add character and color to describe the type of the files
|
||||
dsuf='' dpre=''
|
||||
if (( $+v[realdir] )); then
|
||||
filepath=$v[realdir]${(Q)v[word]}
|
||||
if [[ -d $filepath ]]; then
|
||||
dsuf=/
|
||||
fi
|
||||
# add color and resolve symlink if have list-colors
|
||||
# detail: http://zsh.sourceforge.net/Doc/Release/Zsh-Modules.html#The-zsh_002fcomplist-Module
|
||||
if (( $#list_colors )) && [[ -a $filepath || -L $filepath ]]; then
|
||||
-ftb-colorize $filepath
|
||||
colorful=1
|
||||
elif [[ -L $filepath ]]; then
|
||||
dsuf=@
|
||||
fi
|
||||
if [[ $options[list_types] == off ]]; then
|
||||
dsuf=''
|
||||
fi
|
||||
fi
|
||||
|
||||
# add color to description if they have group index
|
||||
if (( $+v[group] )); then
|
||||
local color=$group_colors[$v[group]]
|
||||
# add a hidden group index at start of string to keep group order when sorting
|
||||
# first group index is for builtin sort, sencond is for GNU sort
|
||||
tcandidates+=$v[group]$'\b'$color$prefix$dpre$'\0'$v[group]$'\b'$k$'\0'$dsuf
|
||||
else
|
||||
tcandidates+=$default_color$dpre$'\0'$k$'\0'$dsuf
|
||||
fi
|
||||
|
||||
# check group with duplicate member
|
||||
if [[ $show_group == brief ]]; then
|
||||
if (( $+word_map[$v[word]] && $+v[group] )); then
|
||||
duplicate_groups+=$v[group] # add this group
|
||||
duplicate_groups+=$word_map[$v[word]] # add previous group
|
||||
fi
|
||||
word_map[$v[word]]=$v[group]
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
(( same_word )) && tcandidates[2,-1]=()
|
||||
|
||||
# sort and remove sort group or other index
|
||||
zstyle -T ":completion:$_ftb_curcontext" sort
|
||||
if (( $? != 1 )); then
|
||||
if (( colorful )); then
|
||||
# if enable list_colors, we should skip the first field
|
||||
if [[ ${commands[sort]:A:t} != (|busybox*) ]]; then
|
||||
# this is faster but doesn't work if `find` is from busybox
|
||||
tcandidates=(${(f)"$(command sort -u -t '\0' -k 2 <<< ${(pj:\n:)tcandidates})"})
|
||||
else
|
||||
# slower but portable
|
||||
tcandidates=(${(@o)${(@)tcandidates:/(#b)([^$'\0']#)$'\0'(*)/$match[2]$'\0'$match[1]}})
|
||||
tcandidates=(${(@)tcandidates/(#b)(*)$'\0'([^$'\0']#)/$match[2]$'\0'$match[1]})
|
||||
fi
|
||||
else
|
||||
tcandidates=("${(@o)tcandidates}")
|
||||
fi
|
||||
fi
|
||||
typeset -gUa _ftb_complist=("${(@)tcandidates//[0-9]#$bs}")
|
||||
|
||||
# hide needless group
|
||||
if (( $#_ftb_groups )); then
|
||||
local i to_hide indexs=({1..$#_ftb_groups})
|
||||
case $show_group in
|
||||
brief) to_hide=(${indexs:|duplicate_groups}) ;;
|
||||
none) to_hide=($indexs) ;;
|
||||
esac
|
||||
for i in $to_hide; do
|
||||
# NOTE: _ftb_groups is unique array
|
||||
_ftb_groups[i]="__hide__$i"
|
||||
done
|
||||
fi
|
||||
40
eggs/zsh/config/fzf-tab/lib/-ftb-generate-header
Normal file
40
eggs/zsh/config/fzf-tab/lib/-ftb-generate-header
Normal file
@ -0,0 +1,40 @@
|
||||
#!/hint/zsh
|
||||
|
||||
typeset -ga _ftb_headers=()
|
||||
local i tmp group_colors
|
||||
local -i mlen=0 len=0
|
||||
|
||||
# Keep ${group} value for preview, binds, etc without displaying group headers
|
||||
if { -ftb-zstyle -m show-group "quiet" }; then
|
||||
return
|
||||
fi
|
||||
|
||||
if (( $#_ftb_groups == 1 )) && { ! -ftb-zstyle -m single-group "header" }; then
|
||||
return
|
||||
fi
|
||||
|
||||
# calculate the max column width
|
||||
for i in $_ftb_groups; do
|
||||
(( $#i > mlen )) && mlen=$#i
|
||||
done
|
||||
mlen+=1
|
||||
|
||||
-ftb-zstyle -a group-colors group_colors || group_colors=($_ftb_group_colors)
|
||||
|
||||
for (( i=1; i<=$#_ftb_groups; i++ )); do
|
||||
[[ $_ftb_groups[i] == "__hide__"* ]] && continue
|
||||
|
||||
if (( len + $#_ftb_groups[i] > COLUMNS - 5 )); then
|
||||
_ftb_headers+=$tmp
|
||||
tmp='' && len=0
|
||||
fi
|
||||
if (( len + mlen > COLUMNS - 5 )); then
|
||||
# the last column doesn't need padding
|
||||
_ftb_headers+=$tmp$group_colors[i]$_ftb_groups[i]$'\033[00m'
|
||||
tmp='' && len=0
|
||||
else
|
||||
tmp+=$group_colors[i]${(r:$mlen:)_ftb_groups[i]}$'\033[00m'
|
||||
len+=$mlen
|
||||
fi
|
||||
done
|
||||
(( $#tmp )) && _ftb_headers+=$tmp
|
||||
40
eggs/zsh/config/fzf-tab/lib/-ftb-generate-query
Normal file
40
eggs/zsh/config/fzf-tab/lib/-ftb-generate-query
Normal file
@ -0,0 +1,40 @@
|
||||
#!/hint/zsh
|
||||
|
||||
if zmodload -s zsh/pcre; then
|
||||
setopt localoptions rematch_pcre
|
||||
fi
|
||||
|
||||
local key qtype tmp query_string
|
||||
typeset -g _ftb_query=
|
||||
-ftb-zstyle -a query-string query_string || query_string=(prefix input first)
|
||||
for qtype in $query_string; do
|
||||
if [[ $qtype == prefix ]]; then
|
||||
# find the longest common prefix among descriptions
|
||||
local -a keys=(${_ftb_compcap%$'\2'*})
|
||||
tmp=$keys[1]
|
||||
local MATCH match mbegin mend prefix=(${(s::)tmp})
|
||||
for key in ${keys:1}; do
|
||||
(( $#tmp )) || break
|
||||
[[ $key == $tmp* ]] && continue
|
||||
# interpose characters from the current common prefix and $key and see how
|
||||
# many pairs of equal characters we get at the start of the resulting string
|
||||
[[ ${(j::)${${(s::)key[1,$#tmp]}:^prefix}} =~ '^(((.)\3)*)' ]]
|
||||
# truncate common prefix and maintain loop invariant: ${(s::)tmp} == $prefix
|
||||
tmp[$#MATCH/2+1,-1]=""
|
||||
prefix[$#MATCH/2+1,-1]=()
|
||||
done
|
||||
elif [[ $qtype == input ]]; then
|
||||
local fv=${_ftb_compcap[1]#*$'\2'}
|
||||
local -A v=("${(@0)fv}")
|
||||
tmp=$v[PREFIX]
|
||||
if (( $RBUFFER[(i)$v[SUFFIX]] != 1 )); then
|
||||
tmp=${tmp/%$v[SUFFIX]}
|
||||
fi
|
||||
tmp=${${tmp#$v[hpre]}#$v[apre]}
|
||||
fi
|
||||
if (( $query_string[(I)longest] )); then
|
||||
(( $#tmp > $#_ftb_query )) && _ftb_query=$tmp
|
||||
elif [[ -n $tmp ]]; then
|
||||
_ftb_query=$tmp && break
|
||||
fi
|
||||
done
|
||||
10
eggs/zsh/config/fzf-tab/lib/-ftb-version
Normal file
10
eggs/zsh/config/fzf-tab/lib/-ftb-version
Normal file
@ -0,0 +1,10 @@
|
||||
#!/hint/zsh
|
||||
emulate -L zsh -o pipe_fail
|
||||
pushd -q $FZF_TAB_HOME
|
||||
if [[ -d .git ]]; then
|
||||
git describe --long --tags --abbrev=7 2>/dev/null | sed 's/\([^-]*-g\)/r\1/;s/-/./g' \
|
||||
|| printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short=7 HEAD)"
|
||||
else
|
||||
echo unknown
|
||||
fi
|
||||
popd -q
|
||||
38
eggs/zsh/config/fzf-tab/lib/ftb-switch-group
Normal file
38
eggs/zsh/config/fzf-tab/lib/ftb-switch-group
Normal file
@ -0,0 +1,38 @@
|
||||
#!/hint/zsh
|
||||
emulate -L zsh -o extended_glob
|
||||
|
||||
zmodload zsh/mapfile
|
||||
|
||||
# receive arguments
|
||||
local pid=$1 header_lines=$2 tmp_dir=$3 offset=$@[-1]
|
||||
|
||||
# read completion list
|
||||
local -a list=(${(f)mapfile[$tmp_dir/completions.$pid]})
|
||||
|
||||
# get total group count
|
||||
if (( $#list > 10000 )); then
|
||||
local -Ua total=(${(f)"$(print -l ${list:$header_lines} | grep -a -oP '^\x1b\[[0-9;]*m')"})
|
||||
else
|
||||
local -Ua total=(${(M)${list:$header_lines}#$'\x1b['[0-9;]#*m})
|
||||
fi
|
||||
|
||||
# get current group index, start from 2
|
||||
local current=2
|
||||
if [[ -f $tmp_dir/current-group.$pid ]]; then
|
||||
current=$(( $(<$tmp_dir/current-group.$pid) + offset ))
|
||||
fi
|
||||
(( current > $#total )) && current=1
|
||||
(( current == 0 )) && current=$#total
|
||||
echo $current > $tmp_dir/current-group.$pid
|
||||
|
||||
# print headers
|
||||
if (( header_lines != 0 )); then
|
||||
print -l ${list[1,header_lines]/${total[current]}/$'\x1b[1m'}
|
||||
fi
|
||||
|
||||
# print current group
|
||||
if (( $#list > 10000 )); then
|
||||
print -l ${list:$header_lines} | grep -a -F "${total[current]}"
|
||||
else
|
||||
print -l ${(M)${list:$header_lines}:#${total[current]}*}
|
||||
fi
|
||||
100
eggs/zsh/config/fzf-tab/lib/ftb-tmux-popup
Executable file
100
eggs/zsh/config/fzf-tab/lib/ftb-tmux-popup
Executable file
@ -0,0 +1,100 @@
|
||||
#!/hint/zsh
|
||||
# Show results with tmux popup
|
||||
# Example usage:
|
||||
# zstyle ':fzf-tab:*' fzf-command ftb-tmux-popup
|
||||
# zstyle ':fzf-tab:*' popup-pad 0 0
|
||||
# It can also be used as a standalone tool, like:
|
||||
# ls | ftb-tmux-popup
|
||||
emulate -L zsh -o extended_glob
|
||||
|
||||
# import math functions (only if they're not already defined)
|
||||
if (( ! $+functions[zsh_math_func_min] )); then
|
||||
autoload -Uz zmathfunc
|
||||
zmathfunc
|
||||
fi
|
||||
|
||||
: ${tmp_dir:=${TMPPREFIX:-/tmp/zsh}-fzf-tab-$USER}
|
||||
|
||||
# fallback to fzf if it is not running in tmux
|
||||
if (( ! $+TMUX_PANE )); then
|
||||
fzf $@
|
||||
return
|
||||
fi
|
||||
|
||||
local ret=0
|
||||
|
||||
local -a fzf_opts=($@)
|
||||
fzf_opts=(${${fzf_opts/--height*}/--layout*})
|
||||
|
||||
# get position of cursor and size of window
|
||||
local -a tmp=($(command tmux display-message -p "#{pane_top} #{cursor_y} #{pane_left} #{cursor_x} #{window_height} #{window_width} #{status} #{status-position}"))
|
||||
local cursor_y=$((tmp[1] + tmp[2])) cursor_x=$((tmp[3] + tmp[4])) window_height=$tmp[5] window_width=$tmp[6] window_top=0
|
||||
|
||||
if [[ $tmp[8] == 'top' ]]; then
|
||||
window_top=$tmp[7]
|
||||
cursor_y=$((cursor_y + window_top))
|
||||
fi
|
||||
|
||||
# if not called by fzf-tab
|
||||
if (( ! $+IN_FZF_TAB )); then
|
||||
[[ -d $tmp_dir ]] || mkdir -p $tmp_dir
|
||||
cat > $tmp_dir/completions.$$
|
||||
fi
|
||||
|
||||
local text REPLY comp_lines comp_length length popup_pad popup_min_size
|
||||
|
||||
zstyle -a ":fzf-tab:$_ftb_curcontext" popup-pad popup_pad || popup_pad=(0 0)
|
||||
zstyle -a ":fzf-tab:$_ftb_curcontext" popup-min-size popup_min_size || popup_min_size=(0 0)
|
||||
|
||||
# get the size of content, note we should remove all ANSI color code
|
||||
comp_lines=$(( ${#${(f)mapfile[$tmp_dir/completions.$$]}} + $popup_pad[2] ))
|
||||
if (( comp_lines <= 500 )); then
|
||||
comp_length=0
|
||||
for line in ${(f)mapfile[$tmp_dir/completions.$$]}; do
|
||||
length=${(m)#${(S)line//$'\x1b['[0-9]#*m}}
|
||||
(( length >= comp_length )) && comp_length=$length
|
||||
done
|
||||
else
|
||||
# FIXME: can't get the correct width of CJK characters.
|
||||
comp_length=$( command perl -ne 's/\x1b\[[0-9;]*m//g;s/\x00//g; $m= length() if $m < length(); END { print $m }' < $tmp_dir/completions.$$ )
|
||||
fi
|
||||
comp_length=$(( comp_length + $popup_pad[1] ))
|
||||
|
||||
local popup_height popup_y popup_width popup_x adjust_height
|
||||
|
||||
# adjust the popup height if the fzf finder info style is not default
|
||||
if (( $fzf_opts[(I)--info=*(hidden|inline)*] > 0 )); then
|
||||
adjust_height=-1
|
||||
fi
|
||||
|
||||
# calculate the popup height and y position
|
||||
if (( cursor_y * 2 > window_height )); then
|
||||
# show above the cursor
|
||||
popup_height=$(( min(max(comp_lines + 4, popup_min_size[2]), cursor_y - window_top) + adjust_height ))
|
||||
popup_y=$cursor_y
|
||||
if zstyle -T ":fzf-tab:$_ftb_curcontext" popup-smart-tab; then
|
||||
fzf_opts+=(--bind=tab:up,btab:down)
|
||||
fi
|
||||
fzf_opts+=(--layout=default)
|
||||
else
|
||||
# show below the cursor
|
||||
popup_height=$(( min(max(comp_lines + 4, popup_min_size[2]), window_height - cursor_y + window_top - 1) + adjust_height ))
|
||||
popup_y=$(( cursor_y + popup_height + 1 ))
|
||||
fzf_opts+=(--layout=reverse)
|
||||
fi
|
||||
|
||||
# calculate the popup width and x position
|
||||
popup_width=$(( min(max(comp_length + 5, popup_min_size[1]), window_width) ))
|
||||
popup_x=$(( cursor_x + popup_width > window_width ? window_width - popup_width : cursor_x ))
|
||||
|
||||
echo -E "env FZF_DEFAULT_OPTS=${(qq)FZF_DEFAULT_OPTS} SHELL=$ZSH_NAME $commands[fzf] ${(qq)fzf_opts[@]} < $tmp_dir/completions.$$ > $tmp_dir/result-$$" > $tmp_dir/fzf-$$
|
||||
{
|
||||
tmux popup -x $popup_x -y $popup_y \
|
||||
-w $popup_width -h $popup_height \
|
||||
-d $PWD -E ". $tmp_dir/fzf-$$" || ret=$?
|
||||
echo -E "$(<$tmp_dir/result-$$)"
|
||||
} always {
|
||||
command rm $tmp_dir/*-$$
|
||||
(( $+IN_FZF_TAB )) || command rm $tmp_dir/completions.$$
|
||||
}
|
||||
return $ret
|
||||
21
eggs/zsh/config/fzf-tab/lib/zsh-ls-colors/LICENSE
Normal file
21
eggs/zsh/config/fzf-tab/lib/zsh-ls-colors/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Gamma
|
||||
|
||||
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.
|
||||
114
eggs/zsh/config/fzf-tab/lib/zsh-ls-colors/README.md
Normal file
114
eggs/zsh/config/fzf-tab/lib/zsh-ls-colors/README.md
Normal file
@ -0,0 +1,114 @@
|
||||
# zsh-ls-colors
|
||||
|
||||

|
||||
|
||||
A zsh library to use `LS_COLORS` in scripts or other plugins.
|
||||
|
||||
For a simple demo, see the `demo` script in this repo.
|
||||
|
||||
For more advanced usage,
|
||||
instructions are located at top of the source files for `from-mode` and `from-name`.
|
||||
If a use case isn't adequately covered,
|
||||
please open an issue!
|
||||
|
||||
## Using zsh-ls-colors in a plugin
|
||||
|
||||
You can use this as a submodule or a subtree.
|
||||
|
||||
### submodule:
|
||||
|
||||
```sh
|
||||
# Add (only once)
|
||||
git submodule add git://github.com/xPMo/zsh-ls-colors.git ls-colors
|
||||
git commit -m 'Add ls-colors as submodule'
|
||||
|
||||
# Update
|
||||
cd ls-colors
|
||||
git fetch
|
||||
git checkout origin/master
|
||||
cd ..
|
||||
git commit ls-colors -m 'Update ls-colors to latest'
|
||||
```
|
||||
|
||||
### Subtree:
|
||||
|
||||
```sh
|
||||
# Initial add
|
||||
git subtree add --prefix=ls-colors/ --squash -m 'Add ls-colors as a subtree' \
|
||||
git://github.com/xPMo/zsh-ls-colors.git master
|
||||
|
||||
# Update
|
||||
git subtree pull --prefix=ls-colors/ --squash -m 'Update ls-colors to latest' \
|
||||
git://github.com/xPMo/zsh-ls-colors.git master
|
||||
|
||||
|
||||
# Or, after adding a remote:
|
||||
git remote add ls-colors git://github.com/xPMo/zsh-ls-colors.git
|
||||
|
||||
# Initial add
|
||||
git subtree add --prefix=ls-colors/ --squash -m 'Add ls-colors as a subtree' ls-colors master
|
||||
|
||||
# Update
|
||||
git subtree pull --prefix=ls-colors/ --squash -m 'Update ls-colors to latest' ls-colors master
|
||||
```
|
||||
|
||||
### Function namespacing
|
||||
|
||||
Since functions are a public namespace,
|
||||
this plugin allows you to customize the preifix for your plugin:
|
||||
|
||||
```zsh
|
||||
# load functions as my-lscolors::{init,match-by,from-name,from-mode}
|
||||
source ${0:h}/ls-colors/ls-colors.zsh my-lscolors
|
||||
```
|
||||
|
||||
### Parameter namespacing
|
||||
|
||||
While indirect parameter expansion exists with `${(P)var}`,
|
||||
it doesn't play nicely with array parameters.
|
||||
|
||||
There are multiple strategies to prevent unnecessary re-parsing:
|
||||
|
||||
```zsh
|
||||
# Call once when loading.
|
||||
# Pollutes global namespace but prevents re-parsing
|
||||
ls-color::init
|
||||
```
|
||||
|
||||
```zsh
|
||||
# Don't call init at all and only use ::match-by.
|
||||
# Doesn't pollute global namespace but reparses LS_COLORS on every call
|
||||
ls-color::match-by $file lstat
|
||||
```
|
||||
|
||||
```zsh
|
||||
# Initialize within a scope with local parameters.
|
||||
# Best for not polluting global namespace when multiple filenames need to be parsed.
|
||||
(){
|
||||
local -A namecolors modecolors
|
||||
ls-color::init
|
||||
|
||||
for arg; do
|
||||
...
|
||||
done
|
||||
}
|
||||
```
|
||||
|
||||
```zsh
|
||||
# Serialize:
|
||||
typeset -g LS_COLORS_CACHE_FILE=$(mktemp)
|
||||
(){
|
||||
local -A namecolors modecolors
|
||||
ls-color::init
|
||||
typeset -p modecolors namecolors >| $LS_COLORS_CACHE_FILE
|
||||
zcompile $LS_COLORS_CACHE_FILE
|
||||
}
|
||||
|
||||
my-function(){
|
||||
local -A namecolors modecolors
|
||||
source $LS_COLORS_CACHE_FILE
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
65
eggs/zsh/config/fzf-tab/lib/zsh-ls-colors/demo
Executable file
65
eggs/zsh/config/fzf-tab/lib/zsh-ls-colors/demo
Executable file
@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env zsh
|
||||
# set $0 (ref: zdharma.org/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html#zero-handling)
|
||||
0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
|
||||
0="${${(M)0:#/*}:-$PWD/$0}"
|
||||
|
||||
# load library functions
|
||||
source ls-colors.zsh ''
|
||||
|
||||
# to name the functions with a different namespace
|
||||
# call source with a different argument
|
||||
#source my-plugin::ls
|
||||
|
||||
# init (sets modecolors and namecolors)
|
||||
# You have options. Either you can pollute global namespace:
|
||||
ls-color::init
|
||||
# Or you can have ::match-by re-parse colors on every call
|
||||
: # (do nothing)
|
||||
# Or if you have multiple calls, you can parse colors once for a scope:
|
||||
(){
|
||||
local -A modecolors namecolors
|
||||
ls-color::init
|
||||
|
||||
for arg; do
|
||||
ls-color::match-by $arg lstat
|
||||
: do something else
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
# colors can also be added for other globs after init as well:
|
||||
namecolors[*.md]='01' # bold markdown files
|
||||
|
||||
# EXTENDED_GLOB is enabled when matching, so things like this are possible:
|
||||
namecolors[(#i)(*/|)license(|.*)]='04' # underline LICENSE, or license.txt, or similar
|
||||
|
||||
local file reply
|
||||
# color each file in the argument list
|
||||
for file; do
|
||||
ls-color::match-by $file all
|
||||
# point to symlink resolution if it exists
|
||||
print '\e['$reply[1]'m'$file'\e[0m'${reply[2]:+' → \e['$reply[3]'m'$reply[2]'\e[0m'}
|
||||
done
|
||||
|
||||
# =======================
|
||||
# Alternate manual method:
|
||||
for file; do
|
||||
ls-color::match-by $file lstat follow
|
||||
if [[ $reply[2] ]]; then
|
||||
# This is a symlink
|
||||
symlink_color=$reply[1]
|
||||
# If broken, use link color for destination
|
||||
resolved_color=$reply[1]
|
||||
resolved=$reply[2]
|
||||
if [[ -e $file ]]; then
|
||||
# Not broken, update destination color
|
||||
ls-color::match-by $file stat
|
||||
resolved_color=$reply[1]
|
||||
fi
|
||||
print '\e['$symlink_color'm'$file'\e[0m → \e['$resolved_color'm'$resolved'\e[0m'
|
||||
else
|
||||
# This is not a symlink
|
||||
print '\e['$reply[1]'m'$file'\e[0m'
|
||||
fi
|
||||
done
|
||||
|
||||
186
eggs/zsh/config/fzf-tab/lib/zsh-ls-colors/ls-colors.zsh
Normal file
186
eggs/zsh/config/fzf-tab/lib/zsh-ls-colors/ls-colors.zsh
Normal file
@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
# set the prefix for all functions
|
||||
local pfx=${1:-'ls-color'}
|
||||
|
||||
# {{{ From mode
|
||||
# Usage:
|
||||
# $1: filename
|
||||
# $2: The value of struct stat st_mode
|
||||
# If empty, modecolors lookup will be skipped
|
||||
# $3: (If symlink) The value of struct stat st_mode
|
||||
# for the target of $1's symlink. If unset,
|
||||
# interpret as a broken link.
|
||||
# Sets REPLY to the console code
|
||||
${pfx}::from-mode () {
|
||||
|
||||
emulate -L zsh
|
||||
setopt cbases octalzeroes extendedglob
|
||||
|
||||
[[ -z $2 ]] && return 1
|
||||
|
||||
local -i reg=0
|
||||
local -a codes
|
||||
|
||||
local -i st_mode=$(($2))
|
||||
# See man 7 inode for more info
|
||||
# file type
|
||||
case $(( st_mode & 0170000 )) in
|
||||
$(( 0140000 )) ) codes=( $modecolors[so] ) ;;
|
||||
$(( 0120000 )) ) # symlink, special handling
|
||||
if ! (($+3)); then
|
||||
REPLY=$modecolors[or]
|
||||
elif [[ $modecolors[ln] = target ]]; then
|
||||
"$0" "$1" "${@:3}"
|
||||
else
|
||||
REPLY=$modecolors[ln]
|
||||
fi
|
||||
return
|
||||
;;
|
||||
$(( 0100000 )) ) codes=( ); reg=1 ;; # regular file
|
||||
$(( 0060000 )) ) codes=( $modecolors[bd] ) ;;
|
||||
$(( 0040000 )) ) codes=( $modecolors[di] ) ;;
|
||||
$(( 0020000 )) ) codes=( $modecolors[cd] ) ;;
|
||||
$(( 0010000 )) ) codes=( $modecolors[pi] ) ;;
|
||||
esac
|
||||
|
||||
# setuid/setgid/sticky/other-writable
|
||||
(( st_mode & 04000 )) && codes+=( $modecolors[su] )
|
||||
(( st_mode & 02000 )) && codes+=( $modecolors[sg] )
|
||||
(( ! reg )) && case $(( st_mode & 01002 )) in
|
||||
# sticky
|
||||
$(( 01000 )) ) codes+=( $modecolors[st] ) ;;
|
||||
# other-writable
|
||||
$(( 00002 )) ) codes+=( $modecolors[ow] ) ;;
|
||||
# other-writable and sticky
|
||||
$(( 01002 )) ) codes+=( $modecolors[tw] ) ;;
|
||||
esac
|
||||
|
||||
# executable
|
||||
if (( ! $#codes )); then
|
||||
(( st_mode & 0111 )) && codes+=( $modecolors[ex] )
|
||||
fi
|
||||
|
||||
# return nonzero if no matching code
|
||||
[[ ${REPLY::=${(j:;:)codes}} ]]
|
||||
} # }}}
|
||||
# {{{ From name
|
||||
# Usage:
|
||||
# $1: filename
|
||||
#
|
||||
# Sets REPLY to the console code
|
||||
${pfx}::from-name () {
|
||||
|
||||
emulate -L zsh
|
||||
setopt extendedglob
|
||||
|
||||
# Return non-zero if no keys match
|
||||
[[ ${REPLY::=$namecolors[(k)$1]} ]]
|
||||
} # }}}
|
||||
# {{{ Init
|
||||
# WARNING: initializes namecolors and modecolors in global scope
|
||||
${pfx}::init () {
|
||||
emulate -L zsh
|
||||
|
||||
# Use $1 if provided, otherwise use LS_COLORS
|
||||
# Use LSCOLORS on BSD
|
||||
local LS_COLORS=${1:-${LS_COLORS:-$LSCOLORS}}
|
||||
|
||||
# read in LS_COLORS
|
||||
typeset -gA namecolors=(${(@s:=:)${(@s.:.)LS_COLORS}:#[[:alpha:]][[:alpha:]]=*})
|
||||
typeset -gA modecolors=(${(@Ms:=:)${(@s.:.)LS_COLORS}:#[[:alpha:]][[:alpha:]]=*})
|
||||
}
|
||||
# }}}
|
||||
# {{{ Match by
|
||||
# Usage:
|
||||
# $1: filename
|
||||
# Optional (must be $2): g[lobal]: Use existing stat | lstat in parent scope
|
||||
# ${@:2}: Append to reply:
|
||||
# - l[stat] : Look up using lstat (don't follow symlink), if empty match name
|
||||
# - s[tat] : Look up using stat (do follow symlink), if empty match name
|
||||
# - n[ame] : Only match name
|
||||
# - f[ollow]: Get resolution path of symlink
|
||||
# - L[stat] : Same as above but don't match name
|
||||
# - S[tat] : Same as above but don't match name
|
||||
# - a[ll] : If a broken symlink: lstat follow lstat
|
||||
# : If a symlink : lstat follow stat
|
||||
# : Otherwise : lstat
|
||||
# - A[ll] : If a broken symlink: Lstat follow Lstat
|
||||
# : If a symlink : Lstat follow Stat
|
||||
# : Otherwise : Lstat
|
||||
#
|
||||
# or returns non-zero
|
||||
${pfx}::match-by () {
|
||||
emulate -L zsh
|
||||
setopt extendedglob cbases octalzeroes
|
||||
|
||||
local arg REPLY name=$1 pfx=${0%::match-by}
|
||||
shift
|
||||
|
||||
# init in local scope if not using global params
|
||||
if ! [[ -v namecolors && -v modecolors ]]; then
|
||||
local -A namecolors modecolors
|
||||
${pfx}::init
|
||||
fi
|
||||
|
||||
if [[ ${1:l} = (g|global) ]]; then
|
||||
shift
|
||||
else
|
||||
local -a stat lstat
|
||||
declare -ga reply=()
|
||||
fi
|
||||
|
||||
zmodload -F zsh/stat b:zstat
|
||||
for arg; do
|
||||
case ${arg[1]:l} in
|
||||
n|name)
|
||||
${pfx}::from-name $name
|
||||
reply+=("$REPLY")
|
||||
;;
|
||||
l|lstat)
|
||||
(($#lstat)) || zstat -A lstat -L $name || return 1
|
||||
if ((lstat[3] & 0170000 )); then
|
||||
# follow symlink
|
||||
(($#stat)) || zstat -A stat $name 2>/dev/null
|
||||
fi
|
||||
${pfx}::from-mode "$name" "$lstat[3]" $stat[3]
|
||||
if [[ $REPLY || ${2[1]} = L ]]; then
|
||||
reply+=("$REPLY")
|
||||
else # fall back to name
|
||||
"$0" "$name" g n
|
||||
fi
|
||||
;;
|
||||
s|stat)
|
||||
(($#stat)) || zstat -A stat $name || return 1
|
||||
${pfx}::from-mode $name $stat[3]
|
||||
reply+=("$REPLY")
|
||||
if [[ $REPLY || ${arg[1]} = S ]]; then
|
||||
reply+=("$REPLY")
|
||||
else # fall back to name
|
||||
"$0" "$name" g n
|
||||
fi
|
||||
;;
|
||||
f|follow)
|
||||
(($#lstat)) || zstat -A lstat -L $name || return 1
|
||||
reply+=("$lstat[14]")
|
||||
;;
|
||||
a|all)
|
||||
# Match case
|
||||
"$0" "$name" g ${${${arg[1]%a}:+L}:-l}
|
||||
# won't append if empty
|
||||
reply+=($lstat[14])
|
||||
# $stat[14] will be empty if not a symlink
|
||||
if [[ $lstat[14] ]]; then
|
||||
if [[ -e $name ]]; then
|
||||
"$0" "$name" g ${${${arg[1]%a}:+S}:-s}
|
||||
else
|
||||
reply+=($reply[-2])
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
*) return 2 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
# }}}
|
||||
# vim: set foldmethod=marker:
|
||||
16
eggs/zsh/config/fzf-tab/modules/.cvsignore
Normal file
16
eggs/zsh/config/fzf-tab/modules/.cvsignore
Normal file
@ -0,0 +1,16 @@
|
||||
Makefile
|
||||
META-FAQ
|
||||
config.cache
|
||||
config.h
|
||||
config.h.in
|
||||
config.log
|
||||
config.modules
|
||||
config.modules.sh
|
||||
config.status
|
||||
configure
|
||||
cscope.out
|
||||
stamp-h
|
||||
stamp-h.in
|
||||
autom4te.cache
|
||||
*.swp
|
||||
.git
|
||||
4
eggs/zsh/config/fzf-tab/modules/.distfiles
Normal file
4
eggs/zsh/config/fzf-tab/modules/.distfiles
Normal file
@ -0,0 +1,4 @@
|
||||
DISTFILES_SRC='
|
||||
META-FAQ
|
||||
configure config.h.in stamp-h.in
|
||||
'
|
||||
15
eggs/zsh/config/fzf-tab/modules/.editorconfig
Normal file
15
eggs/zsh/config/fzf-tab/modules/.editorconfig
Normal file
@ -0,0 +1,15 @@
|
||||
# Top-most editorconfig file
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
tab_width = 8
|
||||
indent_size = 2
|
||||
indent_style = tab
|
||||
|
||||
[ChangeLog]
|
||||
indent_size = 8
|
||||
|
||||
[*.[ch]]
|
||||
indent_size = 4
|
||||
1
eggs/zsh/config/fzf-tab/modules/.gitignore
vendored
Normal file
1
eggs/zsh/config/fzf-tab/modules/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
zsh/
|
||||
7
eggs/zsh/config/fzf-tab/modules/.preconfig
Executable file
7
eggs/zsh/config/fzf-tab/modules/.preconfig
Executable file
@ -0,0 +1,7 @@
|
||||
#! /bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
autoconf
|
||||
autoheader
|
||||
echo > stamp-h.in
|
||||
539
eggs/zsh/config/fzf-tab/modules/Src/fzftab.c
Normal file
539
eggs/zsh/config/fzf-tab/modules/Src/fzftab.c
Normal file
@ -0,0 +1,539 @@
|
||||
#include "fzftab.mdh"
|
||||
#include "fzftab.pro"
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
const char* get_color(char* file, const struct stat* sb);
|
||||
const char* colorize_from_mode(char* file, const struct stat* sb);
|
||||
const char* colorize_from_name(char* file);
|
||||
int compile_patterns(char* nam, char** list_colors);
|
||||
char* ftb_strcat(char* dst, int n, ...);
|
||||
|
||||
/* Indixes into the terminal string arrays. */
|
||||
#define COL_NO 0
|
||||
#define COL_FI 1
|
||||
#define COL_DI 2
|
||||
#define COL_LN 3
|
||||
#define COL_PI 4
|
||||
#define COL_SO 5
|
||||
#define COL_BD 6
|
||||
#define COL_CD 7
|
||||
#define COL_OR 8
|
||||
#define COL_MI 9
|
||||
#define COL_SU 10
|
||||
#define COL_SG 11
|
||||
#define COL_TW 12
|
||||
#define COL_OW 13
|
||||
#define COL_ST 14
|
||||
#define COL_EX 15
|
||||
#define COL_LC 16
|
||||
#define COL_RC 17
|
||||
#define COL_EC 18
|
||||
#define COL_TC 19
|
||||
#define COL_SP 20
|
||||
#define COL_MA 21
|
||||
#define COL_HI 22
|
||||
#define COL_DU 23
|
||||
#define COL_SA 24
|
||||
|
||||
#define NUM_COLS 25
|
||||
|
||||
/* Names of the terminal strings. */
|
||||
static char* colnames[] = { "no", "fi", "di", "ln", "pi", "so", "bd", "cd", "or", "mi", "su", "sg",
|
||||
"tw", "ow", "st", "ex", "lc", "rc", "ec", "tc", "sp", "ma", "hi", "du", "sa", NULL };
|
||||
|
||||
/* Default values. */
|
||||
static char* defcols[]
|
||||
= { "0", "0", "1;31", "1;36", "33", "1;35", "1;33", "1;33", NULL, NULL, "37;41", "30;43",
|
||||
"30;42", "34;42", "37;44", "1;32", "\033[", "m", NULL, "0", "0", "7", NULL, NULL, "0" };
|
||||
|
||||
static char* fzf_tab_module_version;
|
||||
|
||||
struct pattern {
|
||||
Patprog pat;
|
||||
char color[50];
|
||||
};
|
||||
|
||||
static int opt_list_type = OPT_INVALID;
|
||||
static int pat_cnt = 0;
|
||||
static struct pattern* name_color = NULL;
|
||||
static char mode_color[NUM_COLS][20];
|
||||
|
||||
// TODO: use ZLS_COLORS ?
|
||||
int compile_patterns(char* nam, char** list_colors)
|
||||
{
|
||||
int i, j;
|
||||
// clean old name_color and set pat_cnt = 0
|
||||
if (pat_cnt != 0) {
|
||||
while (--pat_cnt) {
|
||||
freepatprog(name_color[pat_cnt].pat);
|
||||
}
|
||||
free(name_color);
|
||||
}
|
||||
// initialize mode_color with default value
|
||||
for (i = 0; i < NUM_COLS; i++) {
|
||||
if (defcols[i]) {
|
||||
strcpy(mode_color[i], defcols[i]);
|
||||
}
|
||||
}
|
||||
// empty array, just return
|
||||
if (list_colors == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// how many pattens?
|
||||
while (list_colors[pat_cnt] != NULL) {
|
||||
pat_cnt++;
|
||||
}
|
||||
name_color = zshcalloc(pat_cnt * sizeof(struct pattern));
|
||||
|
||||
for (i = 0; i < pat_cnt; i++) {
|
||||
char* pat = ztrdup(list_colors[i]);
|
||||
char* color = strrchr(pat, '=');
|
||||
if (color == NULL)
|
||||
continue;
|
||||
*color = '\0';
|
||||
tokenize(pat);
|
||||
remnulargs(pat);
|
||||
|
||||
// mode=color
|
||||
bool skip = false;
|
||||
for (j = 0; j < NUM_COLS; j++) {
|
||||
if (strpfx(colnames[j], list_colors[i])) {
|
||||
strcpy(mode_color[j], color + 1);
|
||||
name_color[i].pat = NULL;
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
if (skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// name=color
|
||||
if ((name_color[i].pat = patcompile(pat, PAT_ZDUP, NULL))) {
|
||||
strcpy(name_color[i].color, color + 1);
|
||||
}
|
||||
|
||||
free(pat);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char get_suffix(char* file, const struct stat* sb)
|
||||
{
|
||||
struct stat sb2;
|
||||
mode_t filemode = sb->st_mode;
|
||||
|
||||
if (S_ISLNK(filemode))
|
||||
if (strpfx(mode_color[COL_LN], "target")) {
|
||||
if (stat(file, &sb2) == -1) {
|
||||
return '@';
|
||||
}
|
||||
return get_suffix(file, &sb2);
|
||||
} else {
|
||||
return '@';
|
||||
}
|
||||
else
|
||||
return file_type(filemode);
|
||||
}
|
||||
|
||||
const char* get_color(char* file, const struct stat* sb)
|
||||
{
|
||||
// no list-colors, return NULL
|
||||
if (pat_cnt == 0) {
|
||||
return NULL;
|
||||
}
|
||||
const char* ret;
|
||||
if ((ret = colorize_from_mode(file, sb)) || (ret = colorize_from_name(file))) {
|
||||
return ret;
|
||||
}
|
||||
return mode_color[COL_FI];
|
||||
}
|
||||
|
||||
const char* colorize_from_name(char* file)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < pat_cnt; i++) {
|
||||
if (name_color && name_color[i].pat && pattry(name_color[i].pat, file)) {
|
||||
return name_color[i].color;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* colorize_from_mode(char* file, const struct stat* sb)
|
||||
{
|
||||
struct stat sb2;
|
||||
|
||||
switch (sb->st_mode & S_IFMT) {
|
||||
case S_IFDIR:
|
||||
return mode_color[COL_DI];
|
||||
case S_IFLNK: {
|
||||
if (strpfx(mode_color[COL_LN], "target")) {
|
||||
if (stat(file, &sb2) == -1) {
|
||||
return mode_color[COL_OR];
|
||||
}
|
||||
return get_color(file, &sb2);
|
||||
}
|
||||
}
|
||||
case S_IFIFO:
|
||||
return mode_color[COL_PI];
|
||||
case S_IFSOCK:
|
||||
return mode_color[COL_SO];
|
||||
case S_IFBLK:
|
||||
return mode_color[COL_BD];
|
||||
case S_IFCHR:
|
||||
return mode_color[COL_CD];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (sb->st_mode & S_ISUID) {
|
||||
return mode_color[COL_SU];
|
||||
} else if (sb->st_mode & S_ISGID) {
|
||||
return mode_color[COL_SG];
|
||||
// tw ow st ??
|
||||
} else if (sb->st_mode & S_IXUSR) {
|
||||
return mode_color[COL_EX];
|
||||
}
|
||||
|
||||
/* Check for suffix alias */
|
||||
size_t len = strlen(file);
|
||||
/* shortest valid suffix format is a.b */
|
||||
if (len > 2) {
|
||||
const char* suf = file + len - 1;
|
||||
while (suf > file + 1) {
|
||||
if (suf[-1] == '.') {
|
||||
if (sufaliastab->getnode(sufaliastab, suf)) {
|
||||
return mode_color[COL_SA];
|
||||
}
|
||||
break;
|
||||
}
|
||||
suf--;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct {
|
||||
char** array;
|
||||
size_t len;
|
||||
size_t size;
|
||||
} ftb_compcap;
|
||||
|
||||
// Usage:
|
||||
// initialize fzf-tab-generate-compcap -i
|
||||
// output to _ftb_compcap fzf-tab-generate-compcap -o
|
||||
// add a entry fzf-tab-generate-compcap word desc opts
|
||||
static int bin_fzf_tab_compcap_generate(char* nam, char** args, Options ops, UNUSED(int func))
|
||||
{
|
||||
int i;
|
||||
|
||||
if (OPT_ISSET(ops, 'o')) {
|
||||
// write final result to _ftb_compcap
|
||||
setaparam("_ftb_compcap", ftb_compcap.array);
|
||||
ftb_compcap.array = NULL;
|
||||
return 0;
|
||||
} else if (OPT_ISSET(ops, 'i')) {
|
||||
// init
|
||||
if (ftb_compcap.array)
|
||||
freearray(ftb_compcap.array);
|
||||
ftb_compcap.array = zshcalloc(1024 * sizeof(char*));
|
||||
ftb_compcap.len = 0, ftb_compcap.size = 1024;
|
||||
return 0;
|
||||
}
|
||||
if (ftb_compcap.array == NULL) {
|
||||
zwarnnam(nam, "please initialize it first");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// get paramaters
|
||||
char **words = getaparam(args[0]), **dscrs = getaparam(args[1]), *opts = getsparam(args[2]);
|
||||
if (!words || !dscrs || !opts) {
|
||||
zwarnnam(nam, "wrong argument");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *bs = metafy("\2", 1, META_DUP), *nul = metafy("\0word\0", 6, META_DUP);
|
||||
size_t dscrs_cnt = arrlen(dscrs);
|
||||
|
||||
for (i = 0; words[i]; i++) {
|
||||
// TODO: replace '\n'
|
||||
char* dscr = i < dscrs_cnt ? dscrs[i] : words[i];
|
||||
char *buffer = ftb_strcat(NULL, 5, dscr, bs, opts, nul, words[i]);
|
||||
ftb_compcap.array[ftb_compcap.len++] = buffer;
|
||||
|
||||
if (ftb_compcap.len == ftb_compcap.size) {
|
||||
ftb_compcap.array
|
||||
= zrealloc(ftb_compcap.array, (1024 + ftb_compcap.size) * sizeof(char*));
|
||||
ftb_compcap.size += 1024;
|
||||
memset(ftb_compcap.array + ftb_compcap.size - 1024, 0, 1024 * sizeof(char*));
|
||||
}
|
||||
}
|
||||
|
||||
zsfree(bs);
|
||||
zsfree(nul);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// cat n string, return the pointer to the new string
|
||||
// dst will be reallocated if is not big enough
|
||||
// if dst is NULL, it will be allocated
|
||||
char* ftb_strcat(char* dst, int n, ...)
|
||||
{
|
||||
int i, idx;
|
||||
|
||||
va_list valist;
|
||||
va_start(valist, n);
|
||||
|
||||
char* final = dst ? zrealloc(dst, 128) : zalloc(128);
|
||||
size_t size = 128, max_len = 128 - 1;
|
||||
dst = final;
|
||||
|
||||
for (i = 0, idx = 0; i < n; i++) {
|
||||
char* src = va_arg(valist, char*);
|
||||
if (src == NULL)
|
||||
continue;
|
||||
for (; *src != '\0'; dst++, src++, idx++) {
|
||||
if (idx == max_len) {
|
||||
size += size / 2;
|
||||
final = zrealloc(final, size);
|
||||
max_len = size - 1;
|
||||
dst = &final[idx];
|
||||
}
|
||||
*dst = *src;
|
||||
}
|
||||
}
|
||||
*dst = '\0';
|
||||
va_end(valist);
|
||||
|
||||
return final;
|
||||
}
|
||||
|
||||
struct file_color {
|
||||
char *fc_begin;
|
||||
char *fc_end;
|
||||
char *sc;
|
||||
char *suffix;
|
||||
};
|
||||
|
||||
// accept an
|
||||
static struct file_color* fzf_tab_colorize(char* file)
|
||||
{
|
||||
struct file_color *fc = zalloc(sizeof(struct file_color));
|
||||
|
||||
file = unmeta(file);
|
||||
|
||||
struct stat sb;
|
||||
if (lstat(file, &sb) == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char suffix[2] = {0};
|
||||
if (isset(opt_list_type)) {
|
||||
suffix[0] = get_suffix(file, &sb);
|
||||
}
|
||||
const char* color = get_color(file, &sb);
|
||||
|
||||
char* symlink = NULL;
|
||||
const char* symcolor = "";
|
||||
if ((sb.st_mode & S_IFMT) == S_IFLNK) {
|
||||
symlink = zshcalloc(PATH_MAX);
|
||||
size_t end = readlink(file, symlink, PATH_MAX);
|
||||
symlink[end] = '\0';
|
||||
if (stat(file, &sb) == -1) {
|
||||
symcolor = mode_color[COL_OR];
|
||||
} else {
|
||||
char tmp[PATH_MAX];
|
||||
realpath(file, tmp);
|
||||
symcolor = get_color(file, &sb);
|
||||
}
|
||||
}
|
||||
|
||||
if (color != NULL) {
|
||||
fc->fc_begin = ftb_strcat(NULL, 3, mode_color[COL_LC], color, mode_color[COL_RC]);
|
||||
fc->fc_end = ftb_strcat(NULL, 3, mode_color[COL_LC], "0", mode_color[COL_RC]);
|
||||
} else {
|
||||
fc->fc_begin = ztrdup("");
|
||||
fc->fc_end = ztrdup("");
|
||||
}
|
||||
|
||||
fc->suffix = ztrdup(suffix);
|
||||
|
||||
if (symlink != NULL) {
|
||||
fc->sc = ftb_strcat(NULL, 7, mode_color[COL_LC], symcolor, mode_color[COL_RC],
|
||||
symlink, mode_color[COL_LC], "0", mode_color[COL_RC]);
|
||||
free(symlink);
|
||||
} else {
|
||||
fc->sc = ztrdup("");
|
||||
}
|
||||
|
||||
fc->fc_begin = metafy(fc->fc_begin, -1, META_REALLOC);
|
||||
fc->fc_end = metafy(fc->fc_end, -1, META_REALLOC);
|
||||
fc->sc = metafy(fc->sc, -1, META_REALLOC);
|
||||
fc->suffix = metafy(fc->suffix, -1, META_REALLOC);
|
||||
|
||||
return fc;
|
||||
}
|
||||
|
||||
static int bin_fzf_tab_candidates_generate(char* nam, char** args, Options ops, UNUSED(int func))
|
||||
{
|
||||
int i, j;
|
||||
|
||||
if (OPT_ISSET(ops, 'i')) {
|
||||
// compile list_colors pattern
|
||||
if (*args == NULL) {
|
||||
zwarnnam(nam, "please specify an array");
|
||||
return 1;
|
||||
} else {
|
||||
char** array = getaparam(*args);
|
||||
return compile_patterns(nam, array);
|
||||
}
|
||||
}
|
||||
|
||||
char **ftb_compcap = getaparam("_ftb_compcap"), **group_colors = getaparam("group_colors"),
|
||||
*default_color = getsparam("default_color"), *prefix = getsparam("prefix");
|
||||
|
||||
size_t group_colors_len = arrlen(group_colors);
|
||||
size_t ftb_compcap_len = arrlen(ftb_compcap);
|
||||
|
||||
char *bs = metafy("\b", 1, META_DUP), *nul = metafy("\0", 1, META_DUP),
|
||||
*soh = metafy("\2", 1, META_DUP);
|
||||
|
||||
char **candidates = zshcalloc(sizeof(char*) * (ftb_compcap_len + 1)),
|
||||
*filepath = zshcalloc(PATH_MAX * sizeof(char)), *dpre = zshcalloc(128),
|
||||
*dsuf = zshcalloc(128);
|
||||
|
||||
char* first_word = NULL;
|
||||
int same_word = 1;
|
||||
|
||||
for (i = 0; i < ftb_compcap_len; i++) {
|
||||
char *word = "", *group = NULL, *realdir = NULL;
|
||||
strcpy(dpre, "");
|
||||
strcpy(dsuf, "");
|
||||
|
||||
// extract all the variables what we need
|
||||
char** compcap = sepsplit(ftb_compcap[i], soh, 1, 0);
|
||||
char* desc = compcap[0];
|
||||
char** info = sepsplit(compcap[1], nul, 1, 0);
|
||||
|
||||
for (j = 0; info[j]; j += 2) {
|
||||
if (!strcmp(info[j], "word")) {
|
||||
word = info[j + 1];
|
||||
// unquote word
|
||||
parse_subst_string(word);
|
||||
remnulargs(word);
|
||||
untokenize(word);
|
||||
} else if (!strcmp(info[j], "group")) {
|
||||
group = info[j + 1];
|
||||
} else if (!strcmp(info[j], "realdir")) {
|
||||
realdir = info[j + 1];
|
||||
}
|
||||
}
|
||||
|
||||
// check if all the words are the same
|
||||
if (first_word == NULL) {
|
||||
first_word = ztrdup(word);
|
||||
} else if (same_word && strcmp(word, first_word) != 0) {
|
||||
same_word = 0;
|
||||
}
|
||||
|
||||
// add character and color to describe the type of the files
|
||||
if (realdir) {
|
||||
filepath = ftb_strcat(filepath, 2, realdir, word);
|
||||
struct file_color *fc = fzf_tab_colorize(filepath);
|
||||
if (fc != NULL) {
|
||||
dpre = ftb_strcat(dpre, 2, fc->fc_end, fc->fc_begin);
|
||||
if (fc->sc[0] != '\0') {
|
||||
dsuf = ftb_strcat(dsuf, 4, fc->fc_end, fc->suffix, " -> ", fc->sc);
|
||||
} else {
|
||||
dsuf = ftb_strcat(dsuf, 2, fc->fc_end, fc->suffix);
|
||||
}
|
||||
if (dpre[0] != '\0') {
|
||||
setiparam("colorful", 1);
|
||||
}
|
||||
free(fc);
|
||||
}
|
||||
}
|
||||
|
||||
char* result = zshcalloc(256 * sizeof(char));
|
||||
|
||||
// add color to description if they have group index
|
||||
if (group) {
|
||||
// use strtol ?
|
||||
int group_index = atoi(group);
|
||||
char* color = group_index >= group_colors_len ? "" : group_colors[group_index - 1];
|
||||
// add prefix
|
||||
result = ftb_strcat(
|
||||
result, 11, group, bs, color, prefix, dpre, nul, group, bs, desc, nul, dsuf);
|
||||
} else {
|
||||
result = ftb_strcat(result, 6, default_color, dpre, nul, desc, nul, dsuf);
|
||||
}
|
||||
// quotedzputs(result, stdout);
|
||||
// putchar('\n');
|
||||
candidates[i] = result;
|
||||
|
||||
freearray(info);
|
||||
freearray(compcap);
|
||||
}
|
||||
|
||||
setaparam("tcandidates", candidates);
|
||||
setiparam("same_word", same_word);
|
||||
zsfree(dpre);
|
||||
zsfree(dsuf);
|
||||
zsfree(filepath);
|
||||
zsfree(first_word);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct builtin bintab[] = {
|
||||
BUILTIN("fzf-tab-compcap-generate", 0, bin_fzf_tab_compcap_generate, 0, -1, 0, "io", NULL),
|
||||
BUILTIN("fzf-tab-candidates-generate", 0, bin_fzf_tab_candidates_generate, 0, -1, 0, "i", NULL),
|
||||
};
|
||||
|
||||
static struct paramdef patab[] = {
|
||||
STRPARAMDEF("FZF_TAB_MODULE_VERSION", &fzf_tab_module_version),
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
static struct features module_features = {
|
||||
bintab, sizeof(bintab) / sizeof(*bintab),
|
||||
NULL, 0,
|
||||
NULL, 0,
|
||||
patab, sizeof(patab) / sizeof(*patab),
|
||||
0,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
int setup_(UNUSED(Module m)) { return 0; }
|
||||
|
||||
int features_(Module m, char*** features)
|
||||
{
|
||||
*features = featuresarray(m, &module_features);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int enables_(Module m, int** enables) { return handlefeatures(m, &module_features, enables); }
|
||||
|
||||
int boot_(UNUSED(Module m))
|
||||
{
|
||||
fzf_tab_module_version = ztrdup("0.2.2");
|
||||
// different zsh version may have different value of list_type's index
|
||||
// so query it dynamically
|
||||
opt_list_type = optlookup("list_types");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cleanup_(UNUSED(Module m)) { return setfeatureenables(m, &module_features, NULL); }
|
||||
|
||||
int finish_(UNUSED(Module m))
|
||||
{
|
||||
printf("fzf-tab module unloaded.\n");
|
||||
fflush(stdout);
|
||||
return 0;
|
||||
}
|
||||
7
eggs/zsh/config/fzf-tab/modules/Src/fzftab.mdd
Normal file
7
eggs/zsh/config/fzf-tab/modules/Src/fzftab.mdd
Normal file
@ -0,0 +1,7 @@
|
||||
name=aloxaf/fzftab
|
||||
link=dynamic
|
||||
load=no
|
||||
|
||||
autofeatures="b:fzf-tab-colorize p:FZF_TAB_MODULE_VERSION"
|
||||
|
||||
objects="fzftab.o"
|
||||
1
eggs/zsh/config/fzf-tab/test/.gitignore
vendored
Normal file
1
eggs/zsh/config/fzf-tab/test/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.zcompdump
|
||||
174
eggs/zsh/config/fzf-tab/test/comptest
Normal file
174
eggs/zsh/config/fzf-tab/test/comptest
Normal file
@ -0,0 +1,174 @@
|
||||
comptestinit () {
|
||||
setopt extendedglob
|
||||
|
||||
zmodload zsh/zpty || return $?
|
||||
|
||||
comptest_zsh=${ZSH:-zsh}
|
||||
comptest_keymap=e
|
||||
|
||||
while getopts vz: opt; do
|
||||
case $opt in
|
||||
z) comptest_zsh="$OPTARG";;
|
||||
v) comptest_keymap="v";;
|
||||
esac
|
||||
done
|
||||
(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))
|
||||
|
||||
export PS1="<PROMPT>"
|
||||
zpty zsh "$comptest_zsh -f +Z"
|
||||
|
||||
zpty -r zsh log1 "*<PROMPT>*" || {
|
||||
print "first prompt hasn't appeared."
|
||||
return 1
|
||||
}
|
||||
|
||||
comptesteval \
|
||||
"export LC_ALL=${ZSH_TEST_LANG:-C}" \
|
||||
"emulate -R zsh" \
|
||||
"export ZDOTDIR=$ZTST_testdir" \
|
||||
"bindkey -$comptest_keymap" \
|
||||
'LISTMAX=10000000
|
||||
stty 38400 columns 80 rows 24 tabs -icanon -iexten
|
||||
TERM=vt100
|
||||
KEYTIMEOUT=1
|
||||
setopt zle
|
||||
autoload -U compinit
|
||||
compinit -u
|
||||
zstyle ":completion:*:default" list-colors "no=<NO>" "fi=<FI>" "di=<DI>" "ln=<LN>" "pi=<PI>" "so=<SO>" "bd=<BD>" "cd=<CD>" "ex=<EX>" "mi=<MI>" "tc=<TC>" "sp=<SP>" "lc=<LC>" "ec=<EC>\n" "rc=<RC>"
|
||||
zstyle ":completion:*" group-name ""
|
||||
zstyle ":completion:*:messages" format "<MESSAGE>%d</MESSAGE>
|
||||
"
|
||||
zstyle ":completion:*:descriptions" format "<DESCRIPTION>%d</DESCRIPTION>"
|
||||
zstyle ":completion:*:options" verbose yes
|
||||
zstyle ":completion:*:values" verbose yes
|
||||
setopt noalwayslastprompt listrowsfirst completeinword
|
||||
zmodload zsh/complist
|
||||
expand-or-complete-with-report () {
|
||||
print -lr "<WIDGET><expand-or-complete>"
|
||||
zle expand-or-complete
|
||||
print -lr - "<LBUFFER>$LBUFFER</LBUFFER>" "<RBUFFER>$RBUFFER</RBUFFER>"
|
||||
zle clear-screen
|
||||
zle -R
|
||||
}
|
||||
list-choices-with-report () {
|
||||
print -lr "<WIDGET><list-choices>"
|
||||
zle list-choices
|
||||
zle clear-screen
|
||||
zle -R
|
||||
}
|
||||
comp-finish () {
|
||||
print "<WIDGET><finish>"
|
||||
zle kill-whole-line
|
||||
zle clear-screen
|
||||
zle -R
|
||||
}
|
||||
zle-finish () {
|
||||
local buffer="$BUFFER" cursor="$CURSOR" mark="$MARK"
|
||||
(( region_active)) || unset mark
|
||||
BUFFER=""
|
||||
zle -I
|
||||
zle clear-screen
|
||||
zle redisplay
|
||||
print -lr "<WIDGET><finish>" "BUFFER: $buffer" "CURSOR: $cursor"
|
||||
(( $+mark )) && print -lr "MARK: $mark"
|
||||
zle accept-line
|
||||
}
|
||||
zle -N expand-or-complete-with-report
|
||||
zle -N list-choices-with-report
|
||||
zle -N comp-finish
|
||||
zle -N zle-finish
|
||||
bindkey "^I" expand-or-complete-with-report
|
||||
bindkey "^D" list-choices-with-report
|
||||
bindkey "^Z" comp-finish
|
||||
bindkey "^X" zle-finish
|
||||
bindkey -a "^X" zle-finish
|
||||
'
|
||||
}
|
||||
|
||||
zpty_flush() {
|
||||
local junk
|
||||
if zpty -r -t zsh junk \*; then
|
||||
(( ZTST_verbose > 2 )) && print -n -u $ZTST_fd "$*: ${(V)junk}"
|
||||
while zpty -r -t zsh junk \* ; do
|
||||
(( ZTST_verbose > 2 )) && print -n -u $ZTST_fd "${(V)junk}"
|
||||
done
|
||||
(( ZTST_verbose > 2 )) && print -u $ZTST_fd ''
|
||||
fi
|
||||
}
|
||||
|
||||
zpty_run() {
|
||||
zpty -w zsh "$*"
|
||||
zpty -r -m zsh log "*<PROMPT>*" || {
|
||||
print "prompt hasn't appeared."
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
comptesteval () {
|
||||
local tmp=/tmp/comptest.$$
|
||||
|
||||
print -lr - "$@" > $tmp
|
||||
# zpty_flush Before comptesteval
|
||||
zpty -w zsh ". $tmp"
|
||||
zpty -r -m zsh log_eval "*<PROMPT>*" || {
|
||||
print "prompt hasn't appeared."
|
||||
return 1
|
||||
}
|
||||
zpty_flush After comptesteval
|
||||
rm $tmp
|
||||
}
|
||||
|
||||
comptest () {
|
||||
input="$*"
|
||||
zpty -n -w zsh "$input"$'\C-Z'
|
||||
zpty -r -m zsh log "*<WIDGET><finish>*<PROMPT>*" || {
|
||||
print "failed to invoke finish widget."
|
||||
return 1
|
||||
}
|
||||
|
||||
logs=(${(s:<WIDGET>:)log})
|
||||
shift logs
|
||||
|
||||
for log in "$logs[@]"; do
|
||||
if [[ "$log" = (#b)*$'<LBUFFER>'(*)$'</LBUFFER>\r\n<RBUFFER>'(*)$'</RBUFFER>'* ]]; then
|
||||
print -lr "line: {$match[1]}{$match[2]}"
|
||||
fi
|
||||
while (( ${(N)log#*(#b)(<LC><(??)><RC>(*)<EC>|<DESCRIPTION>(*)</DESCRIPTION>|<MESSAGE>(*)</MESSAGE>|<COMPADD>(*)</COMPADD>|<INSERT_POSITIONS>(*)</INSERT_POSITIONS>|<QUERY>(*)</QUERY>|<WARN>(*)</WARN>)} )); do
|
||||
log="${log[$mend[1]+1,-1]}"
|
||||
if (( 0 <= $mbegin[2] )); then
|
||||
if [[ $match[2] != TC && $match[3] != \ # ]]; then
|
||||
print -lr "$match[2]:{${match[3]%${(%):-%E}}}"
|
||||
fi
|
||||
elif (( 0 <= $mbegin[4] )); then
|
||||
print -lr "DESCRIPTION:{$match[4]}"
|
||||
elif (( 0 <= $mbegin[5] )); then
|
||||
print -lr "MESSAGE:{$match[5]}"
|
||||
elif (( 0 <= $mbegin[6] )); then
|
||||
print -lr "COMPADD:{${${match[6]}//[$'\r\n']/}}"
|
||||
elif (( 0 <= $mbegin[7] )); then
|
||||
print -lr "INSERT_POSITIONS:{${${match[7]}//[$'\r\n']/}}"
|
||||
elif (( 0 <= $mbegin[8] )); then
|
||||
print -lr "QUERY:{${match[8]}}"
|
||||
elif (( 0 <= $mbegin[9] )); then
|
||||
print -lr "WARN:{${match[9]}}"
|
||||
fi
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
zletest () {
|
||||
local first=0
|
||||
for input; do
|
||||
# zpty_flush Before zletest
|
||||
# sleep for $KEYTIMEOUT
|
||||
(( first++ )) && { sleep 2 & } | read -t 0.011 -u 0 -k 1
|
||||
zpty -n -w zsh "$input"
|
||||
done
|
||||
zpty -n -w zsh $'\C-X'
|
||||
zpty -r -m zsh log "*<WIDGET><finish>*<PROMPT>*" || {
|
||||
print "failed to invoke finish widget."
|
||||
return 1
|
||||
}
|
||||
# zpty_flush After zletest
|
||||
print -lr "${(@)${(@ps:\r\n:)log##*<WIDGET><finish>}[2,-2]}"
|
||||
}
|
||||
253
eggs/zsh/config/fzf-tab/test/fzftab.ztst
Normal file
253
eggs/zsh/config/fzf-tab/test/fzftab.ztst
Normal file
@ -0,0 +1,253 @@
|
||||
# Tests for fzf tab.
|
||||
|
||||
%prep
|
||||
unset -m LC_\*
|
||||
ZSH_TEST_LANG=
|
||||
langs=(en_{US,GB}.{UTF-,utf}8 en.UTF-8
|
||||
$(locale -a 2>/dev/null | egrep 'utf8|UTF-8'))
|
||||
for LANG in $langs; do
|
||||
if [[ é = ? ]]; then
|
||||
ZSH_TEST_LANG=$LANG
|
||||
break;
|
||||
fi
|
||||
done
|
||||
if [[ $OSTYPE = cygwin ]]; then
|
||||
ZTST_unimplemented="the zsh/zpty module does not work on Cygwin"
|
||||
elif ( zmodload zsh/zpty 2>/dev/null ); then
|
||||
. $ZTST_srcdir/comptest
|
||||
mkdir comp.tmp
|
||||
cd comp.tmp
|
||||
comptestinit -z zsh &&
|
||||
{
|
||||
comptesteval 'compdef _tst tst'
|
||||
mkdir dir1 &&
|
||||
mkdir dir2 &&
|
||||
touch file1 &&
|
||||
touch file2
|
||||
touch dir1/file1
|
||||
git init
|
||||
}
|
||||
else
|
||||
ZTST_unimplemented="the zsh/zpty module is not available"
|
||||
fi
|
||||
|
||||
comptesteval ". $ZTST_srcdir/../fzf-tab.zsh"
|
||||
comptesteval "zstyle ':fzf-tab:*' debug-command $ZTST_srcdir/select -n 1 -h '\$#_ftb_headers' -q '\"\$_ftb_query\"'"
|
||||
comptesteval '
|
||||
zstyle ":fzf-tab:*" default-color "<LC><C0><RC>"
|
||||
zstyle ":fzf-tab:*" single-group color header
|
||||
zstyle ":fzf-tab:*" group-colors "<LC><C1><RC>" "<LC><C2><RC>" "<LC><C3><RC>" "<LC><C4><RC>"
|
||||
fzf-tab-complete-with-report() {
|
||||
print -lr "<WIDGET><fzf-tab-complete>"
|
||||
zle fzf-tab-complete 2>&1
|
||||
print -lr - "<LBUFFER>$LBUFFER</LBUFFER>" "<RBUFFER>$RBUFFER</RBUFFER>"
|
||||
zle clear-screen
|
||||
zle -R
|
||||
}
|
||||
zle -N fzf-tab-complete-with-report
|
||||
bindkey "^I" fzf-tab-complete-with-report
|
||||
'
|
||||
|
||||
%test
|
||||
|
||||
comptest $': \t'
|
||||
0:directories and files
|
||||
>line: {: dir1/}{}
|
||||
>QUERY:{}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{dir1/}
|
||||
>C1:{dir2/}
|
||||
>C1:{file1}
|
||||
>C1:{file2}
|
||||
|
||||
comptest $': d\t'
|
||||
0:unambiguous prefix
|
||||
>line: {: dir}{}
|
||||
|
||||
comptesteval '_tst() { compadd ".#abc" ".#def" ".#hij" }'
|
||||
comptest $'tst ".#"\t'
|
||||
0:unambiguous prefix in quote string
|
||||
>line: {tst ".#abc }{"}
|
||||
>QUERY:{.#}
|
||||
>C0:{.#abc}
|
||||
>C0:{.#def}
|
||||
>C0:{.#hij}
|
||||
|
||||
comptesteval '_tst() { compadd /home /usr /lib; compstate[insert]=menu }'
|
||||
comptest $'tst \t'
|
||||
0:force list
|
||||
>line: {tst /home }{}
|
||||
>QUERY:{/}
|
||||
>C0:{/home}
|
||||
>C0:{/lib}
|
||||
>C0:{/usr}
|
||||
|
||||
comptesteval 'zstyle ":completion:*" menu true'
|
||||
comptest $': d\t'
|
||||
0:prefix
|
||||
>line: {: dir1/}{}
|
||||
>QUERY:{dir}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{dir1/}
|
||||
>C1:{dir2/}
|
||||
|
||||
comptesteval '_tst () { compadd d c b a }'
|
||||
comptest $'tst \t'
|
||||
0:normal
|
||||
>line: {tst a }{}
|
||||
>QUERY:{}
|
||||
>C0:{a}
|
||||
>C0:{b}
|
||||
>C0:{c}
|
||||
>C0:{d}
|
||||
|
||||
comptesteval 'zstyle ":completion:*:tst:*" sort false'
|
||||
comptest $'tst \t'
|
||||
0:no sort
|
||||
>line: {tst d }{}
|
||||
>QUERY:{}
|
||||
>C0:{d}
|
||||
>C0:{c}
|
||||
>C0:{b}
|
||||
>C0:{a}
|
||||
|
||||
comptesteval 'zstyle ":fzf-tab:*:tst:*" fzf-flags -n 1,2'
|
||||
comptest $'tst \t'
|
||||
comptesteval 'zstyle -d ":fzf-tab:*:tst:*" fzf-flags'
|
||||
0:multi select
|
||||
>line: {tst c d }{}
|
||||
>QUERY:{}
|
||||
>C0:{d}
|
||||
>C0:{c}
|
||||
>C0:{b}
|
||||
>C0:{a}
|
||||
|
||||
comptest $': *\t'
|
||||
0:expand
|
||||
>line: {: dir1 dir2 file1 file2 }{}
|
||||
|
||||
comptesteval 'zstyle ":completion:*:warnings" format "<WARN>%d</WARN>"'
|
||||
comptest $': asd\t'
|
||||
0:warnings
|
||||
>line: {: asd}{}
|
||||
>WARN:{`file'}
|
||||
|
||||
# enclose ' for syntax highlight
|
||||
|
||||
comptesteval "touch 'abc def'"
|
||||
comptest $': ./a\t'
|
||||
0:filename with space
|
||||
>line: {: ./abc\ def }{}
|
||||
|
||||
comptest $': ./abdef\C-b\C-b\C-b\t'
|
||||
0:complete in word
|
||||
>line: {: ./abc\ def }{}
|
||||
|
||||
comptest $': ./abc def\C-b\C-b\C-b\C-b\t'
|
||||
comptesteval "rm 'abc def'"
|
||||
0:complete in word(with known bug)
|
||||
>line: {: ./abc\ def}{ def}
|
||||
|
||||
comptesteval 'mkdir -p abc/def/hij abc/dfe/hij'
|
||||
comptest $': ./a/d/h\t'
|
||||
comptesteval 'rm -rd abc'
|
||||
0:nested directory
|
||||
>line: {: ./abc/def/h}{}
|
||||
>QUERY:{d}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{def/}
|
||||
>C1:{dfe/}
|
||||
|
||||
comptesteval '_tst() { a=(a); _describe "group1" a; a=(b); _describe "group2" a }'
|
||||
comptest $'tst \t'
|
||||
0:multi headers
|
||||
>line: {tst a }{}
|
||||
>QUERY:{}
|
||||
>DESCRIPTION:{group1}
|
||||
>DESCRIPTION:{group2}
|
||||
>C1:{·a}
|
||||
>C2:{·b}
|
||||
|
||||
comptest $'git add dir1\t'
|
||||
0:add empty word
|
||||
>line: {git add dir1/}{}
|
||||
|
||||
comptesteval "zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 'r:|=*' 'l:|=* r:|=*'"
|
||||
comptesteval "touch vim.coc"
|
||||
comptest $': coc\t'
|
||||
comptesteval "rm vim.coc; zstyle -d ':completion:*' matcher-list"
|
||||
0:matcher-list
|
||||
>line: {: vim.coc }{}
|
||||
|
||||
comptesteval $'cd dir1'
|
||||
comptest $': ../d\t'
|
||||
comptesteval $'cd ..'
|
||||
0:IPREFIX
|
||||
>line: {: ../dir1/}{}
|
||||
>QUERY:{dir}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{dir1/}
|
||||
>C1:{dir2/}
|
||||
|
||||
comptest $': $PWD/d\t'
|
||||
0:expansion
|
||||
>line: {: $PWD/dir1/}{}
|
||||
>QUERY:{dir}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{dir1/}
|
||||
>C1:{dir2/}
|
||||
|
||||
comptesteval 'echo no > called'
|
||||
comptesteval "touch 'dir\`echo yes > called\`'"
|
||||
comptest $': d\t'
|
||||
echo called:$(<called)
|
||||
comptesteval "rm 'dir\`echo yes > called\`' called"
|
||||
0:don''t expand file name
|
||||
>line: {: dir1/}{}
|
||||
>QUERY:{dir}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{dir1/}
|
||||
>C1:{dir2/}
|
||||
>C1:{dir`echo yes > called`}
|
||||
>called:no
|
||||
|
||||
comptesteval "zstyle ':fzf-tab:*' debug-command true"
|
||||
comptest $': d\t'
|
||||
comptesteval "zstyle ':fzf-tab:*' debug-command $ZTST_srcdir/select -n 1 -h '\$#headers' -q '\"\$query\"'"
|
||||
0:cancel completion
|
||||
>line: {: d}{}
|
||||
|
||||
comptesteval "zstyle ':fzf-tab:*' debug-command $ZTST_srcdir/select -n QUERY -h '\$#headers' -q '\"dragon\"'"
|
||||
comptest $': ./d\t'
|
||||
comptesteval "zstyle ':fzf-tab:*' debug-command $ZTST_srcdir/select -n 1 -h '\$#headers' -q '\"\$query\"'"
|
||||
0:use query directly
|
||||
>line: {: ./dragon}{}
|
||||
>QUERY:{dragon}
|
||||
>DESCRIPTION:{file}
|
||||
>C1:{dir1/}
|
||||
>C1:{dir2/}
|
||||
|
||||
comptesteval 'zstyle ":completion:*" menu false'
|
||||
comptesteval "local prefix_1=1 prefix_2=1 prenofix_3=1"
|
||||
comptest $'echo $pre\t'
|
||||
comptesteval 'zstyle ":completion:*" menu true'
|
||||
0:parameter completion
|
||||
>line: {echo $pre}{}
|
||||
>QUERY:{}
|
||||
>DESCRIPTION:{parameter}
|
||||
>C1:{prefix_1}
|
||||
>C1:{prefix_2}
|
||||
>C1:{prenofix_3}
|
||||
|
||||
comptesteval '_tst() { compadd -J packages -X package openpgp-keys-gentoo-release -MERGING-pnpm-bin }'
|
||||
comptest $'tst \t'
|
||||
0:completions starts with dash
|
||||
>line: {tst }{}
|
||||
>QUERY:{}
|
||||
>C1:{package }
|
||||
>C1:{openpgp-keys-gentoo-release}
|
||||
>C1:{-MERGING-pnpm-bin}
|
||||
%clean
|
||||
|
||||
zmodload -ui zsh/zpty
|
||||
|
||||
27
eggs/zsh/config/fzf-tab/test/runtests.zsh
Normal file
27
eggs/zsh/config/fzf-tab/test/runtests.zsh
Normal file
@ -0,0 +1,27 @@
|
||||
#!/bin/zsh -f
|
||||
|
||||
emulate zsh
|
||||
|
||||
# Run all specified tests, keeping count of which succeeded.
|
||||
# The reason for this extra layer above the test script is to
|
||||
# protect from catastrophic failure of an individual test.
|
||||
# We could probably do that with subshells instead.
|
||||
|
||||
integer success failure skipped retval
|
||||
for file in ${@:1}; do
|
||||
zsh +Z -f ./ztst.zsh $file
|
||||
retval=$?
|
||||
if (( $retval == 2 )); then
|
||||
(( skipped++ ))
|
||||
elif (( $retval )); then
|
||||
(( failure++ ))
|
||||
else
|
||||
(( success++ ))
|
||||
fi
|
||||
done
|
||||
print "**************************************
|
||||
$success successful test script${${success:#1}:+s}, \
|
||||
$failure failure${${failure:#1}:+s}, \
|
||||
$skipped skipped
|
||||
**************************************"
|
||||
return $(( failure ? 1 : 0 ))
|
||||
36
eggs/zsh/config/fzf-tab/test/select
Executable file
36
eggs/zsh/config/fzf-tab/test/select
Executable file
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
zmodload zsh/zutil
|
||||
|
||||
local -A headers range query expect
|
||||
|
||||
# -h: lines of headers
|
||||
# -n: selection index, if it is QUERY, query string will be used
|
||||
# -q: query string
|
||||
# -e: expect key
|
||||
zparseopts -E h:=headers n:=range q:=query e:=expect
|
||||
|
||||
print -r -- "${query//\"/}"
|
||||
print -r -- "$expect"
|
||||
print -r -- "<QUERY>${query//\"/}</QUERY>" >&2
|
||||
|
||||
local -a lines=()
|
||||
while read input; do
|
||||
lines+=(${input%$'\033[00m'})
|
||||
done
|
||||
|
||||
for ((i = 1; i <= headers; i++)); do
|
||||
print -r -- $lines[i] >&2
|
||||
done
|
||||
|
||||
lines[1,headers]=()
|
||||
|
||||
for i in {1..$#lines}; do
|
||||
print -r -- ${lines[i]//$'\0'}"<EC>" >&2
|
||||
done
|
||||
|
||||
if [[ $range != QUERY ]]; then
|
||||
for i in ${(s:,:)range}; do
|
||||
print -r -- $lines[i]
|
||||
done
|
||||
fi
|
||||
581
eggs/zsh/config/fzf-tab/test/ztst.zsh
Executable file
581
eggs/zsh/config/fzf-tab/test/ztst.zsh
Executable file
@ -0,0 +1,581 @@
|
||||
#!/bin/zsh -f
|
||||
# The line above is just for convenience. Normally tests will be run using
|
||||
# a specified version of zsh. With dynamic loading, any required libraries
|
||||
# must already have been installed in that case.
|
||||
#
|
||||
# Takes one argument: the name of the test file. Currently only one such
|
||||
# file will be processed each time ztst.zsh is run. This is slower, but
|
||||
# much safer in terms of preserving the correct status.
|
||||
# To avoid namespace pollution, all functions and parameters used
|
||||
# only by the script begin with ZTST_.
|
||||
#
|
||||
# Options (without arguments) may precede the test file argument; these
|
||||
# are interpreted as shell options to set. -x is probably the most useful.
|
||||
|
||||
# Produce verbose messages if non-zero.
|
||||
# If 1, produce reports of tests executed; if 2, also report on progress.
|
||||
# Defined in such a way that any value from the environment is used.
|
||||
: ${ZTST_verbose:=0}
|
||||
|
||||
# We require all options to be reset, not just emulation options.
|
||||
# Unfortunately, due to the crud which may be in /etc/zshenv this might
|
||||
# still not be good enough. Maybe we should trick it somehow.
|
||||
emulate -R zsh
|
||||
|
||||
# Ensure the locale does not screw up sorting. Don't supply a locale
|
||||
# unless there's one set, to minimise problems.
|
||||
[[ -n $LC_ALL ]] && LC_ALL=C
|
||||
[[ -n $LC_COLLATE ]] && LC_COLLATE=C
|
||||
[[ -n $LC_NUMERIC ]] && LC_NUMERIC=C
|
||||
[[ -n $LC_MESSAGES ]] && LC_MESSAGES=C
|
||||
[[ -n $LANG ]] && LANG=C
|
||||
|
||||
# Don't propagate variables that are set by default in the shell.
|
||||
typeset +x WORDCHARS
|
||||
|
||||
# We need to be able to save and restore the options used in the test.
|
||||
# We use the $options variable of the parameter module for this.
|
||||
zmodload zsh/parameter
|
||||
|
||||
# Note that both the following are regular arrays, since we only use them
|
||||
# in whole array assignments to/from $options.
|
||||
# Options set in test code (i.e. by default all standard options)
|
||||
ZTST_testopts=(${(kv)options})
|
||||
|
||||
setopt extendedglob nonomatch
|
||||
while [[ $1 = [-+]* ]]; do
|
||||
set $1
|
||||
shift
|
||||
done
|
||||
# Options set in main script
|
||||
ZTST_mainopts=(${(kv)options})
|
||||
|
||||
# We run in the current directory, so remember it.
|
||||
ZTST_testdir=$PWD
|
||||
ZTST_testname=$1
|
||||
|
||||
integer ZTST_testfailed
|
||||
|
||||
# This is POSIX nonsense. Because of the vague feeling someone, somewhere
|
||||
# may one day need to examine the arguments of "tail" using a standard
|
||||
# option parser, every Unix user in the world is expected to switch
|
||||
# to using "tail -n NUM" instead of "tail -NUM". Older versions of
|
||||
# tail don't support this.
|
||||
tail() {
|
||||
emulate -L zsh
|
||||
|
||||
if [[ -z $TAIL_SUPPORTS_MINUS_N ]]; then
|
||||
local test
|
||||
test=$(echo "foo\nbar" | command tail -n 1 2>/dev/null)
|
||||
if [[ $test = bar ]]; then
|
||||
TAIL_SUPPORTS_MINUS_N=1
|
||||
else
|
||||
TAIL_SUPPORTS_MINUS_N=0
|
||||
fi
|
||||
fi
|
||||
|
||||
integer argi=${argv[(i)-<->]}
|
||||
|
||||
if [[ $argi -le $# && $TAIL_SUPPORTS_MINUS_N = 1 ]]; then
|
||||
argv[$argi]=(-n ${argv[$argi][2,-1]})
|
||||
fi
|
||||
|
||||
command tail "$argv[@]"
|
||||
}
|
||||
|
||||
# The source directory is not necessarily the current directory,
|
||||
# but if $0 doesn't contain a `/' assume it is.
|
||||
if [[ $0 = */* ]]; then
|
||||
ZTST_srcdir=${0%/*}
|
||||
else
|
||||
ZTST_srcdir=$PWD
|
||||
fi
|
||||
[[ $ZTST_srcdir = /* ]] || ZTST_srcdir="$ZTST_testdir/$ZTST_srcdir"
|
||||
|
||||
|
||||
: ${TMPPREFIX:=/tmp/zsh}
|
||||
ZTST_tmp=${TMPPREFIX}.ztst.$$
|
||||
if ! rm -f $ZTST_tmp || ! mkdir -p $ZTST_tmp || ! chmod go-w $ZTST_tmp; then
|
||||
print "Can't create $ZTST_tmp for exclusive use." >&2
|
||||
exit 1
|
||||
fi
|
||||
# Temporary files for redirection inside tests.
|
||||
ZTST_in=${ZTST_tmp}/ztst.in
|
||||
# hold the expected output
|
||||
ZTST_out=${ZTST_tmp}/ztst.out
|
||||
ZTST_err=${ZTST_tmp}/ztst.err
|
||||
# hold the actual output from the test
|
||||
ZTST_tout=${ZTST_tmp}/ztst.tout
|
||||
ZTST_terr=${ZTST_tmp}/ztst.terr
|
||||
|
||||
ZTST_cleanup() {
|
||||
cd $ZTST_testdir
|
||||
rm -rf $ZTST_testdir/dummy.tmp $ZTST_testdir/*.tmp(N) ${ZTST_tmp}
|
||||
}
|
||||
|
||||
# This cleanup always gets performed, even if we abort. Later,
|
||||
# we should try and arrange that any test-specific cleanup
|
||||
# always gets called as well.
|
||||
##trap 'print cleaning up...
|
||||
##ZTST_cleanup' INT QUIT TERM
|
||||
# Make sure it's clean now.
|
||||
rm -rf dummy.tmp *.tmp
|
||||
|
||||
# Report failure. Note that all output regarding the tests goes to stdout.
|
||||
# That saves an unpleasant mixture of stdout and stderr to sort out.
|
||||
ZTST_testfailed() {
|
||||
print -r "Test $ZTST_testname failed: $1"
|
||||
if [[ -n $ZTST_message ]]; then
|
||||
print -r "Was testing: $ZTST_message"
|
||||
fi
|
||||
print -r "$ZTST_testname: test failed."
|
||||
if [[ -n $ZTST_failmsg ]]; then
|
||||
print -r "The following may (or may not) help identifying the cause:
|
||||
$ZTST_failmsg"
|
||||
fi
|
||||
ZTST_testfailed=1
|
||||
return 1
|
||||
}
|
||||
|
||||
# Print messages if $ZTST_verbose is non-empty
|
||||
ZTST_verbose() {
|
||||
local lev=$1
|
||||
shift
|
||||
if [[ -n $ZTST_verbose && $ZTST_verbose -ge $lev ]]; then
|
||||
print -r -u $ZTST_fd -- $*
|
||||
fi
|
||||
}
|
||||
ZTST_hashmark() {
|
||||
if [[ ZTST_verbose -le 0 && -t $ZTST_fd ]]; then
|
||||
print -n -u$ZTST_fd -- ${(pl:SECONDS::\#::\#\r:)}
|
||||
fi
|
||||
(( SECONDS > COLUMNS+1 && (SECONDS -= COLUMNS) ))
|
||||
}
|
||||
|
||||
if [[ ! -r $ZTST_testname ]]; then
|
||||
ZTST_testfailed "can't read test file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec {ZTST_fd}>&1
|
||||
exec {ZTST_input}<$ZTST_testname
|
||||
|
||||
# The current line read from the test file.
|
||||
ZTST_curline=''
|
||||
# The current section being run
|
||||
ZTST_cursect=''
|
||||
|
||||
# Get a new input line. Don't mangle spaces; set IFS locally to empty.
|
||||
# We shall skip comments at this level.
|
||||
ZTST_getline() {
|
||||
local IFS=
|
||||
while true; do
|
||||
read -u $ZTST_input -r ZTST_curline || return 1
|
||||
[[ $ZTST_curline == \#* ]] || return 0
|
||||
done
|
||||
}
|
||||
|
||||
# Get the name of the section. It may already have been read into
|
||||
# $curline, or we may have to skip some initial comments to find it.
|
||||
# If argument present, it's OK to skip the reset of the current section,
|
||||
# so no error if we find garbage.
|
||||
ZTST_getsect() {
|
||||
local match mbegin mend
|
||||
|
||||
while [[ $ZTST_curline != '%'(#b)([[:alnum:]]##)* ]]; do
|
||||
ZTST_getline || return 1
|
||||
[[ $ZTST_curline = [[:blank:]]# ]] && continue
|
||||
if [[ $# -eq 0 && $ZTST_curline != '%'[[:alnum:]]##* ]]; then
|
||||
ZTST_testfailed "bad line found before or after section:
|
||||
$ZTST_curline"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
# have the next line ready waiting
|
||||
ZTST_getline
|
||||
ZTST_cursect=${match[1]}
|
||||
ZTST_verbose 2 "ZTST_getsect: read section name: $ZTST_cursect"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Read in an indented code chunk for execution
|
||||
ZTST_getchunk() {
|
||||
# Code chunks are always separated by blank lines or the
|
||||
# end of a section, so if we already have a piece of code,
|
||||
# we keep it. Currently that shouldn't actually happen.
|
||||
ZTST_code=''
|
||||
# First find the chunk.
|
||||
while [[ $ZTST_curline = [[:blank:]]# ]]; do
|
||||
ZTST_getline || break
|
||||
done
|
||||
while [[ $ZTST_curline = [[:blank:]]##[^[:blank:]]* ]]; do
|
||||
ZTST_code="${ZTST_code:+${ZTST_code}
|
||||
}${ZTST_curline}"
|
||||
ZTST_getline || break
|
||||
done
|
||||
ZTST_verbose 2 "ZTST_getchunk: read code chunk:
|
||||
$ZTST_code"
|
||||
[[ -n $ZTST_code ]]
|
||||
}
|
||||
|
||||
# Read in a piece for redirection.
|
||||
ZTST_getredir() {
|
||||
local char=${ZTST_curline[1]} fn
|
||||
ZTST_redir=${ZTST_curline[2,-1]}
|
||||
while ZTST_getline; do
|
||||
[[ $ZTST_curline[1] = $char ]] || break
|
||||
ZTST_redir="${ZTST_redir}
|
||||
${ZTST_curline[2,-1]}"
|
||||
done
|
||||
ZTST_verbose 2 "ZTST_getredir: read redir for '$char':
|
||||
$ZTST_redir"
|
||||
|
||||
case $char in
|
||||
('<') fn=$ZTST_in
|
||||
;;
|
||||
('>') fn=$ZTST_out
|
||||
;;
|
||||
('?') fn=$ZTST_err
|
||||
;;
|
||||
(*) ZTST_testfailed "bad redir operator: $char"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
if [[ $ZTST_flags = *q* && $char = '<' ]]; then
|
||||
# delay substituting output until variables are set
|
||||
print -r -- "${(e)ZTST_redir}" >>$fn
|
||||
else
|
||||
print -r -- "$ZTST_redir" >>$fn
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Execute an indented chunk. Redirections will already have
|
||||
# been set up, but we need to handle the options.
|
||||
ZTST_execchunk() {
|
||||
setopt localloops # don't let continue & break propagate out
|
||||
options=($ZTST_testopts)
|
||||
() {
|
||||
unsetopt localloops
|
||||
eval "$ZTST_code"
|
||||
}
|
||||
ZTST_status=$?
|
||||
# careful... ksh_arrays may be in effect.
|
||||
ZTST_testopts=(${(kv)options[*]})
|
||||
options=(${ZTST_mainopts[*]})
|
||||
ZTST_verbose 2 "ZTST_execchunk: status $ZTST_status"
|
||||
return $ZTST_status
|
||||
}
|
||||
|
||||
# Functions for preparation and cleaning.
|
||||
# When cleaning up (non-zero string argument), we ignore status.
|
||||
ZTST_prepclean() {
|
||||
# Execute indented code chunks.
|
||||
while ZTST_getchunk; do
|
||||
ZTST_execchunk >/dev/null || [[ -n $1 ]] || {
|
||||
[[ -n "$ZTST_unimplemented" ]] ||
|
||||
ZTST_testfailed "non-zero status from preparation code:
|
||||
$ZTST_code" && return 0
|
||||
}
|
||||
done
|
||||
}
|
||||
|
||||
# diff wrapper
|
||||
ZTST_diff() {
|
||||
emulate -L zsh
|
||||
setopt extendedglob
|
||||
|
||||
local diff_out
|
||||
integer diff_pat diff_ret
|
||||
|
||||
case $1 in
|
||||
(p)
|
||||
diff_pat=1
|
||||
;;
|
||||
|
||||
(d)
|
||||
;;
|
||||
|
||||
(*)
|
||||
print "Bad ZTST_diff code: d for diff, p for pattern match"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
|
||||
if (( diff_pat )); then
|
||||
local -a diff_lines1 diff_lines2
|
||||
integer failed i l
|
||||
local p
|
||||
|
||||
diff_lines1=("${(f@)$(<$argv[-2])}")
|
||||
diff_lines2=("${(f@)$(<$argv[-1])}")
|
||||
if (( ${#diff_lines1} != ${#diff_lines2} )); then
|
||||
failed=1
|
||||
print -r "Pattern match failed, line mismatch (${#diff_lines1}/${#diff_lines2}):"
|
||||
else
|
||||
for (( i = 1; i <= ${#diff_lines1}; i++ )); do
|
||||
if [[ ${diff_lines2[i]} != ${~diff_lines1[i]} ]]; then
|
||||
failed=1
|
||||
print -r "Pattern match failed, line $i:"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if (( failed )); then
|
||||
for (( l = 1; l <= ${#diff_lines1}; ++l )); do
|
||||
if (( l == i )); then
|
||||
p="-"
|
||||
else
|
||||
p=" "
|
||||
fi
|
||||
print -r -- "$p<${diff_lines1[l]}"
|
||||
done
|
||||
for (( l = 1; l <= ${#diff_lines2}; ++l )); do
|
||||
if (( l == i )); then
|
||||
p="+"
|
||||
else
|
||||
p=" "
|
||||
fi
|
||||
print -r -- "$p>${diff_lines2[l]}"
|
||||
done
|
||||
diff_ret=1
|
||||
fi
|
||||
else
|
||||
diff_out=$(diff -a "$@")
|
||||
diff_ret="$?"
|
||||
if [[ "$diff_ret" != "0" ]]; then
|
||||
print -r -- "$diff_out"
|
||||
fi
|
||||
fi
|
||||
|
||||
return "$diff_ret"
|
||||
}
|
||||
|
||||
ZTST_test() {
|
||||
local last match mbegin mend found substlines
|
||||
local diff_out diff_err
|
||||
local ZTST_skip
|
||||
integer expected_to_fail
|
||||
|
||||
while true; do
|
||||
rm -f $ZTST_in $ZTST_out $ZTST_err
|
||||
touch $ZTST_in $ZTST_out $ZTST_err
|
||||
ZTST_message=''
|
||||
ZTST_failmsg=''
|
||||
found=0
|
||||
diff_out=d
|
||||
diff_err=d
|
||||
|
||||
ZTST_verbose 2 "ZTST_test: looking for new test"
|
||||
|
||||
while true; do
|
||||
ZTST_verbose 2 "ZTST_test: examining line:
|
||||
$ZTST_curline"
|
||||
case $ZTST_curline in
|
||||
(%*) if [[ $found = 0 ]]; then
|
||||
break 2
|
||||
else
|
||||
last=1
|
||||
break
|
||||
fi
|
||||
;;
|
||||
([[:space:]]#)
|
||||
if [[ $found = 0 ]]; then
|
||||
ZTST_getline || break 2
|
||||
continue
|
||||
else
|
||||
break
|
||||
fi
|
||||
;;
|
||||
([[:space:]]##[^[:space:]]*) ZTST_getchunk
|
||||
if [[ $ZTST_curline == (#b)([-0-9]##)([[:alpha:]]#)(:*)# ]]; then
|
||||
ZTST_xstatus=$match[1]
|
||||
ZTST_flags=$match[2]
|
||||
ZTST_message=${match[3]:+${match[3][2,-1]}}
|
||||
else
|
||||
ZTST_testfailed "expecting test status at:
|
||||
$ZTST_curline"
|
||||
return 1
|
||||
fi
|
||||
ZTST_getline
|
||||
found=1
|
||||
;;
|
||||
('<'*) ZTST_getredir || return 1
|
||||
found=1
|
||||
;;
|
||||
('*>'*)
|
||||
ZTST_curline=${ZTST_curline[2,-1]}
|
||||
diff_out=p
|
||||
;&
|
||||
('>'*)
|
||||
ZTST_getredir || return 1
|
||||
found=1
|
||||
;;
|
||||
('*?'*)
|
||||
ZTST_curline=${ZTST_curline[2,-1]}
|
||||
diff_err=p
|
||||
;&
|
||||
('?'*)
|
||||
ZTST_getredir || return 1
|
||||
found=1
|
||||
;;
|
||||
('F:'*) ZTST_failmsg="${ZTST_failmsg:+${ZTST_failmsg}
|
||||
} ${ZTST_curline[3,-1]}"
|
||||
ZTST_getline
|
||||
found=1
|
||||
;;
|
||||
(*) ZTST_testfailed "bad line in test block:
|
||||
$ZTST_curline"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# If we found some code to execute...
|
||||
if [[ -n $ZTST_code ]]; then
|
||||
ZTST_hashmark
|
||||
ZTST_verbose 1 "Running test: $ZTST_message"
|
||||
ZTST_verbose 2 "ZTST_test: expecting status: $ZTST_xstatus"
|
||||
ZTST_verbose 2 "Input: $ZTST_in, output: $ZTST_out, error: $ZTST_terr"
|
||||
|
||||
ZTST_execchunk <$ZTST_in >$ZTST_tout 2>$ZTST_terr
|
||||
|
||||
if [[ -n $ZTST_skip ]]; then
|
||||
ZTST_verbose 0 "Test case skipped: $ZTST_skip"
|
||||
ZTST_skip=
|
||||
if [[ -n $last ]]; then
|
||||
break
|
||||
else
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $ZTST_flags = *f* ]]; then
|
||||
expected_to_fail=1
|
||||
ZTST_xfail_diff() { ZTST_diff "$@" > /dev/null }
|
||||
ZTST_diff=ZTST_xfail_diff
|
||||
else
|
||||
expected_to_fail=0
|
||||
ZTST_diff=ZTST_diff
|
||||
fi
|
||||
|
||||
# First check we got the right status, if specified.
|
||||
if [[ $ZTST_xstatus != - && $ZTST_xstatus != $ZTST_status ]]; then
|
||||
if (( expected_to_fail )); then
|
||||
ZTST_verbose 1 "Test failed, as expected."
|
||||
continue
|
||||
fi
|
||||
ZTST_testfailed "bad status $ZTST_status, expected $ZTST_xstatus from:
|
||||
$ZTST_code${$(<$ZTST_terr):+
|
||||
Error output:
|
||||
$(<$ZTST_terr)}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
ZTST_verbose 2 "ZTST_test: test produced standard output:
|
||||
$(<$ZTST_tout)
|
||||
ZTST_test: and standard error:
|
||||
$(<$ZTST_terr)"
|
||||
|
||||
# Now check output and error.
|
||||
if [[ $ZTST_flags = *q* && -s $ZTST_out ]]; then
|
||||
substlines="$(<$ZTST_out)"
|
||||
rm -rf $ZTST_out
|
||||
print -r -- "${(e)substlines}" >$ZTST_out
|
||||
fi
|
||||
if [[ $ZTST_flags != *d* ]] && ! $ZTST_diff $diff_out -u $ZTST_out $ZTST_tout; then
|
||||
if (( expected_to_fail )); then
|
||||
ZTST_verbose 1 "Test failed, as expected."
|
||||
continue
|
||||
fi
|
||||
ZTST_testfailed "output differs from expected as shown above for:
|
||||
$ZTST_code${$(<$ZTST_terr):+
|
||||
Error output:
|
||||
$(<$ZTST_terr)}"
|
||||
return 1
|
||||
fi
|
||||
if [[ $ZTST_flags = *q* && -s $ZTST_err ]]; then
|
||||
substlines="$(<$ZTST_err)"
|
||||
rm -rf $ZTST_err
|
||||
print -r -- "${(e)substlines}" >$ZTST_err
|
||||
fi
|
||||
if [[ $ZTST_flags != *D* ]] && ! $ZTST_diff $diff_err -u $ZTST_err $ZTST_terr; then
|
||||
if (( expected_to_fail )); then
|
||||
ZTST_verbose 1 "Test failed, as expected."
|
||||
continue
|
||||
fi
|
||||
ZTST_testfailed "error output differs from expected as shown above for:
|
||||
$ZTST_code"
|
||||
return 1
|
||||
fi
|
||||
if (( expected_to_fail )); then
|
||||
ZTST_testfailed "test was expected to fail, but passed."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
ZTST_verbose 1 "Test successful."
|
||||
[[ -n $last ]] && break
|
||||
done
|
||||
|
||||
ZTST_verbose 2 "ZTST_test: all tests successful"
|
||||
|
||||
# reset message to keep ZTST_testfailed output correct
|
||||
ZTST_message=''
|
||||
}
|
||||
|
||||
|
||||
# Remember which sections we've done.
|
||||
typeset -A ZTST_sects
|
||||
ZTST_sects=(prep 0 test 0 clean 0)
|
||||
|
||||
print "$ZTST_testname: starting."
|
||||
|
||||
# Now go through all the different sections until the end.
|
||||
# prep section may set ZTST_unimplemented, in this case the actual
|
||||
# tests will be skipped
|
||||
ZTST_skipok=
|
||||
ZTST_unimplemented=
|
||||
while [[ -z "$ZTST_unimplemented" ]] && ZTST_getsect $ZTST_skipok; do
|
||||
case $ZTST_cursect in
|
||||
(prep) if (( ${ZTST_sects[prep]} + ${ZTST_sects[test]} + \
|
||||
${ZTST_sects[clean]} )); then
|
||||
ZTST_testfailed "\`prep' section must come first"
|
||||
exit 1
|
||||
fi
|
||||
ZTST_prepclean
|
||||
ZTST_sects[prep]=1
|
||||
;;
|
||||
(test)
|
||||
if (( ${ZTST_sects[test]} + ${ZTST_sects[clean]} )); then
|
||||
ZTST_testfailed "bad placement of \`test' section"
|
||||
exit 1
|
||||
fi
|
||||
# careful here: we can't execute ZTST_test before || or &&
|
||||
# because that affects the behaviour of traps in the tests.
|
||||
ZTST_test
|
||||
(( $? )) && ZTST_skipok=1
|
||||
ZTST_sects[test]=1
|
||||
;;
|
||||
(clean)
|
||||
if (( ${ZTST_sects[test]} == 0 || ${ZTST_sects[clean]} )); then
|
||||
ZTST_testfailed "bad use of \`clean' section"
|
||||
else
|
||||
ZTST_prepclean 1
|
||||
ZTST_sects[clean]=1
|
||||
fi
|
||||
ZTST_skipok=
|
||||
;;
|
||||
*) ZTST_testfailed "bad section name: $ZTST_cursect"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n "$ZTST_unimplemented" ]]; then
|
||||
print "$ZTST_testname: skipped ($ZTST_unimplemented)"
|
||||
ZTST_testfailed=2
|
||||
elif (( ! $ZTST_testfailed )); then
|
||||
print "$ZTST_testname: all tests successful."
|
||||
fi
|
||||
ZTST_cleanup
|
||||
exit $(( ZTST_testfailed ))
|
||||
Loading…
x
Reference in New Issue
Block a user