11use std:: sync:: Mutex ;
22use std:: thread;
3+ use std:: time:: { Duration , SystemTime } ;
34
45use indicatif:: MultiProgress ;
56use miette:: Result ;
67use owo_colors:: OwoColorize ;
8+ use timeago:: Formatter ;
79
810use super :: ui:: { self , PluginSpinner } ;
9- use crate :: muxi:: { PluginUpdateStatus , Settings } ;
11+ use crate :: muxi:: { PluginChange , PluginUpdateStatus , Settings } ;
1012
1113pub fn update ( ) -> Result < ( ) > {
1214 let plugins = Settings :: from_lua ( ) ?. plugins ;
@@ -18,19 +20,40 @@ pub fn update() -> Result<()> {
1820
1921 let multi = MultiProgress :: new ( ) ;
2022 let errors = Mutex :: new ( Vec :: new ( ) ) ;
23+ let change_logs = Mutex :: new ( Vec :: new ( ) ) ;
2124
2225 thread:: scope ( |s| {
23- for plugin in plugins {
24- s. spawn ( || {
25- let spinner = PluginSpinner :: new ( & multi, & plugin. name ) ;
26+ for ( index, plugin) in plugins. into_iter ( ) . enumerate ( ) {
27+ let progress = & multi;
28+ let errors_ref = & errors;
29+ let change_logs_ref = & change_logs;
30+
31+ s. spawn ( move || {
32+ let spinner = PluginSpinner :: new ( progress, & plugin. name ) ;
33+ let plugin_name = plugin. name . clone ( ) ;
2634
2735 match plugin. update ( ) {
28- Ok ( PluginUpdateStatus :: Updated { from, to } ) => {
29- let detail = match from {
36+ Ok ( PluginUpdateStatus :: Updated {
37+ from,
38+ to,
39+ changes,
40+ range_url,
41+ } ) => {
42+ let display = match from {
3043 Some ( from) => format ! ( "{from}..{to}" ) ,
3144 None => to,
3245 } ;
46+ let detail = if let Some ( url) = range_url. as_ref ( ) {
47+ ui:: hyperlink ( & display, url)
48+ } else {
49+ display
50+ } ;
3351 spinner. finish_success ( Some ( & detail) ) ;
52+
53+ if !changes. is_empty ( ) {
54+ let log = format_plugin_changes ( & plugin_name, & changes) ;
55+ change_logs_ref. lock ( ) . unwrap ( ) . push ( ( index, log) ) ;
56+ }
3457 }
3558 Ok ( PluginUpdateStatus :: UpToDate { commit } ) => {
3659 spinner. finish_up_to_date ( Some ( & commit) ) ;
@@ -40,17 +63,62 @@ pub fn update() -> Result<()> {
4063 }
4164 Err ( error) => {
4265 spinner. finish_error ( ) ;
43- errors . lock ( ) . unwrap ( ) . push ( ( plugin, error) ) ;
66+ errors_ref . lock ( ) . unwrap ( ) . push ( ( plugin. clone ( ) , error) ) ;
4467 }
4568 }
4669 } ) ;
4770 }
4871 } ) ;
4972
73+ drop ( multi) ;
74+
75+ let mut change_logs = change_logs. into_inner ( ) . unwrap ( ) ;
76+ change_logs. sort_by_key ( |( index, _) | * index) ;
77+ if !change_logs. is_empty ( ) {
78+ println ! ( ) ;
79+ for ( _, log) in change_logs {
80+ println ! ( "{log}" ) ;
81+ }
82+ }
83+
5084 let errors = errors. into_inner ( ) . unwrap ( ) ;
5185 if errors. is_empty ( ) {
5286 Ok ( ( ) )
5387 } else {
5488 Err ( ui:: format_plugin_errors ( & errors, "update" ) )
5589 }
5690}
91+
92+ fn format_plugin_changes ( plugin_name : & str , changes : & [ PluginChange ] ) -> String {
93+ let header = plugin_name. bold ( ) . to_string ( ) ;
94+
95+ let body = changes
96+ . iter ( )
97+ . map ( |change| {
98+ let id_colored = change. id . green ( ) . bold ( ) . to_string ( ) ;
99+ let id_formatted = change
100+ . url
101+ . as_ref ( )
102+ . map_or_else ( || id_colored. clone ( ) , |url| ui:: hyperlink ( & id_colored, url) ) ;
103+
104+ format ! (
105+ " {} {} {}" ,
106+ id_formatted,
107+ change. summary. trim( ) ,
108+ format!( "({})" , format_relative_time( change. time) ) . dimmed( )
109+ )
110+ } )
111+ . collect :: < Vec < _ > > ( )
112+ . join ( "\n " ) ;
113+
114+ format ! ( "{header}\n {body}" )
115+ }
116+
117+ fn format_relative_time ( time : SystemTime ) -> String {
118+ let now = SystemTime :: now ( ) ;
119+ let duration = now
120+ . duration_since ( time)
121+ . unwrap_or_else ( |_| Duration :: from_secs ( 0 ) ) ;
122+
123+ Formatter :: new ( ) . convert ( duration)
124+ }
0 commit comments