@@ -10,6 +10,12 @@ use tempfile::TempDir;
1010use crate :: { management:: archive:: v1, tmux, Result } ;
1111use tmux_lib:: utils;
1212
13+ /// Shell commands that are recognized for prompt line dropping.
14+ ///
15+ /// When capturing pane content, if the active command is one of these shells,
16+ /// we can optionally drop the last N lines to avoid capturing the shell prompt.
17+ const DETECTED_SHELLS : & [ & str ] = & [ "zsh" , "bash" , "fish" ] ;
18+
1319/// Save the tmux sessions, windows and panes into a backup at `backup_dirpath`.
1420///
1521/// After saving, this function returns the path to the backup and the number of
@@ -88,23 +94,36 @@ pub async fn save<P: AsRef<Path>>(
8894 Ok ( ( new_backup_filepath, overview) )
8995}
9096
97+ /// Determine if the given command is a recognized shell.
98+ ///
99+ /// Used to decide whether to drop trailing lines (shell prompt) when capturing pane content.
100+ fn is_shell_command ( command : & str ) -> bool {
101+ DETECTED_SHELLS . contains ( & command)
102+ }
103+
104+ /// Calculate how many lines to drop from pane capture based on the active command.
105+ ///
106+ /// If the pane is running a recognized shell, we drop `num_lines_to_drop` lines
107+ /// to avoid capturing the shell prompt. For other commands, we keep everything.
108+ fn lines_to_drop_for_pane ( pane_command : & str , num_lines_to_drop : usize ) -> usize {
109+ if is_shell_command ( pane_command) {
110+ num_lines_to_drop
111+ } else {
112+ 0
113+ }
114+ }
115+
91116/// For each provided pane, retrieve the content and save it into `destination_dir`.
92117async fn save_panes_content < P : AsRef < Path > > (
93118 panes : Vec < tmux:: pane:: Pane > ,
94119 destination_dir : P ,
95120 num_lines_to_drop : usize ,
96121) -> Result < ( ) > {
97122 let mut handles = Vec :: new ( ) ;
98- let detected_shells = [ "zsh" , "bash" , "fish" ] ;
99123
100124 for pane in panes {
101125 let dest_dir = destination_dir. as_ref ( ) . to_path_buf ( ) ;
102-
103- let drop_n_last_lines = if detected_shells. contains ( & & pane. command [ ..] ) {
104- num_lines_to_drop
105- } else {
106- 0
107- } ;
126+ let drop_n_last_lines = lines_to_drop_for_pane ( & pane. command , num_lines_to_drop) ;
108127
109128 let handle = smol:: spawn ( async move {
110129 let stdout = pane. capture ( ) . await . unwrap ( ) ;
@@ -120,3 +139,102 @@ async fn save_panes_content<P: AsRef<Path>>(
120139 join_all ( handles) . await ;
121140 Ok ( ( ) )
122141}
142+
143+ #[ cfg( test) ]
144+ mod tests {
145+ use super :: * ;
146+
147+ mod shell_detection {
148+ use super :: * ;
149+
150+ #[ test]
151+ fn recognizes_zsh ( ) {
152+ assert ! ( is_shell_command( "zsh" ) ) ;
153+ }
154+
155+ #[ test]
156+ fn recognizes_bash ( ) {
157+ assert ! ( is_shell_command( "bash" ) ) ;
158+ }
159+
160+ #[ test]
161+ fn recognizes_fish ( ) {
162+ assert ! ( is_shell_command( "fish" ) ) ;
163+ }
164+
165+ #[ test]
166+ fn rejects_vim ( ) {
167+ assert ! ( !is_shell_command( "vim" ) ) ;
168+ }
169+
170+ #[ test]
171+ fn rejects_nvim ( ) {
172+ assert ! ( !is_shell_command( "nvim" ) ) ;
173+ }
174+
175+ #[ test]
176+ fn rejects_python ( ) {
177+ assert ! ( !is_shell_command( "python" ) ) ;
178+ }
179+
180+ #[ test]
181+ fn rejects_empty_command ( ) {
182+ assert ! ( !is_shell_command( "" ) ) ;
183+ }
184+
185+ #[ test]
186+ fn rejects_similar_but_different ( ) {
187+ // Shell name as substring shouldn't match
188+ assert ! ( !is_shell_command( "zsh-5.9" ) ) ;
189+ assert ! ( !is_shell_command( "/bin/zsh" ) ) ;
190+ assert ! ( !is_shell_command( "bash-5.2" ) ) ;
191+ }
192+
193+ #[ test]
194+ fn case_sensitive ( ) {
195+ assert ! ( !is_shell_command( "ZSH" ) ) ;
196+ assert ! ( !is_shell_command( "BASH" ) ) ;
197+ assert ! ( !is_shell_command( "Fish" ) ) ;
198+ }
199+ }
200+
201+ mod lines_to_drop {
202+ use super :: * ;
203+
204+ #[ test]
205+ fn drops_lines_for_shells ( ) {
206+ assert_eq ! ( lines_to_drop_for_pane( "zsh" , 2 ) , 2 ) ;
207+ assert_eq ! ( lines_to_drop_for_pane( "bash" , 3 ) , 3 ) ;
208+ assert_eq ! ( lines_to_drop_for_pane( "fish" , 1 ) , 1 ) ;
209+ }
210+
211+ #[ test]
212+ fn zero_drop_for_non_shells ( ) {
213+ assert_eq ! ( lines_to_drop_for_pane( "vim" , 5 ) , 0 ) ;
214+ assert_eq ! ( lines_to_drop_for_pane( "python" , 10 ) , 0 ) ;
215+ assert_eq ! ( lines_to_drop_for_pane( "htop" , 3 ) , 0 ) ;
216+ }
217+
218+ #[ test]
219+ fn zero_requested_means_zero_dropped ( ) {
220+ assert_eq ! ( lines_to_drop_for_pane( "zsh" , 0 ) , 0 ) ;
221+ assert_eq ! ( lines_to_drop_for_pane( "bash" , 0 ) , 0 ) ;
222+ }
223+ }
224+
225+ mod constants {
226+ use super :: * ;
227+
228+ #[ test]
229+ fn detected_shells_includes_common_shells ( ) {
230+ assert ! ( DETECTED_SHELLS . contains( & "zsh" ) ) ;
231+ assert ! ( DETECTED_SHELLS . contains( & "bash" ) ) ;
232+ assert ! ( DETECTED_SHELLS . contains( & "fish" ) ) ;
233+ }
234+
235+ #[ test]
236+ fn detected_shells_is_not_empty ( ) {
237+ assert ! ( !DETECTED_SHELLS . is_empty( ) ) ;
238+ }
239+ }
240+ }
0 commit comments