nex_core/
action_executor.rs1use std::fmt::{Display, Formatter};
2use std::path::{Path, PathBuf};
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum LaunchError {
6 EmptyPath,
7 MissingPath(PathBuf),
8 LaunchFailed { message: String, code: Option<i32> },
9}
10
11impl Display for LaunchError {
12 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
13 match self {
14 Self::EmptyPath => write!(f, "empty path"),
15 Self::MissingPath(path) => write!(f, "path does not exist: {}", path.display()),
16 Self::LaunchFailed { message, code } => {
17 if let Some(code) = code {
18 write!(f, "launch failed: {message} (code {code})")
19 } else {
20 write!(f, "launch failed: {message}")
21 }
22 }
23 }
24 }
25}
26
27impl std::error::Error for LaunchError {}
28
29pub fn launch_path(path: &str) -> Result<(), LaunchError> {
30 let trimmed = path.trim();
31 if trimmed.is_empty() {
32 return Err(LaunchError::EmptyPath);
33 }
34
35 if is_non_filesystem_open_target(trimmed) {
36 return launch_open(trimmed);
37 }
38
39 let candidate = Path::new(trimmed);
40 if !candidate.exists() {
41 return Err(LaunchError::MissingPath(candidate.to_path_buf()));
42 }
43
44 launch_existing_path(candidate)?;
45
46 Ok(())
47}
48
49pub fn launch_open_target(target: &str) -> Result<(), LaunchError> {
50 let trimmed = target.trim();
51 if trimmed.is_empty() {
52 return Err(LaunchError::EmptyPath);
53 }
54 launch_open(trimmed)
55}
56
57#[cfg(target_os = "windows")]
58fn launch_existing_path(candidate: &Path) -> Result<(), LaunchError> {
59 let target = candidate.to_string_lossy().into_owned();
60 launch_open(&target)
61}
62
63#[cfg(target_os = "windows")]
64fn launch_open(target: &str) -> Result<(), LaunchError> {
65 if target.trim().to_ascii_lowercase().starts_with("shell:") {
66 return launch_shell_target(target);
67 }
68
69 use windows_sys::Win32::UI::Shell::ShellExecuteW;
70 use windows_sys::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL;
71
72 let wide_target = to_wide(&target);
73 let result = unsafe {
74 ShellExecuteW(
75 std::ptr::null_mut(),
76 std::ptr::null(),
77 wide_target.as_ptr(),
78 std::ptr::null(),
79 std::ptr::null(),
80 SW_SHOWNORMAL,
81 )
82 } as isize;
83
84 if result <= 32 {
85 return Err(LaunchError::LaunchFailed {
86 message: format!("ShellExecuteW failed for '{target}'"),
87 code: Some(result as i32),
88 });
89 }
90
91 Ok(())
92}
93
94#[cfg(target_os = "windows")]
95fn launch_shell_target(target: &str) -> Result<(), LaunchError> {
96 std::process::Command::new("explorer.exe")
97 .arg(target)
98 .spawn()
99 .map_err(|error| LaunchError::LaunchFailed {
100 message: format!("failed to launch shell target '{target}': {error}"),
101 code: None,
102 })?;
103 Ok(())
104}
105
106#[cfg(not(target_os = "windows"))]
107fn launch_existing_path(_candidate: &Path) -> Result<(), LaunchError> {
108 Ok(())
109}
110
111#[cfg(not(target_os = "windows"))]
112fn launch_open(_target: &str) -> Result<(), LaunchError> {
113 Ok(())
114}
115
116#[cfg(target_os = "windows")]
117fn to_wide(value: &str) -> Vec<u16> {
118 value.encode_utf16().chain(std::iter::once(0)).collect()
119}
120
121fn is_non_filesystem_open_target(value: &str) -> bool {
122 let trimmed = value.trim();
123 if trimmed.is_empty() {
124 return false;
125 }
126
127 let lowered = trimmed.to_ascii_lowercase();
128 if lowered.starts_with("shell:") || lowered.starts_with("ms-") {
129 return true;
130 }
131
132 if trimmed.contains("://") {
133 return true;
134 }
135
136 !looks_like_filesystem_path(trimmed)
137}
138
139fn looks_like_filesystem_path(path: &str) -> bool {
140 if path.starts_with('/') || path.starts_with('\\') {
141 return true;
142 }
143
144 let bytes = path.as_bytes();
145 bytes.len() >= 3 && bytes[1] == b':' && (bytes[2] == b'\\' || bytes[2] == b'/')
146}