diff --git a/crates/engine/src/cmd/fileio/mod.rs b/crates/engine/src/cmd/fileio/mod.rs index c819b9ae6..8c224aa12 100644 --- a/crates/engine/src/cmd/fileio/mod.rs +++ b/crates/engine/src/cmd/fileio/mod.rs @@ -1366,15 +1366,17 @@ pub(crate) fn unique_file_names<'a>(names: impl IntoIterator) -> Vec { let mut taken = std::collections::HashSet::new(); + // The next number to try for each base: a name repeated many times doesn't search the numbers + // taken before it. + let mut next: std::collections::HashMap = std::collections::HashMap::new(); names .into_iter() .map(|base| { let mut name = base.clone(); - for i in 2.. { - if taken.insert(name.to_lowercase()) { - break; - } - name = format!("{base}-{i}"); + while !taken.insert(name.to_lowercase()) { + let n = next.entry(base.to_lowercase()).or_insert(1); + *n += 1; + name = format!("{base}-{n}"); } name }) diff --git a/crates/engine/src/cmd/links.rs b/crates/engine/src/cmd/links.rs index f74c344e7..e8f122e96 100644 --- a/crates/engine/src/cmd/links.rs +++ b/crates/engine/src/cmd/links.rs @@ -182,6 +182,8 @@ pub(crate) fn linked_groups(d: &Document) -> Vec<(String, Vec)> { fn groups(d: &Document, ids: Option<&[NodeId]>) -> Vec { let mut out: Vec = vec![]; let mut index: BTreeMap<(String, String, bool), usize> = BTreeMap::new(); + // Looked up for every linked node: a set, not the list (which may repeat an id many times). + let wanted: Option> = ids.map(|ids| ids.iter().copied().collect()); let mut add = |link: &LinkInfo, key: &str, document: Option, id: Option| { let i = *index.entry((link.path.clone(), key.to_string(), document.is_some())).or_insert_with(|| { out.push(Group { link: link.clone(), key: key.to_string(), ids: vec![], document }); @@ -193,7 +195,7 @@ fn groups(d: &Document, ids: Option<&[NodeId]>) -> Vec { }; d.walk(|n| { if let Some((l, key, document)) = linked(n) - && ids.is_none_or(|ids| ids.contains(&n.id)) + && wanted.as_ref().is_none_or(|ids| ids.contains(&n.id)) { add(l, key, document, Some(n.id)); } diff --git a/crates/mcp/src/prompts.rs b/crates/mcp/src/prompts.rs index 2dff4486e..0c0a43926 100644 --- a/crates/mcp/src/prompts.rs +++ b/crates/mcp/src/prompts.rs @@ -405,7 +405,10 @@ pub fn get(name: &str, args: &Value) -> Result { let ids: Vec<&str> = PROMPTS.iter().map(|p| p.name).collect(); return Err(format!("unknown prompt `{name}` ({})", ids.join("|"))); }; - let mut text = flow(p.text); + let template = flow(p.text); + // (placeholder, value): filled in one pass over the template, so a value that looks like a + // placeholder stays as it is. + let mut values: Vec<(String, String)> = vec![]; for a in p.arguments { let given = args.get(a.name).filter(|v| !v.is_null()).map(|v| match v { Value::String(s) => s.clone(), @@ -416,7 +419,21 @@ pub fn get(name: &str, args: &Value) -> Result { _ if a.required => return Err(format!("prompt `{name}` needs the `{a_name}` argument", a_name = a.name)), _ => a.default.to_string(), }; - text = text.replace(&format!("{{{}}}", a.name), &value); + values.push((format!("{{{}}}", a.name), value)); + } + let mut text = String::with_capacity(template.len()); + let mut rest = template.as_str(); + while let Some(c) = rest.chars().next() { + match values.iter().find(|(placeholder, _)| rest.starts_with(placeholder.as_str())) { + Some((placeholder, value)) => { + text.push_str(value); + rest = rest.get(placeholder.len()..).unwrap_or_default(); + } + None => { + text.push(c); + rest = rest.get(c.len_utf8()..).unwrap_or_default(); + } + } } Ok(json!({ "description": p.description, diff --git a/crates/render/src/encode/quantize.rs b/crates/render/src/encode/quantize.rs index 9c5061f1d..ec04c9f46 100644 --- a/crates/render/src/encode/quantize.rs +++ b/crates/render/src/encode/quantize.rs @@ -197,7 +197,9 @@ pub fn quantize_locked(rgba: &[u8], width: u32, height: u32, o: &PaletteOptions, } let weights = o.reduction.weights(); let dither = if exact { Dither::None } else { o.dither }; - let mut indices = map(&colors, width as usize, &palette, weights, dither, f32::from(o.dither_amount.min(100)) / 100.0, |i| clear(&px[i])); + // (A width past the pixels there are would size the error rows from a number nothing checked.) + let mut indices = + map(&colors, (width as usize).min(colors.len()), &palette, weights, dither, f32::from(o.dither_amount.min(100)) / 100.0, |i| clear(&px[i])); // Drop the entries no pixel uses (fixed palettes), keeping the order. let mut used = [false; 256]; for (i, &ix) in indices.iter().enumerate() { diff --git a/crates/render/src/encode/tga.rs b/crates/render/src/encode/tga.rs index 884276eec..abac01dba 100644 --- a/crates/render/src/encode/tga.rs +++ b/crates/render/src/encode/tga.rs @@ -34,11 +34,11 @@ pub fn encode(rgba: &[u8], width: u32, height: u32, o: &TgaOptions) -> Result().0; - let mut out = Vec::with_capacity(18 + w * h * usize::from(o.depth / 8) + 26); + let mut out = Vec::with_capacity((w * h).saturating_mul(usize::from(o.depth / 8)).saturating_add(18 + 26)); // No image id or colour map, uncompressed true colour, origin (0, 0). out.extend([0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0]); out.extend(w16.to_le_bytes());