@@ -1220,7 +1220,7 @@ impl Painter {
12201220mod tests {
12211221 use super :: * ;
12221222 use crate :: menu:: MenuEvent ;
1223- use crate :: { Completer , Editor , PromptHistorySearch , Suggestion } ;
1223+ use crate :: { Color , Completer , Editor , PromptHistorySearch , Suggestion } ;
12241224 use pretty_assertions:: assert_eq;
12251225 use std:: borrow:: Cow ;
12261226 use std:: sync:: { Arc , Mutex } ;
@@ -1741,4 +1741,196 @@ mod tests {
17411741 ]
17421742 ) ;
17431743 }
1744+
1745+ const SGR_GREEN : & str = "\x1b [92m" ; // DEFAULT_PROMPT_COLOR (LightGreen, palette 10)
1746+ const SGR_CYAN : & str = "\x1b [96m" ; // DEFAULT_INDICATOR_COLOR (LightCyan, palette 14)
1747+ const SGR_PURPLE : & str = "\x1b [35m" ; // DEFAULT_PROMPT_RIGHT_COLOR (Purple, palette 5)
1748+ const SGR_DEFAULT_FG : & str = "\x1b [39m" ; // Color::Default — "terminal foreground"
1749+
1750+ /// A prompt whose color methods are set per-test. Rendering is inherited from
1751+ /// `TestPrompt` since these tests only care about the emitted escapes.
1752+ struct ColoredPrompt {
1753+ prompt : Color ,
1754+ indicator : Color ,
1755+ right : Color ,
1756+ }
1757+
1758+ impl Prompt for ColoredPrompt {
1759+ fn render_prompt_left ( & self ) -> Cow < ' _ , str > {
1760+ TestPrompt . render_prompt_left ( )
1761+ }
1762+ fn render_prompt_right ( & self ) -> Cow < ' _ , str > {
1763+ TestPrompt . render_prompt_right ( )
1764+ }
1765+ fn render_prompt_indicator ( & self , mode : PromptEditMode ) -> Cow < ' _ , str > {
1766+ TestPrompt . render_prompt_indicator ( mode)
1767+ }
1768+ fn render_prompt_multiline_indicator ( & self ) -> Cow < ' _ , str > {
1769+ TestPrompt . render_prompt_multiline_indicator ( )
1770+ }
1771+ fn render_prompt_history_search_indicator (
1772+ & self ,
1773+ search : PromptHistorySearch ,
1774+ ) -> Cow < ' _ , str > {
1775+ TestPrompt . render_prompt_history_search_indicator ( search)
1776+ }
1777+
1778+ fn get_prompt_color ( & self ) -> Color {
1779+ self . prompt
1780+ }
1781+ fn get_indicator_color ( & self ) -> Color {
1782+ self . indicator
1783+ }
1784+ fn get_prompt_right_color ( & self ) -> Color {
1785+ self . right
1786+ }
1787+ }
1788+ /// Like `capture_repaint`, but with configurable ANSI coloring. Also returns
1789+ /// whether the paint took the large-buffer branch, since `repaint_buffer`
1790+ /// recomputes that field and a caller cannot force it.
1791+ fn capture_repaint_ansi (
1792+ prompt : & dyn Prompt ,
1793+ lines : & PromptLines ,
1794+ use_ansi_coloring : bool ,
1795+ ) -> ( String , bool ) {
1796+ let mut p = Painter :: new ( W :: capture ( ) ) ;
1797+ p. terminal_size = ( 20 , 10 ) ;
1798+ p. prompt_start_row . mark_verified ( 0 ) ;
1799+ p. prompt_height = 1 ;
1800+ p. repaint_buffer (
1801+ prompt,
1802+ lines,
1803+ PromptEditMode :: Default ,
1804+ None ,
1805+ use_ansi_coloring,
1806+ & None ,
1807+ )
1808+ . expect ( "repaint_buffer failed" ) ;
1809+ let large = p. large_buffer ;
1810+ (
1811+ String :: from_utf8_lossy ( p. stdout . captured ( ) ) . into_owned ( ) ,
1812+ large,
1813+ )
1814+ }
1815+
1816+ /// Records which palette entries crossterm's `SetForegroundColor` selected
1817+ /// for the pre-migration defaults. crossterm's `Green`/`Cyan` are the
1818+ /// *bright* entries (10/14) — `DarkGreen`/`DarkCyan` are 2/6 — so the
1819+ /// nu-ansi-term replacements have to be the `Light*` variants to keep the
1820+ /// prompt looking the same.
1821+ ///
1822+ /// Green and cyan intentionally re-encode from the 256-color form
1823+ /// (`38;5;10`) to the aixterm form (`92`); both select palette entry 10.
1824+ #[ test]
1825+ fn crossterm_defaults_were_the_bright_palette_entries ( ) {
1826+ use crossterm:: {
1827+ style:: { Color as CtColor , SetForegroundColor } ,
1828+ Command ,
1829+ } ;
1830+
1831+ fn crossterm_sgr ( color : CtColor ) -> String {
1832+ let mut buf = String :: new ( ) ;
1833+ SetForegroundColor ( color)
1834+ . write_ansi ( & mut buf)
1835+ . expect ( "write_ansi failed" ) ;
1836+ buf
1837+ }
1838+
1839+ for ( name, crossterm, palette) in [
1840+ ( "prompt" , CtColor :: Green , 10 ) ,
1841+ ( "indicator" , CtColor :: Cyan , 14 ) ,
1842+ ( "right prompt" , CtColor :: AnsiValue ( 5 ) , 5 ) ,
1843+ ] {
1844+ assert_eq ! (
1845+ crossterm_sgr( crossterm) ,
1846+ Color :: Fixed ( palette) . prefix( ) . to_string( ) ,
1847+ "{name} default selected a different palette entry than assumed"
1848+ ) ;
1849+ }
1850+
1851+ // "Unstyled" has no palette entry; both spellings are SGR 39.
1852+ assert_eq ! (
1853+ Color :: Default . prefix( ) . to_string( ) ,
1854+ crossterm_sgr( CtColor :: Reset )
1855+ ) ;
1856+ }
1857+
1858+ /// The trait's default colors reach the terminal as the expected SGR
1859+ /// sequences, on the small-buffer path.
1860+ #[ test]
1861+ fn default_prompt_colors_emit_expected_sgr ( ) {
1862+ let ( out, _) =
1863+ capture_repaint_ansi ( & TestPrompt , & make_lines ( "> " , "" , "RP" , "hi" , "" ) , true ) ;
1864+
1865+ assert ! (
1866+ out. contains( SGR_GREEN ) ,
1867+ "left prompt color missing: {out:?}"
1868+ ) ;
1869+ assert ! ( out. contains( SGR_CYAN ) , "indicator color missing: {out:?}" ) ;
1870+ assert ! (
1871+ out. contains( SGR_PURPLE ) ,
1872+ "right prompt color missing: {out:?}"
1873+ ) ;
1874+ }
1875+
1876+ /// `Color::Default` must emit an explicit SGR 39, never an empty prefix —
1877+ /// an empty one would let the active color bleed into an unstyled prompt,
1878+ /// which is the starship bug (#1046).
1879+ #[ test]
1880+ fn default_color_emits_explicit_foreground_reset ( ) {
1881+ let prompt = ColoredPrompt {
1882+ prompt : Color :: Default ,
1883+ indicator : Color :: Default ,
1884+ right : Color :: Default ,
1885+ } ;
1886+ let ( out, _) = capture_repaint_ansi ( & prompt, & make_lines ( "> " , "" , "RP" , "hi" , "" ) , true ) ;
1887+
1888+ assert ! (
1889+ out. contains( SGR_DEFAULT_FG ) ,
1890+ "Color::Default must emit an explicit SGR 39, not nothing: {out:?}"
1891+ ) ;
1892+ assert ! (
1893+ !out. contains( SGR_GREEN ) ,
1894+ "no default color should leak through: {out:?}"
1895+ ) ;
1896+ }
1897+
1898+ /// `print_large_buffer` has its own three color call sites that the other
1899+ /// capture tests never reach.
1900+ ///
1901+ /// The bulk sits in `after_cursor` so `extra_rows` stays 0 and the right
1902+ /// prompt is still drawn; a tall `before_cursor` would suppress it (see
1903+ /// `test_layout_right_prompt_suppressed_in_large_buffer`).
1904+ #[ test]
1905+ fn large_buffer_path_emits_prompt_colors ( ) {
1906+ let tall = "line\n " . repeat ( 15 ) ;
1907+ let ( out, large) =
1908+ capture_repaint_ansi ( & TestPrompt , & make_lines ( "> " , "" , "RP" , "hi" , & tall) , true ) ;
1909+
1910+ assert ! ( large, "expected the large-buffer path to be taken" ) ;
1911+ assert ! (
1912+ out. contains( SGR_GREEN ) ,
1913+ "left prompt color missing: {out:?}"
1914+ ) ;
1915+ assert ! ( out. contains( SGR_CYAN ) , "indicator color missing: {out:?}" ) ;
1916+ assert ! (
1917+ out. contains( SGR_PURPLE ) ,
1918+ "right prompt color missing: {out:?}"
1919+ ) ;
1920+ }
1921+
1922+ /// Every color write stays inside its `use_ansi_coloring` guard. Checks the
1923+ /// color sequences only — `repaint_buffer` always emits a leading `\x1b[0m`.
1924+ #[ test]
1925+ fn no_prompt_colors_when_ansi_coloring_disabled ( ) {
1926+ let ( out, _) =
1927+ capture_repaint_ansi ( & TestPrompt , & make_lines ( "> " , "" , "RP" , "hi" , "" ) , false ) ;
1928+
1929+ for sgr in [ SGR_GREEN , SGR_CYAN , SGR_PURPLE ] {
1930+ assert ! (
1931+ !out. contains( sgr) ,
1932+ "emitted {sgr:?} with coloring disabled: {out:?}"
1933+ ) ;
1934+ }
1935+ }
17441936}
0 commit comments