.bimrc support added. Unlick traditional vim, it's not settings, but quick commands you can use in documents. README.md has details.

This commit is contained in:
Storm Dragon
2026-05-15 22:10:36 -04:00
parent 05e59cbbc8
commit 919ea87f36
2 changed files with 210 additions and 1 deletions
+23
View File
@@ -43,11 +43,34 @@ mode and supports:
`4x`, `5w`, `5dw`, `3rX`, and `2s` `4x`, `5w`, `5dw`, `3rX`, and `2s`
- `Esc` return to command mode - `Esc` return to command mode
- `Ctrl+G` show file, modified state, line, and column info - `Ctrl+G` show file, modified state, line, and column info
- `,name` then `Enter` run a command defined in `~/.bimrc` and insert its output
- `:w`, `:w path`, `:q`, `:q!`, `:wq`, and `:wq path` - `:w`, `:w path`, `:q`, `:q!`, `:wq`, and `:wq path`
There is no persistent ruler/status line. The bottom line is used only for There is no persistent ruler/status line. The bottom line is used only for
commands, messages, and explicit `Ctrl+G` file info. commands, messages, and explicit `Ctrl+G` file info.
## `~/.bimrc` commands
`bim` can load simple command shortcuts from `~/.bimrc`. Each non-empty,
non-comment line is a command name, an equals sign, and the shell command to run:
```text
date=date +'%A, %B %d, %Y'
uuid=uuidgen
```
In command mode, type `,date` then `Enter` to run the configured `date` command
and insert its standard output at the cursor. `Esc` cancels a pending command,
and Backspace edits it before it runs. `bim` appends one space after inserted
command output so short snippets are ready for continued typing.
Commands are not run when `bim` starts. They only run when invoked explicitly
with `,name`. `bim` does not source `~/.bimrc`; it parses command definitions as
data. For safety, `~/.bimrc` must be a regular file owned by the current user,
must not be a symlink, and must not be writable by group or other users.
Command names may contain letters, digits, underscores, and hyphens, and must
start with a letter or underscore.
Run it with: Run it with:
```bash ```bash
+187 -1
View File
@@ -15,6 +15,8 @@ renderedMessage=$'\001'
pendingInput="" pendingInput=""
pendingCommand="" pendingCommand=""
commandCount="" commandCount=""
bimCommandActive=0
bimCommandName=""
lastChangeKeys="" lastChangeKeys=""
insertChangeKeys="" insertChangeKeys=""
replayKeysRemaining=0 replayKeysRemaining=0
@@ -33,6 +35,58 @@ undoCursorCol=0
undoTopRow=0 undoTopRow=0
undoDirty=0 undoDirty=0
insertUndoSaved=0 insertUndoSaved=0
startupMessage=""
declare -A bimCommands=()
trim_whitespace() {
local value="$1"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
printf '%s' "$value"
}
load_bimrc() {
local bimrcPath="${HOME:-}/.bimrc"
local permissions line trimmed name commandText invalidCount=0
[[ -n "${HOME:-}" && -e "$bimrcPath" ]] || return
if [[ -L "$bimrcPath" || ! -f "$bimrcPath" || ! -O "$bimrcPath" ]]; then
startupMessage="Ignoring unsafe ~/.bimrc"
return
fi
permissions="$(stat -c %A "$bimrcPath" 2>/dev/null)" || {
startupMessage="Could not inspect ~/.bimrc"
return
}
if [[ "${permissions:5:1}" == "w" || "${permissions:8:1}" == "w" ]]; then
startupMessage="Ignoring group/world-writable ~/.bimrc"
return
fi
while IFS= read -r line || [[ -n "$line" ]]; do
trimmed="$(trim_whitespace "$line")"
[[ -z "$trimmed" || "${trimmed:0:1}" == "#" ]] && continue
if [[ "$line" != *"="* ]]; then
((invalidCount++))
continue
fi
name="$(trim_whitespace "${line%%=*}")"
commandText="$(trim_whitespace "${line#*=}")"
if [[ "$name" =~ ^[A-Za-z_][A-Za-z0-9_-]*$ && -n "$commandText" ]]; then
bimCommands["$name"]="$commandText"
else
((invalidCount++))
fi
done < "$bimrcPath"
if ((invalidCount > 0)); then
startupMessage="Ignored $invalidCount invalid ~/.bimrc line(s)"
fi
}
load_bimrc
if [[ -n "$filePath" && -f "$filePath" ]]; then if [[ -n "$filePath" && -f "$filePath" ]]; then
mapfile -t lines < "$filePath" mapfile -t lines < "$filePath"
@@ -58,6 +112,7 @@ trap cleanup EXIT
trap 'exit 130' INT TERM trap 'exit 130' INT TERM
stty raw -echo stty raw -echo
printf '\033[?1049h\033[?25l' printf '\033[?1049h\033[?25l'
statusMessage="$startupMessage"
screen_size() { screen_size() {
screenRows="$(tput lines 2>/dev/null || printf '24')" screenRows="$(tput lines 2>/dev/null || printf '24')"
@@ -97,7 +152,9 @@ file_info() {
draw_message() { draw_message() {
local message="$statusMessage" local message="$statusMessage"
if [[ -n "$pendingCommand" || -n "$commandCount" ]]; then if ((bimCommandActive)); then
message=",$bimCommandName"
elif [[ -n "$pendingCommand" || -n "$commandCount" ]]; then
message="${commandCount}${pendingCommand}" message="${commandCount}${pendingCommand}"
fi fi
@@ -453,6 +510,82 @@ repeat_last_change() {
statusMessage="" statusMessage=""
} }
bim_command_exists() {
local name="$1"
[[ -n "${bimCommands[$name]+set}" ]]
}
bim_command_has_prefix() {
local prefix="$1"
local name
for name in "${!bimCommands[@]}"; do
[[ "$name" == "$prefix"* ]] && return 0
done
return 1
}
run_bim_command() {
local name="$1"
local commandText="${bimCommands[$name]}"
local output exitStatus
output="$(bash -c "$commandText" </dev/null 2>/dev/null)"
exitStatus=$?
if ((exitStatus != 0)); then
statusMessage=",$name failed with status $exitStatus"
return
fi
if [[ -z "$output" ]]; then
statusMessage=",$name produced no output"
return
fi
insert_text_at_cursor "${output} "
statusMessage="Inserted ,$name"
}
handle_bim_command_key() {
local key="$1"
case "$key" in
$'\033')
bimCommandActive=0
bimCommandName=""
statusMessage=""
return
;;
$'\r'|$'\n')
if bim_command_exists "$bimCommandName"; then
run_bim_command "$bimCommandName"
else
statusMessage="Unknown command: ,$bimCommandName"
fi
bimCommandActive=0
bimCommandName=""
return
;;
$'\177'|$'\b')
if ((${#bimCommandName} > 0)); then
bimCommandName="${bimCommandName:0:${#bimCommandName}-1}"
fi
return
;;
esac
if [[ "$key" =~ ^[A-Za-z0-9_-]$ ]]; then
bimCommandName+="$key"
if ! bim_command_has_prefix "$bimCommandName"; then
statusMessage="Unknown command: ,$bimCommandName"
bimCommandActive=0
bimCommandName=""
fi
else
statusMessage="Invalid command name: ,$bimCommandName"
bimCommandActive=0
bimCommandName=""
fi
}
save_undo() { save_undo() {
undoLines=("${lines[@]}") undoLines=("${lines[@]}")
undoCursorRow="$cursorRow" undoCursorRow="$cursorRow"
@@ -536,6 +669,42 @@ backspace_insert() {
fi fi
} }
insert_text_at_cursor() {
local text="$1"
local line="${lines[cursorRow]}"
local prefix="${line:0:cursorCol}"
local suffix="${line:cursorCol}"
local rest="$text"
local segment
local -a parts=()
local i lastIndex
[[ -n "$text" ]] || return 1
while [[ "$rest" == *$'\n'* ]]; do
segment="${rest%%$'\n'*}"
parts+=("$segment")
rest="${rest#*$'\n'}"
done
parts+=("$rest")
save_undo
if ((${#parts[@]} == 1)); then
lines[cursorRow]="${prefix}${parts[0]}${suffix}"
cursorCol=$((cursorCol + ${#parts[0]}))
else
lastIndex=$((${#parts[@]} - 1))
parts[0]="${prefix}${parts[0]}"
parts[lastIndex]="${parts[lastIndex]}${suffix}"
lines=("${lines[@]:0:cursorRow}" "${parts[@]}" "${lines[@]:cursorRow+1}")
cursorRow=$((cursorRow + lastIndex))
cursorCol=$((${#parts[lastIndex]} - ${#suffix}))
fi
dirty=1
insertUndoSaved=0
clamp_cursor
}
delete_under_cursor() { delete_under_cursor() {
local line="${lines[cursorRow]}" local line="${lines[cursorRow]}"
if ((cursorCol < ${#line})); then if ((cursorCol < ${#line})); then
@@ -1132,6 +1301,13 @@ prompt_command() {
handle_command_key() { handle_command_key() {
local key="$1" local key="$1"
local count i local count i
if ((bimCommandActive)); then
handle_bim_command_key "$key"
commandCount=""
pendingCommand=""
return
fi
if [[ -n "$pendingCommand" ]]; then if [[ -n "$pendingCommand" ]]; then
count="$(command_count)" count="$(command_count)"
if [[ "$pendingCommand" == "r" ]]; then if [[ "$pendingCommand" == "r" ]]; then
@@ -1382,6 +1558,16 @@ handle_command_key() {
commandCount="" commandCount=""
prompt_command prompt_command
;; ;;
,)
commandCount=""
if ((${#bimCommands[@]} == 0)); then
statusMessage="No ~/.bimrc commands"
else
bimCommandActive=1
bimCommandName=""
statusMessage=""
fi
;;
$'\007') $'\007')
commandCount="" commandCount=""
file_info file_info