39 |
40 | > Terminal is [iTerm2](https://iterm2.com/) — Font is [JetBrains Mono](https://www.jetbrains.com/lp/mono/) — Terminal theme is [Seoul256](https://github.com/junegunn/seoul256.vim)
41 | >
42 | > See how to make your terminal look exactly like the demo [here](https://github.com/reobin/typewritten/discussions/128)
43 |
44 | ## Features
45 |
46 | - Asynchronous git info
47 | - Fully customizable
48 | - [Colors for any of the prompt sections](https://typewritten.dev/#/prompt_color_customization)
49 | - [Prompt layout](https://typewritten.dev/#/prompt_customization?id=typewritten_prompt_layout)
50 | - [Prompt symbol](https://typewritten.dev/#/prompt_customization?id=typewritten_symbol)
51 | - [Constant display of git home directory](https://typewritten.dev/#/prompt_customization?id=typewritten_git_relative_path)
52 | - [Cursor](https://typewritten.dev/#/prompt_customization?id=typewritten_cursor)
53 | - [Prefix on right prompt](https://typewritten.dev/#/prompt_customization?id=typewritten_right_prompt_prefix)
54 |
55 | ## Quick start
56 |
57 | ### [npm](https://npmjs.com/get-npm)
58 |
59 | ```shell
60 | npm install -g typewritten
61 | ```
62 |
63 | That's it. The script will make the necessary symlinks to `fpath` and set the prompt in your `.zshrc`.
64 |
65 | ### [Homebrew](https://brew.sh)
66 |
67 | ```shell
68 | brew install typewritten
69 | ```
70 |
71 | Then load typewritten in your `.zshrc` by using zsh prompinit:
72 |
73 | ```shell
74 | autoload -U promptinit; promptinit
75 | prompt typewritten
76 | ```
77 |
78 | ### Manual
79 |
80 | Clone the typewritten repository somewhere you can easily link. I recommend creating a `.zsh` directory at root.
81 |
82 | ```shell
83 | mkdir -p "$HOME/.zsh"
84 | git clone https://github.com/reobin/typewritten.git "$HOME/.zsh/typewritten"
85 | ```
86 |
87 | Load typewritten in your `.zshrc` by using zsh prompinit:
88 |
89 | ```shell
90 | fpath+=$HOME/.zsh/typewritten
91 | autoload -U promptinit; promptinit
92 | prompt typewritten
93 | ```
94 |
95 | Note: if using `oh-my-zsh`, set `ZSH_THEME=""` in your `.zshrc` to disable oh-my-zsh themes.
96 |
97 | ### Other ways to install
98 |
99 | Many other ways to install typewritten are available in the [docs](https://typewritten.dev/#/installation)
100 |
101 | ## Customization
102 |
103 | typewritten is customizable in many ways. To keep the readme file as lean as possible, the documentation was moved to [https://typewritten.dev](https://typewritten.dev).
104 |
105 | The documentation is separated into two parts:
106 |
107 | - [Prompt customization](https://typewritten.dev/#/prompt_customization), everything to do with how and where the info is displayed
108 | - [Prompt color customization](https://typewritten.dev/#/prompt_color_customization), set a custom color for any of the prompt sections
109 |
110 | **Example of 3 customized typewritten prompts**
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | ## Contributors ✨
121 |
122 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
123 |
124 |
125 |
126 |
127 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
182 |
183 | ## Supporters 🌞
184 |
185 | [](https://github.com/reobin/typewritten/stargazers)
186 |
187 | [](https://github.com/reobin/typewritten/network/members)
188 |
189 | ## Credits
190 |
191 | - `pure` layout is inspired by [Pure](https://github.com/sindresorhus/pure)
192 | - `npm` install and uninstall scripts are from [Spaceship prompt](https://github.com/spaceship-prompt/spaceship-prompt)
193 |
--------------------------------------------------------------------------------
/async:
--------------------------------------------------------------------------------
1 | async.zsh
--------------------------------------------------------------------------------
/async.zsh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env zsh
2 |
3 | #
4 | # zsh-async
5 | #
6 | # version: v1.8.6
7 | # author: Mathias Fredriksson
8 | # url: https://github.com/mafredri/zsh-async
9 | #
10 |
11 | typeset -g ASYNC_VERSION=1.8.6
12 | # Produce debug output from zsh-async when set to 1.
13 | typeset -g ASYNC_DEBUG=${ASYNC_DEBUG:-0}
14 |
15 | # Execute commands that can manipulate the environment inside the async worker. Return output via callback.
16 | _async_eval() {
17 | local ASYNC_JOB_NAME
18 | # Rename job to _async_eval and redirect all eval output to cat running
19 | # in _async_job. Here, stdout and stderr are not separated for
20 | # simplicity, this could be improved in the future.
21 | {
22 | eval "$@"
23 | } &> >(ASYNC_JOB_NAME=[async/eval] _async_job 'command -p cat')
24 | }
25 |
26 | # Wrapper for jobs executed by the async worker, gives output in parseable format with execution time
27 | _async_job() {
28 | # Disable xtrace as it would mangle the output.
29 | setopt localoptions noxtrace
30 |
31 | # Store start time for job.
32 | float -F duration=$EPOCHREALTIME
33 |
34 | # Run the command and capture both stdout (`eval`) and stderr (`cat`) in
35 | # separate subshells. When the command is complete, we grab write lock
36 | # (mutex token) and output everything except stderr inside the command
37 | # block, after the command block has completed, the stdin for `cat` is
38 | # closed, causing stderr to be appended with a $'\0' at the end to mark the
39 | # end of output from this job.
40 | local jobname=${ASYNC_JOB_NAME:-$1} out
41 | out="$(
42 | local stdout stderr ret tok
43 | {
44 | stdout=$(eval "$@")
45 | ret=$?
46 | duration=$(( EPOCHREALTIME - duration )) # Calculate duration.
47 |
48 | print -r -n - $'\0'${(q)jobname} $ret ${(q)stdout} $duration
49 | } 2> >(stderr=$(command -p cat) && print -r -n - " "${(q)stderr}$'\0')
50 | )"
51 | if [[ $out != $'\0'*$'\0' ]]; then
52 | # Corrupted output (aborted job?), skipping.
53 | return
54 | fi
55 |
56 | # Grab mutex lock, stalls until token is available.
57 | read -r -k 1 -p tok || return 1
58 |
59 | # Return output ().
60 | print -r -n - "$out"
61 |
62 | # Unlock mutex by inserting a token.
63 | print -n -p $tok
64 | }
65 |
66 | # The background worker manages all tasks and runs them without interfering with other processes
67 | _async_worker() {
68 | # Reset all options to defaults inside async worker.
69 | emulate -R zsh
70 |
71 | # Make sure monitor is unset to avoid printing the
72 | # pids of child processes.
73 | unsetopt monitor
74 |
75 | # Redirect stderr to `/dev/null` in case unforseen errors produced by the
76 | # worker. For example: `fork failed: resource temporarily unavailable`.
77 | # Some older versions of zsh might also print malloc errors (know to happen
78 | # on at least zsh 5.0.2 and 5.0.8) likely due to kill signals.
79 | exec 2>/dev/null
80 |
81 | # When a zpty is deleted (using -d) all the zpty instances created before
82 | # the one being deleted receive a SIGHUP, unless we catch it, the async
83 | # worker would simply exit (stop working) even though visible in the list
84 | # of zpty's (zpty -L). This has been fixed around the time of Zsh 5.4
85 | # (not released).
86 | if ! is-at-least 5.4.1; then
87 | TRAPHUP() {
88 | return 0 # Return 0, indicating signal was handled.
89 | }
90 | fi
91 |
92 | local -A storage
93 | local unique=0
94 | local notify_parent=0
95 | local parent_pid=0
96 | local coproc_pid=0
97 | local processing=0
98 |
99 | local -a zsh_hooks zsh_hook_functions
100 | zsh_hooks=(chpwd periodic precmd preexec zshexit zshaddhistory)
101 | zsh_hook_functions=(${^zsh_hooks}_functions)
102 | unfunction $zsh_hooks &>/dev/null # Deactivate all zsh hooks inside the worker.
103 | unset $zsh_hook_functions # And hooks with registered functions.
104 | unset zsh_hooks zsh_hook_functions # Cleanup.
105 |
106 | close_idle_coproc() {
107 | local -a pids
108 | pids=(${${(v)jobstates##*:*:}%\=*})
109 |
110 | # If coproc (cat) is the only child running, we close it to avoid
111 | # leaving it running indefinitely and cluttering the process tree.
112 | if (( ! processing )) && [[ $#pids = 1 ]] && [[ $coproc_pid = $pids[1] ]]; then
113 | coproc :
114 | coproc_pid=0
115 | fi
116 | }
117 |
118 | child_exit() {
119 | close_idle_coproc
120 |
121 | # On older version of zsh (pre 5.2) we notify the parent through a
122 | # SIGWINCH signal because `zpty` did not return a file descriptor (fd)
123 | # prior to that.
124 | if (( notify_parent )); then
125 | # We use SIGWINCH for compatibility with older versions of zsh
126 | # (pre 5.1.1) where other signals (INFO, ALRM, USR1, etc.) could
127 | # cause a deadlock in the shell under certain circumstances.
128 | kill -WINCH $parent_pid
129 | fi
130 | }
131 |
132 | # Register a SIGCHLD trap to handle the completion of child processes.
133 | trap child_exit CHLD
134 |
135 | # Process option parameters passed to worker.
136 | while getopts "np:uz" opt; do
137 | case $opt in
138 | n) notify_parent=1;;
139 | p) parent_pid=$OPTARG;;
140 | u) unique=1;;
141 | z) notify_parent=0;; # Uses ZLE watcher instead.
142 | esac
143 | done
144 |
145 | # Terminate all running jobs, note that this function does not
146 | # reinstall the child trap.
147 | terminate_jobs() {
148 | trap - CHLD # Ignore child exits during kill.
149 | coproc : # Quit coproc.
150 | coproc_pid=0 # Reset pid.
151 |
152 | if is-at-least 5.4.1; then
153 | trap '' HUP # Catch the HUP sent to this process.
154 | kill -HUP -$$ # Send to entire process group.
155 | trap - HUP # Disable HUP trap.
156 | else
157 | # We already handle HUP for Zsh < 5.4.1.
158 | kill -HUP -$$ # Send to entire process group.
159 | fi
160 | }
161 |
162 | killjobs() {
163 | local tok
164 | local -a pids
165 | pids=(${${(v)jobstates##*:*:}%\=*})
166 |
167 | # No need to send SIGHUP if no jobs are running.
168 | (( $#pids == 0 )) && continue
169 | (( $#pids == 1 )) && [[ $coproc_pid = $pids[1] ]] && continue
170 |
171 | # Grab lock to prevent half-written output in case a child
172 | # process is in the middle of writing to stdin during kill.
173 | (( coproc_pid )) && read -r -k 1 -p tok
174 |
175 | terminate_jobs
176 | trap child_exit CHLD # Reinstall child trap.
177 | }
178 |
179 | local request do_eval=0
180 | local -a cmd
181 | while :; do
182 | # Wait for jobs sent by async_job.
183 | read -r -d $'\0' request || {
184 | # Unknown error occurred while reading from stdin, the zpty
185 | # worker is likely in a broken state, so we shut down.
186 | terminate_jobs
187 |
188 | # Stdin is broken and in case this was an unintended
189 | # crash, we try to report it as a last hurrah.
190 | print -r -n $'\0'"'[async]'" $(( 127 + 3 )) "''" 0 "'$0:$LINENO: zpty fd died, exiting'"$'\0'
191 |
192 | # We use `return` to abort here because using `exit` may
193 | # result in an infinite loop that never exits and, as a
194 | # result, high CPU utilization.
195 | return $(( 127 + 1 ))
196 | }
197 |
198 | # We need to clean the input here because sometimes when a zpty
199 | # has died and been respawned, messages will be prefixed with a
200 | # carraige return (\r, or \C-M).
201 | request=${request#$'\C-M'}
202 |
203 | # Check for non-job commands sent to worker
204 | case $request in
205 | _killjobs) killjobs; continue;;
206 | _async_eval*) do_eval=1;;
207 | esac
208 |
209 | # Parse the request using shell parsing (z) to allow commands
210 | # to be parsed from single strings and multi-args alike.
211 | cmd=("${(z)request}")
212 |
213 | # Name of the job (first argument).
214 | local job=$cmd[1]
215 |
216 | # Check if a worker should perform unique jobs, unless
217 | # this is an eval since they run synchronously.
218 | if (( !do_eval )) && (( unique )); then
219 | # Check if a previous job is still running, if yes,
220 | # skip this job and let the previous one finish.
221 | for pid in ${${(v)jobstates##*:*:}%\=*}; do
222 | if [[ ${storage[$job]} == $pid ]]; then
223 | continue 2
224 | fi
225 | done
226 | fi
227 |
228 | # Guard against closing coproc from trap before command has started.
229 | processing=1
230 |
231 | # Because we close the coproc after the last job has completed, we must
232 | # recreate it when there are no other jobs running.
233 | if (( ! coproc_pid )); then
234 | # Use coproc as a mutex for synchronized output between children.
235 | coproc command -p cat
236 | coproc_pid="$!"
237 | # Insert token into coproc
238 | print -n -p "t"
239 | fi
240 |
241 | if (( do_eval )); then
242 | shift cmd # Strip _async_eval from cmd.
243 | _async_eval $cmd
244 | else
245 | # Run job in background, completed jobs are printed to stdout.
246 | _async_job $cmd &
247 | # Store pid because zsh job manager is extremely unflexible (show jobname as non-unique '$job')...
248 | storage[$job]="$!"
249 | fi
250 |
251 | processing=0 # Disable guard.
252 |
253 | if (( do_eval )); then
254 | do_eval=0
255 |
256 | # When there are no active jobs we can't rely on the CHLD trap to
257 | # manage the coproc lifetime.
258 | close_idle_coproc
259 | fi
260 | done
261 | }
262 |
263 | #
264 | # Get results from finished jobs and pass it to the to callback function. This is the only way to reliably return the
265 | # job name, return code, output and execution time and with minimal effort.
266 | #
267 | # If the async process buffer becomes corrupt, the callback will be invoked with the first argument being `[async]` (job
268 | # name), non-zero return code and fifth argument describing the error (stderr).
269 | #
270 | # usage:
271 | # async_process_results
272 | #
273 | # callback_function is called with the following parameters:
274 | # $1 = job name, e.g. the function passed to async_job
275 | # $2 = return code
276 | # $3 = resulting stdout from execution
277 | # $4 = execution time, floating point e.g. 2.05 seconds
278 | # $5 = resulting stderr from execution
279 | # $6 = has next result in buffer (0 = buffer empty, 1 = yes)
280 | #
281 | async_process_results() {
282 | setopt localoptions unset noshwordsplit noksharrays noposixidentifiers noposixstrings
283 |
284 | local worker=$1
285 | local callback=$2
286 | local caller=$3
287 | local -a items
288 | local null=$'\0' data
289 | integer -l len pos num_processed has_next
290 |
291 | typeset -gA ASYNC_PROCESS_BUFFER
292 |
293 | # Read output from zpty and parse it if available.
294 | while zpty -r -t $worker data 2>/dev/null; do
295 | ASYNC_PROCESS_BUFFER[$worker]+=$data
296 | len=${#ASYNC_PROCESS_BUFFER[$worker]}
297 | pos=${ASYNC_PROCESS_BUFFER[$worker][(i)$null]} # Get index of NULL-character (delimiter).
298 |
299 | # Keep going until we find a NULL-character.
300 | if (( ! len )) || (( pos > len )); then
301 | continue
302 | fi
303 |
304 | while (( pos <= len )); do
305 | # Take the content from the beginning, until the NULL-character and
306 | # perform shell parsing (z) and unquoting (Q) as an array (@).
307 | items=("${(@Q)${(z)ASYNC_PROCESS_BUFFER[$worker][1,$pos-1]}}")
308 |
309 | # Remove the extracted items from the buffer.
310 | ASYNC_PROCESS_BUFFER[$worker]=${ASYNC_PROCESS_BUFFER[$worker][$pos+1,$len]}
311 |
312 | len=${#ASYNC_PROCESS_BUFFER[$worker]}
313 | if (( len > 1 )); then
314 | pos=${ASYNC_PROCESS_BUFFER[$worker][(i)$null]} # Get index of NULL-character (delimiter).
315 | fi
316 |
317 | has_next=$(( len != 0 ))
318 | if (( $#items == 5 )); then
319 | items+=($has_next)
320 | $callback "${(@)items}" # Send all parsed items to the callback.
321 | (( num_processed++ ))
322 | elif [[ -z $items ]]; then
323 | # Empty items occur between results due to double-null ($'\0\0')
324 | # caused by commands being both pre and suffixed with null.
325 | else
326 | # In case of corrupt data, invoke callback with *async* as job
327 | # name, non-zero exit status and an error message on stderr.
328 | $callback "[async]" 1 "" 0 "$0:$LINENO: error: bad format, got ${#items} items (${(q)items})" $has_next
329 | fi
330 | done
331 | done
332 |
333 | (( num_processed )) && return 0
334 |
335 | # Avoid printing exit value when `setopt printexitvalue` is active.`
336 | [[ $caller = trap || $caller = watcher ]] && return 0
337 |
338 | # No results were processed
339 | return 1
340 | }
341 |
342 | # Watch worker for output
343 | _async_zle_watcher() {
344 | setopt localoptions noshwordsplit
345 | typeset -gA ASYNC_PTYS ASYNC_CALLBACKS
346 | local worker=$ASYNC_PTYS[$1]
347 | local callback=$ASYNC_CALLBACKS[$worker]
348 |
349 | if [[ -n $2 ]]; then
350 | # from man zshzle(1):
351 | # `hup' for a disconnect, `nval' for a closed or otherwise
352 | # invalid descriptor, or `err' for any other condition.
353 | # Systems that support only the `select' system call always use
354 | # `err'.
355 |
356 | # this has the side effect to unregister the broken file descriptor
357 | async_stop_worker $worker
358 |
359 | if [[ -n $callback ]]; then
360 | $callback '[async]' 2 "" 0 "$0:$LINENO: error: fd for $worker failed: zle -F $1 returned error $2" 0
361 | fi
362 | return
363 | fi;
364 |
365 | if [[ -n $callback ]]; then
366 | async_process_results $worker $callback watcher
367 | fi
368 | }
369 |
370 | _async_send_job() {
371 | setopt localoptions noshwordsplit noksharrays noposixidentifiers noposixstrings
372 |
373 | local caller=$1
374 | local worker=$2
375 | shift 2
376 |
377 | zpty -t $worker &>/dev/null || {
378 | typeset -gA ASYNC_CALLBACKS
379 | local callback=$ASYNC_CALLBACKS[$worker]
380 |
381 | if [[ -n $callback ]]; then
382 | $callback '[async]' 3 "" 0 "$0:$LINENO: error: no such worker: $worker" 0
383 | else
384 | print -u2 "$caller: no such async worker: $worker"
385 | fi
386 | return 1
387 | }
388 |
389 | zpty -w $worker "$@"$'\0'
390 | }
391 |
392 | #
393 | # Start a new asynchronous job on specified worker, assumes the worker is running.
394 | #
395 | # Note if you are using a function for the job, it must have been defined before the worker was
396 | # started or you will get a `command not found` error.
397 | #
398 | # usage:
399 | # async_job []
400 | #
401 | async_job() {
402 | setopt localoptions noshwordsplit noksharrays noposixidentifiers noposixstrings
403 |
404 | local worker=$1; shift
405 |
406 | local -a cmd
407 | cmd=("$@")
408 | if (( $#cmd > 1 )); then
409 | cmd=(${(q)cmd}) # Quote special characters in multi argument commands.
410 | fi
411 |
412 | _async_send_job $0 $worker "$cmd"
413 | }
414 |
415 | #
416 | # Evaluate a command (like async_job) inside the async worker, then worker environment can be manipulated. For example,
417 | # issuing a cd command will change the PWD of the worker which will then be inherited by all future async jobs.
418 | #
419 | # Output will be returned via callback, job name will be [async/eval].
420 | #
421 | # usage:
422 | # async_worker_eval []
423 | #
424 | async_worker_eval() {
425 | setopt localoptions noshwordsplit noksharrays noposixidentifiers noposixstrings
426 |
427 | local worker=$1; shift
428 |
429 | local -a cmd
430 | cmd=("$@")
431 | if (( $#cmd > 1 )); then
432 | cmd=(${(q)cmd}) # Quote special characters in multi argument commands.
433 | fi
434 |
435 | # Quote the cmd in case RC_EXPAND_PARAM is set.
436 | _async_send_job $0 $worker "_async_eval $cmd"
437 | }
438 |
439 | # This function traps notification signals and calls all registered callbacks
440 | _async_notify_trap() {
441 | setopt localoptions noshwordsplit
442 |
443 | local k
444 | for k in ${(k)ASYNC_CALLBACKS}; do
445 | async_process_results $k ${ASYNC_CALLBACKS[$k]} trap
446 | done
447 | }
448 |
449 | #
450 | # Register a callback for completed jobs. As soon as a job is finnished, async_process_results will be called with the
451 | # specified callback function. This requires that a worker is initialized with the -n (notify) option.
452 | #
453 | # usage:
454 | # async_register_callback
455 | #
456 | async_register_callback() {
457 | setopt localoptions noshwordsplit nolocaltraps
458 |
459 | typeset -gA ASYNC_PTYS ASYNC_CALLBACKS
460 | local worker=$1; shift
461 |
462 | ASYNC_CALLBACKS[$worker]="$*"
463 |
464 | # Enable trap when the ZLE watcher is unavailable, allows
465 | # workers to notify (via -n) when a job is done.
466 | if [[ ! -o interactive ]] || [[ ! -o zle ]]; then
467 | trap '_async_notify_trap' WINCH
468 | elif [[ -o interactive ]] && [[ -o zle ]]; then
469 | local fd w
470 | for fd w in ${(@kv)ASYNC_PTYS}; do
471 | if [[ $w == $worker ]]; then
472 | zle -F $fd _async_zle_watcher # Register the ZLE handler.
473 | break
474 | fi
475 | done
476 | fi
477 | }
478 |
479 | #
480 | # Unregister the callback for a specific worker.
481 | #
482 | # usage:
483 | # async_unregister_callback
484 | #
485 | async_unregister_callback() {
486 | typeset -gA ASYNC_CALLBACKS
487 |
488 | unset "ASYNC_CALLBACKS[$1]"
489 | }
490 |
491 | #
492 | # Flush all current jobs running on a worker. This will terminate any and all running processes under the worker, use
493 | # with caution.
494 | #
495 | # usage:
496 | # async_flush_jobs
497 | #
498 | async_flush_jobs() {
499 | setopt localoptions noshwordsplit
500 |
501 | local worker=$1; shift
502 |
503 | # Check if the worker exists
504 | zpty -t $worker &>/dev/null || return 1
505 |
506 | # Send kill command to worker
507 | async_job $worker "_killjobs"
508 |
509 | # Clear the zpty buffer.
510 | local junk
511 | if zpty -r -t $worker junk '*'; then
512 | (( ASYNC_DEBUG )) && print -n "async_flush_jobs $worker: ${(V)junk}"
513 | while zpty -r -t $worker junk '*'; do
514 | (( ASYNC_DEBUG )) && print -n "${(V)junk}"
515 | done
516 | (( ASYNC_DEBUG )) && print
517 | fi
518 |
519 | # Finally, clear the process buffer in case of partially parsed responses.
520 | typeset -gA ASYNC_PROCESS_BUFFER
521 | unset "ASYNC_PROCESS_BUFFER[$worker]"
522 | }
523 |
524 | #
525 | # Start a new async worker with optional parameters, a worker can be told to only run unique tasks and to notify a
526 | # process when tasks are complete.
527 | #
528 | # usage:
529 | # async_start_worker [-u] [-n] [-p ]
530 | #
531 | # opts:
532 | # -u unique (only unique job names can run)
533 | # -n notify through SIGWINCH signal
534 | # -p pid to notify (defaults to current pid)
535 | #
536 | async_start_worker() {
537 | setopt localoptions noshwordsplit noclobber
538 |
539 | local worker=$1; shift
540 | local -a args
541 | args=("$@")
542 | zpty -t $worker &>/dev/null && return
543 |
544 | typeset -gA ASYNC_PTYS
545 | typeset -h REPLY
546 | typeset has_xtrace=0
547 |
548 | if [[ -o interactive ]] && [[ -o zle ]]; then
549 | # Inform the worker to ignore the notify flag and that we're
550 | # using a ZLE watcher instead.
551 | args+=(-z)
552 |
553 | if (( ! ASYNC_ZPTY_RETURNS_FD )); then
554 | # When zpty doesn't return a file descriptor (on older versions of zsh)
555 | # we try to guess it anyway.
556 | integer -l zptyfd
557 | exec {zptyfd}>&1 # Open a new file descriptor (above 10).
558 | exec {zptyfd}>&- # Close it so it's free to be used by zpty.
559 | fi
560 | fi
561 |
562 | # Workaround for stderr in the main shell sometimes (incorrectly) being
563 | # reassigned to /dev/null by the reassignment done inside the async
564 | # worker.
565 | # See https://github.com/mafredri/zsh-async/issues/35.
566 | integer errfd=-1
567 |
568 | # Redirect of errfd is broken on zsh 5.0.2.
569 | if is-at-least 5.0.8; then
570 | exec {errfd}>&2
571 | fi
572 |
573 | # Make sure async worker is started without xtrace
574 | # (the trace output interferes with the worker).
575 | [[ -o xtrace ]] && {
576 | has_xtrace=1
577 | unsetopt xtrace
578 | }
579 |
580 | if (( errfd != -1 )); then
581 | zpty -b $worker _async_worker -p $$ $args 2>&$errfd
582 | else
583 | zpty -b $worker _async_worker -p $$ $args
584 | fi
585 | local ret=$?
586 |
587 | # Re-enable it if it was enabled, for debugging.
588 | (( has_xtrace )) && setopt xtrace
589 | (( errfd != -1 )) && exec {errfd}>& -
590 |
591 | if (( ret )); then
592 | async_stop_worker $worker
593 | return 1
594 | fi
595 |
596 | if ! is-at-least 5.0.8; then
597 | # For ZSH versions older than 5.0.8 we delay a bit to give
598 | # time for the worker to start before issuing commands,
599 | # otherwise it will not be ready to receive them.
600 | sleep 0.001
601 | fi
602 |
603 | if [[ -o interactive ]] && [[ -o zle ]]; then
604 | if (( ! ASYNC_ZPTY_RETURNS_FD )); then
605 | REPLY=$zptyfd # Use the guessed value for the file desciptor.
606 | fi
607 |
608 | ASYNC_PTYS[$REPLY]=$worker # Map the file desciptor to the worker.
609 | fi
610 | }
611 |
612 | #
613 | # Stop one or multiple workers that are running, all unfetched and incomplete work will be lost.
614 | #
615 | # usage:
616 | # async_stop_worker []
617 | #
618 | async_stop_worker() {
619 | setopt localoptions noshwordsplit
620 |
621 | local ret=0 worker k v
622 | for worker in $@; do
623 | # Find and unregister the zle handler for the worker
624 | for k v in ${(@kv)ASYNC_PTYS}; do
625 | if [[ $v == $worker ]]; then
626 | zle -F $k
627 | unset "ASYNC_PTYS[$k]"
628 | fi
629 | done
630 | async_unregister_callback $worker
631 | zpty -d $worker 2>/dev/null || ret=$?
632 |
633 | # Clear any partial buffers.
634 | typeset -gA ASYNC_PROCESS_BUFFER
635 | unset "ASYNC_PROCESS_BUFFER[$worker]"
636 | done
637 |
638 | return $ret
639 | }
640 |
641 | #
642 | # Initialize the required modules for zsh-async. To be called before using the zsh-async library.
643 | #
644 | # usage:
645 | # async_init
646 | #
647 | async_init() {
648 | (( ASYNC_INIT_DONE )) && return
649 | typeset -g ASYNC_INIT_DONE=1
650 |
651 | zmodload zsh/zpty
652 | zmodload zsh/datetime
653 |
654 | # Load is-at-least for reliable version check.
655 | autoload -Uz is-at-least
656 |
657 | # Check if zsh/zpty returns a file descriptor or not,
658 | # shell must also be interactive with zle enabled.
659 | typeset -g ASYNC_ZPTY_RETURNS_FD=0
660 | [[ -o interactive ]] && [[ -o zle ]] && {
661 | typeset -h REPLY
662 | zpty _async_test :
663 | (( REPLY )) && ASYNC_ZPTY_RETURNS_FD=1
664 | zpty -d _async_test
665 | }
666 | }
667 |
668 | async() {
669 | async_init
670 | }
671 |
672 | async "$@"
673 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reobin/typewritten/0dd11cc5b2681158820f06881b5cdb3b42f9c924/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | typewritten.dev
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 |
130 |
131 | ### Example #4 (wrong format)
132 |
133 | ```shell
134 | export TYPEWRITTEN_PROMPT_LAYOUT="half_pure"
135 | export TYPEWRITTEN_COLORS=";wrong_format:" # default values will be used
136 | ```
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/docs/prompt_customization.md:
--------------------------------------------------------------------------------
1 | # Prompt customization
2 |
3 | Here lie all the options related to how or where the information is displayed on the prompt.
4 |
5 | Click on an option's name to see more info.
6 |
7 | | Option | Description | Available options | Default value |
8 | | --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------- |
9 | | [TYPEWRITTEN_PROMPT_LAYOUT](#typewritten_prompt_layout) | Defines how the prompt is displayed. | `singleline`, `half_pure` , `pure`, `singleline_verbose`, and `multiline` | `singleline` |
10 | | [TYPEWRITTEN_SYMBOL](#typewritten_symbol) | Defines the prompt symbol. | Any string value | `❯` |
11 | | [TYPEWRITTEN_ARROW_SYMBOL](#typewritten_arrow_symbol) | Defines the arrow symbol. | Any string value | `->` |
12 | | [TYPEWRITTEN_RELATIVE_PATH](#typewritten_relative_path) | Defines what the current directory display is relative to. | `git`, `home`, `adaptive`, or `off` | `git` |
13 | | [TYPEWRITTEN_CURSOR](#typewritten_cursor) | Defines the used cursor. | `underscore`, `beam`, `block`, or `terminal` | `underscore` |
14 | | [TYPEWRITTEN_LEFT_PROMPT_PREFIX](#typewritten_left_prompt_prefix-and-typewritten_right_prompt_prefix) | Defines what is displayed just before the prompt symbol on the left. | Any string | |
15 | | [TYPEWRITTEN_LEFT_PROMPT_PREFIX_FUNCTION](#typewritten_left_prompt_prefix_function-and-typewritten_right_prompt_prefix_function) | Defines what is displayed just before the prompt symbol on the left. Takes in a function name. | Any function name | |
16 | | [TYPEWRITTEN_RIGHT_PROMPT_PREFIX](#typewritten_left_prompt_prefix-and-typewritten_right_prompt_prefix) | Defines what is displayed just before the right part of the prompt. | Any string | |
17 | | [TYPEWRITTEN_RIGHT_PROMPT_PREFIX_FUNCTION](#typewritten_left_prompt_prefix_function-and-typewritten_right_prompt_prefix_function) | Defines what is displayed just before the prompt symbol on the left. Takes in a function name. | Any function name | |
18 |
19 | > All of these options are configurable through your `.zshrc` file like this:
20 | >
21 | > ```shell
22 | > export TYPEWRITTEN_PROMPT_LAYOUT="singleline"
23 | > ```
24 |
25 | ## TYPEWRITTEN_PROMPT_LAYOUT
26 |
27 | **Default single line (`TYPEWRITTEN_PROMPT_LAYOUT="singleline"`)**
28 |
29 |
100 |
101 | ## TYPEWRITTEN_RELATIVE_PATH
102 |
103 | By default, when in a git repository, the git root directory is always displayed no matter how far you are inside it.
104 |
105 | **Default behaviour (`TYPEWRITTEN_RELATIVE_PATH="git"`)**
106 |
107 | `/.../` is displayed when the nesting gets more than one level deep.
108 |
109 |
110 |
111 |
112 |
113 | When outside of a git repository, only the basename of the current directory is displayed.
114 |
115 |
116 |
117 |
118 |
119 | **Relative to `$HOME` (`TYPEWRITTEN_RELATIVE_PATH="home"`)**
120 |
121 | When set to `home`, the full directory path relative to `$HOME` is displayed at all times.
122 |
123 |
124 |
125 |
126 |
127 | **Adaptive (`git` and `home`) (`TYPEWRITTEN_RELATIVE_PATH="adaptive"`)**
128 |
129 | Adaptive display means that if you are in a git repository, the current directory will show the current path relative to the git root directory.
130 |
131 |
132 |
133 |
134 |
135 | If you are outside of a git repository, the path will be displayed relative to `$HOME`.
136 |
137 |
138 |
139 |
140 |
141 | **Off (`TYPEWRITTEN_RELATIVE_PATH="off"`)**
142 |
143 | This option being off means that at all times, the current directory display only shows the basename of the current directory.
144 |
145 |
168 |
169 | **Terminal (`TYPEWRITTEN_CURSOR="terminal"`)**
170 |
171 | By using this option, typewritten stops managing cursor preference. The cursor used will be the one configured by your terminal emulator.
172 |
173 | ## TYPEWRITTEN_LEFT_PROMPT_PREFIX and TYPEWRITTEN_RIGHT_PROMPT_PREFIX
174 |
175 | This option is stricly used as a string value. To use functions, take a look at [TYPEWRITTEN_LEFT_PROMPT_PREFIX_FUNCTION](#typewritten_left_prompt_prefix_function-and-typewritten_right_prompt_prefix_function).
176 |
177 | **Display a string value**
178 |
179 | ```sh
180 | export TYPEWRITTEN_RIGHT_PROMPT_PREFIX="# "
181 | ```
182 |
183 |
184 |
185 |
186 |
187 | ## TYPEWRITTEN_LEFT_PROMPT_PREFIX_FUNCTION and TYPEWRITTEN_RIGHT_PROMPT_PREFIX_FUNCTION
188 |
189 | This variable should be assigned to a function name, and will be evaluated whenever the prompt is rendered.
190 |
191 | ### Display the `date`
192 |
193 | Since `date` is a function name, it will execute and the output will be displayed as the prefix:
194 |
195 | ```sh
196 | export TYPEWRITTEN_LEFT_PROMPT_PREFIX_FUNCTION=date
197 | ```
198 |
199 |
200 |
201 |
202 |
203 | ### Display `date` with options
204 |
205 | To customize what the timestamp will look like, you can use options:
206 |
207 | ```sh
208 | export TYPEWRITTEN_LEFT_PROMPT_PREFIX_FUNCTION=(date +%H:%M:%S)
209 | ```
210 |
211 |
212 |
213 |
214 |
215 | ### Use a custom function
216 |
217 | You can create your own function to display more complex values:
218 |
219 | ```sh
220 | complex_time() {
221 | local time=$(date +%H:%M:%S)
222 | echo "time: $time"
223 | }
224 |
225 | export TYPEWRITTEN_LEFT_PROMPT_PREFIX_FUNCTION=complex_time
226 | ```
227 |
228 |
229 |
230 |
231 |
232 | **Bonus example**
233 |
234 | Display kube_context:
235 |
236 | ```sh
237 | display_kube_context() {
238 | tw_kube_context="$(kubectl config current-context 2> /dev/null)"
239 |
240 | if [[ $tw_kube_context != "" ]]; then
241 | echo "($(basename $tw_kube_context))"
242 | fi
243 | }
244 |
245 | export TYPEWRITTEN_LEFT_PROMPT_PREFIX_FUNCTION=display_kube_context
246 | ```
247 |
--------------------------------------------------------------------------------
/docs/return_code.md:
--------------------------------------------------------------------------------
1 | # Return code
2 |
3 | When an error happens, the prompt symbol changes to a red color and the return code is displayed on the left.
4 |
5 |
6 |
7 |
8 |
9 | The return code display can be disabled by setting `TYPEWRITTEN_DISABLE_RETURN_CODE` to `true` in your `.zshrc`
10 |
11 |
--------------------------------------------------------------------------------
/docs/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow:
3 | Sitemap: https://typewritten.dev/sitemap.xml
4 | Host: https://typewritten.dev
5 |
--------------------------------------------------------------------------------
/docs/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | https://typewritten.dev/daily0.7
4 | https://typewritten.dev/#/installation/daily0.7
5 | https://typewritten.dev/#/prompt_customization/daily0.7
6 | https://typewritten.dev/#/prompt_color_customization/daily0.7
7 | https://typewritten.dev/#/git_status_indicator/daily0.7
8 | https://typewritten.dev/#/return_code/daily0.7
9 | https://typewritten.dev/#/credits/daily0.7
10 |
11 |
--------------------------------------------------------------------------------
/lib/colors.zsh:
--------------------------------------------------------------------------------
1 | declare -Ag tw_color_mappings=(
2 | "foreground" "default"
3 | "primary" "magenta"
4 | "secondary" "blue"
5 | "notice" "yellow"
6 | "accent" "default"
7 |
8 | "info_positive" "green"
9 | "info_negative" "red"
10 | "info_neutral_1" "yellow"
11 | "info_neutral_2" "blue"
12 | "info_special" "cyan"
13 | )
14 |
15 | if [[ $TYPEWRITTEN_COLOR_MAPPINGS =~ ^[#_0-9a-zA-Z]+:[#_0-9a-zA-Z]+(\;[#_0-9a-zA-Z]+:[#_0-9a-zA-Z]+)*$ ]]; then
16 | tw_values=($(echo $TYPEWRITTEN_COLOR_MAPPINGS | tr ";" "\n"))
17 | for tw_value in $tw_values; do
18 | tw_value_definition=($(echo $tw_value | tr ":" "\n"))
19 | tw_color_mappings[$tw_value_definition[1]]=$tw_value_definition[2]
20 | done
21 | elif [[ ! -z $TYPEWRITTEN_COLOR_MAPPINGS ]]; then
22 | echo "$TYPEWRITTEN_COLOR_MAPPINGS is not formatted correctly.
23 | Format it like so: \"value:#009090;value:red\", etc."
24 | fi
25 |
26 | declare -Ag tw_colors=(
27 | "prompt" $tw_color_mappings[foreground]
28 | "current_directory" $tw_color_mappings[primary]
29 | "symbol" $tw_color_mappings[secondary]
30 | "symbol_root" $tw_color_mappings[notice]
31 |
32 | "arrow" $tw_color_mappings[accent]
33 | "right_prompt_prefix" $tw_color_mappings[accent]
34 | "left_prompt_prefix" $tw_color_mappings[accent]
35 | "virtual_env" $tw_color_mappings[accent]
36 |
37 | "symbol_error" $tw_color_mappings[info_negative]
38 | "error_code" $tw_color_mappings[info_negative]
39 |
40 | "host_user_connector" $tw_color_mappings[accent]
41 | "host" $tw_color_mappings[info_neutral_1]
42 | "user" $tw_color_mappings[info_neutral_1]
43 |
44 | "git_branch" $tw_color_mappings[primary]
45 | "git_rebasing" $tw_color_mappings[primary]
46 | "git_status_deleted" $tw_color_mappings[info_negative]
47 | "git_status_staged" $tw_color_mappings[info_positive]
48 | "git_status_stash" $tw_color_mappings[info_neutral_1]
49 | "git_status_modified" $tw_color_mappings[info_neutral_1]
50 | "git_status_new" $tw_color_mappings[info_neutral_2]
51 | "git_status_diverged" $tw_color_mappings[info_neutral_2]
52 | "git_status_ahead" $tw_color_mappings[info_neutral_2]
53 | "git_status_behind" $tw_color_mappings[info_neutral_2]
54 | "git_status_renamed" $tw_color_mappings[info_special]
55 | "git_status_unmerged" $tw_color_mappings[info_special]
56 | )
57 |
58 | if [[ $TYPEWRITTEN_COLORS =~ ^[#_0-9a-zA-Z]+:[#_0-9a-zA-Z]+(\;[#_0-9a-zA-Z]+:[#_0-9a-zA-Z]+)*$ ]]; then
59 | tw_values=($(echo $TYPEWRITTEN_COLORS | tr ";" "\n"))
60 | for tw_value in $tw_values; do
61 | tw_value_definition=($(echo $tw_value | tr ":" "\n"))
62 | tw_colors[$tw_value_definition[1]]=$tw_value_definition[2]
63 | done
64 | elif [[ ! -z $TYPEWRITTEN_COLORS ]]; then
65 | echo "$TYPEWRITTEN_COLORS is not formatted correctly.
66 | Format it like so: \"value:#009090;value:red\", etc."
67 | fi
68 |
--------------------------------------------------------------------------------
/lib/git.zsh:
--------------------------------------------------------------------------------
1 | tw_git_home() {
2 | local tw_current_directory="$1"
3 | local tw_git_toplevel="$2"
4 | local tw_git_home=""
5 | if [ "$tw_git_toplevel" != "" -a "$tw_git_toplevel" != "$tw_current_directory" ]; then
6 | local tw_repo_name=`basename $tw_git_toplevel`
7 | tw_git_home="$tw_repo_name"
8 | fi;
9 | if [ "$tw_git_home" != "" ]; then
10 | # check how many directories are between
11 | local tw_current_nesting="${tw_current_directory//[^\/]}"
12 | local tw_repo_nesting="${tw_git_toplevel//[^\/]}"
13 | local tw_diff=`expr ${#tw_current_nesting} - ${#tw_repo_nesting}`
14 | if [ $tw_diff -eq 1 ]; then
15 | echo "$tw_git_home/"
16 | else
17 | echo "$tw_git_home/.../"
18 | fi;
19 | else
20 | echo ""
21 | fi;
22 | }
23 |
24 | tw_git_branch() {
25 | echo "$(command git symbolic-ref --short -q HEAD)"
26 | }
27 |
28 | tw_git_status() {
29 | local tw_git_status_response=$(command git status --porcelain -b 2> /dev/null)
30 |
31 | local tw_git_status_display=""
32 |
33 | # rebasing
34 | if $(command grep "^## HEAD (no branch)" <<< "$tw_git_status_response" &> /dev/null); then
35 | tw_git_status_display="%F{$tw_colors[git_rebasing]}rebasing"
36 | else
37 | # staged changes
38 | if $(command grep "^A. " <<< "$tw_git_status_response" &> /dev/null) ||
39 | $(command grep "^M. " <<< "$tw_git_status_response" &> /dev/null) ||
40 | $(command grep "^D. " <<< "$tw_git_status_response" &> /dev/null); then
41 | tw_git_status_display+=" %F{$tw_colors[git_status_staged]}+"
42 | fi
43 |
44 | # new file
45 | if $(command grep "?? " <<< "$tw_git_status_response" &> /dev/null); then
46 | tw_git_status_display+=" %F{$tw_colors[git_status_new]}?"
47 | fi
48 |
49 | # modified
50 | if $(command grep "^.M " <<< "$tw_git_status_response" &> /dev/null); then
51 | tw_git_status_display+=" %F{$tw_colors[git_status_modified]}!"
52 | fi
53 |
54 | # renamed
55 | if $(command grep "^R. " <<< "$tw_git_status_response" &> /dev/null); then
56 | tw_git_status_display+=" %F{$tw_colors[git_status_renamed]}»"
57 | fi
58 |
59 | # deleted
60 | if $(command grep "^.D " <<< "$tw_git_status_response" &> /dev/null); then
61 | tw_git_status_display+=" %F{$tw_colors[git_status_deleted]}—"
62 | fi
63 |
64 | # unmerged
65 | if $(command grep "^UU " <<< "$tw_git_status_response" &> /dev/null); then
66 | tw_git_status_display+=" %F{$tw_colors[git_status_unmerged]}#"
67 | fi
68 |
69 | local tw_is_ahead=false
70 | if $(command grep "^## .*ahead" <<< "$tw_git_status_response" &> /dev/null); then
71 | tw_is_ahead=true
72 | fi
73 |
74 | local tw_is_behind=false
75 | if $(command grep "^## .*behind" <<< "$tw_git_status_response" &> /dev/null); then
76 | tw_is_behind=true
77 | fi
78 |
79 | if [[ "$tw_is_ahead" == true && "$tw_is_behind" == true ]]; then
80 | # diverged
81 | tw_git_status_display+=" %F{$tw_colors[git_status_diverged]}~"
82 | elif [[ "$tw_is_ahead" == true ]]; then
83 | tw_git_status_display+=" %F{$tw_colors[git_status_ahead]}|•"
84 | elif [[ "$tw_is_behind" == true ]]; then
85 | tw_git_status_display+=" %F{$tw_colors[git_status_behind]}•|"
86 | fi
87 |
88 | # stashed
89 | local tw_stash_count=`git stash list | wc -l`
90 | if [[ $tw_stash_count -gt 0 ]]; then
91 | tw_git_status_display+=" %F{$tw_colors[git_status_stash]}$"
92 | fi
93 | fi
94 |
95 | echo "$tw_git_status_display"
96 | }
97 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typewritten",
3 | "version": "1.5.2",
4 | "description": "A minimal, lightweight, informative zsh prompt theme",
5 | "files": [
6 | "typewritten.zsh",
7 | "async.zsh",
8 | "lib",
9 | "scripts"
10 | ],
11 | "scripts": {
12 | "postinstall": "./scripts/install.sh",
13 | "postuninstall": "./scripts/uninstall.sh"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/reobin/typewritten.git"
18 | },
19 | "keywords": [
20 | "typewritten",
21 | "zsh",
22 | "prompt",
23 | "theme",
24 | "minimal",
25 | "shell",
26 | "git",
27 | "terminal"
28 | ],
29 | "author": "Robin Gagnon (https://reobin.dev)",
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "https://github.com/reobin/typewritten/issues"
33 | },
34 | "homepage": "https://typewritten.dev",
35 | "directories": {
36 | "lib": "lib"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/prompt_typewritten_setup:
--------------------------------------------------------------------------------
1 | typewritten.zsh
--------------------------------------------------------------------------------
/scripts/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env zsh
2 | #
3 | # This script is a clone/fork of the spaceship-prompt install script and it was
4 | # changed in order to install the typewritten prompt instead.
5 | #
6 | # Original author: Denys Dovhan, denysdovhan.com
7 | # From: https://github.com/spaceship-prompt/spaceship-prompt
8 |
9 | # ------------------------------------------------------------------------------
10 | # Colors
11 | # Set color variables for colorful output
12 | # ------------------------------------------------------------------------------
13 |
14 | # If we have tput, let's set colors
15 | if [[ ! -z $(which tput 2> /dev/null) ]]; then
16 | reset=$(tput sgr0)
17 | bold=$(tput bold)
18 | red=$(tput setaf 1)
19 | green=$(tput setaf 2)
20 | yellow=$(tput setaf 3)
21 | blue=$(tput setaf 4)
22 | magenta=$(tput setaf 5)
23 | cyan=$(tput setaf 6)
24 | fi
25 |
26 | # ------------------------------------------------------------------------------
27 | # VARIABLES
28 | # Paths to important resources
29 | # ------------------------------------------------------------------------------
30 |
31 | ZSHRC="${ZDOTDIR:-$HOME}/.zshrc"
32 | REPO='https://github.com/reobin/typewritten.git'
33 |
34 | SOURCE="$PWD/typewritten.zsh"
35 | ASYNC_SOURCE="$PWD/async.zsh"
36 | USER_SOURCE="${ZDOTDIR:-$HOME}/.typewritten-prompt"
37 |
38 | DEST='/usr/local/share/zsh/site-functions'
39 | USER_DEST="${ZDOTDIR:-$HOME}/.zfunctions"
40 |
41 | # ------------------------------------------------------------------------------
42 | # HELPERS
43 | # Useful functions for common tasks
44 | # ------------------------------------------------------------------------------
45 |
46 | # Paint text in specific color with reset
47 | # USAGE:
48 | # paint [text...]
49 | paint() {
50 | local color=$1 rest=${@:2}
51 | echo "$color$rest$reset"
52 | }
53 |
54 | # Aliases for common used colors
55 | # Colon at the end is required: https://askubuntu.com/a/521942
56 | # USAGE:
57 | # info|warn|error|success|code [...text]
58 | info() { paint "$cyan" "typewritten: $@" ; }
59 | warn() { paint "$yellow" "typewritten: $@" ; }
60 | error() { paint "$red" "typewritten: $@" ; }
61 | success() { paint "$green" "typewritten: $@" ; }
62 | code() { paint "$bold" "typewritten: $@" ; }
63 |
64 | # Append text in .zshrc
65 | # USAGE:
66 | # append_zshrc [text...]
67 | append_zshrc() {
68 | info "These lines will be added to your \"${ZDOTDIR:-$HOME}/.zshrc\" file:"
69 | code "$@"
70 | echo "$@" >> "${ZDOTDIR:-$HOME}/.zshrc"
71 | }
72 |
73 | # ------------------------------------------------------------------------------
74 | # MAIN
75 | # Checkings and installing process
76 | # ------------------------------------------------------------------------------
77 |
78 | main() {
79 | # How we install typewritten:
80 | # 1. Install via NPM
81 | # 2. Install via curl or wget
82 | if [[ ! -f "$SOURCE" ]]; then
83 | warn "typewritten is not present in current directory"
84 | # Clone repo into the ${ZDOTDIR:-$HOME}/.typewritten-prompt and change SOURCE
85 | git clone "$REPO" "$USER_SOURCE"
86 | SOURCE="$USER_SOURCE/typewritten.zsh"
87 | ASYNC_SOURCE="$USER_SOURCE/async.zsh"
88 | else
89 | info "typewritten is present in current directory"
90 | fi
91 |
92 | # If we can't symlink to the site-functions, then try to use .zfunctions instead
93 | if [[ ! -w "$DEST" ]]; then
94 | error "Failed to symlink $SOURCE to $DEST."
95 | error "Failed to symlink $ASYNC_SOURCE to $DEST."
96 |
97 | # Use $USER_DEST instead
98 | DEST="$USER_DEST"
99 |
100 | info "Adding $DEST to fpath..."
101 | echo 'fpath=($fpath "'"$DEST"'")' >> "$ZSHRC"
102 |
103 | info "Trying to symlink $SOURCE to $DEST"
104 | info "Trying to symlink $ASYNC_SOURCE to $DEST"
105 | fi
106 |
107 | # Link prompt entry point to fpath
108 | info "Linking $SOURCE to $DEST/prompt_typewritten_setup..."
109 | info "Linking $ASYNC_SOURCE to $DEST/async..."
110 | mkdir -p "$DEST"
111 | ln -sf "$SOURCE" "$DEST/prompt_typewritten_setup"
112 | ln -sf "$ASYNC_SOURCE" "$DEST/async"
113 |
114 | # If 'prompt typewritten' is already present in .zshrc, then skip
115 | if sed 's/#.*//' "$ZSHRC" | grep -q "prompt typewritten"; then
116 | warn "typewritten is already present in .zshrc!"
117 | exit
118 | fi
119 |
120 | # Enabling statements for .zshrc
121 | msg="\n# Set typewritten ZSH as a prompt"
122 | msg+="\nautoload -U promptinit; promptinit"
123 | msg+="\nprompt typewritten"
124 |
125 | # Check if appending was successful and perform corresponding actions
126 | if append_zshrc "$msg"; then
127 | success "Done! Please, reload your terminal."
128 | echo
129 | else
130 | error "Cannot automatically insert prompt init commands."
131 | error "Please insert these line into your \"${ZDOTDIR:-$HOME}/.zshrc\" file:"
132 | code "$msg"
133 | exit 1
134 | fi
135 | }
136 |
137 | main "$@"
138 |
--------------------------------------------------------------------------------
/scripts/uninstall.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env zsh
2 |
3 | # This script is a clone/fork of the spaceship-prompt uninstall script and it was
4 | # changed in order to install the typewritten prompt instead.
5 | #
6 | # Original author: Denys Dovhan, denysdovhan.com
7 | # From: https://github.com/spaceship-prompt/spaceship-prompt
8 |
9 | # ------------------------------------------------------------------------------
10 | # Colors
11 | # Set color variables for colorful output
12 | # ------------------------------------------------------------------------------
13 |
14 | # If we have tput, let's set colors
15 | if [[ ! -z $(which tput 2> /dev/null) ]]; then
16 | reset=$(tput sgr0)
17 | bold=$(tput bold)
18 | red=$(tput setaf 1)
19 | green=$(tput setaf 2)
20 | yellow=$(tput setaf 3)
21 | blue=$(tput setaf 4)
22 | magenta=$(tput setaf 5)
23 | cyan=$(tput setaf 6)
24 | fi
25 |
26 | # ------------------------------------------------------------------------------
27 | # VARIABLES
28 | # Paths to important resources
29 | # ------------------------------------------------------------------------------
30 |
31 | ZSHRC="${ZDOTDIR:-$HOME}/.zshrc"
32 | USER_SOURCE="${ZDOTDIR:-$HOME}/.typewritten-prompt"
33 |
34 | PROMPT_SETUP="prompt_typewritten_setup"
35 | PROMPT_ASYNC="async"
36 |
37 | GLOBAL_DEST_SETUP="/usr/local/share/zsh/site-functions/$PROMPT_SETUP"
38 | USER_DEST_SETUP="${ZDOTDIR:-$HOME}/.zfunctions/$PROMPT_SETUP"
39 |
40 | GLOBAL_DEST_ASYNC="/usr/local/share/zsh/site-functions/$PROMPT_ASYNC"
41 | USER_DEST_ASYNC="${ZDOTDIR:-$HOME}/.zfunctions/$PROMPT_ASYNC"
42 |
43 | # ------------------------------------------------------------------------------
44 | # HELPERS
45 | # Useful functions for common tasks
46 | # ------------------------------------------------------------------------------
47 |
48 | # Paint text in specific color with reset
49 | # USAGE:
50 | # paint [text...]
51 | paint() {
52 | local color=$1 rest=${@:2}
53 | echo "$color$rest$reset"
54 | }
55 |
56 | # Aliases for common used colors
57 | # Colon at the end is required: https://askubuntu.com/a/521942
58 | # USAGE:
59 | # info|warn|error|success|code [...text]
60 | info() { paint "$cyan" "typewritten: $@" ; }
61 | warn() { paint "$yellow" "typewritten: $@" ; }
62 | error() { paint "$red" "typewritten: $@" ; }
63 | success() { paint "$green" "typewritten: $@" ; }
64 | code() { paint "$bold" "typewritten: $@" ; }
65 |
66 | # Check if symlink is exists and remove it
67 | # USAGE:
68 | # rmln
69 | rmln() {
70 | local target=$1
71 | if [[ -L "$target" ]]; then
72 | info "Removing $target..."
73 | rm -f "$target"
74 | fi
75 | }
76 |
77 | # ------------------------------------------------------------------------------
78 | # MAIN
79 | # Checkings and uninstalling process
80 | # ------------------------------------------------------------------------------
81 |
82 | remove_zshrc_content() {
83 | info "Removing typewritten from \"${ZDOTDIR:-$HOME}/.zshrc\""
84 | # Remove enabling statements from .zshrc
85 | # and remove typewritten configuration
86 | sed '/^# Set typewritten ZSH as a prompt$/d' "$ZSHRC" | \
87 | sed '/^autoload -U promptinit; promptinit$/d' | \
88 | sed '/^prompt typewritten$/d' | \
89 | sed '/.*TYPEWRITTEN_.*=.*$/d' > "$ZSHRC.bak" && \
90 | mv -- "$ZSHRC.bak" "$ZSHRC"
91 | }
92 |
93 | main() {
94 | # Remove prompt setup symlink
95 | if [[ -L "$GLOBAL_DEST_SETUP" || -L "$USER_DEST_SETUP" ]]; then
96 | rmln "$GLOBAL_DEST_SETUP"
97 | rmln "$USER_DEST_SETUP"
98 | else
99 | warn "Symlinks to typewritten setup are not found."
100 | fi
101 |
102 | # Remove prompt async symlink
103 | if [[ -L "$GLOBAL_DEST_ASYNC" || -L "$USER_DEST_ASYNC" ]]; then
104 | rmln "$GLOBAL_DEST_ASYNC"
105 | rmln "$USER_DEST_ASYNC"
106 | else
107 | warn "Symlinks to typewritten async are not found."
108 | fi
109 |
110 | # Remove typewritten from .zshrc
111 | if grep -q "typewritten" "$ZSHRC"; then
112 | if [[ '-y' == $1 ]]; then
113 | remove_zshrc_content
114 | else
115 | read "answer?Would you like to remove you typewritten ZSH configuration from .zshrc? (y/N)"
116 | if [[ 'y' == ${answer:l} ]]; then
117 | read "answer?Are you sure? Any symlinks to your ZSH configuration file might be removed? (y/N)"
118 | if [[ 'y' == ${answer:l} ]]; then
119 | remove_zshrc_content
120 | fi
121 | fi
122 | fi
123 | else
124 | warn "typewritten configuration not found in \"${ZDOTDIR:-$HOME}/.zshrc\"!"
125 | fi
126 |
127 | success "Done! typewritten installation has been removed!"
128 | success "Please, reload your terminal."
129 | }
130 |
131 | main "$@"
132 |
--------------------------------------------------------------------------------
/typewritten.plugin.zsh:
--------------------------------------------------------------------------------
1 | typewritten.zsh
--------------------------------------------------------------------------------
/typewritten.zsh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env zsh
2 |
3 | # ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____
4 | # ||t |||y |||p |||e |||w |||r |||i |||t |||t |||e |||n ||
5 | # ||__|||__|||__|||__|||__|||__|||__|||__|||__|||__|||__||
6 | # |/__\|/__\|/__\|/__\|/__\|/__\|/__\|/__\|/__\|/__\|/__\|
7 | #
8 | # A minimal, informative zsh prompt theme
9 | #
10 |
11 | export TYPEWRITTEN_ROOT=${${(%):-%x}:A:h}
12 |
13 | source "$TYPEWRITTEN_ROOT/async.zsh"
14 | async_init
15 |
16 | source "$TYPEWRITTEN_ROOT/lib/colors.zsh"
17 | source "$TYPEWRITTEN_ROOT/lib/git.zsh"
18 |
19 | BREAK_LINE="
20 | "
21 |
22 | local tw_prompt_symbol="❯"
23 | if [ ! -z "$TYPEWRITTEN_SYMBOL" ]; then
24 | tw_prompt_symbol="$TYPEWRITTEN_SYMBOL"
25 | fi;
26 |
27 | local tw_base_symbol_color="%F{$tw_colors[symbol]}"
28 | if [[ $(id -u) -eq 0 ]]; then
29 | tw_base_symbol_color="%F{$tw_colors[symbol_root]}"
30 | fi
31 |
32 | local tw_prompt_color="%(?,$tw_base_symbol_color,%F{$tw_colors[symbol_error]})"
33 | local tw_return_code="%(?,,%F{$tw_colors[error_code]}%? )"
34 | if [ "$TYPEWRITTEN_DISABLE_RETURN_CODE" = true ]; then
35 | tw_prompt_color="$tw_base_symbol_color"
36 | tw_return_code=""
37 | fi;
38 |
39 | tw_user_host="%F{$tw_colors[host]}%n%F{$tw_colors[host_user_connector]}@%F{$tw_colors[user]}%m"
40 | tw_prompt="$tw_prompt_color$tw_return_code$tw_prompt_symbol %F{$tw_colors[prompt]}"
41 |
42 | tw_current_directory_color="$tw_colors[current_directory]"
43 | tw_git_branch_color="$tw_colors[git_branch]"
44 |
45 | local tw_arrow_symbol="->"
46 | if [ ! -z "$TYPEWRITTEN_ARROW_SYMBOL" ]; then
47 | tw_arrow_symbol="$TYPEWRITTEN_ARROW_SYMBOL"
48 | fi;
49 | tw_arrow="%F{$tw_colors[arrow]}$tw_arrow_symbol"
50 |
51 | tw_get_virtual_env() {
52 | if [[ -z $VIRTUAL_ENV_DISABLE_PROMPT ]]; then
53 | local tw_virtual_env=""
54 | if [[ ! -z $VIRTUAL_ENV ]]; then
55 | tw_virtual_env="($(basename $VIRTUAL_ENV)) "
56 | elif [[ ! -z $CONDA_PROMPT_MODIFIER ]]; then
57 | tw_virtual_env="$(basename $CONDA_PROMPT_MODIFIER)"
58 | fi;
59 |
60 | if [[ $tw_virtual_env != "" ]]; then
61 | echo "%F{$tw_colors[virtual_env]}$tw_virtual_env"
62 | fi;
63 | fi;
64 | }
65 |
66 | tw_get_displayed_wd() {
67 | local tw_git_branch=$tw_prompt_data[tw_git_branch]
68 | local tw_git_home=$tw_prompt_data[tw_git_home]
69 |
70 | local tw_home_relative_wd="%~"
71 | local tw_git_relative_wd="$tw_git_home%c"
72 |
73 | local tw_displayed_wd="$tw_git_relative_wd"
74 |
75 | # The pure layout defaults to home relative working directory, but allows customization
76 | if [[ "$TYPEWRITTEN_PROMPT_LAYOUT" = pure* && "$TYPEWRITTEN_RELATIVE_PATH" = "" ]]; then
77 | tw_displayed_wd=$tw_home_relative_wd
78 | fi;
79 |
80 | if [[ "$TYPEWRITTEN_RELATIVE_PATH" = "home" ]]; then
81 | tw_displayed_wd=$tw_home_relative_wd
82 | fi;
83 |
84 | if [[ "$TYPEWRITTEN_RELATIVE_PATH" = "adaptive" ]]; then
85 | if [[ "$tw_git_branch" = "" ]]; then
86 | tw_displayed_wd=$tw_home_relative_wd
87 | fi;
88 | fi;
89 |
90 | echo "%F{$tw_current_directory_color}$tw_displayed_wd"
91 | }
92 |
93 | tw_get_left_prompt_prefix() {
94 | local tw_left_prompt_prefix=""
95 |
96 | if [[ ! -z $TYPEWRITTEN_LEFT_PROMPT_PREFIX || ! -z $TYPEWRITTEN_LEFT_PROMPT_PREFIX_FUNCTION ]]; then
97 | tw_left_prompt_prefix="%F{$tw_colors[left_prompt_prefix]}"
98 | fi;
99 |
100 | if [[ ! -z $TYPEWRITTEN_LEFT_PROMPT_PREFIX ]]; then
101 | tw_left_prompt_prefix="$tw_left_prompt_prefix$TYPEWRITTEN_LEFT_PROMPT_PREFIX "
102 | fi;
103 |
104 | if [[ ! -z $TYPEWRITTEN_LEFT_PROMPT_PREFIX_FUNCTION ]]; then
105 | local value=$($TYPEWRITTEN_LEFT_PROMPT_PREFIX_FUNCTION) 2>/dev/null
106 |
107 | if [[ ! -z $value ]]; then
108 | tw_left_prompt_prefix="$tw_left_prompt_prefix$value "
109 | fi;
110 | fi;
111 |
112 | echo $tw_left_prompt_prefix
113 | }
114 |
115 | tw_get_right_prompt_prefix() {
116 | local tw_right_prompt_prefix=""
117 |
118 | if [[ ! -z $TYPEWRITTEN_RIGHT_PROMPT_PREFIX || ! -z $TYPEWRITTEN_RIGHT_PROMPT_PREFIX_FUNCTION ]]; then
119 | tw_right_prompt_prefix="%F{$tw_colors[right_prompt_prefix]}"
120 | fi;
121 |
122 | if [[ ! -z $TYPEWRITTEN_RIGHT_PROMPT_PREFIX ]]; then
123 | tw_right_prompt_prefix="$tw_right_prompt_prefix$TYPEWRITTEN_RIGHT_PROMPT_PREFIX "
124 | fi;
125 |
126 | if [[ ! -z $TYPEWRITTEN_RIGHT_PROMPT_PREFIX_FUNCTION ]]; then
127 | local value=$($TYPEWRITTEN_RIGHT_PROMPT_PREFIX_FUNCTION) 2>/dev/null
128 |
129 | if [[ ! -z $value ]]; then
130 | tw_right_prompt_prefix="$tw_right_prompt_prefix$value "
131 | fi;
132 | fi;
133 |
134 | echo $tw_right_prompt_prefix
135 | }
136 |
137 | tw_redraw() {
138 | local tw_displayed_wd="$(tw_get_displayed_wd)"
139 |
140 | local tw_full_prompt="$(tw_get_virtual_env)$(tw_get_left_prompt_prefix)$tw_prompt"
141 |
142 | local tw_layout="$TYPEWRITTEN_PROMPT_LAYOUT"
143 | local tw_git_info="$tw_prompt_data[tw_git_branch]$tw_prompt_data[tw_git_status]"
144 |
145 | if [ "$tw_layout" = "half_pure" ]; then
146 | PROMPT="$BREAK_LINE%F{$tw_git_branch_color}$tw_git_info$BREAK_LINE$tw_full_prompt"
147 | RPROMPT="$(tw_get_right_prompt_prefix)$tw_displayed_wd"
148 | else
149 | local tw_git_arrow_info=""
150 | if [ "$tw_git_info" != "" ]; then
151 | tw_git_arrow_info=" $tw_arrow %F{$tw_git_branch_color}$tw_git_info"
152 | fi;
153 |
154 | local tw_right_prompt_prefix="$(tw_get_right_prompt_prefix)"
155 |
156 | PROMPT="$tw_full_prompt"
157 | RPROMPT="$tw_right_prompt_prefix$tw_displayed_wd$tw_git_arrow_info"
158 |
159 | if [ "$tw_layout" = "pure" ]; then
160 | PROMPT="$BREAK_LINE$tw_displayed_wd$tw_git_arrow_info$BREAK_LINE$tw_full_prompt"
161 | RPROMPT=""
162 | fi;
163 |
164 | if [ "$tw_layout" = "pure_verbose" ]; then
165 | PROMPT="$BREAK_LINE$tw_user_host $tw_displayed_wd$tw_git_arrow_info$BREAK_LINE$tw_full_prompt"
166 | RPROMPT=""
167 | fi;
168 |
169 | if [ "$tw_layout" = "singleline_verbose" ]; then
170 | PROMPT="$tw_user_host $tw_full_prompt"
171 | RPROMPT="$tw_right_prompt_prefix$tw_displayed_wd$tw_git_arrow_info"
172 | fi;
173 |
174 | if [ "$tw_layout" = "multiline" ]; then
175 | PROMPT="$BREAK_LINE$tw_user_host$BREAK_LINE$tw_full_prompt"
176 | RPROMPT="$tw_right_prompt_prefix$tw_displayed_wd$tw_git_arrow_info"
177 | fi;
178 | fi;
179 |
180 | zle -R && zle reset-prompt
181 | }
182 |
183 | tw_async_init_worker() {
184 | async_start_worker tw_worker -n
185 | async_register_callback tw_worker tw_prompt_callback
186 | }
187 |
188 | tw_prompt_callback() {
189 | local tw_name=$1 tw_code=$2 tw_output=$3
190 | if (( tw_code == 2 )) || (( tw_code == 3 )) || (( tw_code == 130 )); then
191 | # reinit async workers
192 | async_stop_worker tw_worker
193 | tw_async_init_worker
194 | tw_async_init_tasks
195 | elif (( tw_code )); then
196 | tw_async_init_tasks
197 | fi;
198 | tw_prompt_data[$tw_name]=$tw_output
199 | tw_redraw
200 | }
201 |
202 | tw_async_init_tasks() {
203 | typeset -Ag tw_prompt_data
204 |
205 | local tw_current_pwd="$PWD"
206 | async_worker_eval tw_worker builtin cd -q $tw_current_pwd
207 |
208 | local tw_git_hide_status="$(git config --get oh-my-zsh.hide-status 2>/dev/null)"
209 | if [[ "$tw_git_hide_status" != "1" ]]; then
210 | local tw_git_toplevel="$(git rev-parse --show-toplevel 2>/dev/null)"
211 |
212 | if [[ "$tw_current_pwd" != $tw_prompt_data[tw_current_pwd] ]]; then
213 | async_flush_jobs tw_worker
214 | tw_prompt_data[tw_git_home]=
215 | fi;
216 |
217 | if [[ "$tw_git_toplevel" != $tw_prompt_data[tw_git_toplevel] ]]; then
218 | async_flush_jobs tw_worker
219 | tw_prompt_data[tw_git_branch]=
220 | tw_prompt_data[tw_git_status]=
221 | fi;
222 |
223 | tw_prompt_data[tw_git_toplevel]="$tw_git_toplevel"
224 | tw_prompt_data[tw_current_pwd]="$tw_current_pwd"
225 | if [[ "$TYPEWRITTEN_RELATIVE_PATH" = "git" || "$TYPEWRITTEN_RELATIVE_PATH" = "adaptive" ]]; then
226 | async_job tw_worker tw_git_home $tw_current_pwd $tw_git_toplevel
227 | fi;
228 | async_job tw_worker tw_git_branch
229 | async_job tw_worker tw_git_status
230 | else
231 | tw_prompt_data[tw_git_branch]=
232 | tw_prompt_data[tw_git_status]=
233 | fi;
234 |
235 | tw_redraw
236 | }
237 |
238 | # prompt cursor fix when exiting vim
239 | tw_fix_cursor() {
240 | local tw_cursor="\e[3 q"
241 | if [ "$TYPEWRITTEN_CURSOR" = "block" ]; then
242 | tw_cursor="\e[1 q"
243 | elif [ "$TYPEWRITTEN_CURSOR" = "beam" ]; then
244 | tw_cursor="\e[5 q"
245 | fi;
246 | echo -ne "$tw_cursor"
247 | }
248 |
249 | tw_setup() {
250 | tw_async_init_worker
251 | tw_async_init_tasks
252 |
253 | zmodload zsh/zle
254 | autoload -Uz add-zsh-hook
255 | if [ "$TYPEWRITTEN_CURSOR" != "terminal" ]; then
256 | add-zsh-hook precmd tw_fix_cursor
257 | fi;
258 | add-zsh-hook precmd tw_async_init_tasks
259 |
260 | PROMPT="$tw_prompt"
261 | }
262 | tw_setup
263 |
264 | zle_highlight=( default:fg=$tw_colors[prompt] )
265 |
--------------------------------------------------------------------------------
/typewritten.zsh-theme:
--------------------------------------------------------------------------------
1 | typewritten.zsh
--------------------------------------------------------------------------------