# https://github.com/huijunchen9260/dmenufm
# Dmenu-based directory browser
# Dependency: dmenu; xclip
# Dependency on extraction: tar; unlzma; bunzip2; unrar;
#           unzip; uncompress; 7z; unxz; cabextract.


### OPTIONS AND VARIABLES

## OPTIONS
# FILES LOCATION
[ -n "$FM_PATH" ] || FM_PATH="$HOME/.config/dmenufm"
[ -n "$FM_TRASH" ] || FM_TRASH="$FM_PATH/trash"
[ -n "$FM_BMKFILE" ] || FM_BMKFILE="$FM_PATH/dmenufm_bookmark"
[ -n "$FM_CMDFILE" ] || FM_CMDFILE="$FM_PATH/dmenufm_command"
[ -n "$FM_HISFILE" ] || FM_HISFILE="$FM_PATH/dmenufm_history"
[ -n "$FM_LASTPATH" ] || FM_LASTPATH="$FM_PATH/dmenufm_lastpath"
[ -n "$FM_REMFILE" ] || FM_REMFILE="$FM_PATH/dmenufm_bulk_rename"
# Max number for history
[ -n "$FM_MAX_HIS_LENGTH" ] || FM_MAX_HIS_LENGTH=5000
# FONTS
[ -n "$FM_GENERIC_FONT" ] || FM_GENERIC_FONT="Iosevka-11"
[ -n "$FM_NOTIF_FONT" ] || FM_NOTIF_FONT="Iosevka-11"
[ -n "$FM_DANGER_FONT" ] || FM_DANGER_FONT="Iosevka-14"
# COLORs
[ -n "$FM_GENERIC_COLOR" ] || FM_GENERIC_COLOR="#d79921"
[ -n "$FM_ACTION_COLOR_LV1" ] || FM_ACTION_COLOR_LV1="#98971a"
[ -n "$FM_ACTION_COLOR_LV2" ] || FM_ACTION_COLOR_LV2="#FF8C00"
[ -n "$FM_ACTION_COLOR_BULK" ] || FM_ACTION_COLOR_BULK="#CB06CB"

# xdg-open directories
XDGDIR1="/usr/share/applications"
XDGDIR2="/usr/local/share/applications"

## GLOBAL VARIABLES
DMENU="dmenu -x 300 -y 220 -w 800"
CHOICE="placeholder"
actCHOICE="placeholder"
allselection="placeholder"
TARGET="./"
BACKWARD="../"
ENDSELECTION="End Selection"
allowmulti="NotAllowed"
ACTION="Actions"
TERM="Terminal"
FM_PCP="PCP - Copy path"
FM_NEW="NEW - Create new file / directory"
FM_DEL="DEL - Delete files / directories"
FM_MVR="MVR - Move files / directories"
FM_YAK="YAK - Copy files / directories"
FM_LNK="LNK - Symbolically link files / directories"
FM_REM="REM - Rename files / directories"
FM_TRH="TRH - Trash of dmenufm"
FM_HIS="HIS - History of dmenufm"
FM_BMK="BMK - Bookmark for dmenufm"
FM_CMD="CMD - Frequently used command"
ACTLIST="$FM_PCP:$FM_NEW:$FM_MVR:$FM_YAK:$FM_LNK:$FM_DEL:$FM_TRH:$FM_REM:$FM_HIS:$FM_BMK:$FM_CMD"


### FUNCTIONS

## OS FUNCTION

get_os () {
  case $OSTYPE in
    # Mac OS X / macOS.
    darwin*) FM_OPENER=open ;;
  esac
}

## OPEN FUNCTIONS

# You may need to modify executecmd
# to match how your terminal execute the command.
# See README.md for more detail
executecmd () {
  software=$(printf '%s' "$1" | awk -F ' ' '{print $1}')
  if < "$(find "$XDGDIR1" "$XDGDIR2" *"$software"*.desktop \
    | tail -n 1)" grep "Terminal=false"; then
    printf '%s' "$1" | ${SHELL:-"/bin/sh"}
  else
    $TERMINAL -e $1 | ${SHELL:-"/bin/sh"}
  fi
}

# Open files with xdg-open, if not compression.
# Compressions are extracted by its file extension.
# Use extraction to put content in a new directory.
# To edit default applications in xdg-open,
# sudo $EDITOR /usr/share/applications/mimeinfo.cache
open () {
  mimetype=$(xdg-mime query filetype "$1")
  case "$1" in
    *.tar.bz2|*.tar.xz|*.tbz2) extraction "tar xvjf" "$1" ;;
    *.tar.gz|*.tgz) extraction "tar xvzf" "$1" ;;
    *.lzma) extraction "unlzma" "$1" ;;
    *.bz2) extraction "bunzip2" "$1" ;;
    *.rar) extraction "unrar x -ad" "$1" ;;
    *.gz) extraction "gunzip" "$1" ;;
    *.tar) extraction "tar xvf" "$1" ;;
    *.zip) extraction "unzip" "$1" ;;
    *.Z) extraction "uncompress" "$1" ;;
    *.7z) extraction "7z x" "$1" ;;
    *.xz) extraction "unxz" "$1" ;;
    *.exe) extraction "cabextract" "$1" ;;
    *)
      case "$mimetype" in
        text/*|*x-empty*|*json*)
          # Text file opened in $EDITOR or default opener
          if [ -n "$TERMINAL" ] && [ -n "$EDITOR" ]; then
            $TERMINAL -e $EDITOR "$1"
          else
            ${FM_OPENER:-xdg-open} "$1"
          fi
          ;;
        *)
          appdesktop=$(xdg-mime query default "$mimetype")
          if < "$(find "$XDGDIR1" "$XDGDIR2" -name "$appdesktop" \
            | tail -n 1)" grep "Terminal=false"; then
            ${FM_OPENER:-xdg-open} "$1"
          else
            $TERMINAL -e ${FM_OPENER:-xdg-open} "$1"
          fi
          ;;
      esac
  esac
}


## MENUFUNCTIONS

# Generate directories
# Variables cannot store multiline string, so transform to ':'
# Misbehaving due to space in file/directory name,
# so add ' at beginning and end.

quote () { printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" ; }

menudir () {
  DIRs=$(for dir in *; do [ -d "$dir" ] \
    && printf '%s:' "$dir/"; done \
    | sed "1 s/^/'/; s/$/'/")
}

# Generate dotdirectories
# Delete extra ./ and ../ (Need use = as sed delimiter)
menudotdir () {
  DOTDIRs=$(for dir in .*; do [ -d "$dir" ] \
    && printf '%s:' "$dir/"; done \
    | sed "1 s/^/'/; s/$/'/; s=./==; s=../==")
}

# Generate files
menufile () {
  FILEs=$(for file in *; do [ -f "$file" ] \
    && printf '%s:' "$file"; done \
    | sed "1 s/^/'/; s/$/'/")
}

# Generate dotfiles
menudotfile () {
  DOTFILEs=$(for file in .*; do [ -f "$file" ] && \
    printf '%s:' "$file"; done \
    | sed "1 s/^/'/; s/$/'/")
}

# Generate default menu
menu () {
  menudir
  menudotdir
  menufile
  menudotfile
}

# Update menu in each move
update_menu () {
  list=
  # Rebuild list in every move
  for element in $keeplist; do
    case $element in
      DIRs) menudir && list="$list$DIRs" ;;
      FILEs) menufile && list="$list$FILEs" ;;
      DOTDIRs) menudotdir && list="$list$DOTDIRs" ;;
      DOTFILEs) menudotfile && list="$list$DOTFILEs" ;;
    esac
  done
}

# Generate menu for action choice, only for current directory.
# $1 is the prompt, $2 is the selected background color
Generate_action_menu () {
  while [ -n "$actCHOICE" ]; do
    update_menu
    # Default menu list if no arguments given
    [ -z "$keeplist" ] && menu \
      && list="$DIRs$FILEs$DOTDIRs$DOTFILEs"
    # Generate menu with/without arguments
    # Show only the current directory and one level of parent directory
    twopwd=$(printf '%s' "$PWD" \
      | awk -F '/' '{print $(NF-1)"/"$NF}')
    if [ -n "$multiselection" ]; then
      actCHOICE=$(printf '%s:' "$BACKWARD" "$TARGET" \
        "$ENDSELECTION" "$list" \
        | tr ':' '\n' \
        | sed "s/'//g; /^$/ d" \
        | verticalprompt "$1" "$2")
    elif [ "$allowmulti" != "NotAllowed" ]; then
      actCHOICE=$(printf '%s:' "$BACKWARD" "$TARGET" \
        "$allowmulti" "$allselection" "$list" \
        | tr ':' '\n' \
        | sed "s/'//g; /^$/ d" \
        | verticalprompt "$1" "$2")
    else
      actCHOICE=$(printf '%s:' "$BACKWARD" "$TARGET" "$list" \
        | tr ':' '\n' \
        | sed "s/'//g; /^$/ d" \
        | verticalprompt "$1" "$2")
    fi
    # Outcome matching
    if [ "$actCHOICE" = "$TARGET" ]; then
      HERE=$(printf '%s' "$PWD")
      name=$(printf '%s' "$HERE" \
        | awk -F '/' '{print $NF}')
      [ -n "$multiselection" ] \
        && multilist="$multilist:$HERE" \
        && cd "../"
      break
    elif [ "$actCHOICE" = "$BACKWARD" ]; then
      cd "../" || return
      dmenufm_history
    elif [ "$actCHOICE" = "$ENDSELECTION" ]; then
      multiselection=
      break
    elif [ "$actCHOICE" = "$allowmulti" ]; then
      multiselection="true"
      break
    elif [ "$actCHOICE" = "$allselection" ]; then
      multiselection=
      HERE=$(printf '%s' "$PWD")
      name=$(printf '%s' "$HERE" \
        | awk -F '/' '{print $NF}')
      break
    elif [ -d "$actCHOICE" ]; then
      cd "$actCHOICE" || exit 1
      dmenufm_history
      continue
    elif [ -f "$actCHOICE" ] || [ -n "$rename" ]; then
      HERE=$(printf '%s' "$PWD/$actCHOICE")
      name=$(printf '%s' "$HERE" \
        | awk -F '/' '{print $NF}')
      [ -n "$multiselection" ] \
        && multilist="$multilist:$HERE"
      break
    else
      HERE=
      name=
      multiselection=
      actCHOICE="placeholder"
      break
    fi
  done
}

## PROMPT FUNCTIONS
verticalprompt () {
  # $1 is prompt
  $DMENU -fn "$FM_GENERIC_FONT" -i -sb "$2" -l 10 -p "$1"
}

horizontalprompt () {
  # $1 is prompt
  $DMENU -fn "$FM_GENERIC_FONT" -i -sb "$2" -p "$1" <&-
}

notifyprompt () {
  $DMENU -fn "$FM_NOTIF_FONT" -sb "#d79921" -sf "#1d2021" \
    -nf "#000000" -nb "#000000" -p "$1" <&-
}

# Prompt that used in dangerous action
dangerprompt () {
  # From Luke Smith
  # Use && command to execute the command after "Yes"
  [ "$(printf "No\\nYes" \
    | $DMENU -fn "$FM_DANGER_FONT" -i -p "$1" \
    -nb darkred -sb red -sf white -nf gray )" = "Yes" ]
}


## TOOL FUNCTIONS

# Store every move between directories into history
dmenufm_history () {
  [ ! -d "$FM_PATH" ] \
    && mkdir -p "$FM_PATH"
  dirmark=$(printf '%s' "$PWD" \
    | awk -F '/' '{print $NF}')
  printf '%s\n' "$dirmark - $PWD" \
    | sed "/^$/ d" >> "$FM_HISFILE"
  # Limit the max number of history
  hisnum=$(wc -l "$FM_HISFILE" | awk '{print $1}')
  if [ "$hisnum" -ge "$FM_MAX_HIS_LENGTH" ]; then
    sed "1d" "$FM_HISFILE" > "$FM_HISFILE.backup" \
      && cp "$FM_HISFILE.backup" "$FM_HISFILE"
  fi
}

# Bulk rename function.
# No argument

bulk_rename () {
  if [ -n "$SELECTED" ]; then
    printf '%s\n' "$SELECTED" \
      | sed "s=/$==g" \
      > "$FM_REMFILE.backup"
    awk -F '/' '{print $NF}' "$FM_REMFILE.backup" \
      > "$FM_REMFILE"
    awk -F '/' '{$NF=""; print $0}' "$FM_REMFILE.backup" \
      | tr ' ' '/' \
      > "$FM_REMFILE.dirname"
    open "$FM_REMFILE"
    if [ $(< "$FM_REMFILE" wc -l) \
      -ne $(< "$FM_REMFILE.backup" wc -l) ]; then
      notifyprompt \
        "ERROR: Lines mismatch in rename file; do nothing." \
        && return
    else
      renamevar=$(paste -d ':' \
        "$FM_REMFILE.backup" \
        "$FM_REMFILE.dirname" \
        "$FM_REMFILE")
      # Set IFS for for loop as \n
      oldIFS=$IFS
      IFS="
      "
      for selection in $(printf '%s' "$renamevar"); do
        start=$(printf '%s' "$selection" \
          | awk -F ':' '{print $1}')
        destination=$(printf '%s' "$selection" \
          | awk -F ':' '{print $2$3}')
        if [ "$start" = "$destination" ]; then
          continue
        else
          mv "$start" "$destination"
        fi
      done && notifyprompt "Selected renamed"
      IFS="$oldIFS"
    fi
  else
    return
  fi
}

destmenu () {
  Generate_action_menu "Destination: " \
    "$FM_ACTION_COLOR_LV2" \
    || return \
    && rename= \
    && allowmulti="NotAllowed" \
    && actCHOICE="placeholder"
  [ -n "$HERE" ] \
    && destination="$HERE" \
    && destname="$name"
  if [ -n "$destination" ]; then
    oldIFS=$IFS
    IFS="
    "
    for selection in $(printf '%s' "$SELECTED"); do
      $cmd "$selection" "$destination"
    done && notifyprompt \
      "Selected files / directories $cmdname to $destname"
    IFS="$oldIFS"
  fi

}

# Extract files and make a new directory to contain all the files.
extraction () {
  # $1: command to extract compress
  # $2: compression name
  # Example: extraction "unzip" "$HOME/filename.zip"
  dirname="$(printf '%s' "$2" \
    | awk -F '/' '{printf $NF}' \
    | awk -F '.' '{print $1}')"
  mkdir -p "$dirname" && cd "$dirname" || exit
  $1 "$2"
  notifyprompt "$CHOICE extracted to $dirname"
}

dmenufm_multiselect () {
  # $1 for the prompt when multi selecting
  # $2 for prompt if it is dangerous
  multiprompt=$1
  dangerousmessage=$2
  HERE=
  multilist=
  Generate_action_menu "$multiprompt" "$FM_ACTION_COLOR_BULK" \
    || return && allowmulti="NotAllowed"
  while [ -n "$multiselection" ]; do
    Generate_action_menu "$multiprompt" "$FM_ACTION_COLOR_BULK" \
      || return && allowmulti="NotAllowed"
    HERE=$multilist
    allowmulti="NotAllowed"
  done

  SELECTED=$(printf '%s' "$HERE" \
    | cut -b2- \
    | tr ':' '\n' \
    | uniq)
  allowmulti="NotAllowed"

  [ "$actCHOICE" = "$ENDSELECTION" ] \
    && printf '%s' "$SELECTED" \
    | verticalprompt "Selected files (Enter to continue): " \
    "$FM_ACTION_COLOR_LV1"
  [ "$actCHOICE" = "$ENDSELECTION" ] \
    && [ -n "$dangerousmessage" ] \
    && dangerprompt "$dangerousmessage"
}

# Actions that for dmenufm
# Note: Use `[ -n "$VAR" ]` after each menu to check
# whether the menu is correctly execute.
#   If not, then the later command will not execute.
dmenufm_action () {
  move=$(printf '%s' "$ACTLIST" \
    | tr ':' '\n' \
    | verticalprompt "Actions:" "$FM_GENERIC_COLOR")
  case $move in
    "$FM_PCP")
      # Copy path to xclip, and send notification.
      Generate_action_menu "Copy directory/file path: " \
        "$FM_ACTION_COLOR_LV1" || return
      [ -n "$HERE" ] && printf '%s' "$HERE" \
        | xclip -selection clipboard \
        && notifyprompt "Path $name copied to clipboard."
      ;;
    "$FM_NEW")
      name=$(horizontalprompt "Dir ends w/ slash; File w/o: " \
        "$FM_ACTION_COLOR_LV1") || return
      if [ -n "$( printf "$name" | grep '/')" ]; then
        mkdir -p "$name" \
          && notifyprompt "Directory $name created."
      else
        :>"$name" \
          && notifyprompt "File $name created."
      fi
      ;;
    "$FM_MVR"|"$FM_YAK"|"$FM_LNK")
      cmd="mv" && cmdname="moved" \
        && cmdverb="move" \
        && allowmulti="Bulk move" \
        && allselection="Bulk move all"
      [ "$move" = "$FM_YAK" ] \
        && cmd="cp -R" \
        && cmdname="copied" \
        && cmdverb="copy" \
        && allowmulti="Bulk copy" \
        && allselection="Bulk copy all"
      [ "$move" = "$FM_LNK" ] \
        && cmd="ln -s" \
        && cmdname="linked" \
        && cmdverb="link" \
        && allowmulti="Bulk link" \
        && allselection="Bulk link all"
      Generate_action_menu "Source: " "$FM_ACTION_COLOR_LV1" \
        || return \
        && allowmulti="NotAllowed"
      if [ -n "$multiselection" ]; then
        # Multi-selection mode
        # No need for dangerousmessage
        dmenufm_multiselect "Select files / directories to $cmdverb: " ""
        [ "$actCHOICE" = "$ENDSELECTION" ] \
          && destmenu
      elif [ "$actCHOICE" = "$allselection" ]; then
        # All-selection mode
        SELECTED=$(printf '%s' "$list" \
          | tr ':' '\n' \
          | sed "s/'//g; /^$/ d; s=^=$PWD/=g")
        [ -n "$SELECTED" ] \
          && printf '%s' "$SELECTED" \
          | verticalprompt "Selected files (Enter to continue): " "$FM_ACTION_COLOR_LV1"
        [ -n "$SELECTED" ] \
          && destmenu
      else
        # Single-selection mode
        [ -n "$HERE" ] \
          && SELECTED="$HERE" \
          && rename="true"
        [ -d "$SELECTED" ] \
          && cd "../"
        [ -n "$SELECTED" ] \
          && destmenu
        rename=
      fi
      ;;
    "$FM_REM")
      allowmulti="Bulk Rename"
      allselection="Bulk Rename all"
      Generate_action_menu "Source: " \
        "$FM_ACTION_COLOR_LV1" \
        || return \
        && allowmulti="NotAllowed"
      if [ -n "$multiselection" ]; then
        dmenufm_multiselect "Select files / directories to rename: " ""
        bulk_rename
      elif [ "$actCHOICE" = "$allselection" ]; then
        SELECTED=$(printf '%s' "$list" \
          | tr ':' '\n' \
          | sed "s/'//g; /^$/ d; s=^=$PWD/=g")
        [ -n "$SELECTED" ] \
          && printf '%s' "$SELECTED" \
          | verticalprompt "Selected files (Enter to continue): " "$FM_ACTION_COLOR_LV1"
        bulk_rename
      else
        SELECTED="$HERE"
        bulk_rename
      fi
      ;;
    "$FM_DEL")
      # Allow multiple files to be selected
      allowmulti="Bulk Remove"
      allselection="Bulk Remove all"
      # Choose file/directory in current directory to remove
      Generate_action_menu "Remove file / directory: " \
        "$FM_ACTION_COLOR_LV1" \
        || return \
        && allowmulti="NotAllowed"
      if [ -n "$multiselection" ]; then
        # Multi-selection mode
        dmenufm_multiselect "Select files / directories to remove: " \
          "Are you sure you would like to delete all selected files?"
        if [ $(echo $?) -eq 0 ]; then
          oldIFS=$IFS
          IFS="
          "
          for selection in $(printf '%s' "$SELECTED"); do
            rm -rf "$selection"
          done && notifyprompt "Selected removed."
          IFS="$oldIFS"
        fi
      elif [ "$actCHOICE" = "$allselection" ]; then
        SELECTED=$(printf '%s' "$list" \
          | tr ':' '\n' \
          | sed "s/'//g; /^$/ d; s=^=$PWD/=g")
        printf '%s' "$SELECTED" \
          | verticalprompt "Selected files (Enter to continue): " \
          "$FM_ACTION_COLOR_LV1"
        if dangerprompt "Remove all the files / directories in $name?"; then
          oldIFS=$IFS
          IFS="
          "
          for selection in $(printf '%s' "$SELECTED"); do
            rm -rf "$selection"
          done && notifyprompt "All files / directories removed."
          IFS="$oldIFS"
        fi
      else
        # Single-selection mode
        allowmulti="NotAllowed"
        # Check the chosen on is directory or not
        [ -n "$HERE" ] \
          && [ -d "$HERE" ] \
          && result=$?
        if [ -n "$HERE" ] \
          && dangerprompt "Remove all files and/or subdirectories in $name?" \
          && rm -rf "$HERE"; then
          # If chosen one is directory,
          # send notification and cd to parent directory
          if [ "$result" = "0" ]; then
            notifyprompt "$name removed." || return
            cd "../" || return
            # Reset result value
            result=
          else
            # If not, just send notification.
            notifyprompt "$name removed." || return
          fi
        else
          return
        fi
      fi
      ;;
    "$FM_TRH")
      # Allow multiple files to be selected
      allowmulti="Bulk Trash"
      allselection="Bulk Trash all"
      [ ! -d "$FM_TRASH" ] \
        && mkdir -p "$FM_TRASH"
      trashmenu=$(printf '%s\n' "Move to trash" \
        "Go to trash" "Empty trash" \
        | sed "/^$/ d" \
        | verticalprompt "Dmenufm Trash" "$FM_GENERIC_COLOR")
      case $trashmenu in
        "Move to trash")
          Generate_action_menu "Move file/directory to trash: " \
            "$FM_ACTION_COLOR_LV1" \
            || return \
            && allowmulti="NotAllowed"
          if [ -n "$multiselection" ]; then
            # Multi-selection mode
            dmenufm_multiselect "Select files / directories to move to trash: " \
              "Are you sure you would like to move all selected files to trash?"
            if [ $(echo $?) -eq 0 ]; then
              oldIFS=$IFS
              IFS="
              "
              for selection in $(printf '%s' "$SELECTED"); do
                mv "$selection" "$FM_TRASH"
              done && notifyprompt "Selected files moved to trash"
              IFS="$oldIFS"
            fi
          elif [ "$actCHOICE" = "$allselection" ]; then
            SELECTED=$(printf '%s' "$list" \
              | tr ':' '\n' \
              | sed "s/'//g; /^$/ d; s=^=$PWD/=g")
            printf '%s' "$SELECTED" \
              | verticalprompt "Selected files (Enter to continue): " \
              "$FM_ACTION_COLOR_LV1"
            if dangerprompt "Move all the files / directories in $name to trash?"; then
              oldIFS=$IFS
              IFS="
              "
              for selection in $(printf '%s' "$SELECTED"); do
                mv "$selection" "$FM_TRASH"
              done && notifyprompt "All files / directories moved to trash."
              IFS="$oldIFS"
            fi
          else
            # Single-selection mode
            allowmulti="NotAllowed"
            # Check the chosen on is directory or not
            [ -n "$HERE" ] \
              && [ -d "$HERE" ] \
              && result=$?
            # Say yes in dangerprompt, and send notification. Or do nothing.
            if [ -n "$HERE" ] \
              && dangerprompt "Move all files and/or subdirectories in $name to trash?" \
              && mv "$HERE" "$FM_TRASH"; then
              if [ $result -eq 0 ]; then
                notifyprompt "$name moved to trash." || return
                cd "../" || return
                result=
              else
                notifyprompt "$name moved to trash." || return
              fi
            else
              return
            fi
          fi
          ;;
        "Go to trash")
          cd "$FM_TRASH" || return
          ;;
        "Empty trash")
          # Lesson: You cannot quote a wildcard. No quote on *.
          if dangerprompt "Remove all files and/or directory in trash?" \
            && rm -rf "$FM_TRASH"/*; then
            notifyprompt "Trash is empty." || return
          else
            return
          fi
          ;;
      esac
      ;;
    "$FM_HIS")
      # Use sed command to mimic reverse of cat for POSIX.
      goto=$(sed '1!G;h;$!d' "$FM_HISFILE" \
        | verticalprompt "Dmenufm History" "$FM_GENERIC_COLOR")
      destination=$(printf '%s' "$goto" \
        | awk -F ' - ' '{print $2}')
      cd "$destination" || return
      ;;
    "$FM_BMK")
      markmenu=$(printf '%s\n' "$(cat "$FM_BMKFILE")" \
        "Add BMK" "Delete BMK" \
        | sed "/^$/ d" \
        |  verticalprompt "Dmenufm Bookmark" "$FM_GENERIC_COLOR")
      case "$markmenu" in
        "Add BMK")
          Generate_action_menu "Choose file / directory and add to BMK: " \
            "$FM_ACTION_COLOR_LV1" || return
          mark=$(printf '%s' "$HERE" | awk -F '/' '{print $NF}')
          [ -n "$mark" ] \
            && printf '%s\n' "$mark - $HERE" >> "$FM_BMKFILE"
          [ -n "$mark" ] \
            && sed "/^$/ d" "$FM_BMKFILE" \
            > "$FM_BMKFILE.backup" \
            && cp "$FM_BMKFILE.backup" "$FM_BMKFILE"
          ;;
        "Delete BMK")
          delbmk=$(< "$FM_BMKFILE" \
            verticalprompt "Delete chosen bmk: " "darkred") \
            || return
          # POSIX way of sed -i.
          # Assign address as '=' by '\=pattern='
          # Create backup file and cp to original file.
          [ -n "$delbmk" ] \
            && sed "\=$delbmk= d" "$FM_BMKFILE" \
            > "$FM_BMKFILE.backup" \
            && cp "$FM_BMKFILE.backup" "$FM_BMKFILE"
          ;;
        *)
          destination=$(printf '%s' "$markmenu" \
            | awk -F ' - ' '{print $2}')
          if [ -n "$destination" ]; then
            cd "$destination" || open "$destination"
          fi
      esac
      ;;
    "$FM_CMD")
      cmdmenu=$(printf '%s\n' "$(cat "$FM_CMDFILE")" \
        "Add CMD" "Delete CMD" "Type and execute" \
        | sed "/^$/ d" \
        | verticalprompt "Dmenufm Custom Command" "$FM_GENERIC_COLOR")
      case "$cmdmenu" in
        "Add CMD")
          addcmd=$(horizontalprompt "Recording your command: " \
            "$FM_ACTION_COLOR_LV1") || return
          desp=$(horizontalprompt "Enter command description: " \
            "$FM_ACTION_COLOR_LV1") || return
          [ -n "$addcmd" ] \
            && printf '%s\n' "$addcmd - $desp" \
            >> "$FM_CMDFILE"
          [ -n "$addcmd" ] \
            && sed "/^$/ d" "$FM_CMDFILE" \
            > "$FM_CMDFILE.backup" \
            && cp "$FM_CMDFILE.backup" "$FM_CMDFILE"
          ;;
        "Delete CMD")
          delcmd=$(< "$FM_CMDFILE" \
            verticalprompt "Delete chosen command: " "darkred")
          # POSIX way of sed -i.
          # Assign address as '+' by '\+pattern+'
          # '=' seems not usable in command
          # Create backup file and cp to original file.
          [ -n "$delcmd" ] \
            && sed "\+$delcmd+ d" "$FM_CMDFILE" \
            > "$FM_CMDFILE.backup" \
            && cp "$FM_CMDFILE.backup" "$FM_CMDFILE"
          ;;
        "Type and execute")
          execmd=$(horizontalprompt "Type and execute: " "$FM_ACTION_COLOR_LV1")
          [ -n "$execmd" ] && executecmd "$execmd"
          ;;
        *)
          execmd=$(printf '%s' "$cmdmenu" | awk -F ' - ' '{print $1}')
          executecmd "$execmd"
          ;;
      esac
      ;;
  esac
  dmenufm_menu
}

### MAIN FUNCTIONS

dmenufm_menu () {
  while [ -n "$CHOICE" ]; do
    update_menu
    # Default menu list if no arguments given
    [ -z "$keeplist" ] \
      && menu \
      && list="$DIRs$FILEs$DOTDIRs$DOTFILEs"
    # Generate menu with/without arguments
    # Show only the current directory and one level of parent directory
    twopwd=$(printf '%s' "$PWD" \
      | awk -F '/' '{print $(NF-1)"/"$NF}')
    CHOICE=$(printf '%s:' "$BACKWARD" "$TARGET" \
      "$ACTION" "$TERM" "$list" \
      | tr ':' '\n' \
      | sed "s/'//g; /^$/ d" \
      | verticalprompt "$twopwd" "$FM_GENERIC_COLOR")
    # Outcome matching
    if [ "$CHOICE" = "$TARGET" ]; then
      open "$PWD"
    elif [ "$CHOICE" = "$BACKWARD" ]; then
      cd "../"
      dmenufm_history
    elif [ "$CHOICE" = "$ACTION" ]; then
      dmenufm_action
    elif [ "$CHOICE" = "$TERM" ]; then
      $TERMINAL
    elif [ -d "$CHOICE" ]; then
      cd "$CHOICE" || exit 1
      dmenufm_history
      continue
    elif [ -f "$CHOICE" ]; then
      open "$PWD/$CHOICE"
    else
      break
    fi
  done
}

### ARGUMENTS

while [ -n "$1" ]; do
  case $1 in
    "-d"|"--directory" )
      keeplist="${keeplist} DIRs"
      ;;
    "-f"|"--file" )
      keeplist="${keeplist} FILEs"
      ;;
    "-D"|"--dotdirectory" )
      keeplist="${keeplist} DOTDIRs"
      ;;
    "-F"|"--dotfile" )
      keeplist="${keeplist} DOTFILEs"
      ;;
    "-p"|"--lastpath" )
      outputpath="placeholder"
      ;;
    "-h"|"--help" )
      printf " Optional arguments for custom usage:
      -d | --directory: only directories
      -f | --file: only show files
      -D | --dotdirectory: only show hidden directories
      -F | --dotfile: only show hidden files
      -p | --lastpath: opens in last working directory (cd on exit)
      -h | --help: Show this message\\n"
      exit 0
      ;;
    *)
      if [ -d "$1" ]; then
        cd "$1"
      else
        printf '%s\n' "Invalid option"
        exit 1
      fi
      ;;
  esac
  shift
done

### RUN THE MAIN FUNCTION

# --lastpath option:
[ -n "$outputpath" ] && cd "$(cat "$FM_LASTPATH")"

dmenufm_menu

printf '%s' "$PWD" > "$FM_LASTPATH"
