├── k.plugin.zsh ├── k.sh └── readme.md /k.plugin.zsh: -------------------------------------------------------------------------------- 1 | k.sh -------------------------------------------------------------------------------- /k.sh: -------------------------------------------------------------------------------- 1 | zmodload zsh/datetime 2 | zmodload -F zsh/stat b:zstat 3 | 4 | k () { 5 | # ---------------------------------------------------------------------------- 6 | # Setup 7 | # ---------------------------------------------------------------------------- 8 | 9 | # Stop stat failing when a directory contains either no files or no hidden files 10 | # Track if we _accidentally_ create a new global variable 11 | setopt local_options null_glob typeset_silent no_auto_pushd nomarkdirs 12 | 13 | # Process options and get files/directories 14 | typeset -a o_all o_almost_all o_human o_si o_directory o_group_directories \ 15 | o_no_directory o_no_vcs o_sort o_sort_reverse o_help 16 | zparseopts -E -D \ 17 | a=o_all -all=o_all \ 18 | A=o_almost_all -almost-all=o_almost_all \ 19 | c=o_sort \ 20 | d=o_directory -directory=o_directory \ 21 | -group-directories-first=o_group_directories \ 22 | h=o_human -human=o_human \ 23 | -si=o_si \ 24 | n=o_no_directory -no-directory=o_no_directory \ 25 | -no-vcs=o_no_vcs \ 26 | r=o_sort_reverse -reverse=o_sort_reverse \ 27 | -sort:=o_sort \ 28 | S=o_sort \ 29 | t=o_sort \ 30 | u=o_sort \ 31 | U=o_sort \ 32 | -help=o_help 33 | 34 | # Print Help if bad usage, or they asked for it 35 | if [[ $? != 0 || "$o_help" != "" ]] 36 | then 37 | print -u2 "Usage: k [options] DIR" 38 | print -u2 "Options:" 39 | print -u2 "\t-a --all list entries starting with ." 40 | print -u2 "\t-A --almost-all list all except . and .." 41 | print -u2 "\t-c sort by ctime (inode change time)" 42 | print -u2 "\t-d --directory list only directories" 43 | print -u2 "\t-n --no-directory do not list directories" 44 | print -u2 "\t-h --human show filesizes in human-readable format" 45 | print -u2 "\t --si with -h, use powers of 1000 not 1024" 46 | print -u2 "\t-r --reverse reverse sort order" 47 | print -u2 "\t-S sort by size" 48 | print -u2 "\t-t sort by time (modification time)" 49 | print -u2 "\t-u sort by atime (use or access time)" 50 | print -u2 "\t-U Unsorted" 51 | print -u2 "\t --sort WORD sort by WORD: none (U), size (S)," 52 | print -u2 "\t time (t), ctime or status (c)," 53 | print -u2 "\t atime or access or use (u)" 54 | print -u2 "\t --no-vcs do not get VCS status (much faster)" 55 | print -u2 "\t --help show this help" 56 | return 1 57 | fi 58 | 59 | # Check for conflicts 60 | if [[ "$o_directory" != "" && "$o_no_directory" != "" ]]; then 61 | print -u2 "$o_directory and $o_no_directory cannot be used together" 62 | return 1 63 | fi 64 | 65 | # case is like a mnemonic for sort order: 66 | # lower-case for standard, upper-case for descending 67 | local S_ORD="o" R_ORD="O" SPEC="n" # default: by name 68 | 69 | # translate ls options to glob-qualifiers, 70 | # ignoring "--sort" prefix of long-args form 71 | case ${o_sort:#--sort} in 72 | -U|none) SPEC="N";; 73 | -t|time) SPEC="m";; 74 | -c|ctime|status) SPEC="c";; 75 | -u|atime|access|use) SPEC="a";; 76 | # reverse default order for sort by size 77 | -S|size) S_ORD="O" R_ORD="o" SPEC="L";; 78 | esac 79 | 80 | if [[ "$o_sort_reverse" == "" ]]; then 81 | typeset SORT_GLOB="${S_ORD}${SPEC}" 82 | else 83 | typeset SORT_GLOB="${R_ORD}${SPEC}" 84 | fi 85 | if [[ "$o_group_directories" != "" ]]; then 86 | SORT_GLOB="oe:[[ -d \$REPLY ]];REPLY=\$?:$SORT_GLOB" 87 | fi 88 | 89 | # Check which numfmt available (if any), warn user if not available 90 | typeset -i numfmt_available=0 91 | typeset -i gnumfmt_available=0 92 | if [[ "$o_human" != "" ]]; then 93 | if [[ $+commands[numfmt] == 1 ]]; then 94 | numfmt_available=1 95 | elif [[ $+commands[gnumfmt] == 1 ]]; then 96 | gnumfmt_available=1 97 | else 98 | print -u2 "'numfmt' or 'gnumfmt' command not found, human readable output will not work." 99 | print -u2 "\tFalling back to normal file size output" 100 | # Set o_human to off 101 | o_human="" 102 | fi 103 | fi 104 | 105 | # Create numfmt local function 106 | numfmt_local () { 107 | if [[ "$o_si" != "" ]]; then 108 | if (( $numfmt_available )); then 109 | numfmt --to=si $1 110 | elif (( $gnumfmt_available )); then 111 | gnumfmt --to=si $1 112 | fi 113 | else 114 | if (( $numfmt_available )); then 115 | numfmt --to=iec $1 116 | elif (( $gnumfmt_available )); then 117 | gnumfmt --to=iec $1 118 | fi 119 | fi 120 | } 121 | 122 | # Set if we're in a repo or not 123 | typeset -i INSIDE_WORK_TREE=0 124 | if [[ $(command git rev-parse --is-inside-work-tree 2>/dev/null) == true ]]; then 125 | INSIDE_WORK_TREE=1 126 | fi 127 | 128 | # Setup array of directories to print 129 | typeset -a base_dirs 130 | typeset base_dir 131 | 132 | if [[ "$@" == "" ]]; then 133 | base_dirs=. 134 | else 135 | base_dirs=($@) 136 | fi 137 | 138 | 139 | # Colors 140 | # ---------------------------------------------------------------------------- 141 | # default colors 142 | K_COLOR_DI="0;34" # di:directory 143 | K_COLOR_LN="0;35" # ln:symlink 144 | K_COLOR_SO="0;32" # so:socket 145 | K_COLOR_PI="0;33" # pi:pipe 146 | K_COLOR_EX="0;31" # ex:executable 147 | K_COLOR_BD="34;46" # bd:block special 148 | K_COLOR_CD="34;43" # cd:character special 149 | K_COLOR_SU="30;41" # su:executable with setuid bit set 150 | K_COLOR_SG="30;46" # sg:executable with setgid bit set 151 | K_COLOR_TW="30;42" # tw:directory writable to others, with sticky bit 152 | K_COLOR_OW="30;43" # ow:directory writable to others, without sticky bit 153 | K_COLOR_BR="0;30" # branch 154 | 155 | # read colors if osx and $LSCOLORS is defined 156 | if [[ $(uname) == 'Darwin' && -n $LSCOLORS ]]; then 157 | # Translate OSX/BSD's LSCOLORS so we can use the same here 158 | K_COLOR_DI=$(_k_bsd_to_ansi $LSCOLORS[1] $LSCOLORS[2]) 159 | K_COLOR_LN=$(_k_bsd_to_ansi $LSCOLORS[3] $LSCOLORS[4]) 160 | K_COLOR_SO=$(_k_bsd_to_ansi $LSCOLORS[5] $LSCOLORS[6]) 161 | K_COLOR_PI=$(_k_bsd_to_ansi $LSCOLORS[7] $LSCOLORS[8]) 162 | K_COLOR_EX=$(_k_bsd_to_ansi $LSCOLORS[9] $LSCOLORS[10]) 163 | K_COLOR_BD=$(_k_bsd_to_ansi $LSCOLORS[11] $LSCOLORS[12]) 164 | K_COLOR_CD=$(_k_bsd_to_ansi $LSCOLORS[13] $LSCOLORS[14]) 165 | K_COLOR_SU=$(_k_bsd_to_ansi $LSCOLORS[15] $LSCOLORS[16]) 166 | K_COLOR_SG=$(_k_bsd_to_ansi $LSCOLORS[17] $LSCOLORS[18]) 167 | K_COLOR_TW=$(_k_bsd_to_ansi $LSCOLORS[19] $LSCOLORS[20]) 168 | K_COLOR_OW=$(_k_bsd_to_ansi $LSCOLORS[21] $LSCOLORS[22]) 169 | fi 170 | 171 | # read colors if linux and $LS_COLORS is defined 172 | # if [[ $(uname) == 'Linux' && -n $LS_COLORS ]]; then 173 | 174 | # fi 175 | 176 | # ---------------------------------------------------------------------------- 177 | # Loop over passed directories and files to display 178 | # ---------------------------------------------------------------------------- 179 | for base_dir in $base_dirs 180 | do 181 | # ---------------------------------------------------------------------------- 182 | # Display name if multiple paths were passed 183 | # ---------------------------------------------------------------------------- 184 | if [[ "$#base_dirs" > 1 ]]; then 185 | # Only add a newline if its not the first iteration 186 | if [[ "$base_dir" != "${base_dirs[1]}" ]]; then 187 | print 188 | fi 189 | print -r "${base_dir}:" 190 | fi 191 | # ---------------------------------------------------------------------------- 192 | # Vars 193 | # ---------------------------------------------------------------------------- 194 | 195 | typeset -a MAX_LEN A RESULTS STAT_RESULTS 196 | typeset TOTAL_BLOCKS 197 | 198 | # Get now 199 | typeset K_EPOCH="${EPOCHSECONDS:?}" 200 | 201 | typeset -i TOTAL_BLOCKS=0 202 | 203 | MAX_LEN=(0 0 0 0 0 0) 204 | 205 | # Array to hold results from `stat` call 206 | RESULTS=() 207 | 208 | # only set once per directory so must be out of the main loop 209 | typeset -i IS_GIT_REPO=0 210 | typeset GIT_TOPLEVEL 211 | 212 | typeset -i LARGE_FILE_COLOR=196 213 | typeset -a SIZELIMITS_TO_COLOR 214 | SIZELIMITS_TO_COLOR=( 215 | 1024 46 # <= 1kb 216 | 2048 82 # <= 2kb 217 | 3072 118 # <= 3kb 218 | 5120 154 # <= 5kb 219 | 10240 190 # <= 10kb 220 | 20480 226 # <= 20kb 221 | 40960 220 # <= 40kb 222 | 102400 214 # <= 100kb 223 | 262144 208 # <= 0.25mb || 256kb 224 | 524288 202 # <= 0.5mb || 512kb 225 | ) 226 | typeset -i ANCIENT_TIME_COLOR=236 # > more than 2 years old 227 | typeset -a FILEAGES_TO_COLOR 228 | FILEAGES_TO_COLOR=( 229 | 0 196 # < in the future, #spooky 230 | 60 255 # < less than a min old 231 | 3600 252 # < less than an hour old 232 | 86400 250 # < less than 1 day old 233 | 604800 244 # < less than 1 week old 234 | 2419200 244 # < less than 28 days (4 weeks) old 235 | 15724800 242 # < less than 26 weeks (6 months) old 236 | 31449600 240 # < less than 1 year old 237 | 62899200 238 # < less than 2 years old 238 | ) 239 | 240 | # ---------------------------------------------------------------------------- 241 | # Build up list of files/directories to show 242 | # ---------------------------------------------------------------------------- 243 | 244 | typeset -a show_list 245 | show_list=() 246 | 247 | # Check if it even exists 248 | if [[ ! -e $base_dir ]]; then 249 | print -u2 "k: cannot access $base_dir: No such file or directory" 250 | 251 | # If its just a file, skip the directory handling 252 | elif [[ -f $base_dir ]]; then 253 | show_list=($base_dir) 254 | 255 | #Directory, add its contents 256 | else 257 | # Break total blocks of the front of the stat call, then push the rest to results 258 | if [[ "$o_all" != "" && "$o_almost_all" == "" && "$o_no_directory" == "" ]]; then 259 | show_list+=($base_dir/.) 260 | show_list+=($base_dir/..) 261 | fi 262 | 263 | if [[ "$o_all" != "" || "$o_almost_all" != "" ]]; then 264 | if [[ "$o_directory" != "" ]]; then 265 | show_list+=($base_dir/*(D/$SORT_GLOB)) 266 | elif [[ "$o_no_directory" != "" ]]; then 267 | #Use (^/) instead of (.) so sockets and symlinks get displayed 268 | show_list+=($base_dir/*(D^/$SORT_GLOB)) 269 | else 270 | show_list+=($base_dir/*(D$SORT_GLOB)) 271 | fi 272 | else 273 | if [[ "$o_directory" != "" ]]; then 274 | show_list+=($base_dir/*(/$SORT_GLOB)) 275 | elif [[ "$o_no_directory" != "" ]]; then 276 | #Use (^/) instead of (.) so sockets and symlinks get displayed 277 | show_list+=($base_dir/*(^/$SORT_GLOB)) 278 | else 279 | show_list+=($base_dir/*($SORT_GLOB)) 280 | fi 281 | fi 282 | fi 283 | 284 | # ---------------------------------------------------------------------------- 285 | # Stat call to get directory listing 286 | # ---------------------------------------------------------------------------- 287 | typeset -i i=1 j=1 k=1 288 | typeset -a STATS_PARAMS_LIST 289 | typeset fn statvar h 290 | typeset -A sv 291 | 292 | STATS_PARAMS_LIST=() 293 | for fn in $show_list 294 | do 295 | statvar="stats_$i" 296 | typeset -A $statvar 297 | zstat -H $statvar -Lsn -F "%s^%d^%b^%H:%M^%Y" -- "$fn" # use lstat, render mode/uid/gid to strings 298 | STATS_PARAMS_LIST+=($statvar) 299 | i+=1 300 | done 301 | 302 | 303 | # On each result calculate padding by getting max length on each array member 304 | for statvar in "${STATS_PARAMS_LIST[@]}" 305 | do 306 | sv=("${(@Pkv)statvar}") 307 | if [[ ${#sv[mode]} -gt $MAX_LEN[1] ]]; then MAX_LEN[1]=${#sv[mode]} ; fi 308 | if [[ ${#sv[nlink]} -gt $MAX_LEN[2] ]]; then MAX_LEN[2]=${#sv[nlink]} ; fi 309 | if [[ ${#sv[uid]} -gt $MAX_LEN[3] ]]; then MAX_LEN[3]=${#sv[uid]} ; fi 310 | if [[ ${#sv[gid]} -gt $MAX_LEN[4] ]]; then MAX_LEN[4]=${#sv[gid]} ; fi 311 | 312 | if [[ "$o_human" != "" ]]; then 313 | h=$(numfmt_local ${sv[size]}) 314 | if (( ${#h} > $MAX_LEN[5] )); then MAX_LEN[5]=${#h}; fi 315 | else 316 | if [[ ${#sv[size]} -gt $MAX_LEN[5] ]]; then MAX_LEN[5]=${#sv[size]}; fi 317 | fi 318 | 319 | TOTAL_BLOCKS+=$sv[blocks] 320 | done 321 | 322 | # Print total block before listing 323 | echo "total $TOTAL_BLOCKS" 324 | 325 | # ---------------------------------------------------------------------------- 326 | # Loop through each line of stat, pad where appropriate and do git dirty checking 327 | # ---------------------------------------------------------------------------- 328 | 329 | typeset REPOMARKER 330 | typeset REPOBRANCH 331 | typeset PERMISSIONS HARDLINKCOUNT OWNER GROUP FILESIZE FILESIZE_OUT DATE NAME SYMLINK_TARGET 332 | typeset FILETYPE PER1 PER2 PER3 PERMISSIONS_OUTPUT STATUS 333 | typeset TIME_DIFF TIME_COLOR DATE_OUTPUT 334 | typeset -i IS_DIRECTORY IS_SYMLINK IS_SOCKET IS_PIPE IS_EXECUTABLE IS_BLOCK_SPECIAL IS_CHARACTER_SPECIAL HAS_UID_BIT HAS_GID_BIT HAS_STICKY_BIT IS_WRITABLE_BY_OTHERS 335 | typeset -i COLOR 336 | 337 | k=1 338 | for statvar in "${STATS_PARAMS_LIST[@]}" 339 | do 340 | sv=("${(@Pkv)statvar}") 341 | 342 | # We check if the result is a git repo later, so set a blank marker indication the result is not a git repo 343 | REPOMARKER=" " 344 | REPOBRANCH="" 345 | IS_DIRECTORY=0 346 | IS_SYMLINK=0 347 | IS_SOCKET=0 348 | IS_PIPE=0 349 | IS_EXECUTABLE=0 350 | IS_BLOCK_SPECIAL=0 351 | IS_CHARACTER_SPECIAL=0 352 | HAS_UID_BIT=0 353 | HAS_GID_BIT=0 354 | HAS_STICKY_BIT=0 355 | IS_WRITABLE_BY_OTHERS=0 356 | 357 | PERMISSIONS="${sv[mode]}" 358 | HARDLINKCOUNT="${sv[nlink]}" 359 | OWNER="${sv[uid]}" 360 | GROUP="${sv[gid]}" 361 | FILESIZE="${sv[size]}" 362 | DATE=(${(s:^:)sv[mtime]}) # Split date on ^ 363 | NAME="${sv[name]}" 364 | SYMLINK_TARGET="${sv[link]}" 365 | 366 | # Check for file types 367 | if [[ -d "$NAME" ]]; then IS_DIRECTORY=1; fi 368 | if [[ -L "$NAME" ]]; then IS_SYMLINK=1; fi 369 | if [[ -S "$NAME" ]]; then IS_SOCKET=1; fi 370 | if [[ -p "$NAME" ]]; then IS_PIPE=1; fi 371 | if [[ -x "$NAME" ]]; then IS_EXECUTABLE=1; fi 372 | if [[ -b "$NAME" ]]; then IS_BLOCK_SPECIAL=1; fi 373 | if [[ -c "$NAME" ]]; then IS_CHARACTER_SPECIAL=1; fi 374 | if [[ -u "$NAME" ]]; then HAS_UID_BIT=1; fi 375 | if [[ -g "$NAME" ]]; then HAS_GID_BIT=1; fi 376 | if [[ -k "$NAME" ]]; then HAS_STICKY_BIT=1; fi 377 | if [[ $PERMISSIONS[9] == 'w' ]]; then IS_WRITABLE_BY_OTHERS=1; fi 378 | 379 | # IS_GIT_REPO is a 1 if $NAME is a file/directory in a git repo, OR if $NAME is a git-repo itself 380 | # GIT_TOPLEVEL is set to the directory containing the .git folder of a git-repo 381 | 382 | # is this a git repo 383 | if [[ "$o_no_vcs" != "" ]]; then 384 | IS_GIT_REPO=0 385 | GIT_TOPLEVEL='' 386 | else 387 | if (( IS_DIRECTORY )); 388 | then builtin cd -q $NAME 2>/dev/null || builtin cd -q - >/dev/null && IS_GIT_REPO=0 #Say no if we don't have permissions there 389 | else builtin cd -q $NAME:a:h 2>/dev/null || builtin cd -q - >/dev/null && IS_GIT_REPO=0 390 | fi 391 | if [[ $(command git rev-parse --is-inside-work-tree 2>/dev/null) == true ]]; then 392 | IS_GIT_REPO=1 393 | GIT_TOPLEVEL=$(command git rev-parse --show-toplevel) 394 | else 395 | IS_GIT_REPO=0 396 | fi 397 | builtin cd -q - >/dev/null 398 | fi 399 | 400 | # Get human readable output if necessary 401 | if [[ "$o_human" != "" ]]; then 402 | # I hate making this call twice, but its either that, or do a bunch 403 | # of calculations much earlier. 404 | FILESIZE_OUT=$(numfmt_local $FILESIZE) 405 | else 406 | FILESIZE_OUT=$FILESIZE 407 | fi 408 | 409 | # Pad so all the lines align - firstline gets padded the other way 410 | PERMISSIONS="${(r:MAX_LEN[1]:)PERMISSIONS}" 411 | HARDLINKCOUNT="${(l:MAX_LEN[2]:)HARDLINKCOUNT}" 412 | OWNER="${(l:MAX_LEN[3]:)OWNER}" 413 | GROUP="${(l:MAX_LEN[4]:)GROUP}" 414 | FILESIZE_OUT="${(l:MAX_LEN[5]:)FILESIZE_OUT}" 415 | 416 | # -------------------------------------------------------------------------- 417 | # Colour the permissions - TODO 418 | # -------------------------------------------------------------------------- 419 | # Colour the first character based on filetype 420 | FILETYPE="${PERMISSIONS[1]}" 421 | 422 | # Permissions Owner 423 | PER1="${PERMISSIONS[2,4]}" 424 | 425 | # Permissions Group 426 | PER2="${PERMISSIONS[5,7]}" 427 | 428 | # Permissions User 429 | PER3="${PERMISSIONS[8,10]}" 430 | 431 | PERMISSIONS_OUTPUT="$FILETYPE$PER1$PER2$PER3" 432 | 433 | # -------------------------------------------------------------------------- 434 | # Colour the symlinks 435 | # -------------------------------------------------------------------------- 436 | 437 | # -------------------------------------------------------------------------- 438 | # Colour Owner and Group 439 | # -------------------------------------------------------------------------- 440 | OWNER=$'\e[38;5;241m'"$OWNER"$'\e[0m' 441 | GROUP=$'\e[38;5;241m'"$GROUP"$'\e[0m' 442 | 443 | # -------------------------------------------------------------------------- 444 | # Colour file weights 445 | # -------------------------------------------------------------------------- 446 | COLOR=LARGE_FILE_COLOR 447 | for i j in ${SIZELIMITS_TO_COLOR[@]} 448 | do 449 | (( FILESIZE <= i )) || continue 450 | COLOR=$j 451 | break 452 | done 453 | 454 | FILESIZE_OUT=$'\e[38;5;'"${COLOR}m$FILESIZE_OUT"$'\e[0m' 455 | 456 | # -------------------------------------------------------------------------- 457 | # Colour the date and time based on age, then format for output 458 | # -------------------------------------------------------------------------- 459 | # Setup colours based on time difference 460 | TIME_DIFF=$(( K_EPOCH - DATE[1] )) 461 | TIME_COLOR=$ANCIENT_TIME_COLOR 462 | for i j in ${FILEAGES_TO_COLOR[@]} 463 | do 464 | (( TIME_DIFF < i )) || continue 465 | TIME_COLOR=$j 466 | break 467 | done 468 | 469 | # Format date to show year if more than 6 months since last modified 470 | if (( TIME_DIFF < 15724800 )); then 471 | DATE_OUTPUT="${DATE[2]} ${(r:5:: :)${DATE[3][0,5]}} ${DATE[4]}" 472 | else 473 | DATE_OUTPUT="${DATE[2]} ${(r:6:: :)${DATE[3][0,5]}} ${DATE[5]}" # extra space; 4 digit year instead of 5 digit HH:MM 474 | fi; 475 | DATE_OUTPUT[1]="${DATE_OUTPUT[1]//0/ }" # If day of month begins with zero, replace zero with space 476 | 477 | # Apply colour to formated date 478 | DATE_OUTPUT=$'\e[38;5;'"${TIME_COLOR}m${DATE_OUTPUT}"$'\e[0m' 479 | 480 | # -------------------------------------------------------------------------- 481 | # Colour the repomarker 482 | # -------------------------------------------------------------------------- 483 | if [[ "$o_no_vcs" != "" ]]; then 484 | REPOMARKER="" 485 | elif (( IS_GIT_REPO != 0)); then 486 | # If we're not in a repo, still check each directory if it's a repo, and 487 | # then mark appropriately 488 | if (( INSIDE_WORK_TREE == 0 )); then 489 | REPOBRANCH=$(command git --git-dir="$GIT_TOPLEVEL/.git" --work-tree="${NAME}" rev-parse --abbrev-ref HEAD 2>/dev/null) 490 | if (( IS_DIRECTORY )); then 491 | if command git --git-dir="$GIT_TOPLEVEL/.git" --work-tree="${NAME}" diff --stat --quiet --ignore-submodules HEAD &>/dev/null # if dirty 492 | then REPOMARKER=$'\e[38;5;46m|\e[0m' # Show a green vertical bar for clean 493 | else REPOMARKER=$'\e[0;31m+\e[0m' # Show a red vertical bar if dirty 494 | fi 495 | fi 496 | else 497 | if (( IS_DIRECTORY )); then 498 | # If the directory isn't ignored or clean, we'll just say it's dirty 499 | if command git check-ignore --quiet ${NAME} 2>/dev/null; then STATUS='!!' 500 | elif command git diff --stat --quiet --ignore-submodules ${NAME} 2> /dev/null; then STATUS=''; 501 | else STATUS=' M' 502 | fi 503 | else 504 | # File 505 | STATUS=$(command git status --porcelain --ignored --untracked-files=normal $GIT_TOPLEVEL/${${${NAME:a}##$GIT_TOPLEVEL}#*/}) 506 | fi 507 | STATUS=${STATUS[1,2]} 508 | if [[ $STATUS == ' M' ]]; then REPOMARKER=$'\e[0;31m+\e[0m'; # Tracked & Dirty 509 | elif [[ $STATUS == 'M ' ]]; then REPOMARKER=$'\e[38;5;082m+\e[0m'; # Tracked & Dirty & Added 510 | elif [[ $STATUS == '??' ]]; then REPOMARKER=$'\e[38;5;214m+\e[0m'; # Untracked 511 | elif [[ $STATUS == '!!' ]]; then REPOMARKER=$'\e[38;5;238m|\e[0m'; # Ignored 512 | elif [[ $STATUS == 'A ' ]]; then REPOMARKER=$'\e[38;5;082m+\e[0m'; # Added 513 | else REPOMARKER=$'\e[38;5;082m|\e[0m'; # Good 514 | fi 515 | fi 516 | fi 517 | 518 | # -------------------------------------------------------------------------- 519 | # Colour the filename 520 | # -------------------------------------------------------------------------- 521 | # Unfortunately, the choices for quoting which escape ANSI color sequences are q & qqqq; none of q- qq qqq work. 522 | # But we don't want to quote '.'; so instead we escape the escape manually and use q- 523 | NAME="${${NAME##*/}//$'\e'/\\e}" # also propagate changes to SYMLINK_TARGET below 524 | 525 | if [[ $IS_DIRECTORY == 1 ]]; then 526 | if [[ $IS_WRITABLE_BY_OTHERS == 1 ]]; then 527 | if [[ $HAS_STICKY_BIT == 1 ]]; then 528 | NAME=$'\e['"$K_COLOR_TW"'m'"$NAME"$'\e[0m'; 529 | fi 530 | NAME=$'\e['"$K_COLOR_OW"'m'"$NAME"$'\e[0m'; 531 | fi 532 | NAME=$'\e['"$K_COLOR_DI"'m'"$NAME"$'\e[0m'; 533 | elif [[ $IS_SYMLINK == 1 ]]; then NAME=$'\e['"$K_COLOR_LN"'m'"$NAME"$'\e[0m'; 534 | elif [[ $IS_SOCKET == 1 ]]; then NAME=$'\e['"$K_COLOR_SO"'m'"$NAME"$'\e[0m'; 535 | elif [[ $IS_PIPE == 1 ]]; then NAME=$'\e['"$K_COLOR_PI"'m'"$NAME"$'\e[0m'; 536 | elif [[ $HAS_UID_BIT == 1 ]]; then NAME=$'\e['"$K_COLOR_SU"'m'"$NAME"$'\e[0m'; 537 | elif [[ $HAS_GID_BIT == 1 ]]; then NAME=$'\e['"$K_COLOR_SG"'m'"$NAME"$'\e[0m'; 538 | elif [[ $IS_EXECUTABLE == 1 ]]; then NAME=$'\e['"$K_COLOR_EX"'m'"$NAME"$'\e[0m'; 539 | elif [[ $IS_BLOCK_SPECIAL == 1 ]]; then NAME=$'\e['"$K_COLOR_BD"'m'"$NAME"$'\e[0m'; 540 | elif [[ $IS_CHARACTER_SPECIAL == 1 ]]; then NAME=$'\e['"$K_COLOR_CD"'m'"$NAME"$'\e[0m'; 541 | fi 542 | 543 | # -------------------------------------------------------------------------- 544 | # Colour branch 545 | # -------------------------------------------------------------------------- 546 | REPOBRANCH=$'\e['"$K_COLOR_BR"'m'"$REPOBRANCH"$'\e[0m'; 547 | 548 | # -------------------------------------------------------------------------- 549 | # Format symlink target 550 | # -------------------------------------------------------------------------- 551 | if [[ $SYMLINK_TARGET != "" ]]; then SYMLINK_TARGET=" -> ${SYMLINK_TARGET//$'\e'/\\e}"; fi 552 | 553 | # -------------------------------------------------------------------------- 554 | # Display final result 555 | # -------------------------------------------------------------------------- 556 | print -r -- "$PERMISSIONS_OUTPUT $HARDLINKCOUNT $OWNER $GROUP $FILESIZE_OUT $DATE_OUTPUT $REPOMARKER $NAME$SYMLINK_TARGET $REPOBRANCH" 557 | 558 | k=$((k+1)) # Bump loop index 559 | done 560 | done 561 | } 562 | 563 | _k_bsd_to_ansi() { 564 | local foreground=$1 background=$2 foreground_ansi background_ansi 565 | case $foreground in 566 | a) foreground_ansi=30;; 567 | b) foreground_ansi=31;; 568 | c) foreground_ansi=32;; 569 | d) foreground_ansi=33;; 570 | e) foreground_ansi=34;; 571 | f) foreground_ansi=35;; 572 | g) foreground_ansi=36;; 573 | h) foreground_ansi=37;; 574 | x) foreground_ansi=0;; 575 | esac 576 | case $background in 577 | a) background_ansi=40;; 578 | b) background_ansi=41;; 579 | c) background_ansi=42;; 580 | d) background_ansi=43;; 581 | e) background_ansi=44;; 582 | f) background_ansi=45;; 583 | g) background_ansi=46;; 584 | h) background_ansi=47;; 585 | x) background_ansi=0;; 586 | esac 587 | printf "%s;%s" $background_ansi $foreground_ansi 588 | } 589 | 590 | # http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg 591 | # vim: set ts=2 sw=2 ft=zsh et : 592 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![k.supercrabtree.com](https://raw.githubusercontent.com/supercrabtree/k/gh-pages/k-logo.png)](http://k.supercrabtree.com) 2 | 3 | > k is the new l, yo 4 | 5 | ## Directory listings for zsh with git features 6 | 7 | **k** is a zsh script / plugin to make directory listings more readable, adding a bit of color and some git status information on files and directories. 8 | 9 | ### Git status on entire repos 10 | ![Repository git status](https://raw.githubusercontent.com/supercrabtree/k/gh-pages/repo-dirs.jpg) 11 | 12 | 13 | ### Git status on files within a working tree 14 | ![Repository work tree git status](https://raw.githubusercontent.com/supercrabtree/k/gh-pages/inside-work-tree.jpg) 15 | 16 | ### File weight colours 17 | Files sizes are graded from green for small (< 1k), to red for huge (> 1mb). 18 | 19 | **Human readable files sizes** 20 | Human readable files sizes can be shown by using the `-h` flag, which requires the `numfmt` command to be available. OS X / Darwin does not have a `numfmt` command by default, so GNU coreutils needs to be installed, which provides `gnumfmt` that `k` will also use if available. GNU coreutils can be installed on OS X with [homebrew](http://brew.sh): 21 | 22 | ``` 23 | brew install coreutils 24 | ``` 25 | 26 | ![File weight colours](https://raw.githubusercontent.com/supercrabtree/k/gh-pages/file-size-colors.jpg) 27 | 28 | 29 | ### Rotting dates 30 | Dates fade with age. 31 | 32 | ![Rotting dates](https://raw.githubusercontent.com/supercrabtree/k/gh-pages/dates.jpg) 33 | 34 | 35 | ## Installation 36 | 37 | ### Using [zplug](https://github.com/b4b4r07/zplug) 38 | Load k as a plugin in your `.zshrc` 39 | 40 | ```shell 41 | zplug "supercrabtree/k" 42 | 43 | ``` 44 | ### Using [zgen](https://github.com/tarjoilija/zgen) 45 | 46 | Include the load command in your `.zshrc` 47 | 48 | ```shell 49 | zgen load supercrabtree/k 50 | zgen save 51 | ``` 52 | 53 | ### Using [Antigen](https://github.com/zsh-users/antigen) 54 | 55 | Bundle k in your `.zshrc` 56 | 57 | ```shell 58 | antigen bundle supercrabtree/k 59 | antigen apply 60 | ``` 61 | 62 | ### As an [Oh My ZSH!](https://github.com/robbyrussell/oh-my-zsh) custom plugin 63 | 64 | Clone k into your custom plugins repo 65 | 66 | ```shell 67 | git clone https://github.com/supercrabtree/k $ZSH_CUSTOM/plugins/k 68 | ``` 69 | Then load as a plugin in your `.zshrc` 70 | 71 | ```shell 72 | plugins+=(k) 73 | ``` 74 | 75 | ### Manually 76 | Clone this repository somewhere (~/k for example) 77 | 78 | ```shell 79 | git clone git@github.com:supercrabtree/k.git $HOME/k 80 | ``` 81 | Source the script 82 | 83 | ```shell 84 | source $HOME/k/k.sh 85 | ``` 86 | Put the same line in your `.zshrc` to have `k` available whenever you start a shell. If you don't know what your `.zshrc` is, just do this. 87 | 88 | ```shell 89 | print "source $HOME/k/k.sh" >> $HOME/.zshrc 90 | ``` 91 | 92 | ## Usage 93 | Hit k in your shell 94 | 95 | ```shell 96 | k 97 | ``` 98 | # 😮 99 | 100 | ## Minimum Requirements 101 | zsh 4.3.11 102 | Git 1.7.2 103 | 104 | ## Contributors 105 | [supercrabtree](https://github.com/supercrabtree) 106 | [chrstphrknwtn](https://github.com/chrstphrknwtn) 107 | [zirrostig](https://github.com/zirrostig) 108 | [lejeunerenard](https://github.com/lejeunerenard) 109 | [jozefizso](https://github.com/jozefizso) 110 | [unixorn](https://github.com/unixorn) 111 | [george-b](https://github.com/george-b) 112 | [philpennock](https://github.com/philpennock) 113 | [hoelzro](https://github.com/hoelzro) 114 | [srijanshetty](https://github.com/srijanshetty) 115 | [zblach](https://github.com/zblach) 116 | [mattboll](https://github.com/mattboll) 117 | Pull requests welcome :smile: 118 | 119 | ## Thanks 120 | [Paul Falstad](http://www.falstad.com/) for zsh 121 | [Sindre Sorhus](https://github.com/sindresorhus) for the fast git commands from zsh pure theme 122 | [Rupa](https://github.com/rupa/z) for that slammin' strapline 123 | 124 | Copyright © 2015 George Crabtree & Christopher Newton. MIT License. 125 | --------------------------------------------------------------------------------