diff --git a/crates/ide-build/src/compile/mod.rs b/crates/ide-build/src/compile/mod.rs index ce41a1089..8b4119225 100644 --- a/crates/ide-build/src/compile/mod.rs +++ b/crates/ide-build/src/compile/mod.rs @@ -153,7 +153,7 @@ fn detect_dir(dir: &Path, model: &BuildModel, root: &Path) -> Option gradle_steps(root, model, kind, module_dir.as_deref())?, BuildSystem::Cargo => cargo_steps(&dir, kind), // Go builds the file's package (a module holds many). - BuildSystem::Go => go_steps(file.and_then(Path::parent).unwrap_or(&dir), kind), + BuildSystem::Go => go_steps(file.and_then(Path::parent).unwrap_or(&dir), kind)?, BuildSystem::TypeScript => ts_steps(&dir, kind), BuildSystem::CMake => cmake_steps(&dir, kind, cmake.and_then(<[_]>::first)), }; @@ -374,7 +374,31 @@ fn cmake_steps(dir: &Path, kind: BuildKind, profile: Option<&CMakeProfile>) -> V steps } -fn go_steps(dir: &Path, kind: BuildKind) -> Vec { +fn go_steps(dir: &Path, kind: BuildKind) -> IdeResult> { + if dir.join("go.work").is_file() { + let work_file = dunce::canonicalize(dir.join("go.work"))?; + let modules = ide_core::go::work_modules(&std::fs::read_to_string(dir.join("go.work"))?); + if modules.is_empty() { + return Err(IdeError::invalid("The Go workspace contains no modules")); + } + let mut steps = Vec::new(); + for module in modules { + let module = dir.join(module); + if !module.join("go.mod").is_file() { + return Err(IdeError::not_found(format!("Go workspace module has no go.mod: {}", module.display()))); + } + let mut module_steps = go_module_steps(&module, kind); + for step in &mut module_steps { + step.env.insert("GOWORK".into(), work_file.to_string_lossy().into_owned()); + } + steps.extend(module_steps); + } + return Ok(steps); + } + Ok(go_module_steps(dir, kind)) +} + +fn go_module_steps(dir: &Path, kind: BuildKind) -> Vec { let module_root = dir.ancestors().find(|d| d.join("go.mod").is_file()).unwrap_or(dir); let target = if module_root == dir { "./...".to_owned() } else { format!("./{}/...", rel_str(module_root, dir)) }; let mut args = vec!["build"]; @@ -813,6 +837,68 @@ fn launch_class_dirs(system: BuildSystem, module: &Path, test: bool) -> Vec Vec { + let mut tokens = Vec::new(); + let mut chars = text.chars().peekable(); + while let Some(ch) = chars.next() { + match ch { + '\n' | '(' | ')' => tokens.push(ch.to_string()), + ch if ch.is_whitespace() => {} + '/' if chars.peek() == Some(&'/') => { + for c in chars.by_ref() { + if c == '\n' { + tokens.push("\n".into()); + break; + } + } + } + '"' | '`' => { + let Some(token) = quoted_work_path(&mut chars, ch) else { return Vec::new() }; + tokens.push(token); + } + _ => { + let mut token = ch.to_string(); + while chars.peek().is_some_and(|c| !c.is_whitespace() && *c != '(' && *c != ')') { + token.push(chars.next().unwrap()); + } + tokens.push(token); + } + } + } + let mut out = Vec::new(); + let mut start = true; + let mut tokens = tokens.into_iter(); + while let Some(token) = tokens.next() { + if token == "\n" { + start = true; + continue; + } + if start + && token == "use" + && let Some(path) = tokens.next() + { + if path == "(" { + let mut paths = Vec::new(); + let mut closed = false; + for path in tokens.by_ref() { + if path == ")" { + closed = true; + break; + } + if path != "\n" && !path.is_empty() { + paths.push(PathBuf::from(path)); + } + } + if closed { + out.extend(paths); + } + } else if path != "\n" && !path.is_empty() { + out.push(PathBuf::from(path)); + } + } + start = false; + } + let mut seen = std::collections::HashSet::new(); + out.retain(|path| seen.insert(path.clone())); + out +} + +fn quoted_work_path(chars: &mut std::iter::Peekable>, quote: char) -> Option { + let mut bytes = Vec::new(); + while let Some(ch) = chars.next() { + if ch == quote { + return String::from_utf8(bytes).ok(); + } + if ch == '\\' && quote == '"' { + let escaped = chars.next()?; + let byte = match escaped { + 'a' => 7, + 'b' => 8, + 'f' => 12, + 'n' => 10, + 'r' => 13, + 't' => 9, + 'v' => 11, + '\\' => b'\\', + '"' => b'"', + 'x' => { + let digits: String = chars.by_ref().take(2).collect(); + if digits.len() != 2 { + return None; + } + u8::from_str_radix(&digits, 16).ok()? + } + 'u' | 'U' => { + let width = if escaped == 'u' { 4 } else { 8 }; + let digits: String = chars.by_ref().take(width).collect(); + if digits.len() != width { + return None; + } + let ch = char::from_u32(u32::from_str_radix(&digits, 16).ok()?)?; + bytes.extend_from_slice(ch.to_string().as_bytes()); + continue; + } + '0'..='7' => { + let digits = format!("{escaped}{}", chars.by_ref().take(2).collect::()); + if digits.len() != 3 { + return None; + } + u8::from_str_radix(&digits, 8).ok()? + } + _ => return None, + }; + bytes.push(byte); + } else { + if ch == '\n' && quote == '"' { + return None; + } + bytes.extend_from_slice(ch.to_string().as_bytes()); + } + } + None +} diff --git a/crates/ide-core/src/lib.rs b/crates/ide-core/src/lib.rs index aa7db963a..6234af032 100644 --- a/crates/ide-core/src/lib.rs +++ b/crates/ide-core/src/lib.rs @@ -7,6 +7,7 @@ pub mod editor_settings; pub mod error; pub mod event; +pub mod go; pub mod ids; pub mod ignored; pub mod inspection_settings; diff --git a/crates/ide-run/src/go.rs b/crates/ide-run/src/go.rs index 18e8225bb..5593b46e0 100644 --- a/crates/ide-run/src/go.rs +++ b/crates/ide-run/src/go.rs @@ -269,127 +269,7 @@ pub fn prepare_test_file(root: &Path, config: &mut RunConfig) -> IdeResult<()> { Ok(()) } -/// Module directories from a go.work file. Quoted paths and comments follow Go's file syntax. -pub fn work_modules(text: &str) -> Vec { - let mut tokens = Vec::new(); - let mut chars = text.chars().peekable(); - while let Some(ch) = chars.next() { - match ch { - '\n' | '(' | ')' => tokens.push(ch.to_string()), - ch if ch.is_whitespace() => {} - '/' if chars.peek() == Some(&'/') => { - for c in chars.by_ref() { - if c == '\n' { - tokens.push("\n".into()); - break; - } - } - } - '"' | '`' => { - let Some(token) = quoted_work_path(&mut chars, ch) else { return Vec::new() }; - tokens.push(token); - } - _ => { - let mut token = ch.to_string(); - while chars.peek().is_some_and(|c| !c.is_whitespace() && *c != '(' && *c != ')') { - token.push(chars.next().unwrap()); - } - tokens.push(token); - } - } - } - let mut out = Vec::new(); - let mut start = true; - let mut tokens = tokens.into_iter(); - while let Some(token) = tokens.next() { - if token == "\n" { - start = true; - continue; - } - if start - && token == "use" - && let Some(path) = tokens.next() - { - if path == "(" { - let mut paths = Vec::new(); - let mut closed = false; - for path in tokens.by_ref() { - if path == ")" { - closed = true; - break; - } - if path != "\n" && !path.is_empty() { - paths.push(PathBuf::from(path)); - } - } - if closed { - out.extend(paths); - } - } else if path != "\n" && !path.is_empty() { - out.push(PathBuf::from(path)); - } - } - start = false; - } - let mut seen = std::collections::HashSet::new(); - out.retain(|path| seen.insert(path.clone())); - out -} - -fn quoted_work_path(chars: &mut std::iter::Peekable>, quote: char) -> Option { - let mut bytes = Vec::new(); - while let Some(ch) = chars.next() { - if ch == quote { - return String::from_utf8(bytes).ok(); - } - if ch == '\\' && quote == '"' { - let escaped = chars.next()?; - let byte = match escaped { - 'a' => 7, - 'b' => 8, - 'f' => 12, - 'n' => 10, - 'r' => 13, - 't' => 9, - 'v' => 11, - '\\' => b'\\', - '"' => b'"', - 'x' => { - let digits: String = chars.by_ref().take(2).collect(); - if digits.len() != 2 { - return None; - } - u8::from_str_radix(&digits, 16).ok()? - } - 'u' | 'U' => { - let width = if escaped == 'u' { 4 } else { 8 }; - let digits: String = chars.by_ref().take(width).collect(); - if digits.len() != width { - return None; - } - let ch = char::from_u32(u32::from_str_radix(&digits, 16).ok()?)?; - bytes.extend_from_slice(ch.to_string().as_bytes()); - continue; - } - '0'..='7' => { - let digits = format!("{escaped}{}", chars.by_ref().take(2).collect::()); - if digits.len() != 3 { - return None; - } - u8::from_str_radix(&digits, 8).ok()? - } - _ => return None, - }; - bytes.push(byte); - } else { - if ch == '\n' && quote == '"' { - return None; - } - bytes.extend_from_slice(ch.to_string().as_bytes()); - } - } - None -} +pub use ide_core::go::work_modules; #[cfg(test)] mod test_scope_tests {