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
101258bind ' 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
134271bind -x ' "\C-x\C-m": _ghost_pre_accept_line'
0 commit comments