| 1 | #!/bin/sh |
| 2 | # Requires: rofi, bc, xclip |
| 3 | # Calculator. The rofi-calc plugin wants libqalculate; this needs nothing but |
| 4 | # bc, at the cost of results arriving on Enter rather than as you type: |
| 5 | # type an expression, Enter evaluate it (the result becomes a row) |
| 6 | # Enter on a result row copy it to the clipboard |
| 7 | # Past results stay listed until cleared, so earlier answers can be copied or |
| 8 | # pasted back into a new expression. |
| 9 | set -u |
| 10 | |
| 11 | HIST="${XDG_CACHE_HOME:-$HOME/.cache}/rofi-calc-history" |
| 12 | HIST_MAX=${ROFI_CALC_HISTORY_MAX:-50} |
| 13 | SCALE=${ROFI_CALC_SCALE:-10} |
| 14 | |
| 15 | if [ -z "${ROFI_RETV:-}" ]; then |
| 16 | exec rofi -show calc -modes "calc:$0" |
| 17 | fi |
| 18 | |
| 19 | US=$(printf '\037') |
| 20 | touch "$HIST" |
| 21 | |
| 22 | list() { |
| 23 | printf '\0prompt%scalc\n' "$US" |
| 24 | printf '\0message%sType an expression and press Enter. + - * / ^%%, sqrt(), s()/c()/a() trig, l()/e() log.\n' "$US" |
| 25 | printf '\0keep-selection%strue\n' "$US" |
| 26 | while IFS=' ' read -r expr result; do |
| 27 | [ -n "$result" ] || continue |
| 28 | printf '%s = %s\0info%s%s\n' "$expr" "$result" "$US" "$result" |
| 29 | done < "$HIST" |
| 30 | [ -s "$HIST" ] && printf 'clear history\0info%s@clear\n' "$US" |
| 31 | return 0 |
| 32 | } |
| 33 | |
| 34 | evaluate() { |
| 35 | # -l pulls in the math library (sqrt, s, c, a, l, e) and sets scale=20, |
| 36 | # so scale has to be set after it to control the printed precision. |
| 37 | result=$(printf 'scale=%s\n%s\n' "$SCALE" "$1" | bc -l 2>/dev/null | tail -n1) |
| 38 | [ -n "$result" ] || return 1 |
| 39 | |
| 40 | # bc pads to the full scale (2/1 = .5000000000) and leads with a bare dot; |
| 41 | # trim both so results look like results. |
| 42 | result=$(printf '%s' "$result" | sed -e 's/\([.][0-9]*[1-9]\)0*$/\1/' -e 's/[.]0*$//' -e 's/^\([-]\{0,1\}\)[.]/\10./') |
| 43 | printf '%s' "$result" |
| 44 | } |
| 45 | |
| 46 | case "$ROFI_RETV" in |
| 47 | 0) list ;; |
| 48 | 1) |
| 49 | info=${ROFI_INFO:-} |
| 50 | if [ "$info" = "@clear" ]; then |
| 51 | : > "$HIST" |
| 52 | list |
| 53 | elif [ -n "$info" ]; then |
| 54 | # Redirect: xclip backgrounds itself still holding the pipe rofi |
| 55 | # reads this script on, and rofi hangs with the keyboard grabbed |
| 56 | # until it sees EOF there. |
| 57 | printf '%s' "$info" | xclip -selection clipboard -i >/dev/null 2>&1 |
| 58 | fi |
| 59 | ;; |
| 60 | 2) |
| 61 | expr=${1:-} |
| 62 | [ -n "$expr" ] || { list; exit 0; } |
| 63 | if result=$(evaluate "$expr") && [ -n "$result" ]; then |
| 64 | tmp="$HIST.$$" |
| 65 | printf '%s\t%s\n' "$expr" "$result" > "$tmp" |
| 66 | grep -v "^$(printf '%s' "$expr" | sed 's/[][\.*^$/]/\\&/g') " "$HIST" >> "$tmp" 2>/dev/null |
| 67 | head -n "$HIST_MAX" "$tmp" > "$HIST" |
| 68 | rm -f "$tmp" |
| 69 | else |
| 70 | printf '\0message%s<span foreground="#bf616a">Not a valid expression</span>\n' "$US" |
| 71 | fi |
| 72 | list |
| 73 | ;; |
| 74 | 3) |
| 75 | expr=$(printf '%s' "${1:-}" | sed 's/ = .*$//') |
| 76 | if [ -n "$expr" ]; then |
| 77 | grep -v "^$(printf '%s' "$expr" | sed 's/[][\.*^$/]/\\&/g') " "$HIST" > "$HIST.$$" 2>/dev/null |
| 78 | mv "$HIST.$$" "$HIST" |
| 79 | fi |
| 80 | list |
| 81 | ;; |
| 82 | esac |