m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-14 14:23:47 -05:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Junegunn Choi
40d934e378 0.11.3 2016-02-07 11:00:10 +09:00
Junegunn Choi
e95d82748f Use $SHELL to start $FZF_DEFAULT_COMMAND (#481) 2016-02-07 01:49:29 +09:00
Junegunn Choi
30bd0b53db Fix #481 - Use $SHELL instead of sh in execute action
Note that $SHELL only points to the default shell instead of the current
shell. If you're on a non-default shell, you might want to override the
value like follows.

  SHELL=zsh fzf --bind 'enter:execute:echo $ZSH_VERSION; sleep 1'
2016-02-03 04:46:02 +09:00
Junegunn Choi
1893eca41a Handle SIGTERM gracefully (#482) 2016-02-02 17:51:21 +09:00
Junegunn Choi
82067463b8 [completion] _fzf_complete_COMMAND_post for post processing
e.g.

_fzf_complete_foo() {
  _fzf_complete "--multi --reverse --header-lines=3" "$@" < <(
    ls -al
  )
}

_fzf_complete_foo_post() {
  awk '{print $NF}'
}

[ -n "$BASH" ] && complete -F _fzf_complete_foo -o default -o bashdefault foo
2016-01-29 01:31:04 +09:00
Junegunn Choi
ce9c51d399 Typo 2016-01-20 01:39:55 +09:00
Junegunn Choi
96176476f3 Make fuzzy completion customizable with _fzf_compgen_{path,dir}
Notes:
- You can now override _fzf_compgen_path and _fzf_compgen_dir functions
  to use custom commands such as ag instead of find for listing
  completion candidates.
    - The first argument is the base path to start traversal
- Removed file-only completion in bash, i.e. _fzf_file_completion.
  Maintaining a list of commands that only expect files, not
  directories, is cumbersome (there are too many) and error-prone.

TBD:
- Added $FZF_COMPLETION_DIR_COMMANDS to customize the list of commands
  which use directory-only completion. The default is "cd pushd rmdir".
  Not sure if it's the best approach to address the requirement, I'll
  leave it as an undocumented feature.

Related: #406 (@thomcom), #456 (@frizinak)
2016-01-20 01:38:24 +09:00
11 changed files with 138 additions and 68 deletions

View File

@@ -1,6 +1,16 @@
CHANGELOG CHANGELOG
========= =========
0.11.3
------
- Graceful exit on SIGTERM (#482)
- `$SHELL` instead of `sh` for `execute` action and `$FZF_DEFAULT_COMMAND` (#481)
- Changes in fuzzy completion API
- [`_fzf_compgen_{path,dir}`](https://github.com/junegunn/fzf/commit/9617647)
- [`_fzf_complete_COMMAND_post`](https://github.com/junegunn/fzf/commit/8206746)
for post-processing
0.11.2 0.11.2
------ ------

View File

@@ -259,6 +259,14 @@ export FZF_COMPLETION_TRIGGER='~~'
# Options to fzf command # Options to fzf command
export FZF_COMPLETION_OPTS='+c -x' export FZF_COMPLETION_OPTS='+c -x'
# Use ag instead of the default find command for listing candidates.
# - The first argument to the function is the base path to start traversal
# - Note that ag only lists files not directories
# - See the source code (completion.{bash,zsh}) for the details.
_fzf_compgen_path() {
ag -g "" "$1"
}
``` ```
#### Supported commands #### Supported commands

View File

@@ -2,8 +2,8 @@
set -u set -u
[[ "$@" =~ --pre ]] && version=0.11.2 pre=1 || [[ "$@" =~ --pre ]] && version=0.11.3 pre=1 ||
version=0.11.2 pre=0 version=0.11.3 pre=0
auto_completion= auto_completion=
key_bindings= key_bindings=

View File

@@ -21,7 +21,7 @@ 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Jan 2016" "fzf 0.11.2" "fzf - a command-line fuzzy finder" .TH fzf 1 "Feb 2016" "fzf 0.11.3" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder

View File

@@ -10,6 +10,26 @@
# - $FZF_COMPLETION_TRIGGER (default: '**') # - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $FZF_COMPLETION_OPTS (default: empty)
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() {
echo "$1"
\find -L "$1" \
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
if ! declare -f _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() {
\find -L "$1" \
-name .git -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
###########################################################
_fzf_orig_completion_filter() { _fzf_orig_completion_filter() {
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' | sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
awk -F= '{gsub(/[^a-z0-9_= ;]/, "_", $1); print $1"="$2}' awk -F= '{gsub(/[^a-z0-9_= ;]/, "_", $1); print $1"="$2}'
@@ -113,7 +133,7 @@ __fzf_generic_path_completion() {
[ -z "$dir" ] && dir='.' [ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}" [ "$dir" != "/" ] && dir="${dir/%\//}"
tput sc tput sc
matches=$(\find -L "$dir" $1 -a -not -path "$dir" -print 2> /dev/null | sed 's@^\./@@' | $fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do matches=$(eval "$1 $(printf %q "$dir")" | $fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do
printf "%q$3 " "$item" printf "%q$3 " "$item"
done) done)
matches=${matches% } matches=${matches% }
@@ -136,15 +156,10 @@ __fzf_generic_path_completion() {
fi fi
} }
_fzf_feed_fifo() (
rm -f "$fifo"
mkfifo "$fifo"
cat <&0 > "$fifo" &
)
_fzf_complete() { _fzf_complete() {
local fifo cur selected trigger cmd fzf local cur selected trigger cmd fzf post
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$" post="$(caller 0 | awk '{print $2}')_post"
type -t $post > /dev/null 2>&1 || post=cat
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf" [ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g') cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g')
@@ -153,12 +168,10 @@ _fzf_complete() {
if [[ ${cur} == *"$trigger" ]]; then if [[ ${cur} == *"$trigger" ]]; then
cur=${cur:0:${#cur}-${#trigger}} cur=${cur:0:${#cur}-${#trigger}}
_fzf_feed_fifo "$fifo"
tput sc tput sc
selected=$(eval "cat '$fifo' | $fzf $FZF_COMPLETION_OPTS $1 -q '$cur'" | tr '\n' ' ') selected=$(cat | $fzf $FZF_COMPLETION_OPTS $1 -q "$cur" | $post | tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace" selected=${selected% } # Strip trailing space not to repeat "-o nospace"
tput rc tput rc
rm -f "$fifo"
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
COMPREPLY=("$selected") COMPREPLY=("$selected")
@@ -171,21 +184,16 @@ _fzf_complete() {
} }
_fzf_path_completion() { _fzf_path_completion() {
__fzf_generic_path_completion \ __fzf_generic_path_completion _fzf_compgen_path "-m" "" "$@"
"-name .git -prune -o -name .svn -prune -o ( -type d -o -type f -o -type l )" \
"-m" "" "$@"
} }
# Deprecated. No file only completion.
_fzf_file_completion() { _fzf_file_completion() {
__fzf_generic_path_completion \ _fzf_path_completion "$@"
"-name .git -prune -o -name .svn -prune -o ( -type f -o -type l )" \
"-m" "" "$@"
} }
_fzf_dir_completion() { _fzf_dir_completion() {
__fzf_generic_path_completion \ __fzf_generic_path_completion _fzf_compgen_dir "" "/" "$@"
"-name .git -prune -o -name .svn -prune -o -type d" \
"" "/" "$@"
} }
_fzf_complete_kill() { _fzf_complete_kill() {
@@ -239,13 +247,12 @@ _fzf_complete_unalias() {
# fzf options # fzf options
complete -o default -F _fzf_opts_completion fzf complete -o default -F _fzf_opts_completion fzf
d_cmds="cd pushd rmdir" d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
f_cmds=" a_cmds="
awk cat diff diff3 awk cat diff diff3
emacs emacsclient ex file ftp g++ gcc gvim head hg java emacs emacsclient ex file ftp g++ gcc gvim head hg java
javac ld less more mvim nvim patch perl python ruby javac ld less more mvim nvim patch perl python ruby
sed sftp sort source tail tee uniq vi view vim wc xdg-open" sed sftp sort source tail tee uniq vi view vim wc xdg-open
a_cmds="
basename bunzip2 bzip2 chmod chown curl cp dirname du basename bunzip2 bzip2 chmod chown curl cp dirname du
find git grep gunzip gzip hg jar find git grep gunzip gzip hg jar
ln ls mv open rm rsync scp ln ls mv open rm rsync scp
@@ -253,11 +260,11 @@ a_cmds="
x_cmds="kill ssh telnet unset unalias export" x_cmds="kill ssh telnet unset unalias export"
# Preserve existing completion # Preserve existing completion
if [ "$_fzf_completion_loaded" != '0.10.8' ]; then if [ "$_fzf_completion_loaded" != '0.11.3' ]; then
# Really wish I could use associative array but OSX comes with bash 3.2 :( # Really wish I could use associative array but OSX comes with bash 3.2 :(
eval $(complete | \grep '\-F' | \grep -v _fzf_ | eval $(complete | \grep '\-F' | \grep -v _fzf_ |
\grep -E " ($(echo $d_cmds $f_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter) \grep -E " ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
export _fzf_completion_loaded=0.10.8 export _fzf_completion_loaded=0.11.3
fi fi
if type _completion_loader > /dev/null 2>&1; then if type _completion_loader > /dev/null 2>&1; then
@@ -278,21 +285,16 @@ _fzf_defc() {
fi fi
} }
# Directory
for cmd in $d_cmds; do
_fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o plusdirs"
done
# File
for cmd in $f_cmds; do
_fzf_defc "$cmd" _fzf_file_completion "-o default -o bashdefault"
done
# Anything # Anything
for cmd in $a_cmds; do for cmd in $a_cmds; do
_fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault" _fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault"
done done
# Directory
for cmd in $d_cmds; do
_fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o plusdirs"
done
unset _fzf_defc unset _fzf_defc
# Kill completion # Kill completion
@@ -307,4 +309,4 @@ complete -F _fzf_complete_unset -o default -o bashdefault unset
complete -F _fzf_complete_export -o default -o bashdefault export complete -F _fzf_complete_export -o default -o bashdefault export
complete -F _fzf_complete_unalias -o default -o bashdefault unalias complete -F _fzf_complete_unalias -o default -o bashdefault unalias
unset cmd d_cmds f_cmds a_cmds x_cmds unset cmd d_cmds a_cmds x_cmds

View File

@@ -10,12 +10,32 @@
# - $FZF_COMPLETION_TRIGGER (default: '**') # - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $FZF_COMPLETION_OPTS (default: empty)
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() {
echo "$1"
\find -L "$1" \
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
if ! declare -f _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() {
\find -L "$1" \
-name .git -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
###########################################################
__fzf_generic_path_completion() { __fzf_generic_path_completion() {
local base lbuf find_opts fzf_opts suffix tail fzf dir leftover matches nnm local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches nnm
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar" # (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
base=${(Q)1} base=${(Q)1}
lbuf=$2 lbuf=$2
find_opts=$3 compgen=$3
fzf_opts=$4 fzf_opts=$4
suffix=$5 suffix=$5
tail=$6 tail=$6
@@ -33,7 +53,7 @@ __fzf_generic_path_completion() {
[ -z "$dir" ] && dir='.' [ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}" [ "$dir" != "/" ] && dir="${dir/%\//}"
dir=${~dir} dir=${~dir}
matches=$(\find -L "$dir" ${=find_opts} -a -not -path "$dir" -print 2> /dev/null | sed 's@^\./@@' | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "$leftover" | while read item; do matches=$(eval "$compgen $(printf %q "$dir")" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "$leftover" | while read item; do
printf "%q$suffix " "$item" printf "%q$suffix " "$item"
done) done)
matches=${matches% } matches=${matches% }
@@ -50,37 +70,29 @@ __fzf_generic_path_completion() {
} }
_fzf_path_completion() { _fzf_path_completion() {
__fzf_generic_path_completion "$1" "$2" \ __fzf_generic_path_completion "$1" "$2" _fzf_compgen_path \
"-name .git -prune -o -name .svn -prune -o ( -type d -o -type f -o -type l )" \
"-m" "" " " "-m" "" " "
} }
_fzf_dir_completion() { _fzf_dir_completion() {
__fzf_generic_path_completion "$1" "$2" \ __fzf_generic_path_completion "$1" "$2" _fzf_compgen_dir \
"-name .git -prune -o -name .svn -prune -o -type d" \
"" "/" "" "" "/" ""
} }
_fzf_feed_fifo() (
rm -f "$fifo"
mkfifo "$fifo"
cat <&0 > "$fifo" &
)
_fzf_complete() { _fzf_complete() {
local fifo fzf_opts lbuf fzf matches local fzf_opts lbuf fzf matches post
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
fzf_opts=$1 fzf_opts=$1
lbuf=$2 lbuf=$2
post="${funcstack[2]}_post"
type $post > /dev/null 2>&1 || post=cat
[ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf" [ ${FZF_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
_fzf_feed_fifo "$fifo" matches=$(cat | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "${(Q)prefix}" | $post | tr '\n' ' ')
matches=$(cat "$fifo" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=fzf_opts} -q "${(Q)prefix}" | tr '\n' ' ')
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches" LBUFFER="$lbuf$matches"
fi fi
zle redisplay zle redisplay
rm -f "$fifo"
} }
_fzf_complete_telnet() { _fzf_complete_telnet() {
@@ -145,7 +157,7 @@ fzf-completion() {
zle redisplay zle redisplay
# Trigger sequence given # Trigger sequence given
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
d_cmds=(cd pushd rmdir) d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}} [ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}} [ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}}

View File

@@ -8,7 +8,7 @@ import (
const ( const (
// Current version // Current version
version = "0.11.2" version = "0.11.3"
// Core // Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond coordinatorDelayMax time.Duration = 100 * time.Millisecond

View File

@@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"io" "io"
"os" "os"
"os/exec"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
) )
@@ -59,7 +58,7 @@ func (r *Reader) readFromStdin() {
} }
func (r *Reader) readFromCommand(cmd string) { func (r *Reader) readFromCommand(cmd string) {
listCommand := exec.Command("sh", "-c", cmd) listCommand := util.ExecCommand(cmd)
out, err := listCommand.StdoutPipe() out, err := listCommand.StdoutPipe()
if err != nil { if err != nil {
return return

View File

@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"os" "os"
"os/exec"
"os/signal" "os/signal"
"regexp" "regexp"
"sort" "sort"
@@ -720,7 +719,7 @@ func quoteEntry(entry string) string {
func executeCommand(template string, replacement string) { func executeCommand(template string, replacement string) {
command := strings.Replace(template, "{}", replacement, -1) command := strings.Replace(template, "{}", replacement, -1)
cmd := exec.Command("sh", "-c", command) cmd := util.ExecCommand(command)
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
@@ -734,7 +733,7 @@ func (t *Terminal) Loop() {
<-t.startChan <-t.startChan
{ // Late initialization { // Late initialization
intChan := make(chan os.Signal, 1) intChan := make(chan os.Signal, 1)
signal.Notify(intChan, os.Interrupt, os.Kill) signal.Notify(intChan, os.Interrupt, os.Kill, syscall.SIGTERM)
go func() { go func() {
<-intChan <-intChan
t.reqBox.Set(reqQuit, nil) t.reqBox.Set(reqQuit, nil)

View File

@@ -5,6 +5,7 @@ import "C"
import ( import (
"os" "os"
"os/exec"
"time" "time"
"unicode/utf8" "unicode/utf8"
) )
@@ -126,3 +127,12 @@ func TrimLen(runes []rune) int {
} }
return i - j + 1 return i - j + 1
} }
// ExecCommand executes the given command with $SHELL
func ExecCommand(command string) *exec.Cmd {
shell := os.Getenv("SHELL")
if len(shell) == 0 {
shell = "sh"
}
return exec.Command(shell, "-c", command)
}

View File

@@ -893,6 +893,22 @@ class TestGoFZF < TestBase
File.unlink output rescue nil File.unlink output rescue nil
end end
def test_execute_shell
# Custom script to use as $SHELL
output = tempname + '.out'
File.unlink output rescue nil
writelines tempname, ['#!/usr/bin/env bash', "echo $1 / $2 > #{output}"]
system "chmod +x #{tempname}"
tmux.send_keys "echo foo | SHELL=#{tempname} fzf --bind 'enter:execute:{}bar'", :Enter
tmux.until { |lines| lines[-2].include? '1/1' }
tmux.send_keys :Enter
tmux.send_keys 'C-c'
assert_equal ['-c / "foo"bar'], File.readlines(output).map(&:chomp)
ensure
File.unlink output rescue nil
end
def test_cycle def test_cycle
tmux.send_keys "seq 8 | #{fzf :cycle}", :Enter tmux.send_keys "seq 8 | #{fzf :cycle}", :Enter
tmux.until { |lines| lines[-2].include? '8/8' } tmux.until { |lines| lines[-2].include? '8/8' }
@@ -1269,7 +1285,7 @@ module CompletionTest
tmux.send_keys 'C-u' tmux.send_keys 'C-u'
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab, pane: 0 tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab, pane: 0
tmux.until(1) { |lines| lines.item_count > 0 } tmux.until(1) { |lines| lines.item_count > 0 }
tmux.send_keys :Enter tmux.send_keys 'C-K', :Enter
tmux.until do |lines| tmux.until do |lines|
tmux.send_keys 'C-L' tmux.send_keys 'C-L'
lines[-1].end_with?('/tmp/fzf\ test/foobar') lines[-1].end_with?('/tmp/fzf\ test/foobar')
@@ -1339,6 +1355,20 @@ module CompletionTest
tmux.send_keys 'C-L' tmux.send_keys 'C-L'
lines[-1] == "kill #{pid}" lines[-1] == "kill #{pid}"
end end
def test_custom_completion
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
tmux.prepare
tmux.send_keys 'ls /tmp/**', :Tab, pane: 0
tmux.until(1) { |lines| lines.item_count == 11 }
tmux.send_keys :BTab, :BTab, :BTab
tmux.until(1) { |lines| lines[-2].include? '(3)' }
tmux.send_keys :Enter
tmux.until do |lines|
tmux.send_keys 'C-L'
lines[-1] == "ls /tmp 1 2"
end
end
ensure ensure
Process.kill 'KILL', pid.to_i rescue nil if pid Process.kill 'KILL', pid.to_i rescue nil if pid
end end