├── LICENSE
├── README
└── bed
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | ||
2 | || ||
3 | ||/||___ ||
4 | || /` )____________||_/|
5 | ||/___ //_/_/_/_/_/_/||/ |
6 | ||(___)/_/_/_/_/_/_/_|| |
7 | || |_|_|_|_|_|_|_|| /|
8 | || | | | | | | | ||/||
9 | ||~~~~~~~~~~~~~~~~~~~||
10 | || ||
11 |
12 | Bash only text editor
13 |
14 | [ DESCRIPTION ]
15 |
16 | Line-based/modal visual editor. Navigate with the arrow keys and edit with
17 | vim-like bindings. Supports basic file editing and viewing (e.g. scrolling,
18 | paging up/down, line editing, deleting, saving/loading).
19 |
20 | [ ENVIRONMENT VARIABLES ]
21 |
22 | BED_FILE_PROMPT Prompt shown when setting a file
23 | BED_REFRESH_TIMEOUT How long to wait idle until redrawing
24 | BED_ICON String to be shown in the top left of the status bar
25 |
26 | [ KEYBINDS ]
27 |
28 | Each bind is set with an environment variable 'BED_KEY_'. Values for
29 | the binds are the actual contents the keypress(es) would insert.
30 |
31 | Name Default Description
32 | ---- ------- -----------
33 | PGUP pgup Move up a window's worth of lines
34 | PGDN pgdn Move down a window's worth of lines
35 | UP ↑ Move up a line
36 | DOWN ↓ Move down a line
37 | QUIT q Exit bed
38 | FILE f Set target file
39 | READ r Read file into the buffer
40 | WRITE w Write buffer into the file
41 | EDIT e Modify current line
42 | APPEND a Append new line, move to it and begin editing
43 | DELETE d Delete the current line
44 | NEW n Insert new line after current
45 |
--------------------------------------------------------------------------------
/bed:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | declare -a buffer
4 | declare -i line base
5 | declare file message modified
6 |
7 | buffer=() # File contents
8 | line=0 # Currently selected line (0 means the buffer is empty)
9 | base=1 # Top-most line shown
10 | file= # Currently addressed file
11 | message="Welcome to bed (press 'q' to quit)." # Feedback text in the status bar
12 | modified=false # Tracking whether a file was modified
13 |
14 | shopt -s extglob # Ensure advanced pattern matching is available
15 | shopt -s checkwinsize; (:) # Enable and then trigger a terminal size refresh
16 | trap redraw WINCH ALRM # Attach WINCH and ALRM to redraw the screen
17 | trap die EXIT HUP USR1 # Attach most exit codes to cleanup and exit
18 | trap quit INT
19 |
20 | set_buffer_file() {
21 | bind 'set disable-completion off' 2>/dev/null # Enable completion
22 | printf '\e[?25h' # Enable cursor
23 | if read -rei "$1$file" -p "${BED_FILE_PROMPT:=Path: }" file; then
24 | modified=true
25 | fi
26 | bind 'set disable-completion on' 2>/dev/null
27 | }
28 |
29 | read_buffer() {
30 | set_buffer_file "$1" # Update target file (pass on default if present)
31 | mapfile -t -O 1 buffer <"$file" # Read file into an array
32 | if [[ "${buffer[1]}" ]]; then # Ensure that something was actually read into the file
33 | line=1 # Indicate that we have a buffer loaded
34 | modified=false
35 | message="Read ${#buffer[@]} lines from '$file'"
36 | else
37 | message="'$file' is empty"
38 | fi
39 | }
40 |
41 | write_buffer() {
42 | true >"$file" # Set the file to an empty text file
43 | for ln in "${buffer[@]}"; do # Write in the buffer to the file
44 | echo "$ln" >>"$file"
45 | done
46 | modified=false
47 | message="Wrote ${#buffer[@]} lines to '$file'"
48 | }
49 |
50 | get_help() {
51 | man bed || message="Failed to get help"
52 | }
53 |
54 | edit_line() {
55 | ((line == 0)) && return # If the line is not possible, do nothing
56 | printf '\e[?25h\e[%sH' "$((line + 2 - base))" # Reset cursor position and enable cursor
57 | read -rei "${buffer[line]}" -p "$(printf '%4s ' "$line")" # Present editable line
58 | if [[ "$REPLY" != "${buffer[line]}" ]]; then # If the line is changed, update and inform
59 | buffer[line]=$REPLY
60 | modified=true
61 | fi
62 | }
63 |
64 | new_line() {
65 | buffer=("" "${buffer[@]:1:line}" "" "${buffer[@]:line+1}")
66 | unset 'buffer[0]'
67 | modified=true
68 | }
69 |
70 | append_line() {
71 | new_line
72 | down
73 | redraw
74 | edit_line
75 | }
76 |
77 | delete_line() {
78 | buffer=("" "${buffer[@]:1:line-1}" "${buffer[@]:line+1}")
79 | unset 'buffer[0]'
80 | ((line > ${#buffer[@]})) && up
81 | modified=true
82 | }
83 |
84 | quit() {
85 | if [[ "$modified" == "true" ]]; then
86 | while :; do
87 | read -rsN1 -p "Buffer modified, save before close? [Y/n/c]" choice
88 | case "$choice" in
89 | Y|y) write_buffer; die;;
90 | N|n) die;;
91 | C|c) message="Quit canceled"; break;;
92 | *) continue;;
93 | esac
94 | done
95 | else
96 | die
97 | fi
98 | }
99 |
100 | up() {
101 | for ((i = 0; i < ${1:-1}; i++)); do
102 | ((line > 1)) && ((line--)) # As long as we can keep going up, go up
103 | ((line < base)) && ((base--)) # Push back the top if we need to
104 | ((base <= 0)) && base=1 # Don't push back if our base is at 1
105 | done
106 | }
107 |
108 | page_up() {
109 | up $((LINES - 3))
110 | }
111 |
112 | down() {
113 | for ((i = 0; i < ${1:-1}; i++)); do
114 | ((line < ${#buffer[@]})) && ((line++)) # If we can go down, go down
115 | ((line > base + LINES - 3)) && ((base++)) # Move window down if needed
116 | done
117 | }
118 |
119 | page_down() {
120 | down $((LINES - 3))
121 | }
122 |
123 | die() {
124 | bind 'set disable-completion off' 2>/dev/null # Enable completion
125 | printf '\e[?25h\e[?7h\e[?1047l' # Reset terminal to sane mode
126 | exit "${errno:-0}" # Assume that we're exiting without an error
127 | }
128 |
129 | redraw() {
130 | (printf '\e[H\e[?25l\e[100m%*s\r %s \e[46;30m %s \e[0;100m L%s W%s\e[m' \
131 | "$COLUMNS" "$message" "${BED_ICON:=🛏 }" \
132 | "$(basename "$file")" "$line" "${#buffer[line]}") # Status line, among others
133 | for ((i = base; i - base < LINES - 2; i++)); do # Iterate over shown lines
134 | ((i != line)) && printf '\e[90m' # Fade line number if not selected
135 | ((i > ${#buffer[@]})) && printf '\n\e[K ~\e[m' || \
136 | printf '\n\e[K%4s\e[m %s' "$i" "${buffer[i]}" # Print the line
137 | done
138 | printf '\n' # Add final newline to seperate commandline
139 | }
140 |
141 | key() {
142 | case "$1" in
143 | ${BED_KEY_PGUP:=$'\E[5~'}) page_up;;
144 | ${BED_KEY_PGDN:=$'\E[6~'}) page_down;;
145 | ${BED_KEY_UP:=$'\E[A'}*) up;;
146 | ${BED_KEY_DOWN:=$'\E[B'}*) down;;
147 | ${BED_KEY_HELP:=$'\b'}) get_help;; # Open the manpage (breaks stuff)
148 | ${BED_KEY_QUIT:=q}) quit;;
149 | ${BED_KEY_FILE:=f}) set_buffer_file;;
150 | ${BED_KEY_READ:=r}) read_buffer;;
151 | ${BED_KEY_WRITE:=w}) write_buffer;;
152 | ${BED_KEY_EDIT:=e}|'') edit_line;;
153 | ${BED_KEY_APPEND:=a}) append_line;;
154 | ${BED_KEY_DELETE:=d}) delete_line;;
155 | ${BED_KEY_NEW:=n}) new_line;;
156 | esac
157 | }
158 |
159 | main() {
160 | printf '\e[?1047h' # Switch to alternative buffer
161 | if [[ "$1" ]]; then # If a file was provided in the terminal pre-load it
162 | redraw # Draw out the UI before loading file
163 | read_buffer "$1"
164 | fi
165 | while redraw; do # Keep redrawing when we can (allow WINCH signals to get handled)
166 | local -a k=()
167 | local -i i=1
168 | if read -rsN1 -t"${BED_REFRESH_TIMEOUT:=0.1}" k[0]; then # Check for ready input
169 | while read -rsN1 -t0.0001 k[$i]; do ((i++)); done # Multibyte hack
170 | key "$(printf '%s' "${k[@]}")" # Handle keypress event
171 | fi
172 | done
173 | }
174 |
175 | main "$@"
176 |
--------------------------------------------------------------------------------