@@ -52,6 +52,7 @@ use crate::colors::{StyleManager, color_name};
5252use crate :: config:: Files ;
5353use crate :: dired:: { self , DiredOutput } ;
5454use crate :: { Config , ListState , LsError , PathData , get_block_size} ;
55+ use lscolors:: Indicator ;
5556
5657/// Width of the standard Unix permissions string (one file-type char plus
5758/// nine permission bits, e.g. `drwxr-xr-x`).
@@ -91,9 +92,8 @@ pub(crate) struct DisplayItemName {
9192 pub ( crate ) dired_name_len : usize ,
9293}
9394
94- #[ derive( PartialEq , Eq ) ]
95+ #[ derive( Clone , Copy , PartialEq , Eq ) ]
9596pub ( crate ) enum IndicatorStyle {
96- None ,
9797 Slash ,
9898 FileType ,
9999 Classify ,
@@ -705,35 +705,31 @@ fn display_item_name(
705705 }
706706 }
707707
708- if config. indicator_style != IndicatorStyle :: None {
709- let sym = classify_file ( path) ;
710-
711- let char_opt = match config. indicator_style {
712- IndicatorStyle :: Classify => sym,
713- IndicatorStyle :: FileType => {
714- // Don't append an asterisk.
715- match sym {
716- Some ( '*' ) => None ,
717- _ => sym,
718- }
719- }
720- IndicatorStyle :: Slash => {
721- // Append only a slash.
722- match sym {
723- Some ( '/' ) => Some ( '/' ) ,
724- _ => None ,
725- }
726- }
727- IndicatorStyle :: None => None ,
728- } ;
708+ let is_long_symlink = config. format == Format :: Long
709+ && path. file_type ( ) . is_some_and ( FileType :: is_symlink)
710+ && !path. must_dereference ;
729711
730- if let Some ( c) = char_opt {
712+ if !is_long_symlink {
713+ if let Some ( c) = indicator_char ( path, config. indicator_style ) {
731714 let _ = name. write_char ( c) ;
732715 }
733716 }
734717
735718 let dired_name_len = if config. dired { name. len ( ) } else { 0 } ;
736719
720+ let has_mi_or_or = style_manager. as_ref ( ) . is_some_and ( |sm| {
721+ sm. has_indicator_style ( Indicator :: OrphanedSymbolicLink )
722+ || sm. has_indicator_style ( Indicator :: MissingFile )
723+ } ) ;
724+ // Only stat symlink target when:
725+ // 1. Color is enabled AND LS_COLORS has mi= or or=, OR
726+ // 2. Long format AND (--classify or --file-type)
727+ let should_stat_target = has_mi_or_or
728+ || matches ! (
729+ config. indicator_style,
730+ Some ( IndicatorStyle :: Classify ) | Some ( IndicatorStyle :: FileType )
731+ ) ;
732+
737733 if config. format == Format :: Long
738734 && path. file_type ( ) . is_some_and ( FileType :: is_symlink)
739735 && !path. must_dereference
@@ -745,10 +741,11 @@ fn display_item_name(
745741 // We might as well color the symlink output after the arrow.
746742 // This makes extra system calls, but provides important information that
747743 // people run `ls -l --color` are very interested in.
748- if let Some ( style_manager) = & mut style_manager {
749- let escaped_target = escape_name_with_locale ( target_path. as_os_str ( ) , config) ;
750- // We get the absolute path to be able to construct PathData with valid Metadata.
751- // This is because relative symlinks will fail to get_metadata.
744+ let escaped_target = escape_name_with_locale ( target_path. as_os_str ( ) , config) ;
745+
746+ // We get the absolute path to be able to construct PathData with valid Metadata.
747+ // This is because relative symlinks will fail to get_metadata.
748+ if should_stat_target {
752749 let absolute_target = if target_path. is_relative ( ) {
753750 match path. path ( ) . parent ( ) {
754751 Some ( p) => & p. join ( & target_path) ,
@@ -757,7 +754,6 @@ fn display_item_name(
757754 } else {
758755 & target_path
759756 } ;
760-
761757 match fs:: canonicalize ( absolute_target) {
762758 Ok ( resolved_target) => {
763759 let target_data = PathData :: new (
@@ -769,43 +765,62 @@ fn display_item_name(
769765 false ,
770766 ) ;
771767
772- // Check if the target actually needs coloring
773- let md_option: Option < Metadata > = target_data
774- . metadata ( )
775- . cloned ( )
776- . or_else ( || target_data. p_buf . symlink_metadata ( ) . ok ( ) ) ;
777- let style = style_manager. colors . style_for_path_with_metadata (
778- & target_data. p_buf ,
779- md_option. as_ref ( ) ,
780- ) ;
781-
782- if style. is_some ( ) {
783- // Only apply coloring if there's actually a style
784- name. push ( color_name (
785- escaped_target,
786- & target_data,
787- style_manager,
788- None ,
789- is_wrap ( name. len ( ) ) ,
790- ) ) ;
768+ let target_display = if let Some ( style_manager) = style_manager {
769+ let md = match target_data. metadata ( ) {
770+ Some ( md) => Some ( Cow :: Borrowed ( md) ) ,
771+ None => {
772+ target_data. p_buf . symlink_metadata ( ) . ok ( ) . map ( Cow :: Owned )
773+ }
774+ } ;
775+ // Check if the target actually needs coloring
776+ if style_manager
777+ . colors
778+ . style_for_path_with_metadata ( & target_data. p_buf , md. as_deref ( ) )
779+ . is_some ( )
780+ {
781+ // Only apply coloring if there's actually a style
782+ color_name (
783+ escaped_target,
784+ & target_data,
785+ style_manager,
786+ None ,
787+ is_wrap ( name. len ( ) ) ,
788+ )
789+ } else {
790+ // For regular files with no coloring, just use plain text
791+ escaped_target
792+ }
791793 } else {
792- // For regular files with no coloring, just use plain text
793- name. push ( escaped_target) ;
794+ escaped_target
795+ } ;
796+ name. push ( target_display) ;
797+ // Add appropriate indicator based on indicator_style
798+ if let Some ( c) = indicator_char ( & target_data, config. indicator_style ) {
799+ if matches ! (
800+ config. indicator_style,
801+ Some ( IndicatorStyle :: Classify )
802+ | Some ( IndicatorStyle :: FileType )
803+ | Some ( IndicatorStyle :: Slash )
804+ ) {
805+ let _ = name. write_char ( c) ;
806+ }
794807 }
795808 }
796809 Err ( _) => {
797- name . push (
798- style_manager. apply_missing_target_style (
810+ if let Some ( style_manager ) = & mut style_manager {
811+ name . push ( style_manager. apply_missing_target_style (
799812 escaped_target,
800813 is_wrap ( name. len ( ) ) ,
801- ) ,
802- ) ;
814+ ) ) ;
815+ } else {
816+ // If no coloring is required, we just use target as is.
817+ // with the right quoting
818+ name. push ( escaped_target) ;
819+ }
803820 }
804821 }
805822 } else {
806- // If no coloring is required, we just use target as is.
807- // Apply the right quoting
808- name. push ( escape_name_with_locale ( target_path. as_os_str ( ) , config) ) ;
823+ name. push ( & escaped_target) ;
809824 }
810825 }
811826 Err ( err) => {
@@ -1118,6 +1133,23 @@ fn display_item_long(
11181133 Ok ( ( ) )
11191134}
11201135
1136+ fn indicator_char ( path : & PathData , style : Option < IndicatorStyle > ) -> Option < char > {
1137+ let style = style?;
1138+ let sym = classify_file ( path) ;
1139+
1140+ match style {
1141+ IndicatorStyle :: Classify => sym,
1142+ IndicatorStyle :: FileType => match sym {
1143+ Some ( '*' ) => None ,
1144+ _ => sym,
1145+ } ,
1146+ IndicatorStyle :: Slash => match sym {
1147+ Some ( '/' ) => Some ( '/' ) ,
1148+ _ => None ,
1149+ } ,
1150+ }
1151+ }
1152+
11211153fn classify_file ( path : & PathData ) -> Option < char > {
11221154 let file_type = path. file_type ( ) ?;
11231155
0 commit comments