@@ -113,6 +113,36 @@ def set_title(pane: Any, key: str, label: str, *, active: bool) -> None:
113113 set_title (pane_query , "q" , "Query" , active = active_pane == "query" )
114114 set_title (pane_results , "r" , "Results" , active = active_pane == "results" )
115115
116+ def _update_vim_mode_visuals (self : UINavigationMixinHost ) -> None :
117+ """Update all visual indicators based on current vim mode.
118+
119+ This updates:
120+ - Border color on query pane (orange for NORMAL, green for INSERT)
121+ - Cursor color (via CSS class)
122+ - Status bar mode indicator
123+
124+ Only shows vim mode indicators when query pane has focus.
125+ """
126+ from sqlit .core .vim import VimMode
127+
128+ try :
129+ query_area = self .query_one ("#query-area" )
130+ has_query_focus = self .query_input .has_focus
131+ except Exception :
132+ return
133+
134+ # Update CSS classes for border and cursor color
135+ # Only show vim mode colors when query pane has focus
136+ query_area .remove_class ("vim-normal" , "vim-insert" )
137+ if has_query_focus :
138+ if self .vim_mode == VimMode .NORMAL :
139+ query_area .add_class ("vim-normal" )
140+ else :
141+ query_area .add_class ("vim-insert" )
142+
143+ # Also update the status bar
144+ self ._update_status_bar ()
145+
116146 def _update_status_bar (self : UINavigationMixinHost ) -> None :
117147 """Update status bar with connection and vim mode info."""
118148 from sqlit .core .vim import VimMode
@@ -132,19 +162,16 @@ def _update_status_bar(self: UINavigationMixinHost) -> None:
132162
133163 connecting_config = getattr (self , "_connecting_config" , None )
134164
135- if getattr (self , "_query_executing" , False ):
136- conn_info = ""
137- elif connecting_config is not None :
165+ if connecting_config is not None :
138166 connect_spinner = getattr (self , "_connect_spinner" , None )
139167 spinner = connect_spinner .frame if connect_spinner else SPINNER_FRAMES [0 ]
140168 source_emoji = connecting_config .get_source_emoji ()
141169 conn_info = f"[#FBBF24]{ spinner } Connecting to { source_emoji } { connecting_config .name } [/]"
142170 elif getattr (self , "_connection_failed" , False ):
143171 conn_info = "[#ff6b6b]Connection failed[/]"
144172 elif self .current_config :
145- display_info = get_connection_display_info (self .current_config )
146173 source_emoji = self .current_config .get_source_emoji ()
147- conn_info = f"[#4ADE80]Connected to { source_emoji } { self .current_config .name } [/] ( { display_info } ) "
174+ conn_info = f"[#4ADE80]Connected to { source_emoji } { self .current_config .name } [/]"
148175 if direct_active :
149176 conn_info += " [dim](direct, not saved)[/]"
150177 else :
@@ -159,21 +186,6 @@ def _update_status_bar(self: UINavigationMixinHost) -> None:
159186 if getattr (self , "_debug_mode" , False ) or getattr (self , "_debug_idle_scheduler" , False ):
160187 status_parts .append (f"[bold cyan]{ schema_spinner .frame } Indexing...[/]" )
161188
162- # Check if query is executing
163- query_spinner = getattr (self , "_query_spinner" , None )
164- if query_spinner and query_spinner .running :
165- import time
166-
167- from sqlit .shared .core .utils import format_duration_ms
168-
169- start_time = getattr (self , "_query_start_time" , None )
170- if start_time :
171- elapsed_ms = (time .perf_counter () - start_time ) * 1000
172- elapsed_str = format_duration_ms (elapsed_ms , always_seconds = True )
173- status_parts .append (f"[bold yellow]{ query_spinner .frame } Executing [{ elapsed_str } ][/] [dim]^z to cancel[/]" )
174- else :
175- status_parts .append (f"[bold yellow]{ query_spinner .frame } Executing[/] [dim]^z to cancel[/]" )
176-
177189 # Check if in a transaction
178190 if getattr (self , "in_transaction" , False ):
179191 status_parts .append ("[bold magenta]⚡ TRANSACTION[/]" )
@@ -182,18 +194,23 @@ def _update_status_bar(self: UINavigationMixinHost) -> None:
182194 if status_str :
183195 status_str += " "
184196
185- # Build left side content
197+ # Build left side content - mode indicator is always preserved
198+ mode_str = ""
199+ mode_plain = ""
186200 try :
187201 if self .query_input .has_focus :
188202 if self .vim_mode == VimMode .NORMAL :
189- mode_str = f"[bold orange1]-- { self .vim_mode .value } --[/]"
203+ # Warm beige background for NORMAL mode
204+ mode_str = "[bold #1e1e1e on #D8C499] NORMAL [/] "
205+ mode_plain = " NORMAL "
190206 else :
191- mode_str = f"[dim]-- { self .vim_mode .value } --[/]"
192- left_content = f"{ status_str } { mode_str } { conn_info } "
193- else :
194- left_content = f"{ status_str } { conn_info } "
207+ # Soft green background for INSERT mode
208+ mode_str = "[bold #1e1e1e on #91C58D] INSERT [/] "
209+ mode_plain = " INSERT "
195210 except Exception :
196- left_content = f"{ status_str } { conn_info } "
211+ pass
212+
213+ left_content = f"{ status_str } { mode_str } { conn_info } "
197214
198215 notification = getattr (self , "_last_notification" , "" )
199216 timestamp = getattr (self , "_last_notification_time" , "" )
@@ -212,11 +229,43 @@ def _update_status_bar(self: UINavigationMixinHost) -> None:
212229 right_str = launch_str
213230 right_plain = launch_plain
214231
215- if notification :
216- # Normal/warning notifications on right side
217- import re
232+ import re
218233
219- left_plain = re .sub (r"\[.*?\]" , "" , left_content )
234+ try :
235+ total_width = self .size .width - 2
236+ except Exception :
237+ total_width = 80
238+
239+ left_plain = re .sub (r"\[.*?\]" , "" , left_content )
240+
241+ # Build right side content - executing status takes priority over notification
242+ if getattr (self , "_query_executing" , False ):
243+ query_spinner = getattr (self , "_query_spinner" , None )
244+ if query_spinner and query_spinner .running :
245+ import time
246+
247+ from sqlit .shared .core .utils import format_duration_ms
248+
249+ start_time = getattr (self , "_query_start_time" , None )
250+ if start_time :
251+ elapsed_ms = (time .perf_counter () - start_time ) * 1000
252+ elapsed_str = format_duration_ms (elapsed_ms , always_seconds = True )
253+ right_content = f"[bold yellow]{ query_spinner .frame } Executing [{ elapsed_str } ][/] [dim]^z to cancel[/]"
254+ right_content_plain = f" Executing [{ elapsed_str } ] ^z to cancel"
255+ else :
256+ right_content = f"[bold yellow]{ query_spinner .frame } Executing[/] [dim]^z to cancel[/]"
257+ right_content_plain = " Executing ^z to cancel"
258+ else :
259+ right_content = "[bold yellow]Executing...[/]"
260+ right_content_plain = "Executing..."
261+
262+ gap = total_width - len (left_plain ) - len (right_content_plain )
263+ if gap > 2 :
264+ status .update (f"{ left_content } { ' ' * gap } { right_content } " )
265+ else :
266+ status .update (f"{ left_content } { right_content } " )
267+ elif notification :
268+ # Show notification right-aligned
220269 time_prefix = f"[dim]{ timestamp } [/] " if timestamp else ""
221270
222271 if severity == "warning" :
@@ -225,26 +274,12 @@ def _update_status_bar(self: UINavigationMixinHost) -> None:
225274 notif_str = f"{ time_prefix } { notification } "
226275
227276 notif_plain = f"{ timestamp } { notification } " if timestamp else notification
228-
229- try :
230- total_width = self .size .width - 2
231- except Exception :
232- total_width = 80
233-
234277 gap = total_width - len (left_plain ) - len (notif_plain )
235278 if gap > 2 :
236279 status .update (f"{ left_content } { ' ' * gap } { notif_str } " )
237280 else :
238- status .update (notif_str )
281+ status .update (f" { left_content } { notif_str } " )
239282 elif right_str :
240- import re
241-
242- left_plain = re .sub (r"\[.*?\]" , "" , left_content )
243- try :
244- total_width = self .size .width - 2
245- except Exception :
246- total_width = 80
247-
248283 gap = total_width - len (left_plain ) - len (right_plain )
249284 if gap > 2 :
250285 status .update (f"{ left_content } { ' ' * gap } { right_str } " )
0 commit comments