@@ -7,11 +7,16 @@ pub use cloud_mode_v2_view::{CloudModeV2SlashCommandView, Section as CloudModeV2
77pub use data_source:: * ;
88pub use view:: { CloseReason , InlineSlashCommandView , SlashCommandsEvent } ;
99
10+ #[ cfg( feature = "local_fs" ) ]
11+ use std:: path:: PathBuf ;
12+
1013use ai:: skills:: SkillReference ;
1114use warp_core:: features:: FeatureFlag ;
1215use warp_core:: send_telemetry_from_ctx;
1316use warp_core:: ui:: appearance:: Appearance ;
1417use warp_core:: ui:: theme:: AnsiColorIdentifier ;
18+ #[ cfg( feature = "local_fs" ) ]
19+ use warp_util:: path:: { CleanPathResult , LineAndColumnArg } ;
1520use warpui:: clipboard:: ClipboardContent ;
1621use warpui:: { SingletonEntity , ViewContext } ;
1722
@@ -43,6 +48,8 @@ use crate::terminal::input::slash_command_model::{
4348use crate :: terminal:: input:: {
4449 CompletionsTrigger , Event , Input , InputAction , InputSuggestionsMode , UserQueryMenuAction ,
4550} ;
51+ #[ cfg( feature = "local_fs" ) ]
52+ use crate :: terminal:: model:: session:: Session ;
4653use crate :: terminal:: view:: TerminalAction ;
4754use crate :: ui_components:: color_dot;
4855use crate :: view_components:: DismissibleToast ;
@@ -109,6 +116,33 @@ impl SlashCommandTrigger {
109116 }
110117}
111118
119+ #[ cfg( feature = "local_fs" ) ]
120+ fn open_file_command_path (
121+ session : & Session ,
122+ current_dir : & str ,
123+ raw_arg : & str ,
124+ ) -> ( PathBuf , Option < LineAndColumnArg > ) {
125+ let parsed_path = CleanPathResult :: with_line_and_column_number ( raw_arg. trim ( ) ) ;
126+ // The argument may contain shell-escaped characters (e.g. `\ ` for spaces) from auto-suggest.
127+ // Unescape them so the path matches the actual filesystem entry.
128+ let unescaped_path = session. shell_family ( ) . unescape ( & parsed_path. path ) ;
129+ // Expand `~` to the user's home directory.
130+ let expanded_path = shellexpand:: tilde ( & unescaped_path) ;
131+
132+ let shell_path = session
133+ . convert_directory_to_typed_path_buf ( current_dir. to_owned ( ) )
134+ . join ( session. convert_directory_to_typed_path_buf ( expanded_path. into_owned ( ) ) )
135+ . normalize ( ) ;
136+ let file_path = session
137+ . maybe_convert_to_native_path ( & shell_path. to_path ( ) )
138+ . unwrap_or_else ( |err| {
139+ log:: warn!( "unable to convert /open-file path to native path: {err:?}" ) ;
140+ PathBuf :: from ( shell_path. to_string_lossy ( ) . into_owned ( ) )
141+ } ) ;
142+
143+ ( file_path, parsed_path. line_and_column_num )
144+ }
145+
112146impl Input {
113147 pub ( super ) fn select_slash_command (
114148 & mut self ,
@@ -525,9 +559,6 @@ impl Input {
525559 #[ cfg( feature = "local_fs" ) ]
526560 match argument {
527561 Some ( args) if !args. is_empty ( ) => {
528- use shellexpand:: tilde;
529- use warp_util:: path:: CleanPathResult ;
530-
531562 let Some ( session_id) = self . active_block_session_id ( ) else {
532563 return false ;
533564 } ;
@@ -555,20 +586,14 @@ impl Input {
555586 . active_block_metadata
556587 . as_ref ( )
557588 . and_then ( |metadata| metadata. current_working_directory ( ) )
558- . map ( std :: path :: PathBuf :: from ) ;
589+ . map ( str :: to_owned ) ;
559590
560591 let Some ( current_dir) = current_dir else {
561592 return false ;
562593 } ;
563594
564- let parsed_path = CleanPathResult :: with_line_and_column_number ( args. trim ( ) ) ;
565- // The argument may contain shell-escaped characters (e.g. `\ ` for
566- // spaces) from auto-suggest. Unescape them so the path matches the
567- // actual filesystem entry.
568- let unescaped_path = session. shell_family ( ) . unescape ( & parsed_path. path ) ;
569- // Expand `~` to the user's home directory.
570- let expanded_path = tilde ( & unescaped_path) ;
571- let file_path = current_dir. join ( & * expanded_path) ;
595+ let ( file_path, line_col) =
596+ open_file_command_path ( & session, & current_dir, args) ;
572597
573598 match std:: fs:: metadata ( & file_path) {
574599 Ok ( metadata) if metadata. is_file ( ) => {
@@ -577,7 +602,7 @@ impl Input {
577602 ctx. dispatch_typed_action ( & TerminalAction :: OpenCodeInWarp {
578603 path : file_path,
579604 layout : external_editor:: settings:: EditorLayout :: SplitPane ,
580- line_col : parsed_path . line_and_column_num ,
605+ line_col,
581606 } ) ;
582607 }
583608 Ok ( _) => {
@@ -1219,6 +1244,68 @@ impl Input {
12191244 }
12201245}
12211246
1247+ #[ cfg( all( test, feature = "local_fs" , windows) ) ]
1248+ mod tests {
1249+ use super :: * ;
1250+ use std:: sync:: Arc ;
1251+
1252+ use crate :: terminal:: model:: session:: command_executor:: testing:: TestCommandExecutor ;
1253+ use crate :: terminal:: model:: session:: SessionInfo ;
1254+ use crate :: terminal:: shell:: ShellType ;
1255+ use crate :: terminal:: ShellLaunchData ;
1256+
1257+ fn wsl_session ( ) -> Session {
1258+ Session :: new (
1259+ SessionInfo :: new_for_test ( ) . with_shell_type ( ShellType :: Bash ) ,
1260+ Arc :: new ( TestCommandExecutor :: default ( ) ) ,
1261+ )
1262+ . with_shell_launch_data ( ShellLaunchData :: WSL {
1263+ distro : "Ubuntu" . to_owned ( ) ,
1264+ } )
1265+ }
1266+
1267+ #[ test]
1268+ fn open_file_command_converts_wsl_paths_to_host_paths ( ) {
1269+ let session = wsl_session ( ) ;
1270+ let cases = [
1271+ (
1272+ "/home/ubuntu" ,
1273+ "subdir/test.txt" ,
1274+ r"\\WSL$\Ubuntu\home\ubuntu\subdir\test.txt" ,
1275+ None ,
1276+ ) ,
1277+ (
1278+ "/home/ubuntu/project" ,
1279+ "../test.txt" ,
1280+ r"\\WSL$\Ubuntu\home\ubuntu\test.txt" ,
1281+ None ,
1282+ ) ,
1283+ (
1284+ "/home/ubuntu" ,
1285+ "subdir/file\\ name.txt" ,
1286+ r"\\WSL$\Ubuntu\home\ubuntu\subdir\file name.txt" ,
1287+ None ,
1288+ ) ,
1289+ (
1290+ "/home/ubuntu" ,
1291+ "subdir/test.txt:4:2" ,
1292+ r"\\WSL$\Ubuntu\home\ubuntu\subdir\test.txt" ,
1293+ Some ( LineAndColumnArg {
1294+ line_num : 4 ,
1295+ column_num : Some ( 2 ) ,
1296+ } ) ,
1297+ ) ,
1298+ ] ;
1299+
1300+ for ( current_dir, raw_arg, expected_path, expected_line_col) in cases {
1301+ let ( path, line_col) = open_file_command_path ( & session, current_dir, raw_arg) ;
1302+
1303+ assert_eq ! ( path, PathBuf :: from( expected_path) ) ;
1304+ assert_eq ! ( line_col, expected_line_col) ;
1305+ }
1306+ }
1307+ }
1308+
12221309/// Returns true when the conversation with `conversation_id` is associated with a cloud Oz
12231310/// `AmbientAgentTask`. Used as the defensive runtime gate for `/continue-locally` so a
12241311/// keybinding-triggered execution can't fall through onto a non-cloud-Oz conversation after
0 commit comments