#!/bin/sh set -eu # Application info . "sh/info.sh" # helper functions . "sh/helper.sh" # tools . "sh/tools.sh" # Configuration . "sh/config.sh" # Theme . "sh/theme.sh" # awk scripts . "sh/awk.sh" # query history . "sh/history.sh" # notmuch functions . "sh/notmuch.sh" # list generators . "sh/lists.sh" # preview functions . "sh/preview.sh" # varia functions . "sh/varia.sh" # tags . "sh/tags.sh" # keys . "sh/keys.sh" # send emails . "sh/send.sh" if [ "${1:-}" = "--list-threads" ]; then shift nmquery="$1" list_threads "$nmquery" exit fi if [ "${1:-}" = "--list-messages-in-thread" ]; then shift thread="$1" list_messages_in_thread "$thread" exit fi if [ "${1:-}" = "--preview-thread" ]; then shift threadid="$1" preview_message "thread" "$threadid" exit fi if [ "${1:-}" = "--preview-html-thread" ]; then shift threadid="$1" preview_message "thread" "$threadid" "html" exit fi if [ "${1:-}" = "--preview-message" ]; then shift messageid="$1" preview_message "id" "$messageid" exit fi if [ "${1:-}" = "--preview-html-message" ]; then shift messageid="$1" preview_message "id" "$messageid" "html" exit fi if [ "${1:-}" = "--view-source" ]; then shift messageid="$1" $NOTMUCH show --part=0 id:"$messageid" | $CATEMAIL | less exit fi if [ "${1:-}" = "--show-message-parts" ]; then shift messageid="$1" nm_message_parts "$messageid" exit fi # FZF defaults FZF_DEFAULT_PREVIEW_WINDOW="right,80,border-line,wrap-word" FZF_ALTERNATE_PREVIEW_WINDOW="bottom,80%,border-line,wrap-word" FZF_DEFAULT_THREAD_LINE="{1} {3} ({4})" if [ "${1:-}" = "--open-part" ]; then shift messageid="$1" res=$(nm_message_parts "$messageid" | awk '{print NR"\t"$0}' | $FZF \ --raw \ --reverse \ --wrap=word \ --delimiter="$(printf '\t')" \ --with-nth=2 \ --accept-nth=1 \ --margin='30%' \ --bind='change:best' \ --bind="$KEYS_DOWN_HP:half-page-down" \ --bind="$KEYS_UP_HP:half-page-up" \ --bind="$KEYS_ENTER:accept" \ --bind="$KEYS_BACK,$KEYS_CANCEL,backward-eof:abort" || true) [ "$res" ] || exit tmpfile=$(mktemp) nm_message_get_part "$messageid" "$res" > "$tmpfile" if ynprompt "Are you sure you want to open this file?" "$(ls -lh "$tmpfile")\n$(file -b "$tmpfile")"; then # TODO: should we clean up at some point? open "$tmpfile" >/dev/null 2>/dev/stdout fi exit fi if [ "${1:-}" = "--archive" ]; then shift [ "$1" = "thread" ] && thread=1 shift [ "${thread:-}" ] && field="thread" || field="id" query="$(echo " $*" | sed "s/\s\(\S\)/ $field:\1/g")" tagmod="$(echo " $TAG_INBOX $TAGS_NEW" | sed "s/\s\(\S\)/ -\1/g")" $NOTMUCH tag $tagmod "$query" exit fi if [ "${1:-}" = "--delete" ]; then shift [ "$1" = "thread" ] && thread=1 shift [ "${thread:-}" ] && field="thread" || field="id" query="$(echo " $*" | sed "s/\s\(\S\)/ $field:\1/g")" $NOTMUCH tag -$TAG_INBOX +$TAG_DEL "$query" exit fi if [ "${1:-}" = "--undelete" ]; then shift query="$(echo " $*" | sed "s/\s\(\S\)/ thread:\1/g")" $NOTMUCH tag -$TAG_DEL "$query" exit fi if [ "${1:-}" = "--flag" ]; then shift [ "$1" = "thread" ] && thread=1 shift [ "${thread:-}" ] && field="thread" || field="id" query="$(echo " $*" | sed "s/\s\(\S\)/ $field:\1/g")" $NOTMUCH tag +"$TAG_FLAGGED" "$query" exit fi if [ "${1:-}" = "--unflag" ]; then shift [ "$1" = "thread" ] && thread=1 shift [ "${thread:-}" ] && field="thread" || field="id" query="$(echo " $*" | sed "s/\s\(\S\)/ $field:\1/g")" $NOTMUCH tag -"$TAG_FLAGGED" "$query" exit fi if [ "${1:-}" = "--purge" ]; then shift query="tag:$TAG_DEL and ($(echo " $*" | sed "s/\s\(\S\)/ thread:\1/g"))" files=$(nm_files_all "$query") if ynprompt "Are you sure you want to purge these files?" "$files"; then printf '%s' "$files" | xargs -d "\n" rm nm_new fi exit fi if [ "${1:-}" = "--tag-modify" ]; then shift [ "$1" = "thread" ] && thread=1 shift [ "$1" = "add" ] && sign=+ || sign=- shift [ "${thread:-}" ] && field="thread" || field="id" query="$(echo " $*" | sed "s/\s\(\S\)/ $field:\1/g")" if [ "$sign" = "+" ]; then tagquery="" label_text="Add tag" else tagquery="$query" label_text="Remove tag" fi res=$(list_tags "$tagquery" | $FZF \ --color="border:$ANSICOLORTAGS" \ --color="label:$ANSICOLORTAGS" \ --tac \ --bind="$KEYS_DOWN_HP:half-page-down" \ --bind="$KEYS_UP_HP:half-page-up" \ --bind="$KEYS_BACK,$KEYS_CANCEL,backward-eof:abort" \ --bind="$KEYS_ENTER:accept-or-print-query" \ --bind="$KEYS_ENTER_ALTERNATIVE:transform:echo \"print(\$FZF_QUERY)+accept\"" \ --margin='20%' \ --border=double \ --border-label=" $label_text " | head -1 || true) [ "$res" ] || exit $NOTMUCH tag "$sign$res" "$query" exit fi if [ "${1:-}" = "--show-thread" ]; then shift thread="$1" res=$(list_messages_in_thread "$thread" | $FZF \ --raw \ --reverse \ --multi \ --delimiter="$(printf '\t')" \ --with-nth="$FZF_DEFAULT_THREAD_LINE" \ --accept-nth=5 \ --bind="$KEYS_CANCEL:transform:[ \$FZF_SELECT_COUNT -gt 0 ] && echo clear-multi || echo abort" \ --bind="$KEYS_PREVIEW_DOWN:preview-down" \ --bind="$KEYS_PREVIEW_UP:preview-up" \ --bind="$KEYS_PREVIEW_DOWN_HP:preview-half-page-down" \ --bind="$KEYS_PREVIEW_UP_HP:preview-half-page-up" \ --bind="$KEYS_CYCLE_DISPLAY:change-with-nth({1} {2} {3} ({4})|$FZF_DEFAULT_THREAD_LINE)" \ --bind="$KEYS_DOWN_HP:half-page-down" \ --bind="$KEYS_UP_HP:half-page-up" \ --bind="$KEYS_ENTER:" \ --bind="$KEYS_BACK,$KEYS_CANCEL,backward-eof:abort" \ --bind="$KEYS_PREVIEW_TOGGLE_SIZE:change-preview-window($FZF_ALTERNATE_PREVIEW_WINDOW|$FZF_DEFAULT_PREVIEW_WINDOW)" \ --bind="$KEYS_VIEW_SOURCE:execute:$0 --view-source {5}" \ --bind="$KEYS_HTML_PREVIEW:change-preview:$0 --preview-html-message {5}" \ --bind="$KEYS_TAG_ADD:execute($0 --tag-modify message add {+5})+reload:$0 --list-messages-in-thread \"$thread\"" \ --bind="$KEYS_TAG_REMOVE:execute($0 --tag-modify message del {+5})+reload:$0 --list-messages-in-thread \"$thread\"" \ --bind="$KEYS_ARCHIVE:execute($0 --archive message {+5})+reload:$0 --list-messages-in-thread \"$thread\"" \ --bind="$KEYS_DELETE:execute($0 --delete message {+5})+reload:$0 --list-messages-in-thread \"$thread\"" \ --bind="$KEYS_OPEN:execute:$0 --open-part {5}" \ --bind="$KEYS_FLAG:execute($0 --flag message {+5})+reload:$0 --list-messages-in-thread \"$thread\"" \ --bind="$KEYS_UNFLAG:execute($0 --unflag message {+5})+reload:$0 --list-messages-in-thread \"$thread\"" \ --bind="$KEYS_REPLY:execute:$0 --reply message {5}" \ --bind="$KEYS_VIEW_LOGS:execute:$0 --view-logs" \ --bind="$KEYS_COMPOSE:execute:$0 --compose" \ --bind="focus:change-preview($0 --preview-message {5})+transform-footer:printf '\033[4mMesssage parts\033[0m\n';$0 --show-message-parts {5}" \ --preview="$0 --preview-message {5}" \ --preview-window="$FZF_DEFAULT_PREVIEW_WINDOW" || true) exit fi if [ "${1:-}" = "--list-deleted" ]; then shift list_threads "tag:$TAG_DEL" | $FZF \ --header-first \ --margin='5%' \ --header='DELETED MESSAGES' \ --footer="$KEYS_PURGE_ALL: purge ALL threads / $KEYS_DELETE: purge selected threads / $KEYS_ENTER_ALTERNATIVE: undelete selected threads" \ --border='double' \ --color="header:red:bold:underline,border:red,selected-bg:88" \ --tiebreak=index \ --reverse \ --multi \ --delimiter="$(printf '\t')" \ --with-nth='{1} {2} ({3})' \ --bind="$KEYS_CANCEL:transform:[ \$FZF_SELECT_COUNT -gt 0 ] && echo clear-multi || echo abort" \ --bind="$KEYS_DOWN_HP:half-page-down" \ --bind="$KEYS_UP_HP:half-page-up" \ --bind="$KEYS_ENTER:" \ --bind="$KEYS_PURGE_ALL:select-all+execute($0 --purge {+4})+reload:$0 --list-threads \"tag:$TAG_DEL\"" \ --bind="$KEYS_DELETE:execute($0 --purge {+4})+reload:$0 --list-threads \"tag:$TAG_DEL\"" \ --bind="$KEYS_ENTER_ALTERNATIVE:execute($0 --undelete {+4})+reload:$0 --list-threads \"tag:$TAG_DEL\"" \ --bind="$KEYS_VIEW_LOGS:execute:$0 --view-logs" exit fi if [ "${1:-}" = "--compose" ]; then label_text=" Select sender " address=$(list_addresses | $FZF \ --delimiter="$(printf '\t')" \ --with-nth=2 \ --accept-nth=1 \ --color="label:$ANSICOLORFROM" \ --tiebreak=index \ --margin='20%' \ --bind="$KEYS_DOWN_HP:half-page-down" \ --bind="$KEYS_UP_HP:half-page-up" \ --bind="$KEYS_ENTER:accept-or-print-query" \ --bind="$KEYS_ENTER_ALTERNATIVE:transform:echo \"print(\$FZF_QUERY)+accept\"" \ --bind="$KEYS_CANCEL:print()+accept" \ --border=double \ --border-label="$label_text" | head -1 || true) tmpfile=$(mktemp --suffix='.eml') { echo "From: $PRIMARY_NAME <$PRIMARY_EMAIL>" echo "To: $address" echo "Cc: " [ "$BCC_TO_SELF" ] && echo "Bcc: $PRIMARY_NAME <$PRIMARY_EMAIL>" echo "Subject: " echo "" echo "Message body goes here" } > "$tmpfile" $EDITOR "$tmpfile" # Confirm and send send "$tmpfile" rm -f "$tmpfile" exit fi if [ "${1:-}" = "--reply" ]; then shift [ "$1" = "thread" ] && thread=1 shift [ ! "${thread:-}" ] && messageid="$1" || messageid="$(nm_last_message_in_thread "$1")" tmpfile=$(mktemp --suffix='.eml') if [ "$BCC_TO_SELF" ]; then $NOTMUCH reply id:"$messageid" | awk '!f && /^From:/ { print "Bcc: "substr($0, 7); f=1 } {print}' > "$tmpfile" else $NOTMUCH reply id:"$messageid" > "$tmpfile" fi $EDITOR "$tmpfile" # Confirm and send send "$tmpfile" rm -f "$tmpfile" exit fi if [ "${1:-}" = "--sync" ]; then $NOTMUCH new && $MBSYNC -a && $NOTMUCH new exit fi if [ "${1:-}" = "--send-test-email" ]; then tmpfile=$(mktemp --suffix='.eml') { echo "From: $PRIMARY_NAME <$PRIMARY_EMAIL>" echo "To: $PRIMARY_NAME <$PRIMARY_EMAIL>" [ "$BCC_TO_SELF" ] && echo "Bcc: $PRIMARY_NAME <$PRIMARY_EMAIL>" echo "Subject: Test E-Mail from $APP_NAME version $APP_VERSION" echo "" echo "This is a test email sent from from [$APP_NAME]($APP_WEBSITE)." } > "$tmpfile" send "$tmpfile" "force" rm -f "$tmpfile" exit fi if [ "${1:-}" = "--view-logs" ]; then shift printf "0 Send test message\n1 Close\n" | $FZF \ --sync \ --bind='start:pos(2)' \ --bind='ctrl-c,ctrl-g,ctrl-q,esc:pos(2)+accept' \ --bind="enter:transform: [ {1} = 0 ] && $0 --send-test-email || echo accept" \ --reverse \ --no-input \ --header="Here are the latest SMTP logs" \ --preview-window='60%,border-line,wrap-word,follow,noinfo' \ --margin='5%,5%,5%,15%' \ --preview="tail -f \"$SMTPLOGFILE\" | $CATLOG" \ --with-nth=2.. \ --accept-nth=1 \ --header-border='line' \ --color='border:yellow,header:yellow' || true exit fi # Set terminal title if [ "$SET_TERMINAL_TITLE" = "yes" ]; then printf '\033]0;%s\007' "$WINDOW_TITLE" fi # Modes APPEND="append" NWSRCH="search" CMD_SEARCH_TAG="tag-" ADDRESS_TO="to-" ADDRESS_FROM="from-" CMD_SEARCH_ADDRESS="address-" CMD_EDIT_QUERY="edit-query" CMD_GOTO_INBOX="go-to-inbox" CMD_GOTO_UNREAD="go-to-unread" CMD_GOTO_FLAGGED="go-to-flagged" while true; do nmquery="${nmquery:-tag:$TAG_INBOX}" [ "$(tail -1 "$NMFHIST")" = "$nmquery" ] || echo "$nmquery" >> "$NMFHIST" cmd=$(list_threads "$nmquery" | $FZF \ --no-clear \ --header-first \ --header="Query: $nmquery" \ --tiebreak=index \ --reverse \ --multi \ --delimiter="$(printf '\t')" \ --with-nth="{1} {2} ({3})" \ --bind="$KEYS_SEARCH_TAG_REPLCE:print($CMD_SEARCH_TAG$NWSRCH)+accept" \ --bind="$KEYS_SEARCH_TAG_APPEND:print($CMD_SEARCH_TAG$APPEND)+accept" \ --bind="$KEYS_SEARCH_FROM_REPLCE:print($CMD_SEARCH_ADDRESS$ADDRESS_FROM$NWSRCH)+accept" \ --bind="$KEYS_SEARCH_FROM_APPEND:print($CMD_SEARCH_ADDRESS$ADDRESS_FROM$APPEND)+accept" \ --bind="$KEYS_SEARCH_TO_REPLCE:print($CMD_SEARCH_ADDRESS$ADDRESS_TO$NWSRCH)+accept" \ --bind="$KEYS_SEARCH_TO_APPEND:print($CMD_SEARCH_ADDRESS$ADDRESS_TO$APPEND)+accept" \ --bind="$KEYS_SEARCH_EDIT_QUERY:print($CMD_EDIT_QUERY)+accept" \ --bind="$KEYS_PREVIEW_DOWN:preview-down" \ --bind="$KEYS_PREVIEW_UP:preview-up" \ --bind="$KEYS_PREVIEW_DOWN_HP:preview-half-page-down" \ --bind="$KEYS_PREVIEW_UP_HP:preview-half-page-up" \ --bind="$KEYS_CANCEL:clear-multi" \ --bind="$KEYS_ENTER:execute($0 --show-thread {4})+reload:$0 --list-threads \"$nmquery\"" \ --bind="$KEYS_DOWN_HP:half-page-down" \ --bind="$KEYS_UP_HP:half-page-up" \ --bind="$KEYS_PREVIEW_TOGGLE_SIZE:change-preview-window($FZF_ALTERNATE_PREVIEW_WINDOW|$FZF_DEFAULT_PREVIEW_WINDOW)" \ --bind="$KEYS_HTML_PREVIEW:change-preview:$0 --preview-html-thread {4}" \ --bind="$KEYS_TAG_ADD:execute($0 --tag-modify thread add {+4})+reload:$0 --list-threads \"$nmquery\"" \ --bind="$KEYS_TAG_REMOVE:execute($0 --tag-modify thread del {+4})+reload:$0 --list-threads \"$nmquery\"" \ --bind="$KEYS_ARCHIVE:execute($0 --archive thread {+4})+reload:$0 --list-threads \"$nmquery\"" \ --bind="$KEYS_DELETE:execute($0 --delete thread {+4})+reload:$0 --list-threads \"$nmquery\"" \ --bind="$KEYS_FLAG:execute($0 --flag thread {+4})+reload:$0 --list-threads \"$nmquery\"" \ --bind="$KEYS_UNFLAG:execute($0 --unflag thread {+4})+reload:$0 --list-threads \"$nmquery\"" \ --bind="$KEYS_SEARCH_DELETED:execute:$0 --list-deleted" \ --bind="$KEYS_SEARCH_INBOX:print($CMD_GOTO_INBOX)+accept" \ --bind="$KEYS_SEARCH_UNREAD:print($CMD_GOTO_UNREAD)+accept" \ --bind="$KEYS_SEARCH_FLAGGED:print($CMD_GOTO_FLAGGED)+accept" \ --bind="$KEYS_VIEW_LOGS:execute:$0 --view-logs" \ --bind="$KEYS_REPLY:execute:$0 --reply thread {4}" \ --bind="$KEYS_SYNC:execute($0 --sync)+reload:$0 --list-threads \"$nmquery\"" \ --bind="$KEYS_COMPOSE:execute:$0 --compose" \ --bind="$KEYS_GOTO_FIRST:first" \ --bind="focus:change-preview:$0 --preview-thread {4}" \ --preview="$0 --preview-thread {4}" \ --preview-window="$FZF_DEFAULT_PREVIEW_WINDOW" | head -1 || true) [ -n "$cmd" ] || exit 0 case "$cmd" in $CMD_SEARCH_TAG*) tag=$(list_tags | $FZF \ --color="border:$ANSICOLORTAGS" \ --color="label:$ANSICOLORTAGS" \ --tac \ --margin='20%' \ --bind="$KEYS_DOWN_HP:half-page-down" \ --bind="$KEYS_UP_HP:half-page-up" \ --bind="$KEYS_ENTER:accept-or-print-query" \ --bind="$KEYS_ENTER_ALTERNATIVE:transform:echo \"print(\$FZF_QUERY)+accept\"" \ --border=double \ --border-label=" Select tag " | head -1 || true) [ -n "$tag" ] || continue [ "$cmd" = "$CMD_SEARCH_TAG$APPEND" ] && nmquery="$nmquery and tag:$tag" || nmquery="tag:$tag" ;; $CMD_SEARCH_ADDRESS*) case "$cmd" in *$ADDRESS_FROM*) label_text=" Select sender " field='from' ;; *) label_text=" Select recipient " field='to' ;; esac address=$(list_addresses | $FZF \ --delimiter="$(printf '\t')" \ --with-nth=2 \ --accept-nth=1 \ --color="label:$ANSICOLORFROM" \ --tiebreak=index \ --margin='20%' \ --bind="$KEYS_DOWN_HP:half-page-down" \ --bind="$KEYS_UP_HP:half-page-up" \ --bind="$KEYS_ENTER:accept-or-print-query" \ --bind="$KEYS_ENTER_ALTERNATIVE:transform:echo \"print(\$FZF_QUERY)+accept\"" \ --border=double \ --border-label="$label_text" | head -1 || true) [ -n "$address" ] || continue case "$cmd" in *$APPEND) nmquery="$nmquery and $field:$address" ;; *) nmquery="$field:$address" ;; esac ;; $CMD_EDIT_QUERY*) nmquery=$(list_query_history | $FZF \ --color="border:$ANSICOLORSUBJ" \ --color="label:$ANSICOLORSUBJ" \ --tac \ --margin='20%' \ --query="$nmquery" \ --disabled \ --print-query \ --prompt="Query: " \ --bind='focus:replace-query' \ --bind="$KEYS_DOWN_HP:half-page-down" \ --bind="$KEYS_UP_HP:half-page-up" \ --bind="$KEYS_ENTER:accept" \ --border=double \ --border-label=' Query history ' | head -1 || true) ;; "$CMD_GOTO_INBOX") nmquery="tag:$TAG_INBOX" ;; "$CMD_GOTO_UNREAD") nmquery="tag:$TAG_UNREAD" ;; "$CMD_GOTO_FLAGGED") nmquery="tag:$TAG_FLAGGED" ;; *) ;; esac done