You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

385 lines
17 KiB

  1. # -*- mode: sh; sh-indentation: 4; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
  2. # Copyright (c) 2018 Sebastian Gniazdowski
  3. # Copyright (c) 2018, 2019 Philippe Troin (F-i-f on GitHub)
  4. #
  5. # Theme support using ini-files.
  6. #
  7. zmodload zsh/zutil 2>/dev/null
  8. emulate -LR zsh
  9. setopt extendedglob typesetsilent warncreateglobal
  10. autoload colors; colors
  11. typeset -g FAST_WORK_DIR
  12. : ${FAST_WORK_DIR:=$FAST_BASE_DIR}
  13. FAST_WORK_DIR=${~FAST_WORK_DIR}
  14. local -A map
  15. map=( "XDG:" "${XDG_CONFIG_HOME:-$HOME/.config}/fsh/"
  16. "LOCAL:" "/usr/local/share/fsh/"
  17. "HOME:" "$HOME/.fsh/"
  18. "OPT:" "/opt/local/share/fsh/"
  19. )
  20. FAST_WORK_DIR=${${FAST_WORK_DIR/(#m)(#s)(XDG|LOCAL|HOME|OPT):(#c0,1)/${map[${MATCH%:}:]}}%/}
  21. local OPT_HELP OPT_VERBOSE OPT_QUIET OPT_RESET OPT_LIST OPT_TEST OPT_SECONDARY OPT_SHOW OPT_COPY OPT_OV_RESET
  22. local OPT_PALETTE OPT_CDWD OPT_XCHG OPT_OV_XCHG
  23. local -A opthash
  24. zparseopts -E -D -A opthash h -help v -verbose q -quiet r -reset l -list t -test -secondary \
  25. s -show -copy-shipped-theme: R -ov-reset p -palette w -workdir \
  26. x -xchg y -ov-xchg || \
  27. { echo "Improper options given, see help (-h/--help)"; return 1; }
  28. (( ${+opthash[-h]} + ${+opthash[--help]} )) && OPT_HELP="-h"
  29. (( ${+opthash[-v]} + ${+opthash[--verbose]} )) && OPT_VERBOSE="-v"
  30. (( ${+opthash[-q]} + ${+opthash[--quiet]} )) && OPT_QUIET="-q"
  31. (( ${+opthash[-r]} + ${+opthash[--reset]} )) && OPT_RESET="-r"
  32. (( ${+opthash[-l]} + ${+opthash[--list]} )) && OPT_LIST="-l"
  33. (( ${+opthash[-t]} + ${+opthash[--test]} )) && OPT_TEST="-t"
  34. (( ${+opthash[--secondary]} )) && OPT_SECONDARY="--secondary"
  35. (( ${+opthash[-s]} + ${+opthash[--show]} )) && OPT_SHOW="-s"
  36. (( ${+opthash[--copy-shipped-theme]} )) && OPT_COPY="${opthash[--copy-shipped-theme]}"
  37. (( ${+opthash[-R]} + ${+opthash[--ov-reset]} )) && OPT_OV_RESET="-R"
  38. (( ${+opthash[-p]} + ${+opthash[--palette]} )) && OPT_PALETTE="-p"
  39. (( ${+opthash[-w]} + ${+opthash[--workdir]} )) && OPT_CDWD="-w"
  40. (( ${+opthash[-x]} + ${+opthash[--xchg]} )) && OPT_XCHG="-x"
  41. (( ${+opthash[-y]} + ${+opthash[--ov-xchg]} )) && OPT_OV_XCHG="-y"
  42. local -a match mbegin mend
  43. local MATCH; integer MBEGIN MEND
  44. [[ -n "$OPT_CDWD" ]] && {
  45. builtin cd $FAST_WORK_DIR
  46. return 0
  47. }
  48. [[ -n "$OPT_PALETTE" ]] && {
  49. local n
  50. local -a __colors
  51. for n in {000..255}
  52. do
  53. __colors+=("%F{$n}$n%f")
  54. done
  55. print -cP $__colors
  56. return
  57. }
  58. [[ -n "$OPT_SHOW" ]] && {
  59. print -r -- "Currently active theme: ${fg_bold[yellow]}$FAST_THEME_NAME$reset_color"
  60. ( source "$FAST_WORK_DIR"/current_theme.zsh 2>/dev/null && print "Main theme (loaded at startup of a session): ${fg_bold[yellow]}$FAST_THEME_NAME$reset_color" || print "No main theme is set"; )
  61. return 0
  62. }
  63. [[ -n "$OPT_COPY" ]] && {
  64. [[ ! -f "$FAST_BASE_DIR"/themes/"${OPT_COPY%.ini}.ini" ]] && { print "Theme \`$OPT_COPY' doesn't exist in FSH plugin dir ($FAST_BASE_DIR/themes)"; return 1; }
  65. [[ ! -r "$FAST_BASE_DIR"/themes/"${OPT_COPY%.ini}.ini" ]] && { print "Theme \`$OPT_COPY' isn't readable in FSH plugin dir ($FAST_BASE_DIR/themes)"; return 1; }
  66. [[ -n "$1" ]] && {
  67. [[ ! -e "$1" && ! -e ${1:h} ]] && { print "Destination path doesn't exist, aborting"; return 1; }
  68. }
  69. command cp -vf "$FAST_BASE_DIR"/themes/"${OPT_COPY%.ini}.ini" "${${1:-.}%.ini}.ini" || return 1
  70. return 0
  71. }
  72. [[ -n "$OPT_RESET" ]] && { command rm -f "$FAST_WORK_DIR"/{current_theme.zsh,secondary_theme.zsh}; [[ -z "$OPT_QUIET" ]] && print "Reset done (no theme is now set, restart is required)"; return 0; }
  73. [[ -n "$OPT_OV_RESET" ]] && { command rm -f "$FAST_WORK_DIR"/theme_overlay.zsh; [[ -z "$OPT_QUIET" ]] && print "Overlay-reset done, it is inactive (restart is required)"; return 0; }
  74. [[ -n "$OPT_LIST" ]] && {
  75. [[ -z "$OPT_QUIET" ]] && print -r -- "Available themes:"
  76. print -rl -- "$FAST_BASE_DIR"/themes/*.ini(:t:r)
  77. return 0
  78. }
  79. [[ -n "$OPT_HELP" ]] && {
  80. print -r -- "Usage: fast-theme [-h/--help] [-v/--verbose] [-q/--quiet] [-t/--test] <theme-name|theme-path>"
  81. print -r -- " fast-theme [-r/--reset] [-l/--list] [-s/--show] [-p/--palette] [-w/--workdir]"
  82. print -r -- " fast-theme --copy-shipped-theme {theme-name} [destination-path]"
  83. print -r -- ""
  84. print -r -- "Default action (after providing <theme-name> or <theme-path>) is to switch"
  85. print -r -- "current session and any future sessions to the new theme. Using <theme-path>,"
  86. print -r -- "i.e.: a path to an ini file means using custom, own theme. The path can use an"
  87. print -r -- "\"XDG:\" shorthand (e.g.: \"XDG:mytheme\") that will point to ~/.config/fsh/<theme>.ini"
  88. print -r -- "(or \$XDG_CONFIG_HOME/fsh/<theme>.ini in general if the variable is set in the"
  89. print -r -- "environment). If the INI file pointed in the path is \"*overlay*\", then it is"
  90. print -r -- "not a full theme, but an additional theme-snippet that overwrites only selected"
  91. print -r -- "styles of the main theme."
  92. print -r -- ""
  93. print -r -- "Other path-shorthands:"
  94. print -r -- "LOCAL: = /usr/local/share/fsh/"
  95. print -r -- "HOME: = $HOME/.fsh/"
  96. print -r -- "OPT: = /opt/local/share/fsh/"
  97. print -r -- ""
  98. print -r -- "-r/--reset - unset any theme, use default highlighting (requires restart)"
  99. print -r -- "-R/--ov-reset - unset overlay, use styles only from main-theme (requires restart)"
  100. print -r -- "-l/--list - list names of available themes"
  101. print -r -- "-t/--test - show test block of code after switching theme"
  102. print -r -- "-s/--show - get and display the theme currently being set"
  103. print -r -- "-p/--palette - just print all 256 colors and exit (useful when creating a theme)"
  104. print -r -- "-w/--workdir - cd into \$FAST_WORK_DIR (if not set, then into the plugin directory)"
  105. print -r -- "-v/--verbose - more messages during operation"
  106. print -r -- "-q/--quiet - no default messages"
  107. print -r -- ""
  108. print -r -- "The option --copy-shipped-theme allows easy copying of one of the 6 shipped"
  109. print -r -- "themes into given destination path. Normal use means changing directory to"
  110. print -r -- "e.g.: ~/.config/fsh, and then issuing e.g.: \`fast-theme --copy-shipped-theme"
  111. print -r -- "clean mytheme', to obtain a template for own new theme."
  112. return 0
  113. }
  114. [[ -z "$1" ]] && { print -u2 "Provide a theme (its name or path to its file) to switch to, aborting (see -h/--help)"; return 1; }
  115. # FAST_HIGHLIGHT_STYLES key onto ini-file key
  116. map=(
  117. default "-"
  118. unknown-token "-"
  119. reserved-word "-"
  120. subcommand "- reserved-word"
  121. alias "- command builtin"
  122. suffix-alias "- alias command builtin"
  123. builtin "-"
  124. function "- builtin command"
  125. command "-"
  126. precommand "- command"
  127. commandseparator "-"
  128. hashed-command "- command"
  129. path "-"
  130. path_pathseparator "pathseparator"
  131. globbing "- back-or-dollar-double-quoted-argument" # fallback: variable in string "text $var text"
  132. globbing-ext "- double-quoted-argument" # fallback: the string "abc..."
  133. history-expansion "-"
  134. single-hyphen-option "- single-quoted-argument"
  135. double-hyphen-option "- double-quoted-argument"
  136. back-quoted-argument "-"
  137. single-quoted-argument "-"
  138. double-quoted-argument "-"
  139. dollar-quoted-argument "-"
  140. back-or-dollar-double-quoted-argument "- back-dollar-quoted-argument"
  141. back-dollar-quoted-argument "- back-or-dollar-double-quoted-argument"
  142. assign "- reserved-word"
  143. redirection "- reserved-word"
  144. comment "-"
  145. variable "-"
  146. mathvar "- forvar variable"
  147. mathnum "- fornum"
  148. matherr "- incorrect-subtle"
  149. assign-array-bracket "-"
  150. for-loop-variable "forvar mathvar variable"
  151. for-loop-number "fornum mathnum"
  152. for-loop-operator "foroper reserved-word"
  153. for-loop-separator "forsep commandseparator"
  154. exec-descriptor "- reserved-word"
  155. here-string-tri "-"
  156. here-string-text "- subtle-bg"
  157. here-string-var "- back-or-dollar-double-quoted-argument"
  158. secondary "-"
  159. recursive-base "- default"
  160. case-input "- variable"
  161. case-parentheses "- reserved-word"
  162. case-condition "- correct-subtle"
  163. correct-subtle "-"
  164. incorrect-subtle "-"
  165. subtle-separator "- commandseparator"
  166. subtle-bg "- correct-subtle"
  167. path-to-dir "- path"
  168. paired-bracket "- subtle-bg correct-subtle"
  169. bracket-level-1 "-"
  170. bracket-level-2 "-"
  171. bracket-level-3 "-"
  172. global-alias "- alias suffix-alias"
  173. single-sq-bracket "-"
  174. double-sq-bracket "-"
  175. double-paren "-"
  176. optarg-string "- double-quoted-argument"
  177. optarg-number "- mathnum"
  178. )
  179. # In which order to generate entries
  180. local -a order
  181. order=(
  182. default unknown-token reserved-word alias suffix-alias builtin function command precommand
  183. commandseparator hashed-command path path_pathseparator globbing globbing-ext history-expansion
  184. single-hyphen-option double-hyphen-option back-quoted-argument single-quoted-argument
  185. double-quoted-argument dollar-quoted-argument back-or-dollar-double-quoted-argument
  186. back-dollar-quoted-argument assign redirection comment variable mathvar
  187. mathnum matherr assign-array-bracket for-loop-variable for-loop-number for-loop-operator
  188. for-loop-separator exec-descriptor here-string-tri here-string-text here-string-var secondary
  189. case-input case-parentheses case-condition correct-subtle incorrect-subtle subtle-separator subtle-bg
  190. path-to-dir paired-bracket bracket-level-1 bracket-level-2 bracket-level-3
  191. global-alias subcommand single-sq-bracket double-sq-bracket double-paren
  192. optarg-string optarg-number recursive-base
  193. )
  194. [[ -n "$OPT_VERBOSE" ]] && print "Number of styles available for customization: ${#order}"
  195. # Named colors
  196. local -a color
  197. color=( red green blue yellow cyan magenta black white default )
  198. #
  199. # Execution starts here
  200. #
  201. local -A out
  202. local THEME_NAME THEME_PATH="$1"
  203. if [[ "$1" = */* || "$1" = (XDG|LOCAL|HOME|OPT):* ]]; then
  204. 1="${${1/(#s)XDG:/${${XDG_CONFIG_HOME:-$HOME/.config}%/}/fsh/}%.ini}.ini"
  205. 1="${${1/(#s)LOCAL://usr/local/share/fsh/}%.ini}.ini"
  206. 1="${${1/(#s)HOME:/$HOME/.fsh/}%.ini}.ini"
  207. 1="${${1/(#s)OPT://opt/local/share/fsh/}%.ini}.ini"
  208. 1=${~1} # allow user to quote ~
  209. [[ ! -f "$1" ]] && { print -u2 "No such theme \`$1', aborting"; return 1; }
  210. [[ ! -r "$1" ]] && { print -u2 "Theme \`$1' unreadable, aborting"; return 1; }
  211. THEME_NAME="${1:t:r}"
  212. .fast-read-ini-file "$1" out ""
  213. else
  214. [[ ! -f "$FAST_BASE_DIR/themes/$1.ini" ]] && { print -u2 "No such theme \`$1', aborting"; return 1; }
  215. [[ ! -r "$FAST_BASE_DIR/themes/$1.ini" ]] && { print -u2 "Theme \`$1' unreadable, aborting"; return 1; }
  216. THEME_NAME="$1"
  217. .fast-read-ini-file "$FAST_BASE_DIR/themes/$1.ini" out ""
  218. fi
  219. [[ -z "$OPT_SECONDARY" ]] && { [[ "$THEME_NAME" = *"overlay"* ]] && local outfile="theme_overlay.zsh" || local outfile="current_theme.zsh"; } || local outfile="secondary_theme.zsh"
  220. [[ -z "$OPT_XCHG" && -z "$OPT_OV_XCHG" ]] && command rm -f "$FAST_WORK_DIR"/"$outfile"
  221. # Set a zstyle and a parameter to carry theme name
  222. if [[ -z "$OPT_SECONDARY" && -z "$OPT_XCHG" && -z "$OPT_OV_XCHG" ]]; then
  223. [[ "$THEME_NAME" != *"overlay"* ]] && {
  224. print -r -- 'zstyle :plugin:fast-syntax-highlighting theme "'"$THEME_NAME"'"' >>! "$FAST_WORK_DIR"/"$outfile"
  225. print -r -- 'typeset -g FAST_THEME_NAME="'"$THEME_NAME"'"' >>! "$FAST_WORK_DIR"/"$outfile"
  226. zstyle :plugin:fast-syntax-highlighting theme "$THEME_NAME"
  227. typeset -g FAST_THEME_NAME="$THEME_NAME"
  228. }
  229. elif [[ -z "$OPT_XCHG" && -z "$OPT_OV_XCHG" ]]; then
  230. local FAST_THEME_NAME="$THEME_NAME"
  231. fi
  232. # Store from which file the theme or overlay is being loaded
  233. [[ "$THEME_NAME" != *"overlay" && -z "$OPT_OV_XCHG" ]] && FAST_HIGHLIGHT_STYLES[${FAST_THEME_NAME}-path]="$THEME_PATH" || FAST_HIGHLIGHT_STYLES[${FAST_THEME_NAME}-ov-path]="$THEME_PATH"
  234. # Generate current_theme.zsh or secondary_theme.zsh, traversing ini-file associative array
  235. local k kk
  236. local inikey inival result result2 first_val isbg
  237. integer ov_counter=0 first
  238. for k in "${order[@]}"; do
  239. first=1
  240. for kk in ${(s. .)map[$k]} default; do
  241. [[ "$kk" = "-" ]] && kk="$k"
  242. (( first )) && first_val="$kk"
  243. inikey="${out[(i)<*>_${kk}]}"
  244. [[ -n "$inikey" ]] && {
  245. (( !first )) && [[ -z "$OPT_QUIET" ]] && {
  246. [[ $kk = default ]] && {
  247. [[ "$THEME_NAME" != *"overlay"* ]] && print "Missing style: $first_val"
  248. } || print "For style $first_val, went for fallback style $kk"
  249. }
  250. break
  251. }
  252. first=0
  253. [[ "$THEME_NAME" = *"overlay"* ]] && break
  254. done
  255. # ORIG: Clear orig-style when loading a new theme, not overlay
  256. [[ -z "$OPT_OV_XCHG" ]] && unset "FAST_HIGHLIGHT_STYLES[orig-style-$k]"
  257. # ORIG: Restore orig-style when loading a new overlay
  258. [[ -n "$OPT_OV_XCHG" && -n "${FAST_HIGHLIGHT_STYLES[orig-style-$k]}" ]] && { FAST_HIGHLIGHT_STYLES[${FAST_THEME_NAME}$k]="${FAST_HIGHLIGHT_STYLES[orig-style-$k]}"; unset "FAST_HIGHLIGHT_STYLES[orig-style-$k]"; }
  259. # Set only the keys provided in theme
  260. [[ -z "$inikey" ]] && { [[ -z "$OPT_QUIET" && "$THEME_NAME" != *"overlay"* ]] && print "Missing style $first_val"; continue; }
  261. inival="${out[$inikey]}"
  262. if [[ "$k" = "secondary" && -z "$OPT_SECONDARY" && -n "$inival" ]]; then
  263. fast-theme -q --secondary "$inival"
  264. fi
  265. result=""
  266. if [[ $k = secondary ]]; then
  267. result="$inival"
  268. else
  269. for kk in ${(s:,:)inival}
  270. do
  271. if [[ $kk = (none|(no-|)(bold|blink|conceal|reverse|standout|underline)) ]]; then
  272. result+="${result:+,}$kk"
  273. else
  274. isbg=0
  275. if [[ $kk = bg:* ]]; then
  276. isbg=1
  277. kk=${kk#bg:}
  278. fi
  279. if [[ $kk = (${(~j:|:)color}) || $kk = [0-9]## || $kk = \#[0-9a-fA-F](#c6,6) ]]; then
  280. result+="${result:+,}"
  281. (( isbg )) && result+="bg=" || result+="fg="
  282. result+="$kk"
  283. else
  284. print "cannot parse style $k: unknown color or style element $kk"
  285. fi
  286. fi
  287. done
  288. fi
  289. if [[ "$THEME_NAME" = *"overlay"* || -n "$OPT_OV_XCHG" ]]; then
  290. (( ++ ov_counter ))
  291. [[ -z "$OPT_XCHG$OPT_OV_XCHG" ]] && print -r -- ': ${FAST_HIGHLIGHT_STYLES[${FAST_THEME_NAME}'"$k"']::='"$result"'}' >>! "$FAST_WORK_DIR"/"$outfile"
  292. # ORIG: Save original value of the overwritten style
  293. FAST_HIGHLIGHT_STYLES[orig-style-$k]=${FAST_HIGHLIGHT_STYLES[${FAST_THEME_NAME}$k]}
  294. # Overwrite theme's style
  295. FAST_HIGHLIGHT_STYLES[${FAST_THEME_NAME}$k]="$result"
  296. else
  297. [[ -z "$OPT_XCHG$OPT_OV_XCHG" ]] && print -r -- ': ${FAST_HIGHLIGHT_STYLES['"${FAST_THEME_NAME}$k"']:='"$result"'}' >>! "$FAST_WORK_DIR"/"$outfile"
  298. FAST_HIGHLIGHT_STYLES[${FAST_THEME_NAME}$k]="$result"
  299. fi
  300. done
  301. # This can overwrite some of *_STYLES fields
  302. # Re-apply overlay on top of the theme we switched to
  303. [[ "$THEME_NAME" != *"overlay"* ]] && [[ -r "$FAST_WORK_DIR"/theme_overlay.zsh ]] && source "$FAST_WORK_DIR"/theme_overlay.zsh
  304. zcompile $FAST_WORK_DIR/$outfile 2>/dev/null
  305. [[ -z "$OPT_QUIET" ]] && {
  306. if [[ "$THEME_NAME" != *"overlay"* ]]; then
  307. print "Switched to theme \`$THEME_NAME' (current session, and future sessions)" || \
  308. else
  309. print "Processed the overlay ($ov_counter keys found), it is now active (for current session, and future sessions)"
  310. fi
  311. }
  312. [[ -n "$OPT_TEST" ]] && {
  313. print -zr '
  314. # Subshell, assignments, math-mode
  315. echo $(cat /etc/hosts |& grep -i "hello337")
  316. local param1="text ${+variable[test]} text ${var} text"; typeset param2='"'"'other $variable'"'"'
  317. math=$(( 10 + HISTSIZ + HISTSIZE + $SAVEHIST )) size=$(( 0 ))
  318. # Programming-like usage, bracket matching - through distinct colors; note the backslash quoting
  319. for (( ii = 1; ii <= size; ++ ii )); do
  320. if [[ "${cmds[ii]} string" = "| string" ]]
  321. then
  322. sidx=${buffer[(in:ii:)\$\(?#[^\\\\]\)]} # find opening cmd-subst
  323. (( sidx <= len + 100 )) && {
  324. eidx=${buffer[(b:sidx:ii)[^\\\\]\)]} # find closing cmd-subst
  325. }
  326. fi
  327. done
  328. # Regular command-line usage
  329. repeat 0 {
  330. zsh -i -c "cat /etc/shells* | grep -x --line-buffered -i '"'/bin/zsh'"'"
  331. builtin exit $return_value
  332. fast-theme -tq default
  333. fsh-alias -tq default-X # alias '"'"'fsh-alias=fast-theme'"'"' works just like the previous line
  334. command -v git | grep ".+git" && echo $'"'"'Git is installed'"'"'
  335. git checkout -m --ours /etc/shells && git status-X
  336. gem install asciidoctor
  337. cat <<<$PATH | tr : \\n > /dev/null 2>/usr/local
  338. man -a fopen fopen-X
  339. CFLAGS="-g -Wall -O0" ./configure
  340. }
  341. '
  342. }
  343. return 0
  344. # vim:ft=zsh:et:sw=4:sts=4