removed most unused stuff (bash will be the minimal config, zsh the extendet)

This commit is contained in:
pika 2025-01-05 20:04:44 +01:00
parent 33ba7df9ef
commit 38a0834f40
13 changed files with 192 additions and 1125 deletions

507
.bashrc
View file

@ -1,20 +1,14 @@
#!/bin/bash
# ─< Helper functions >─────────────────────────────────────────────────────────────────
function echo_error() { echo -e "\033[0;1;31merror:\033[0;31m\t${*}\033[0m"; }
function echo_binfo() { echo -e "\033[0;1;34mInfo:\033[0;34m\t${*}\033[0m"; }
function echo_info() { echo -e "\033[0;35m${*}\033[0m"; }
function echo_error() { echo -e "\033[0;1;31m❌ ERROR:\033[0;31m\t${*}\033[0m"; }
function echo_binfo() { echo -e "\033[0;1;34m⚠️ WARNING:\033[0;34m\t${*}\033[0m"; }
function echo_info() { echo -e "\033[0;35m INFO:${*}\033[0m"; }
# ─< check if command exists >────────────────────────────────────────────────────────────
command_exists() {
command -v "$1" >/dev/null 2>&1
}
if command_exists curl; then
alias install_pkg='bash --norc -c "$(curl -sSL https://git.k4li.de/pika/scripts/raw/branch/main/bash/snippets/install_pkg.sh)" -- '
else
echo_error "curl is not installed, universal install disabled!"
fi
# ─< Silent execution >─────────────────────────────────────────────────────────────────
silentexec() {
"$@" >/dev/null 2>&1
@ -33,15 +27,6 @@ check_root() {
fi
}
# ─< ble.sh -- https://github.com/akinomyoga/ble.sh >─────────────────────────────────────
p_blesh() {
if [[ ! -f $HOME/.local/share/blesh/ble.sh ]]; then
bash -norc -c "$(curl -sSL https://git.k4li.de/pika/scripts/raw/branch/main/bash/installs/install-blesh.sh)"
else
source "$HOME/.local/share/blesh/ble.sh" --attach=none
fi
}
_sensible.bash_() {
# Sensible Bash - An attempt at saner Bash defaults
# Maintainer: mrzool <http://mrzool.cc>
@ -151,7 +136,7 @@ _sensible.bash_() {
_defaults_() {
# ─< set keybinding mode >────────────────────────────────────────────────────────────────
set -o emacs
# set -o emacs
# set -o vim
# If set, the pattern "**" used in a pathname expansion context will
@ -165,6 +150,7 @@ _defaults_() {
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
debian_chroot=$(cat /etc/debian_chroot)
fi
# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm* | rxvt*)
@ -175,141 +161,18 @@ _defaults_() {
alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
if [ -f ~/.completions/ssh ]; then
. ~/.completions/ssh
fi
}
p_basher() {
if [[ ! -d $HOME/.basher/ ]]; then
git clone --depth=1 https://github.com/basherpm/basher.git ~/.basher
else
export PATH="$HOME/.basher/bin:$PATH"
eval "$(basher init - bash)" && silentexec $(cd $HOME/.basher/ && git pull)
fi
}
p_has() {
if ! command_exists has; then
inst_has() {
git clone https://github.com/kdabir/has.git /tmp/has && cd /tmp/has && sudo make install
}
echo_info "Installing has"
silentexec inst_has
else
tools() {
local pkgs="bash zsh git curl make cmake gcc g++ rg docker composer node npm php jre python3 go cargo"
has $pkgs
}
fi
}
# plugins() {
# ─< qfc -- https://github.com/pindexis/qfc >─────────────────────────────────────────────
# [[ -s "$HOME/.qfc/bin/qfc.sh" ]] && source "$HOME/.qfc/bin/qfc.sh"
# p_basher
# p_has
# p_blesh
# }
_color_prompt_() {
# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
xterm-color | *-256color) color_prompt=yes ;;
esac
# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
# force_color_prompt=yes
if [ -n "$force_color_prompt" ]; then
if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
# We have color support; assume it's compliant with Ecma-48
# (ISO/IEC-6429). (Lack of such support is extremely rare, and such
# a case would tend to support setf rather than setaf.)
color_prompt=yes
else
color_prompt=
if [ -f "$HOME/.bash_aliases" ]; then
. "$HOME/.bash_aliases"
fi
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
if [ -f "$HOME/.bash/ssh" ]; then
. "$HOME/.bash/ssh"
fi
unset color_prompt force_color_prompt
fi
}
_zoxide_() {
if command_exists zoxide; then
eval "$(zoxide init bash)"
fi
}
_fancy_ls_() {
# ─< colorized ls >─────────────────────────────────────────────────────────────────────────
if command_exists exa; then
alias ls="exa --icons -l --git"
alias l="exa --long --no-filesize --no-permissions --no-time --git --colour-scale --icons"
alias ll="exa --all --long --no-filesize --no-permissions --no-time --git --colour-scale --icons"
alias tree="exa --icons -l --tree"
elif command_exists lsd; then
alias ls="lsd -l -1 -h1 --almost-all --git"
alias l="lsd -1"
alias ll="lsd -1 --almost-all"
alias clearl="command clear && l"
alias tree="lsd --tree"
elif command_exists eza; then
alias ls="eza --icons --long --git"
alias l="eza --icons -l"
alias ll="eza --icons -laa"
alias tree="eza --icons -l --tree"
else
alias ls="ls --color=always -lAph"
alias l="ls --color=always -lph -w1"
alias ll="ls --color=always -lph"
fi
}
_tmux_() {
# ─< t stands for tmux >────────────────────────────────────────────────────────────────────
if command_exists tmux; then
tmux_y="-- tmux-session active! | connecting to active session --"
tmux_n="-- no tmux-session found! | creating one --"
ta() {
command tmux list-sessions >/dev/null 2>&1
if [ $? -eq 0 ]; then
if command_exists notify-send; then
notify-send "$tmux_y"
sleep 0.5
tmux attach
else
echo_info "$tmux_y"
sleep 0.5
tmux attach
fi
else
if command_exists notify-send; then
notify-send "$tmux_n"
sleep 0.5
tmux
else
echo_info "$tmux_n"
sleep 0.5
tmux
fi
fi
}
alias ts="tmux source $HOME/.tmux.conf"
fi
}
_cli_qol_() {
# ─< colored everything >───────────────────────────────────────────────────────────────────
alias ip="ip --color=always"
@ -342,50 +205,13 @@ _cli_qol_() {
else
curl -s https://ohmyposh.dev/install.sh | sudo bash -s -- -d /usr/bin/
fi
}
_games() {
alias g2048='bash --norc -c "$(curl -sSL https://git.k4li.de/pika/scripts/raw/branch/main/bash/games/2048.sh)"'
alias gwordle='bash --norc -c "$(curl -sSL https://git.k4li.de/pika/scripts/raw/branch/main/bash/games/wordle.sh)"'
}
# ─< t stands for trash(-cli) >───────────────────────────────────────────────────────────────
_trash() {
if command_exists trash; then
alias rm="trash"
alias t="trash"
elif command_exists trash-cli; then
alias rm="trash-cli"
alias t="trash-cli"
else
echo_error "-- You do not have trash or trash-cli installed! Your 'rm' will be permanent! --"
fi
}
# ─< rsync >────────────────────────────────────────────────────────────────────────────────
_rsync_() {
_rrsync() {
if command_exists find; then
numbers=0
for source in "${@:1:$#-1}"; do
numbers=$(($numbers + $(find "$source" -type f | wc -l)))
done
destination="${@: -1}"
rsync -avP --info=progress2 "${@:1:$#-1}" "$destination" | pv -lpes $numbers
else
echo_error "-- We could not find 'find'. Please install 'find' to enable the progress bar. (hit 'CTRL + C' to exit now) --"
sleep 5
rsync -avP --info=progress2 "$@"
fi
}
# ─< rsync >────────────────────────────────────────────────────────────────────────────────
if command_exists rsync; then
alias cp="_rrsync"
alias scp="rsync -avP"
alias cp="scp"
fi
}
_cat_() {
# ─< bat alias >────────────────────────────────────────────────────────────────────────────
if command_exists batcat; then
alias cat="batcat --color=always -p --paging=never"
@ -396,51 +222,17 @@ _cat_() {
alias less="bat --paging always --color=always"
alias gd="bat --diff"
fi
}
_fetches_() {
# ─< fastfetch >────────────────────────────────────────────────────────────────────────────
if command_exists fastfetch; then
alias ff="fastfetch"
alias clearff="command clear & fastfetch"
alias clearf="command clear & fastfetch"
if fastfetch --config os >/dev/null 2>&1; then
alias f="fastfetch --config os"
if command_exists trash; then
alias rm="trash"
alias t="trash"
elif command_exists trash-cli; then
alias rm="trash-cli"
alias t="trash-cli"
else
git clone https://git.k4li.de/mirrors/fastfetch.git $HOME/.local/share/fastfetch >/dev/null 2>&1
exec $SHELL
echo_error "-- You do not have trash or trash-cli installed! Your 'rm' will be permanent! --"
fi
command clear &
fastfetch
fi
}
_nmap_() {
# ─< set nmap-alias >───────────────────────────────────────────────────────────────────────
if command_exists nmap; then
alias scanvuln="sudo nmap --script vuln -vvv"
alias sv="scanvuln"
alias portscan="sudo nmap -sT"
alias ps="portscan"
fi
}
_docker_() {
# ─< d stands for docker >──────────────────────────────────────────────────────────────────
if command_exists docker; then
alias up="docker compose up"
alias down="docker compose down"
alias pull="docker compose pull"
alias d="docker"
alias dr="docker run --rm -it"
alias drs="docker compose down && docker compose up -d --remove-orphans --force-recreate"
alias ds="docker ps -a"
alias dc="docker compose"
alias appupdate="docker compose pull && docker compose up -d --force-recreate"
fi
}
_git_() {
# ─< g stands for GIT >─────────────────────────────────────────────────────────────────────
if command_exists git; then
alias g="git"
@ -460,6 +252,133 @@ _git_() {
alias lg="lazygit"
fi
fi
# ─< d stands for docker >──────────────────────────────────────────────────────────────────
if command_exists docker; then
alias up="docker compose up"
alias down="docker compose down"
alias pull="docker compose pull"
alias d="docker"
alias dr="docker run --rm -it"
alias drs="docker compose down && docker compose up -d --remove-orphans --force-recreate"
alias ds="docker ps -a"
alias dc="docker compose"
alias appupdate="docker compose pull && docker compose up -d --force-recreate"
fi
# ─< colorized ls >─────────────────────────────────────────────────────────────────────────
if command_exists exa; then
alias ls="exa --icons -l --git"
alias l="exa --long --no-filesize --no-permissions --no-time --git --colour-scale --icons"
alias ll="exa --all --long --no-filesize --no-permissions --no-time --git --colour-scale --icons"
alias tree="exa --icons -l --tree"
elif command_exists lsd; then
alias ls="lsd -l -1 -h1 --almost-all --git"
alias l="lsd -1"
alias ll="lsd -1 --almost-all"
alias clearl="command clear && l"
alias tree="lsd --tree"
elif command_exists eza; then
alias ls="eza --icons --long --git"
alias l="eza --icons -l"
alias ll="eza --icons -laa"
alias tree="eza --icons -l --tree"
else
alias ls="ls --color=always -lAph"
alias l="ls --color=always -lph -w1"
alias ll="ls --color=always -lph"
fi
# ─< t stands for tmux >────────────────────────────────────────────────────────────────────
if command_exists tmux; then
local tmux_y="$(echo '-- tmux-session active! | connecting to active session --')"
local tmux_n="$(echo '-- no tmux-session found! | creating one --')"
ta() {
# command tmux list-sessions >/dev/null 2>&1
if command tmux list-sessions >/dev/null 2>&1; then
if command_exists notify-send; then
notify-send "TMUX" "${tmux_y}"
sleep 0.5
tmux attach
else
echo_info "${tmux_y}"
sleep 0.5
tmux attach
fi
else
if command_exists notify-send; then
notify-send "TMUX" "${tmux_n}"
sleep 0.5
tmux
else
echo_info "$tmux_n"
sleep 0.5
tmux
fi
fi
}
alias ts="tmux source ~/.tmux.conf"
fi
}
_color_prompt_() {
# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
xterm-color | *-256color) color_prompt=yes ;;
esac
# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
# force_color_prompt=yes
if [ -n "$force_color_prompt" ]; then
if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
# We have color support; assume it's compliant with Ecma-48
# (ISO/IEC-6429). (Lack of such support is extremely rare, and such
# a case would tend to support setf rather than setaf.)
color_prompt=yes
else
color_prompt=
fi
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt
fi
}
_games() {
if command_exists curl; then
echo_info "Games available. Try 'alias | grep g'"
alias g2048='bash --norc -c "$(curl -sSL https://git.k4li.de/pika/scripts/raw/branch/main/bash/games/2048.sh)"'
alias gwordle='bash --norc -c "$(curl -sSL https://git.k4li.de/pika/scripts/raw/branch/main/bash/games/wordle.sh)"'
fi
}
_end() {
# ─< fastfetch >────────────────────────────────────────────────────────────────────────────
if command_exists fastfetch; then
alias ff="fastfetch"
alias clearff="command clear & fastfetch"
alias clearf="command clear & fastfetch"
if fastfetch --config os >/dev/null 2>&1; then
alias f="fastfetch --config os"
else
git clone https://git.k4li.de/mirrors/fastfetch.git $HOME/.local/share/fastfetch >/dev/null 2>&1
exec $SHELL
fi
command clear &
fastfetch
fi
if command_exists cowsay; then
alias clear='clear && cowsay -f tux "$(uptime --pretty)"'
cowsay -f tux "$(uptime --pretty)"
fi
}
# ╭────────╮
@ -471,26 +390,6 @@ _coding_() {
alias h='hugo'
alias hs='hugo server -D --noHTTPCache --disableFastRender'
fi
# ─< c stands for bin/cake >──────────────────────────────────────────────────────────────
alias cake='bin/cake'
alias c='cake'
alias cs='c server -p 1313'
# ─< VSCodium >─────────────────────────────────────────────────────────────────────────────
if command_exists codium; then
alias code="codium"
export EDITOR="codium"
fi
# ─< neovide, the best frontend for any neovim-config >───────────────────────────────────
if [ -d "$HOME/.local/share/neovide/" ]; then
if command_exists neovide; then
alias nvim='neovide --fork'
else
neovide_path=$(find "$HOME/" -name "*neovide*.appimage" 2>/dev/null)
if [ -n "$neovide_path" ]; then
alias nvim="$neovide_path --fork"
fi
fi
fi
# Function to get the IP address
get_ip() {
ip a | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d/ -f1 | head -n 1
@ -508,79 +407,65 @@ _coding_() {
}
get_packager() {
PKG=""
install=""
update=""
refresh=""
remove=""
source /etc/os-release
. /etc/os-release
case "$ID" in
ubuntu | debian)
# Debian-based
ubuntu | debian | pop | kali | zorin | rhinoh)
if command_exists nala; then
PKG="nala"
install="nala install --assume-yes"
upgrade="nala upgrade"
refresh="nala update"
clean="nala autoremove --assume-yes"
remove="nala remove"
purge="nala purge"
search="nala search"
alias update="$_sudo $refresh && $_sudo $upgrade"
alias install="$_sudo $refresh && $_sudo $install"
alias remove="$_sudo $purge"
alias search="$search"
elif command_exists apt-get; then
PKG="apt-get"
install="apt-get install --yes"
upgrade="apt-get upgrade"
refresh="apt-get update"
clean="apt-get autoremove"
remove="apt-get remove"
purge="apt-get purge"
search="apt-cache search"
alias update="$_sudo $refresh && $_sudo $upgrade"
alias install="$_sudo $refresh && $_sudo $install"
alias remove="$_sudo $purge"
alias search="$search"
alias search="nala search"
alias install="$_sudo nala install --assume-yes"
alias update="$_sudo nala update && $_sudo nala upgrade --full"
alias remove="$_sudo nala purge"
else
alias search="apt-cache search"
alias install="$_sudo apt-get install --yes"
alias update="$_sudo apt-get update && $_sudo apt-get upgrade"
alias remove="$_sudo apt-get purge"
fi
alias unbreak="$_sudo dpkg --configure -a"
;;
arch | manjaro | endevouros)
if command_exists yay; then
PKG="yay"
alias install="yay -S --noconfirm"
alias update="yay -Syu"
alias remove="yay -R"
alias search="yay -Ss"
elif command_exists paru; then
PKG="paru"
# Arch-based
arch | manjaro | endevouros | garuda)
if command_exists paru; then
alias search="paru -Ss"
alias install="paru -S --noconfirm"
alias update="paru -Syu"
alias remove="paru -R"
alias search="paru -Ss"
elif command_exists pacman; then
PKG="pacman"
elif command_exists yay; then
alias search="yay -Ss"
alias install="yay -S --noconfirm"
alias update="yay -Syu"
alias remove="yay -R"
else
alias search="$_sudo pacman -Ss"
alias install="$_sudo pacman -S --noconfirm"
alias update="$_sudo pacman -Syu"
alias remove="$_sudo pacman -R"
alias search="$_sudo pacman -Ss"
fi
;;
# RHEL-based
fedora | centos)
PKG="dnf"
alias install="dnf install --yes"
alias update="dnf update"
alias remove="dnf remove"
alias search="dnf search"
alias install="$_sudo dnf install"
alias update="$_sudo dnf update"
alias remove="$_sudo dnf remove"
;;
# openSUSE
opensuse-*)
alias search="zypper search"
alias install="$_sudo zypper install --no-confirm"
alias update="$_sudo zypper update"
alias remove="$_sudo zypper remove"
;;
# Alpine
alpine)
PKG="apk"
install="apk add"
update="apk update"
upgrade="apk upgrade"
remove="apk del"
alias install="$_sudo $install"
alias update="$_sudo $update && $_sudo $upgrade"
alias remove="$_sudo $remove"
alias install="$_sudo apk add"
alias update="$_sudo apk update && $_sudo apk upgrade"
alias remove="$_sudo apk del"
;;
esac
}
@ -589,18 +474,7 @@ get_alias() {
_defaults_
_sensible.bash_
_color_prompt_
_cli_qol_
_cat_
_trash
_nmap_
_tmux_
_docker_
_git_
_rsync_
_zoxide_
_fancy_ls_
_coding_
_fetches_
_games
}
@ -608,8 +482,7 @@ main() {
check_root
get_packager
get_alias
# plugins
# tools
# [[ ${BLE_VERSION-} ]] && ble-attach
_end
}
main

View file

@ -1,50 +0,0 @@
# qfc
Quick Command-line File Completion
![qfc](https://cloud.githubusercontent.com/assets/2557967/8640880/582cb8fe-28ff-11e5-9753-41464dda938e.gif)
qfc is a shell auto-complete alternative which features real-time multi-directories matching: It provides results while you type against files in the current directory and its sub-directories.
This is useful, to avoid the burden of writing the whole path whenever you want to `cd` or `vim` a file, which is frequent especially if you use the terminal as your IDE(The terminal is the best IDE, remember! :-) ).
## Features:
- Real-time matching: Results are displayed while you type.
- Multi-directories && Context relevant matching: if you're in a cvs(git,mercurial) managed directory, qfc will matches against your tracked(or new) files only. This is very useful to avoid 10000+ of dependency files cluttering up the results. for unmanaged dirs, qfc looks for unhidden files up to a maximum depth(set to 3).
- Enhanced Filtering/Sorting of matches.
- No dependencies.
## Requirements
- python (2.7+ or 3.0+)
- Bash-4.0+ or Zshell.
- Linux Or OSX
In OSX, it seems like Bash 3.x is the default shell which is not supported. you have to [update your Bash to 4.0+](http://apple.stackexchange.com/a/24635) or [change your shell to zshell](http://stackoverflow.com/a/1822126/1117720).
## Installation:
- `git clone https://github.com/pindexis/qfc $HOME/.qfc`
- Add the following line to your *rc (.zshrc, .bashrc, .bash_profile in OSX):
`[[ -s "$HOME/.qfc/bin/qfc.sh" ]] && source "$HOME/.qfc/bin/qfc.sh"`
## Usage:
- `Ctrl-f` : complete the word under cursor using qfc
- while qfc is open:
- `TAB`: Append the selected match to the current path.
- `ENTER`: Append the selected match to the current path and returns the result.
- `Ctrl-f`: Returns the current path.
- `Arrow keys`: Navigation between files.
## Even more Productivity:
If you're using zshell or Bash 4.3+, You can combine qfc with commands you frequently use to get one key-stroke experience. For example, I have the following lines in my .zshrc:
```
qfc_quick_command 'cd' '\C-b' 'cd $0'
qfc_quick_command 'vim' '\C-p' 'vim $0'
```
This allows me to switch directories by just pressing Ctrl-b(or editing a file by pressing Ctrl-p).
![qfc](https://cloud.githubusercontent.com/assets/2557967/8654777/78534320-2984-11e5-8684-f18709af0748.gif)
`qfc_quick_command` expects an `id`, `a shortcut`, and a command with `$0` placeholder(which will be replaced with the completion path).
It's recommended to choose a 2-5 length letters only `id`(else you may encounter issues).
Also, be careful with what keyboard shortcuts to choose(mapping some keys can prevent the terminal from working correctly).

View file

@ -1,33 +0,0 @@
#!/usr/bin/env python
import sys
import argparse
import os
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)),'..'))
from qfc import core
def parse_arguments():
parser = argparse.ArgumentParser(description="Qfc: Quick command-line file competion")
parser.add_argument("--search", type=str, help="Search string")
parser.add_argument("--stdout", type=str, help="Where to store result(defaut to stdout)")
parser.set_defaults(func=cmd_get)
args = parser.parse_args()
return args.func(args)
def cmd_get(args):
output = core.get_selected_command_or_input(args.search)
if not output:
output = ""
if args.stdout:
with open(args.stdout,'w+') as save_file:
# the newline character is to make sure 'wc -l' executes correctly
if output:
output+="\n"
save_file.write(output)
else:
print(output)
if __name__ == "__main__":
parse_arguments()

View file

@ -1,126 +0,0 @@
# default key bindings
complete_shortcut="${qfc_complete_SHORTCUT:-\C-f}"
function get_cursor_position(){
# based on a script from http://invisible-island.net/xterm/xterm.faq.html
exec < /dev/tty
oldstty=$(stty -g)
stty raw -echo min 0
# on my system, the following line can be replaced by the line below it
echo -en "\033[6n" > /dev/tty
# tput u7 > /dev/tty # when TERM=xterm (and relatives)
IFS=';' read -r -d R row col
stty $oldstty
# change from one-based to zero based so they work with: tput cup $row $col
row=$((${row:2} - 1)) # strip off the esc-[
col=$((${col} - 1))
echo "$row $col"
}
if [[ -d ~/.qfc/ ]]; then
export PATH=~/.qfc/bin:"${PATH}"
fi
if [[ -n "$ZSH_VERSION" ]]; then
# zshell
function qfc_complete {
# Add a letter and remove it from the buffer.
# when using zsh autocomplete(pressing Tab), then running qfc, the BUFFER(qfc input) won't contain the trailing forward slash(which should happen when using zsh autocomplete for directories).
# pressing a character then removing it makes sure that BUFFER contains what you see on the screen.
BUFFER=${BUFFER}'a'
BUFFER=${BUFFER[0,-2]}
# get the cursor offset within the user input
offset=${CURSOR}
zle beginning-of-line
# get the offset from the start of comandline prompt
col=$(echo $(get_cursor_position) | cut -f 2 -d " ")
# place the cursor at the next line
</dev/tty echo ''
# get the word under cursor
word=${BUFFER[0,offset]}
word=${word##* }
# instruct qfc to store the result (completion path) into a temporary file
tmp_file=$(mktemp -t qfc.XXXXXXX)
</dev/tty qfc --search="$word" --stdout="$tmp_file"
result=$(<$tmp_file)
rm -f $tmp_file
# append the completion path to the user buffer
word_length=${#word}
result_length=${#result}
BUFFER=${BUFFER[1,$((offset-word_length))]}${result}${BUFFER[$((offset+word_length)),-1]}
let "offset = offset - word_length + result_length"
# reset the absolute and relative cursor position, note that it's necessary to get row position after qfc is run, because it may be changed during qfc execution
row=$(echo $(get_cursor_position) | cut -f 1 -d " ")
tput cup $(($row - 1)) $col
CURSOR=${offset}
}
zle -N qfc_complete
bindkey "$complete_shortcut" qfc_complete
function qfc_quick_command(){
if [[ ! -z $1 ]] && [[ ! -z $2 ]] && [[ ! -z $3 ]]; then
func_name='quick_'$1
eval $"function $func_name(){
zle kill-whole-line
qfc_complete
if [[ ! -z \${BUFFER} ]]; then
c='$3'
BUFFER=\${c//'\$0'/\$BUFFER}
zle accept-line
fi
}"
zle -N $func_name
bindkey "$2" $func_name
fi
}
elif [[ -n "$BASH" ]]; then
function qfc_complete {
# pretty similar to zsh flow
offset=${READLINE_POINT}
READLINE_POINT=0
col=$(get_cursor_position | cut -f 2 -d " ")
word=${READLINE_LINE:0:offset}
word=${word##* }
tmp_file=$(mktemp -t qfc.XXXXXXX)
</dev/tty qfc --search="$word" --stdout="$tmp_file"
result=$(<$tmp_file)
rm -f $tmp_file
word_length=${#word}
result_length=${#result}
READLINE_LINE=${READLINE_LINE:0:$((offset-word_length))}${result}${READLINE_LINE:$((offset))}
offset=$(($offset - $word_length + $result_length))
row=$(get_cursor_position | cut -f 1 -d " ")
tput cup $row $col
READLINE_POINT=${offset}
}
bind -x '"'"$complete_shortcut"'":"qfc_complete"'
function qfc_quick_command {
if [[ ! -z $1 ]] && [[ ! -z $2 ]] && [[ ! -z $3 ]]; then
func_name='quick_'$1
eval $"function $func_name(){
READLINE_LINE=''
qfc_complete
if [[ ! -z \${READLINE_LINE} ]]; then
c='$3'
READLINE_LINE=\${c//'\$0'/\$READLINE_LINE}
fi
}"
bind -x '"\e-'"$1"'":"'"${func_name}"'"'
bind '"'"$2"'":""\e-'"$1"'\n"'
fi
}
fi

View file

View file

@ -1,60 +0,0 @@
import sys
BOLD = "\x1b[1m"
CLEAR_FORMATTING = "\x1b[0m"
ERASE_SCREEN = "\x1b[J"
ERASE_LINE = "\x1b[2K"
FOREGROUND_BLACK = "\x1b[30m"
BACKGROUND_WHITE = "\x1b[47m"
def _CURSOR_COLUMN(pos):
# ideally, CSI n G escape code is used to set the absolute horizental position
# Sadly, it's not an Ansi.sys escape code (not supported in all terminals)
# This shim try to simulate it by moving cursor backwards 1000 characters(terminal row width is assumed to be less than that number, which may not be the case for aliens laptops :))
# Then, move cursor pos - 1 characthers forward (the - 1 is because the cursor is at position 1)
c = "\x1b[1000D"
if pos:
c += "\x1b["+str(pos - 1)+"C"
return c
def _CURSOR_PREVIOUS_LINES(number):
return "\x1b["+str(number)+"A"
def _CURSOR_NEXT_LINES(number):
return "\x1b["+str(number)+"B"
def select_text(text):
return (FOREGROUND_BLACK +
BACKGROUND_WHITE +
text.replace(
CLEAR_FORMATTING,
CLEAR_FORMATTING + FOREGROUND_BLACK + BACKGROUND_WHITE)+
CLEAR_FORMATTING)
def bold_text(text):
return (BOLD +
text.replace(
CLEAR_FORMATTING,
CLEAR_FORMATTING + BOLD)+
CLEAR_FORMATTING)
def move_cursor_line_beggining():
sys.stdout.write(_CURSOR_COLUMN(0))
def move_cursor_horizental(n):
sys.stdout.write(_CURSOR_COLUMN(n))
def move_cursor_previous_lines(number_of_lines):
sys.stdout.write(_CURSOR_PREVIOUS_LINES(number_of_lines))
def move_cursor_next_lines(number_of_lines):
sys.stdout.write(_CURSOR_NEXT_LINES(number_of_lines))
def erase_from_cursor_to_end():
sys.stdout.write(ERASE_SCREEN)
def erase_line():
sys.stdout.write(ERASE_LINE)
def flush():
sys.stdout.flush()

View file

@ -1,231 +0,0 @@
import os
import re
from . import keys
from . import ui
from . import readchar
from . import dirhandler
def get_selected_command_or_input(search):
state = State(search)
# draw the screen (prompt + matched strings)
ui.refresh(state)
# wait for user input
prompt(state)
# clear the screen
ui.erase()
# state.input holds the path selected by the user
return state.input
def prompt(state):
while True:
c = readchar.get_symbol()
if c == keys.ENTER:
if state.get_matches():
state.append_match_to_input()
break
elif c == keys.CTRL_F:
break
elif c == keys.TAB:
if state.get_matches():
state.append_match_to_input()
elif c == keys.CTRL_C or c == keys.ESC:
state.reset_input()
break
elif c == keys.CTRL_U:
state.clear_input()
elif c == keys.BACKSPACE:
state.set_input(state.input[0:-1])
elif c == keys.UP or c == keys.CTRL_K:
state.select_previous()
elif c == keys.DOWN or c == keys.CTRL_J:
state.select_next()
elif c == keys.LEFT or c == keys.CTRL_H:
state.go_back()
elif c == keys.RIGHT or c == keys.CTRL_L:
if state.get_matches():
state.append_match_to_input()
elif c < 256 and c >= 32:
state.set_input(state.input + chr(c))
ui.refresh(state)
class State(object):
''' The Current User state, including user written characters, matched commands, and selected one '''
def __init__(self, default_input):
self._selected_command_index = 0
self.matches = []
self.default_input = default_input
self.set_input(default_input)
def get_matches(self):
return self.matches
def reset_input(self):
self.input = self.default_input
def set_input(self, input):
self.input = input if input else ""
self._update()
def append_match_to_input(self):
self.set_input(os.path.join(os.path.dirname(self.input), self.get_selected_match()))
def go_back(self):
isdir = is_dir(self.input)
input_stripped = self.input.rstrip(os.sep)
if not input_stripped:
return
input_splitted = input_stripped.split(os.sep)
entry_name = input_splitted[-1]
if isdir:
entry_name += os.sep
new_input = os.sep.join(input_splitted[0:-1])
if new_input:
new_input += os.sep
self.set_input(new_input)
self.set_selected_entry(entry_name)
def clear_input(self):
self.set_input("")
def clear_selection(self):
self._selected_command_index = 0
def select_next(self):
self._selected_command_index = (self._selected_command_index + 1) % len(self.matches) if len(self.matches) else 0
def select_previous(self):
self._selected_command_index = (self._selected_command_index - 1) % len(self.matches) if len(self.matches) else 0
def _update(self):
self.matches = get_matches(os.getcwd(),self.input)
self._selected_command_index = 0
def get_output(self):
return self.input
def get_selected_match(self):
if len(self.matches):
return self.matches[self._selected_command_index]
else:
raise Exception('No matches found')
def set_selected_entry(self, entry):
if not (entry in self.matches):
return
self._selected_command_index = self.matches.index(entry)
def get_matches(root_dir, user_input):
start_dir = join_paths(root_dir, os.path.dirname(user_input))
search_str = os.path.basename(user_input)
if not os.path.isdir(start_dir):
return []
source_files = dirhandler.get_source_files(start_dir)
filtered_files = filter_files(source_files, search_str)
sorted_files = sort_matches(filtered_files, search_str)
return sorted_files
def filter_files(files, search_str):
""" Filter a list of files based on a search string
:param files: list of files to filter (ie ['/a','/a/b', '/a/b/c', '/b']), the order doesn't matter. 'a' and 'a/' are considered different.
:param search_str, the filtering string (ie 'a')
This function return only first level files/dirs that match the given search string. That is, filtering ['/a','/a/b','/a/b/c'] with the string 'a' returns only ['/a'],
This is to avoid polluting the screen with all files inside a directory when a user look for that directory
"""
matched = set()
if not search_str:
for f in files:
# right strip to the first seperator(ie '/')
f = f[:_index_or_len(f, os.sep)+1]
matched.add(f)
else:
search_str = search_str.lower()
for f in files:
if search_str in f.lower():
index = f.lower().index(search_str)
trail = f[index:]
f = f[:index + _index_or_len(trail, os.sep)+1]
matched.add(f)
return matched
def sort_matches(matches, string):
""" Sort paths according to a string """
files = sorted(matches, key=lambda s: s.lower())
return sorted(files, key=lambda p:get_weight(p, string))
def get_weight(path, string):
""" calculate how much a path matches a given string on a scale of 0->10000, a lower number means a better match.
The string should be present in the path, or this function will fail
The weight is calculated using the following formula:
0 00 0
| | |
Number of elements in the path <----------------| | |--------------------------> is a directory(0) or not(1)
(ie 2 for 'aa/bb' and 3 for 'aa/bb/cc') |
|
v
There is a word in the matched path element that exactly match the given string? ('aa_bb' matches user input 'aa' but doesn't match 'a')
|
No <--------------------|-----------------------------> yes
| |
| |
| |
v v
path element starts with the user input? There is only one word in the matched path element ('aa' but not 'aa_bb'),
| which means the path elements exactly match the string
| |
No <-------|-----Yes(2) No -----|----- Yes (0)
| |
v v
index of the string in the path element + 10 (index of the matched word within path element words + 1)
Maximums are added to make sure things doesn't overlap
"""
weight = 0
if not is_dir(path):
weight += 1
string = string.lower()
p = path.rstrip(os.sep).lower()
path_elems = p.split(os.sep)
weight += min(len(path_elems) * 1000, 10000) # Max 10 elements in path(set it to 10 for longer paths)
if not string:
return weight
elm = next(e for e in path_elems if string in e)
elm_words = [_f for _f in re.split('[\W_]+', elm) if _f]
if string in elm_words:
if len(elm_words) > 1:
weight += 10 * (min(elm_words.index(string),8) + 1) # Max 8 words per path entry
else:
if elm.startswith(string):
weight += 10 * 2
else:
weight += 10 * (10 + min(elm.index(string), 89)) # Max path element with 89 characters
return weight
# Helper functions:
def join_paths(p1, p2):
p1 = (os.path.expandvars(os.path.expanduser(p1)))
p2 = (os.path.expandvars(os.path.expanduser(p2)))
return os.path.normpath(os.path.join(p1, p2))
def _index_or_len(s, c):
if c in s:
return s.index(c)
else:
return len(s)
def is_dir(p):
if p.endswith(os.sep):
return True
return False

View file

@ -1,114 +0,0 @@
import os
import subprocess
import sys
class CVSHandler():
""" Handler of CVS (fir, mercurial...) directories,
The main purpose of this class is to cache external cvs command output, and determine the appropriate files to yield when navigating to a subdirectory of a project.
This basically means that the external command is run once (ie git ls-files), cached, and when calling get_source_files on a subdirectory of the project root (ie project-root/subdir),
filtering from all project files of is done here.
"""
def __init__(self, cvs):
self._roots_cache = {}
self._not_tracked_cache = set()
self.cvs = cvs
def _get_root_from_cache(self, directory):
""" a directory is considered cached if it's the project root or a subdirectory of that project root.
returns the project root dir, or None if the directory is not cached.
"""
if directory in self._roots_cache:
return directory
if os.path.dirname(directory) == directory:
return None
return self._get_root_from_cache(os.path.dirname(directory))
def get_source_files(self, directory):
if directory in self._not_tracked_cache:
return None
root_dir = self._get_root_from_cache(directory)
if not root_dir:
try:
# check if it's a tracked cvs dir, if yes, get the project root and the source files
root_dir = self.cvs._get_root(directory)
self._roots_cache[root_dir] = self.cvs._get_tracked_files(root_dir)
except Exception as e:
# not a cvs tracked dir, save it to not issue that command again
self._not_tracked_cache.add(directory)
return None
files = self._roots_cache[root_dir]
# the passed directory argument is a subdirectory of the project root
if directory != root_dir:
rel_dir = os.path.relpath(directory, root_dir)
files = [f[len(rel_dir)+1:] for f in files if f.startswith(rel_dir)]
return files
class Git():
@staticmethod
def _get_root(directory):
return run_command("cd %s && git rev-parse --show-toplevel" % directory).strip()
@staticmethod
def _get_tracked_files(directory):
return run_command("cd %s && git ls-files && git ls-files --others --exclude-standard" % directory).strip().split('\n')
class Mercurial():
@staticmethod
def _get_root(directory):
return run_command("cd %s && hg root" % directory).strip()
@staticmethod
def _get_tracked_files(directory):
return run_command("cd %s && (hg status -marcu | cut -d' ' -f2)" % directory).strip().split('\n')
class DefaultDirHandler():
""" The default directory handler uses the 'find' external program to return all the files inside a given directory up to MAX_depth depth (ie, if maxdepth=2, returns all files inside that dir, and all files in a subdir of that directory)"""
def __init__(self):
self._cache = {}
self.MAX_DEPTH = 3
def _walk_down(self, start_dir):
try:
out = run_command("find %s -maxdepth %s -type f -not -path '*/\.*'" % (start_dir, self.MAX_DEPTH))
except subprocess.CalledProcessError as e:
# Find returns a non 0 exit status if listing a directory fails (for example, permission denied), but still output all files in other dirs
# ignore those failed directories.
out = e.output
if sys.version_info >= (3, 0):
out = out.decode('utf-8')
if not out:
return []
files = out.split('\n')
return [os.path.relpath(f, start_dir) for f in files if f]
def get_source_files(self, start_dir):
if not start_dir in self._cache:
self._cache[start_dir] = self._walk_down(start_dir)
return self._cache[start_dir]
def run_command(string):
''' fork a process to execute the command string given as argument, returning the string written to STDOUT '''
DEVNULL = open(os.devnull, 'wb')
out = subprocess.check_output(string, stderr=DEVNULL, shell=True)
if sys.version_info >= (3, 0):
return out.decode('utf-8')
return out
git = CVSHandler(Git)
hg = CVSHandler(Mercurial)
default = DefaultDirHandler()
def get_source_files(directory):
""" check first if the given directory is inside a git tracked project, if no, check with mercurial, if no, fallback to the default handler """
files = git.get_source_files(directory)
# if the returned files list is empty, it's considered not a tracked directory
if files:
return files
files = hg.get_source_files(directory)
if files:
return files
return default.get_source_files(directory)

View file

@ -1,19 +0,0 @@
CTRL_C = 3 # Ctrl-c
CTRL_H = 8 # ctrl-h
CTRL_J = 10 # ctrl-h
CTRL_K = 11 # ctrl-h
CTRL_L = 12 # ctrl-h
CTRL_F = 6 # ctrl-f
ENTER = 13 # Enter
CTRL_U = 21 # Ctrl+u
ESC = 27 # Escape
BACKSPACE = 127 # Backspace
TAB = 9 # Tab
RIGHT = -1 # FAKE CODE to abstract away the fact that a multibyte string is needed to represent arrow keys
DOWN = -2 # same
UP = -3 # same
LEFT = -4 # same
SHIFTTAB = -5 # same
SHIFTENTER = -6
SPACE = 32
ANTISLASH = 47

View file

@ -1,65 +0,0 @@
import sys
import tty
import termios
import fcntl
import os
from . import keys
def get_symbol():
''' Read a symbol, which can be a single byte character or a multibyte string'''
ch = read_char()
ch_code = ord(ch)
# check for multibyte string
if ch_code == keys.ESC:
ch = read_char_no_blocking()
if ch == '':
# ESC key pressed
return keys.ESC
elif ch != 'O' and ch != '[':
return ord(ch)
else:
ch = read_char_no_blocking()
if ch == 'A':
return keys.UP
elif ch == 'B':
return keys.DOWN
elif ch == 'C':
return keys.RIGHT
elif ch == 'D':
return keys.LEFT
elif ch == 'Z':
return keys.SHIFTTAB
return ch_code
def read_char():
''' Read a character '''
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd, termios.TCSADRAIN)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
def read_char_no_blocking():
''' Read a character in nonblocking mode, if no characters are present in the buffer, return an empty string '''
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
old_flags = fcntl.fcntl(fd, fcntl.F_GETFL)
try:
tty.setraw(fd, termios.TCSADRAIN)
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags | os.O_NONBLOCK)
return sys.stdin.read(1)
except IOError as e:
ErrorNumber = e[0]
# IOError with ErrorNumber 11(35 in Mac) is thrown when there is nothing to read(Resource temporarily unavailable)
if (sys.platform.startswith("linux") and ErrorNumber != 11) or (sys.platform == "darwin" and ErrorNumber != 35):
raise
return ""
finally:
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags)
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

View file

@ -1,60 +0,0 @@
from . import ansi
import os
import math
def _get_terminal_columns():
''' get the number of terminal columns, used to determine spanned lines of a mark(reqansired for cursor placement) '''
_, columns = os.popen('stty size', 'r').read().split()
return int(columns)
def erase():
''' the commandline cursor is always at the first line (user prompt)
Therefore, erasing the current and following lines clear all output
'''
ansi.move_cursor_line_beggining()
ansi.erase_from_cursor_to_end()
def refresh(state):
''' Redraw the output, this function will be triggered on every user interaction(key pressed)'''
erase()
lines, num_rows = _construct_output(state)
for line in lines:
print(line)
# go up
ansi.move_cursor_previous_lines(num_rows)
# palce the cursor at the end of first line)
ansi.move_cursor_horizental(len(lines[0])+1)
ansi.flush()
def _construct_output(state):
columns = _get_terminal_columns()
def number_of_rows(line):
return int(math.ceil(float(len(line))/columns))
displayed_lines = []
# Number of terminal rows spanned by the output, used to determine how many lines we need to go up(to place the cursor after the prompt) after displaying the output
num_rows = 0
prompt_line = 'Path: ' + state.input
displayed_lines.append(prompt_line)
num_rows += number_of_rows(prompt_line)
matches = state.get_matches()
if matches:
# display commands from Max(0,selected_command_index - 10 +1 ) to Max(10,SelectedCommandIndex + 1)
selected_command_index = matches.index(state.get_selected_match())
matches_to_display = matches[max(0, selected_command_index - 10 + 1):max(10, selected_command_index + 1)]
for index, m in enumerate(matches_to_display):
fm = ' ' + m
num_rows += number_of_rows(fm)
# Formatting text(make searched word bold)
for w in state.input.split(' '):
if w:
fm = fm.replace(w, ansi.bold_text(w))
# highlighting selected command
if m == state.get_selected_match():
fm = ansi.select_text(fm)
displayed_lines.append(fm)
else:
not_found_line = 'Nothing found'
displayed_lines.append(not_found_line)
num_rows += number_of_rows(not_found_line)
return displayed_lines, num_rows

View file

@ -1,48 +0,0 @@
import sys
import os
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)),'..'))
from qfc.core import filter_files, get_weight
def _equals(marks_list1, marks_list2):
l1 = sorted(marks_list1)
l2 = sorted(marks_list2)
if len(l1) != len(l2):
return False
for i,_ in enumerate(l1):
if l1[i] != l2[i]:
return False
return True
def test_filter_files():
files = [
'/',
'/a/',
'/b/',
'/a/b',
'/a/b/c',
'/b/a/',
'/b/a/c',
'd',
'da'
]
assert(_equals(filter_files(files,''), ['/','d','da']))
assert(_equals(filter_files(files,'/'), ['/']))
assert(_equals(filter_files(files,'a'), ['/a/', '/b/a/', 'da']))
def test_weight():
assert(get_weight('a','') == 1001)
assert(get_weight('a/','') == 1000)
assert(get_weight('a/b/','') == 2000)
assert(get_weight('a/b/c','') == 3001)
assert(get_weight('a','a') == 1001)
assert(get_weight('ab','a') == 1021)
assert(get_weight('bab','a') == 1111)
assert(get_weight('a_b','a') == 1011)
assert(get_weight('root/a_b','a') == 2011)
assert(get_weight('root/a_b_c_d_e_f_g_h_i_j_k','k') == 2091)
assert(get_weight('a/b/c/d/e/f/g/h/i/j/k','k') == 10001)
assert(get_weight('a/B/','b') == 2000)