@@ -32,6 +32,8 @@ impl Printer for iTermPrinter {
3232 img : & DynamicImage ,
3333 config : & Config ,
3434 ) -> ViuResult < ( u32 , u32 ) > {
35+ // DEBUG: REMOVE THIS BEFORE MERGE
36+ eprintln ! ( "using iterm" ) ;
3537 let ( width, height) = img. dimensions ( ) ;
3638
3739 // Transform the dynamic image to a PNG which can be given directly to iTerm
@@ -96,7 +98,7 @@ const ITERM_CAP_REPLY_SIZE: usize = "1337;Capabilities=".len();
9698/// Check if the terminal supports "iterm2 inline image protocol" by querying capabilities.
9799///
98100/// This function is based on what is written in <https://iterm2.com/feature-reporting/> and <https://gitlab.com/gnachman/iterm2/-/issues/10236>.
99- fn has_iterm_support ( stdout : & mut impl Write , stdin : & impl ReadKey ) -> ViuResult {
101+ fn has_iterm_support_capabilities ( stdout : & mut impl Write , stdin : & impl ReadKey ) -> ViuResult {
100102 // send the query
101103 write ! (
102104 stdout,
@@ -124,10 +126,10 @@ fn has_iterm_support(stdout: &mut impl Write, stdin: &impl ReadKey) -> ViuResult
124126 }
125127
126128 // DEBUG: REMOVE THIS BEFORE MERGE
127- eprintln ! ( "Iterm2 Response: {:#?}" , response) ;
129+ eprintln ! ( "Iterm2 Response Capabilities : {:#?}" , response) ;
128130
129131 // no response to the "Capabilities" query, or pre-maturely ended without the DSR
130- if response. last ( ) != Some ( & end_seq) || response. len ( ) < ITERM_CAP_REPLY_SIZE + 1 /* BEL */ + 1
132+ if response. last ( ) != Some ( & end_seq) || response. len ( ) < ITERM_CAP_REPLY_SIZE + 1 /* ST */ + 1
131133 /* END SEQ */
132134 {
133135 return Err ( ViuError :: ItermResponse ( response) ) ;
@@ -148,11 +150,85 @@ fn has_iterm_support(stdout: &mut impl Write, stdin: &impl ReadKey) -> ViuResult
148150 Err ( ViuError :: ItermResponse ( response) )
149151}
150152
153+ const ITERM_CELL_REPLY_SIZE : usize = "1337;ReportCellSize=" . len ( ) ;
154+
155+ /// Check if the terminal liekly supports "iterm2 inline image protocol" by querying ReportCellSize.
156+ ///
157+ /// This function is based on what is written in <https://iterm2.com/documentation-escape-codes.html#report-cell-size> and <https://github.com/atanunq/viuer/pull/88>.
158+ fn has_iterm_support_reportcellsize ( stdout : & mut impl Write , stdin : & impl ReadKey ) -> ViuResult {
159+ // send the query
160+ write ! (
161+ stdout,
162+ // Query iterm2 ReportCellSize https://iterm2.com/documentation-escape-codes.html#report-cell-size
163+ "\x1b ]1337;ReportCellSize\x1b \\ "
164+ ) ?;
165+ // send extra "Device Status Report (DSR)" which practically all terminals respond to, to avoid infinitely blocking if not replied to the query above
166+ // see https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
167+ write ! ( stdout, "\x1b [5n" ) ?;
168+
169+ stdout. flush ( ) ?;
170+
171+ let mut response = Vec :: new ( ) ;
172+
173+ let end_seq = Key :: UnknownEscSeq ( vec ! [ '[' , '0' , 'n' ] ) ;
174+
175+ while let Ok ( key) = stdin. read_key ( ) {
176+ // The response will end with the reply to "\x1b[5n", which is "\x1b[0n"
177+ // Also, break if the Unknown key is found, which is returned when we're not in a tty
178+ let should_break = key == end_seq || key == Key :: Unknown ;
179+ response. push ( key) ;
180+ if should_break {
181+ break ;
182+ }
183+ }
184+
185+ // DEBUG: REMOVE THIS BEFORE MERGE
186+ eprintln ! ( "Iterm2 Response ReportCellSize: {:#?}" , response) ;
187+
188+ // no response to the "Capabilities" query, or pre-maturely ended without the DSR
189+ if response. last ( ) != Some ( & end_seq) || response. len ( ) < ITERM_CELL_REPLY_SIZE + 1 /* ST */ + 1
190+ /* END SEQ */
191+ {
192+ return Err ( ViuError :: ItermResponse ( response) ) ;
193+ }
194+
195+ // Check if the response we got actually contains the correct response (we only check the echo name)
196+ const EXPECTED_RESPONSE : & str = "ReportCellSize" ;
197+
198+ let mut chars = EXPECTED_RESPONSE . chars ( ) . peekable ( ) ;
199+ let mut consumed: usize = 0 ;
200+
201+ // Check each key in the response and try to exhaust the "chars" iter, which will indicate the full response is available
202+ for key in & response {
203+ if chars. peek ( ) . map ( |v| Key :: Char ( * v) ) . as_ref ( ) == Some ( key) {
204+ let _ = chars. next ( ) ;
205+ consumed += 1 ;
206+ } else if consumed != 0 {
207+ // we hit something not in the correct sequence, so we break here
208+ // for example we hit "ReportVariable" where we want to stop at "V" instead of trying to continue
209+ break ;
210+ }
211+ }
212+
213+ if chars. next ( ) . is_none ( ) {
214+ return Ok ( ( ) ) ;
215+ }
216+
217+ Err ( ViuError :: ItermResponse ( response) )
218+ }
219+
151220/// Check if the iTerm protocol can be used
152221fn check_iterm_support ( ) -> bool {
153222 let mut stdout = std:: io:: stdout ( ) ;
154223 let term = Term :: stdout ( ) ;
155- if has_iterm_support ( & mut stdout, & term) . is_ok ( ) {
224+ if has_iterm_support_capabilities ( & mut stdout, & term) . is_ok ( ) {
225+ // DEBUG: REMOVE THIS BEFORE MERGE
226+ eprintln ! ( "Capabilities OK" ) ;
227+ return true ;
228+ }
229+ if has_iterm_support_reportcellsize ( & mut stdout, & term) . is_ok ( ) {
230+ // DEBUG: REMOVE THIS BEFORE MERGE
231+ eprintln ! ( "ReportCellSize OK" ) ;
156232 return true ;
157233 }
158234
@@ -212,7 +288,8 @@ mod tests {
212288
213289 let test_data = [
214290 // intro
215- Key :: UnknownEscSeq ( [ ']' , '1' ] . into ( ) ) , // TODO: is this actually returning that, or differently? i dont know how "console" handles OSC
291+ Key :: UnknownEscSeq ( vec ! [ ']' ] ) , // TODO: is this actually returning that, or differently? i dont know how "console" handles OSC
292+ Key :: Char ( '1' ) ,
216293 Key :: Char ( '3' ) ,
217294 Key :: Char ( '3' ) ,
218295 Key :: Char ( '7' ) ,
@@ -239,14 +316,17 @@ mod tests {
239316 Key :: Char ( 'F' ) ,
240317 Key :: Char ( 'S' ) ,
241318 Key :: Char ( 'x' ) ,
242- // BEL / Bell
319+ // ST
243320 Key :: UnknownEscSeq ( vec ! [ '\\' ] ) ,
244321 // DSR
245322 Key :: UnknownEscSeq ( vec ! [ '[' , '0' , 'n' ] ) ,
246323 ] ;
247324 let test_response = TestKeys :: new ( & test_data) ;
248325
249- assert_eq ! ( has_iterm_support( & mut vec, & test_response) . unwrap( ) , ( ) ) ;
326+ assert_eq ! (
327+ has_iterm_support_capabilities( & mut vec, & test_response) . unwrap( ) ,
328+ ( )
329+ ) ;
250330 let stdout = std:: str:: from_utf8 ( & vec) . unwrap ( ) ;
251331
252332 assert_eq ! ( stdout, "\x1b ]1337;Capabilities\x1b \\ \x1b [5n" ) ;
@@ -263,10 +343,81 @@ mod tests {
263343 ] ;
264344 let test_response = TestKeys :: new ( & test_data) ;
265345
266- assert ! ( has_iterm_support ( & mut vec, & test_response) . is_err( ) ) ;
346+ assert ! ( has_iterm_support_capabilities ( & mut vec, & test_response) . is_err( ) ) ;
267347 let stdout = std:: str:: from_utf8 ( & vec) . unwrap ( ) ;
268348
269349 assert_eq ! ( stdout, "\x1b ]1337;Capabilities\x1b \\ \x1b [5n" ) ;
270350 assert ! ( test_response. reached_end( ) ) ;
271351 }
352+
353+ #[ test]
354+ fn reportcellsize_iterm2_should_reply ( ) {
355+ // output captured on konsole 25.08.1
356+ let mut vec = Vec :: new ( ) ;
357+
358+ let test_data = [
359+ // intro
360+ Key :: UnknownEscSeq ( vec ! [ ']' ] ) ,
361+ Key :: Char ( '1' ) ,
362+ Key :: Char ( '3' ) ,
363+ Key :: Char ( '3' ) ,
364+ Key :: Char ( '7' ) ,
365+ Key :: Char ( ';' ) ,
366+ Key :: Char ( 'R' ) ,
367+ Key :: Char ( 'e' ) ,
368+ Key :: Char ( 'p' ) ,
369+ Key :: Char ( 'o' ) ,
370+ Key :: Char ( 'r' ) ,
371+ Key :: Char ( 't' ) ,
372+ Key :: Char ( 'C' ) ,
373+ Key :: Char ( 'e' ) ,
374+ Key :: Char ( 'l' ) ,
375+ Key :: Char ( 'l' ) ,
376+ Key :: Char ( 'S' ) ,
377+ Key :: Char ( 'i' ) ,
378+ Key :: Char ( 'z' ) ,
379+ Key :: Char ( 'e' ) ,
380+ Key :: Char ( '=' ) ,
381+ // actual response
382+ Key :: Char ( '1' ) ,
383+ Key :: Char ( '5' ) ,
384+ Key :: Char ( '.' ) ,
385+ Key :: Char ( '0' ) ,
386+ Key :: Char ( ';' ) ,
387+ Key :: Char ( '1' ) ,
388+ Key :: Char ( '.' ) ,
389+ Key :: Char ( '0' ) ,
390+ // BEL / Bell
391+ Key :: Char ( '\x07' ) ,
392+ // DSR
393+ Key :: UnknownEscSeq ( vec ! [ '[' , '0' , 'n' ] ) ,
394+ ] ;
395+ let test_response = TestKeys :: new ( & test_data) ;
396+
397+ assert_eq ! (
398+ has_iterm_support_reportcellsize( & mut vec, & test_response) . unwrap( ) ,
399+ ( )
400+ ) ;
401+ let stdout = std:: str:: from_utf8 ( & vec) . unwrap ( ) ;
402+
403+ assert_eq ! ( stdout, "\x1b ]1337;ReportCellSize\x1b \\ \x1b [5n" ) ;
404+ assert ! ( test_response. reached_end( ) ) ;
405+ }
406+
407+ #[ test]
408+ fn reportcellsize_should_handle_no_reply ( ) {
409+ let mut vec = Vec :: new ( ) ;
410+
411+ let test_data = [
412+ // DSR
413+ Key :: UnknownEscSeq ( [ '[' , '0' , 'n' ] . into ( ) ) ,
414+ ] ;
415+ let test_response = TestKeys :: new ( & test_data) ;
416+
417+ assert ! ( has_iterm_support_reportcellsize( & mut vec, & test_response) . is_err( ) ) ;
418+ let stdout = std:: str:: from_utf8 ( & vec) . unwrap ( ) ;
419+
420+ assert_eq ! ( stdout, "\x1b ]1337;ReportCellSize\x1b \\ \x1b [5n" ) ;
421+ assert ! ( test_response. reached_end( ) ) ;
422+ }
272423}
0 commit comments