Skip to content

Commit 8dad357

Browse files
A Dynamic Collection of Shell Scripts with Educational Purpose
1 parent 3413b15 commit 8dad357

2 files changed

Lines changed: 191 additions & 169 deletions

File tree

scripts/bash-ghost-text

Lines changed: 191 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
11
#!/usr/bin/env bash
2+
# License: GPLv3
3+
# Credits: Felipe Facundes
24

35
_ghost_history_data=""
46
_ghost_suggestion=""
57
_ghost_prompt_len=0
8+
_ghost_state_file="$HOME/.cache/ghost_mode"
9+
_ghost_mode="clean"
10+
11+
_ghost_load_state() {
12+
if [[ -f "$_ghost_state_file" ]]; then
13+
_ghost_mode=$(cat "$_ghost_state_file" 2>/dev/null || echo "clean")
14+
else
15+
_ghost_mode="clean"
16+
fi
17+
}
18+
19+
_ghost_save_state() {
20+
mkdir -p "$(dirname "$_ghost_state_file")" 2>/dev/null
21+
echo "$_ghost_mode" | tee "$_ghost_state_file" > /dev/null
22+
}
623

724
_ghost_init_history() {
825
_ghost_history_data=$(
@@ -22,6 +39,10 @@ _ghost_refresh() {
2239
history -a
2340
_ghost_init_history
2441
_ghost_update_prompt_len
42+
43+
if [[ "$_ghost_mode" == "ghost" ]]; then
44+
printf '\e[?2026h' >/dev/tty
45+
fi
2546
}
2647

2748
_ghost_get_suggestion() {
@@ -35,100 +56,216 @@ _ghost_get_suggestion() {
3556
done <<< "$_ghost_history_data"
3657
}
3758

38-
_ghost_render() {
39-
if [[ $READLINE_POINT -eq ${#READLINE_LINE} ]]; then
40-
_ghost_get_suggestion
41-
else
42-
_ghost_suggestion=""
43-
fi
44-
59+
# Render modo ghost
60+
_ghost_render_ghost() {
61+
[[ $READLINE_POINT -eq ${#READLINE_LINE} ]] \
62+
&& _ghost_get_suggestion \
63+
|| _ghost_suggestion=""
4564
local col=$(( _ghost_prompt_len + ${#READLINE_LINE} + 1 ))
46-
4765
if [[ -n "$_ghost_suggestion" ]]; then
48-
printf '\e[s\e[%dG\e[38;5;244m%s\e[0m\e[u' "$col" "$_ghost_suggestion" >/dev/tty
66+
printf '\e[s\e[%dG\e[38;5;244m%s\e[0m\e[K\e[u\e[?2026l\e[?2026h' \
67+
"$col" "$_ghost_suggestion" >/dev/tty
4968
else
50-
printf '\e[s\e[%dG\e[K\e[u' "$col" >/dev/tty
69+
printf '\e[s\e[%dG\e[K\e[u\e[?2026l\e[?2026h' "$col" >/dev/tty
5170
fi
5271
}
5372

54-
_ghost_handle_char() {
55-
local char="$1"
56-
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}${char}${READLINE_LINE:$READLINE_POINT}"
73+
# Handlers modo ghost
74+
_ghost_self_insert() {
75+
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}${_ghost_char}${READLINE_LINE:$READLINE_POINT}"
5776
READLINE_POINT=$(( READLINE_POINT + 1 ))
58-
_ghost_render
77+
_ghost_render_ghost
5978
}
6079

61-
# Função para aceitar ghost (Ctrl+F)
6280
_ghost_accept() {
63-
if [[ -n "$_ghost_suggestion" && $READLINE_POINT -eq ${#READLINE_LINE} ]]; then
81+
if [[ $READLINE_POINT -lt ${#READLINE_LINE} ]]; then
82+
READLINE_POINT=$(( READLINE_POINT + 1 ))
83+
elif [[ -n "$_ghost_suggestion" ]]; then
6484
READLINE_LINE+="$_ghost_suggestion"
6585
READLINE_POINT=${#READLINE_LINE}
6686
_ghost_suggestion=""
6787
fi
68-
_ghost_render
88+
_ghost_render_ghost
6989
}
7090

71-
# Função para aceitar palavra (Alt+F)
7291
_ghost_accept_word() {
73-
_ghost_get_suggestion
7492
[[ -z "$_ghost_suggestion" ]] && return
75-
7693
local word="${_ghost_suggestion%% *}"
7794
[[ "$_ghost_suggestion" == *' '* ]] && word+=' '
78-
7995
READLINE_LINE+="$word"
8096
READLINE_POINT=${#READLINE_LINE}
81-
_ghost_suggestion=""
82-
_ghost_render
97+
_ghost_suggestion="${_ghost_suggestion#"$word"}"
98+
_ghost_render_ghost
8399
}
84100

85-
_ghost_backspace() {
101+
# Backspace normal - apaga 1 caractere
102+
_ghost_backspace_ghost() {
86103
if [[ $READLINE_POINT -gt 0 ]]; then
87104
READLINE_LINE="${READLINE_LINE:0:$((READLINE_POINT - 1))}${READLINE_LINE:$READLINE_POINT}"
88105
READLINE_POINT=$(( READLINE_POINT - 1 ))
89106
fi
90-
_ghost_render
107+
_ghost_render_ghost
108+
}
109+
110+
# Ctrl+Backspace - apaga palavra anterior (sem afetar a previsão)
111+
_ghost_backward_kill_word_ghost() {
112+
if [[ $READLINE_POINT -gt 0 ]]; then
113+
local before="${READLINE_LINE:0:$READLINE_POINT}"
114+
local after="${READLINE_LINE:$READLINE_POINT}"
115+
local new_point=$READLINE_POINT
116+
117+
# Remove espaços em branco antes da palavra
118+
while [[ $new_point -gt 0 && "${before:$((new_point - 1)):1}" == " " ]]; do
119+
new_point=$((new_point - 1))
120+
done
121+
122+
# Remove a palavra
123+
while [[ $new_point -gt 0 && "${before:$((new_point - 1)):1}" != " " ]]; do
124+
new_point=$((new_point - 1))
125+
done
126+
127+
READLINE_LINE="${before:0:$new_point}${after}"
128+
READLINE_POINT=$new_point
129+
fi
130+
_ghost_render_ghost
131+
}
132+
133+
_ghost_left_ghost() {
134+
READLINE_POINT=$(( READLINE_POINT > 0 ? READLINE_POINT - 1 : 0 ))
135+
_ghost_render_ghost
136+
}
137+
138+
# Configura binds para modo ghost
139+
_ghost_setup_ghost_binds() {
140+
# Limpa binds anteriores
141+
for i in {32..126}; do
142+
oct=$(printf '\\%03o' "$i")
143+
bind -r "$oct" 2>/dev/null
144+
done
145+
bind -r '\e[C' 2>/dev/null
146+
bind -r '\e[D' 2>/dev/null
147+
bind -r '\C-?' 2>/dev/null
148+
bind -r '\C-h' 2>/dev/null
149+
bind -r '\C-u' 2>/dev/null
150+
bind -r '\C-a' 2>/dev/null
151+
bind -r '\C-e' 2>/dev/null
152+
bind -r '\C-l' 2>/dev/null
153+
bind -r '\ef' 2>/dev/null
154+
155+
# Caracteres imprimíveis com bind -x
156+
for i in {32..126}; do
157+
oct=$(printf '\\%03o' "$i")
158+
if [[ $i -eq 92 ]]; then
159+
bind -x "\"$oct\": _ghost_char='\\\\'; _ghost_self_insert"
160+
elif [[ $i -eq 39 ]]; then
161+
bind -x "\"$oct\": _ghost_char=\"'\"; _ghost_self_insert"
162+
else
163+
safe=$(printf '%q' "$(printf "\\$(printf '%03o' "$i")")")
164+
bind -x "\"$oct\": _ghost_char=$safe; _ghost_self_insert"
165+
fi
166+
done
167+
168+
# Teclas controle modo ghost
169+
bind -x '"\e[C": _ghost_accept'
170+
bind -x '"\e[D": _ghost_left_ghost'
171+
bind -x '"\ef": _ghost_accept_word'
172+
bind -x '"\C-?": _ghost_backspace_ghost' # Backspace normal
173+
bind -x '"\C-h": _ghost_backward_kill_word_ghost' # Ctrl+Backspace
174+
bind -x '"\C-u": READLINE_LINE=""; READLINE_POINT=0; _ghost_suggestion=""; _ghost_render_ghost'
175+
bind -x '"\C-a": READLINE_POINT=0; _ghost_render_ghost'
176+
bind -x '"\C-e": READLINE_POINT=${#READLINE_LINE}; _ghost_render_ghost'
177+
bind -x '"\C-l": clear; _ghost_render_ghost'
178+
}
179+
180+
# Configura binds para modo clean
181+
_ghost_setup_clean_binds() {
182+
# Limpa binds anteriores
183+
for i in {32..126}; do
184+
oct=$(printf '\\%03o' "$i")
185+
bind -r "$oct" 2>/dev/null
186+
done
187+
bind -r '\e[C' 2>/dev/null
188+
bind -r '\e[D' 2>/dev/null
189+
bind -r '\C-?' 2>/dev/null
190+
bind -r '\C-h' 2>/dev/null
191+
bind -r '\C-u' 2>/dev/null
192+
bind -r '\C-a' 2>/dev/null
193+
bind -r '\C-e' 2>/dev/null
194+
bind -r '\C-l' 2>/dev/null
195+
bind -r '\ef' 2>/dev/null
196+
197+
# Caracteres imprimíveis com self-insert nativo
198+
for i in {32..126}; do
199+
if [[ $i -eq 92 ]]; then
200+
bind '"\\\\": self-insert'
201+
elif [[ $i -eq 34 ]]; then
202+
bind '"\\\"": self-insert'
203+
elif [[ $i -eq 39 ]]; then
204+
bind "\"\\'\": self-insert"
205+
else
206+
char=$(printf "\\$(printf '%03o' "$i")")
207+
bind "\"$char\": self-insert"
208+
fi
209+
done
210+
211+
# SETAS 100% NATIVAS
212+
bind '"\e[D": backward-char'
213+
bind '"\e[C": forward-char'
214+
215+
# Backspace normal (nativo)
216+
bind '"\C-?": backward-delete-char'
217+
218+
# Ctrl+Backspace - apaga palavra (nativo)
219+
bind '"\C-h": backward-kill-word'
220+
221+
# Outros atalhos nativos
222+
bind '"\C-u": unix-line-discard'
223+
bind '"\C-a": beginning-of-line'
224+
bind '"\C-e": end-of-line'
225+
bind '"\C-l": clear-screen'
226+
}
227+
228+
# Toggle Ctrl+F
229+
_ghost_toggle() {
230+
if [[ "$_ghost_mode" == "clean" ]]; then
231+
_ghost_mode="ghost"
232+
_ghost_save_state
233+
_ghost_setup_ghost_binds
234+
printf '\e[?2026h' >/dev/tty
235+
_ghost_render_ghost
236+
else
237+
_ghost_mode="clean"
238+
_ghost_save_state
239+
_ghost_suggestion=""
240+
printf '\e[?2026l' >/dev/tty
241+
_ghost_setup_clean_binds
242+
fi
91243
}
92244

93245
_ghost_pre_accept_line() {
94246
printf '\e[K' >/dev/tty
95247
_ghost_suggestion=""
248+
if [[ "$_ghost_mode" == "ghost" ]]; then
249+
printf '\e[?2026l' >/dev/tty
250+
fi
96251
}
97252

253+
# Inicialização
254+
_ghost_load_state
98255
_ghost_init_history
99256
_ghost_update_prompt_len
100257

101258
bind 'set show-mode-in-prompt off'
102259

103-
# Caracteres imprimíveis com self-insert
104-
for i in {32..126}; do
105-
if [[ $i -eq 92 ]]; then
106-
bind '"\\\\": self-insert'
107-
elif [[ $i -eq 34 ]]; then
108-
bind '"\\\"": self-insert'
109-
elif [[ $i -eq 39 ]]; then
110-
bind "\"\\'\": self-insert"
111-
else
112-
char=$(printf "\\$(printf '%03o' "$i")")
113-
bind "\"$char\": self-insert"
114-
fi
115-
done
116-
117-
# Teclas de controle
118-
bind -x '"\C-f": _ghost_accept' # Ctrl+F - aceita ghost (com redesenho)
119-
bind -x '"\ef": _ghost_accept_word' # Alt+F - aceita palavra
120-
bind -x '"\C-?": _ghost_backspace' # Backspace
121-
bind -x '"\C-h": _ghost_backspace' # Ctrl+H
122-
bind -x '"\C-u": READLINE_LINE=""; READLINE_POINT=0; _ghost_suggestion=""; _ghost_render'
123-
bind -x '"\C-a": READLINE_POINT=0; _ghost_render'
124-
bind -x '"\C-e": READLINE_POINT=${#READLINE_LINE}; _ghost_render'
125-
bind -x '"\C-l": clear; _ghost_render'
126-
127-
# SETA ESQUERDA: nativa, sem redesenho
128-
bind '"\e[D": backward-char'
129-
130-
# SETA DIREITA: nativa, sem redesenho, mas não aceita ghost
131-
bind '"\e[C": forward-char'
260+
# Configura binds iniciais baseado no estado
261+
if [[ "$_ghost_mode" == "ghost" ]]; then
262+
_ghost_setup_ghost_binds
263+
else
264+
_ghost_setup_clean_binds
265+
fi
266+
267+
# Ctrl+F toggle (comum)
268+
bind -x '"\C-f": _ghost_toggle'
132269

133270
# Enter
134271
bind -x '"\C-x\C-m": _ghost_pre_accept_line'

0 commit comments

Comments
 (0)