diff --git a/.completions/ssh b/.bash/ssh similarity index 100% rename from .completions/ssh rename to .bash/ssh diff --git a/.bashrc b/.bashrc index 98a777a..1593cae 100644 --- a/.bashrc +++ b/.bashrc @@ -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 @@ -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 + if [ -f "$HOME/.bash_aliases" ]; then + . "$HOME/.bash_aliases" fi - if [ -f ~/.completions/ssh ]; then - . ~/.completions/ssh + + if [ -f "$HOME/.bash/ssh" ]; then + . "$HOME/.bash/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= - 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 -} - -_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" - else - git clone https://git.k4li.de/mirrors/fastfetch.git $HOME/.local/share/fastfetch >/dev/null 2>&1 - exec $SHELL - fi - command clear & - fastfetch + 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 -} -_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 diff --git a/.qfc/README.md b/.qfc/README.md deleted file mode 100644 index 5c56a1b..0000000 --- a/.qfc/README.md +++ /dev/null @@ -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). diff --git a/.qfc/bin/qfc b/.qfc/bin/qfc deleted file mode 100755 index bc47296..0000000 --- a/.qfc/bin/qfc +++ /dev/null @@ -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() diff --git a/.qfc/bin/qfc.sh b/.qfc/bin/qfc.sh deleted file mode 100755 index f6b361e..0000000 --- a/.qfc/bin/qfc.sh +++ /dev/null @@ -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 - = 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 diff --git a/.qfc/qfc/dirhandler.py b/.qfc/qfc/dirhandler.py deleted file mode 100644 index a08d67f..0000000 --- a/.qfc/qfc/dirhandler.py +++ /dev/null @@ -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) - diff --git a/.qfc/qfc/keys.py b/.qfc/qfc/keys.py deleted file mode 100644 index 0568314..0000000 --- a/.qfc/qfc/keys.py +++ /dev/null @@ -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 diff --git a/.qfc/qfc/readchar.py b/.qfc/qfc/readchar.py deleted file mode 100644 index d6c81a7..0000000 --- a/.qfc/qfc/readchar.py +++ /dev/null @@ -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) diff --git a/.qfc/qfc/ui.py b/.qfc/qfc/ui.py deleted file mode 100644 index c2a24ef..0000000 --- a/.qfc/qfc/ui.py +++ /dev/null @@ -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 - diff --git a/.qfc/tests/test_match.py b/.qfc/tests/test_match.py deleted file mode 100644 index 6759534..0000000 --- a/.qfc/tests/test_match.py +++ /dev/null @@ -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) - -