├── 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 | --------------------------------------------------------------------------------