22use std:: os:: unix:: process:: CommandExt ;
33
44use std:: {
5- env,
5+ env, fs ,
66 path:: Path ,
77 process:: { Command , Stdio } ,
88} ;
@@ -12,8 +12,13 @@ use clap::Args;
1212use dialoguer:: { Select , theme:: ColorfulTheme } ;
1313
1414use crate :: {
15- command:: utils:: InstallOptions , consts:: GOUP_GO_VERSION , dir:: Dir , registry:: Registry ,
16- shell:: ShellType , toolchain, version:: Version ,
15+ command:: utils:: InstallOptions ,
16+ consts:: GOUP_GO_VERSION ,
17+ dir:: Dir ,
18+ registry:: { Registry , RegistryIndex } ,
19+ shell:: ShellType ,
20+ toolchain,
21+ version:: Version ,
1722} ;
1823
1924use super :: Run ;
@@ -32,7 +37,33 @@ pub struct Shell {
3237
3338impl Run for Shell {
3439 fn run ( & self ) -> Result < ( ) , anyhow:: Error > {
35- let go_version = self . get_target_version ( ) ?;
40+ let local_versions = Version :: list_go_version ( ) ?;
41+ let go_version = self . get_target_version ( & local_versions) ?;
42+
43+ let mut current_shell_version = None ;
44+ let mut current_default_version = None ;
45+ for v in local_versions. iter ( ) {
46+ if v. session {
47+ current_shell_version = Some ( & v. version ) ;
48+ }
49+ if v. default {
50+ current_default_version = Some ( & v. version ) ;
51+ }
52+ }
53+
54+ if current_shell_version == Some ( & go_version)
55+ || current_shell_version. is_none ( ) && current_default_version == Some ( & go_version)
56+ {
57+ // 如果当前会话和目标一样, 则不切换
58+ // 不在会话当中, 如果默认和目标一样, 则不切换
59+ log:: info!(
60+ "Current environment already uses Go {go_version}, skip enter new shell session." ,
61+ ) ;
62+
63+ return Ok ( ( ) ) ;
64+ }
65+
66+ let go_version = toolchain:: normalize ( & go_version) ;
3667 let goup_home = Dir :: goup_home ( ) ?;
3768 if !goup_home. is_dot_unpacked_success_file_exists ( & go_version) {
3869 return Err ( anyhow ! (
@@ -72,7 +103,7 @@ impl Run for Shell {
72103 . collect :: < Vec < _ > > ( )
73104 . join ( env_separator) ;
74105
75- log:: info!( "Enter new shell session with go version: {}" , go_version , ) ;
106+ log:: info!( "Enter new shell session with Go {go_version}" ) ;
76107 let mut command = Command :: new ( shell. to_string ( ) ) ;
77108 let command = command
78109 . stdin ( Stdio :: inherit ( ) )
@@ -98,39 +129,104 @@ impl Run for Shell {
98129}
99130
100131impl Shell {
101- fn get_target_version ( & self ) -> Result < String , anyhow:: Error > {
102- let versions = Version :: list_go_version ( ) ?;
132+ fn get_target_version ( & self , local_versions : & [ Version ] ) -> Result < String , anyhow:: Error > {
103133 let target_version = if let Some ( version) = & self . version {
104- if !versions . iter ( ) . any ( |v| v. version == * version) {
134+ if !local_versions . iter ( ) . any ( |v| v. version == * version) {
105135 let registry = Registry :: new (
106136 & self . install_options . registry ,
107137 self . install_options . skip_verify ,
108138 self . install_options . enable_check_archive_size ,
109139 ) ;
110140 registry. install_go ( & toolchain:: normalize ( version) ) ?
111141 }
112- version
142+ version. to_owned ( )
143+ } else if let Some ( ver) = self . get_mod_file_version ( local_versions) {
144+ ver
113145 } else {
114- if versions . is_empty ( ) {
146+ if local_versions . is_empty ( ) {
115147 return Err ( anyhow ! (
116148 "Not any go is installed, Install it with `goup install`."
117149 ) ) ;
118150 }
119151 let mut items = Vec :: new ( ) ;
120- let mut pos = 0 ;
121- for ( i, v) in versions. iter ( ) . enumerate ( ) {
152+
153+ let mut session_pos = None ;
154+ let mut default_pos = None ;
155+ for ( i, v) in local_versions. iter ( ) . enumerate ( ) {
122156 items. push ( & v. version ) ;
157+ if v. session {
158+ session_pos = Some ( i) ;
159+ }
123160 if v. default {
124- pos = i ;
161+ default_pos = Some ( i ) ;
125162 }
126163 }
164+ let pos = session_pos. unwrap_or ( default_pos. unwrap_or ( 0 ) ) ;
127165 let selection = Select :: with_theme ( & ColorfulTheme :: default ( ) )
128166 . with_prompt ( "Select a version" )
129167 . items ( & items)
130168 . default ( pos)
131169 . interact ( ) ?;
132- items[ selection]
170+ items[ selection] . to_owned ( )
171+ } ;
172+
173+ Ok ( target_version)
174+ }
175+
176+ fn get_mod_file_version ( & self , local_versions : & [ Version ] ) -> Option < String > {
177+ let current_dir = env:: current_dir ( ) . ok ( ) ?;
178+ let mod_go_version = [ "go.work" , "go.mod" ]
179+ . into_iter ( )
180+ . find_map ( |filename| Self :: parse_go_mod_or_work_file ( current_dir. join ( filename) ) ) ?;
181+
182+ let version_req = match mod_go_version. chars ( ) . filter ( |& v| v == '.' ) . count ( ) {
183+ 0 | 1 => format ! ( "~{mod_go_version}" ) ,
184+ _ => format ! ( "={mod_go_version}" ) ,
133185 } ;
134- Ok ( toolchain:: normalize ( target_version) )
186+ let version = RegistryIndex :: new ( & self . install_options . registry_index )
187+ . match_version_req ( & version_req)
188+ . ok ( ) ?;
189+ if !local_versions. iter ( ) . any ( |v| v. version == version) {
190+ let registry = Registry :: new (
191+ & self . install_options . registry ,
192+ self . install_options . skip_verify ,
193+ self . install_options . enable_check_archive_size ,
194+ ) ;
195+ registry. install_go ( & toolchain:: normalize ( & version) ) . ok ( ) ;
196+ }
197+ Some ( version)
198+ }
199+ fn parse_go_mod_or_work_file ( path : impl AsRef < Path > ) -> Option < String > {
200+ if !path. as_ref ( ) . exists ( ) {
201+ return None ;
202+ }
203+ let mut target = None ;
204+ let content = fs:: read_to_string ( path) . ok ( ) ?;
205+ for line in content. lines ( ) {
206+ let line = line. trim ( ) ;
207+ if line. starts_with ( "//" ) || line. is_empty ( ) {
208+ continue ;
209+ }
210+ // https://go.dev/ref/mod#go-mod-file-go
211+ // go directive
212+ if let Some ( rest) = line. strip_prefix ( "go " ) {
213+ // go 1.22
214+ let v = rest. trim ( ) . to_string ( ) ;
215+ target = Some ( v) ;
216+ continue ;
217+ }
218+ // https://go.dev/ref/mod#go-mod-file-toolchain
219+ // toolchain directive
220+ if let Some ( rest) = line. strip_prefix ( "toolchain " ) {
221+ // toolchain go1.22.3
222+ let rest = rest. trim ( ) . trim_start_matches ( "go" ) . to_string ( ) ;
223+ if rest. is_empty ( ) {
224+ continue ;
225+ }
226+ target = Some ( rest) ;
227+ continue ;
228+ }
229+ }
230+ target
135231 }
136232}
0 commit comments