@@ -64,6 +64,7 @@ def __init__(self, report_status: bool) -> None:
6464
6565 self ._report_status_enabled = report_status
6666 self ._commands_run = 0
67+ self ._running_command = ""
6768 self ._last_command = ""
6869 self ._last_duration = 0.0
6970 self ._command_started = 0.0
@@ -82,13 +83,17 @@ def _report_status(self, state: str) -> None:
8283 return
8384 status = {
8485 "state" : state ,
86+ "running_command" : self ._running_command ,
8587 "commands_run" : self ._commands_run ,
8688 "last_command" : self ._last_command ,
8789 "last_duration" : self ._last_duration ,
8890 }
89- self .set_window_title (STATUS_PREFIX + json .dumps (status ))
90- # prompt-toolkit buffers its output, so flush it or the "running" state would only appear after the command ends
91- get_app_session ().output .flush ()
91+ # If the Textual application exits while a command is running, the terminal is closed before the finalization
92+ # hook reports the end of the command. Writing to it then fails, which doesn't matter because nobody can see it.
93+ with contextlib .suppress (OSError ):
94+ self .set_window_title (STATUS_PREFIX + json .dumps (status ))
95+ # prompt-toolkit buffers its output, so flush it or the "running" state would only appear after the command ends
96+ get_app_session ().output .flush ()
9297
9398 def preloop (self ) -> None :
9499 """Report the initial status before the first prompt."""
@@ -102,7 +107,7 @@ def _on_command_start(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.Po
102107 """
103108 self ._command_depth += 1
104109 if self ._command_depth == 1 :
105- self ._last_command = data .statement .command
110+ self ._running_command = data .statement .command
106111 self ._command_started = time .monotonic ()
107112 self ._report_status ("running" )
108113 return data
@@ -116,8 +121,12 @@ def _on_command_end(self, data: cmd2.plugin.CommandFinalizationData) -> cmd2.plu
116121 return data
117122 self ._command_depth -= 1
118123 if self ._command_depth == 0 :
124+ # Only now does the running command become the last command, so it's never shown next to the duration
125+ # of the command before it
119126 self ._commands_run += 1
127+ self ._last_command = self ._running_command
120128 self ._last_duration = time .monotonic () - self ._command_started
129+ self ._running_command = ""
121130 self ._report_status ("idle" )
122131 return data
123132
@@ -174,17 +183,19 @@ class StatusPanel(Static):
174183 """The state of the embedded cmd2 application as last reported through its window title."""
175184
176185 state : reactive [str ] = reactive ("starting" )
186+ running_command : reactive [str ] = reactive ("" )
177187 commands_run : reactive [int ] = reactive (0 )
178188 last_command : reactive [str ] = reactive ("" )
179189 last_duration : reactive [float ] = reactive (0.0 )
180190
181191 def render (self ) -> str :
182192 """Render the panel from the reactive attributes. Textual refreshes it when any of them change."""
183193 color = "green" if self .state == "idle" else "yellow"
194+ state = f"{ self .state } { self .running_command } " if self .state == "running" else self .state
184195 # The strings come from the terminal, so escape them or something like "[/b]" would be read as markup
185196 return (
186197 "[b]cmd2 status[/b]\n "
187- f"State: [{ color } ]{ escape (self . state )} [/{ color } ]\n "
198+ f"State: [{ color } ]{ escape (state )} [/{ color } ]\n "
188199 f"Commands run: { self .commands_run } \n "
189200 f"Last command: { escape (self .last_command ) or '-' } \n "
190201 f"Last duration: { self .last_duration :.2f} s"
@@ -197,11 +208,13 @@ def apply(self, status: dict[str, Any]) -> None:
197208 """
198209 # Convert every value before setting any of them, so a bad document doesn't half update the panel
199210 state = str (status .get ("state" , "unknown" ))
211+ running_command = str (status .get ("running_command" , "" ))
200212 commands_run = int (status .get ("commands_run" , 0 ))
201213 last_command = str (status .get ("last_command" , "" ))
202214 last_duration = float (status .get ("last_duration" , 0.0 ))
203215
204216 self .state = state
217+ self .running_command = running_command
205218 self .commands_run = commands_run
206219 self .last_command = last_command
207220 self .last_duration = last_duration
@@ -241,12 +254,13 @@ def __init__(self) -> None:
241254
242255 def compose (self ) -> ComposeResult :
243256 """Create the layout: the terminal running cmd2 on the left and the status panels on the right."""
257+ cmd2_command = [sys .executable , __file__ , "--cmd2-only" , "--report-status" ]
244258 yield Header ()
245259 with Horizontal ():
246260 # The border is on a container because a Terminal sizes its pseudo-terminal from its outer size, so a
247261 # border on the Terminal itself makes the cmd2 app draw more rows than are visible and hides its prompt
248262 with Vertical (id = "cmd2-pane" ):
249- yield Terminal (command = [ sys . executable , __file__ , "--cmd2-only" , "--report-status" ] , id = "cmd2" )
263+ yield Terminal (command = cmd2_command , id = "cmd2" )
250264 with Vertical (id = "sidebar" ):
251265 yield StatusPanel (id = "status" )
252266 yield ClockPanel (id = "clock" )
0 commit comments