fn similar_paths_for(err: &AtomwriteError, ctx: &ErrorContext) -> Option<Vec<String>> {
let path = match err {
AtomwriteError::NotFound { path } => path,
_ => return None,
};
let wanted = path.file_name()?.to_string_lossy().to_string();
if wanted.is_empty() {
return None;
}
let root = ctx.workspace.as_ref()?;
let mut builder = ignore::WalkBuilder::new(root);
builder.max_depth(Some(crate::constants::SUGGEST_WALK_MAX_DEPTH));
crate::concurrency::apply_walk_threads(&mut builder, None);
let wanted_c = wanted;
let mut scored: Vec<(f64, String)> =
crate::concurrency::collect_mapped_parallel(&builder, move |entry| {
if !entry.file_type().is_some_and(|ft| ft.is_file()) {
return None;
}
let name = entry.file_name().to_string_lossy();
let score = strsim::jaro_winkler(&wanted_c, &name);
if score >= crate::constants::SUGGEST_MIN_SIMILARITY {
Some((score, entry.path().display().to_string()))
} else {
None
}
});
scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
scored.truncate(crate::constants::SUGGEST_MAX_PATHS);
if scored.is_empty() {
None
} else {
Some(scored.into_iter().map(|(_, p)| p).collect())
}
}
#[derive(Debug, Default, Clone)]
pub struct ErrorContext {
pub workspace_provided: bool,
pub workspace: Option<PathBuf>,
pub verb: Option<&'static str>,
}
#[cold]
fn suggestion_for(err: &AtomwriteError, ctx: &ErrorContext) -> Option<String> {
match err {
AtomwriteError::NotFound { .. } | AtomwriteError::GlobNoMatches { .. } => {
Some(t!("suggestion.not-found").to_string())
}
AtomwriteError::EditPairFailed { index, .. } => {
Some(t!("suggestion.edit-pair-failed", index = index).to_string())
}
AtomwriteError::InvalidInput { reason } => {
let key = if reason.contains(crate::constants::JOIN_REASON_MARKER) {
t!("suggestion.join-input")
} else if reason.contains(crate::constants::FUZZY_CONTENT_CAP_REASON_MARKER) {
t!("suggestion.fuzzy-content-cap")
} else if reason.contains(crate::constants::FUZZY_PATTERN_CAP_REASON_MARKER) {
t!("suggestion.fuzzy-pattern-cap")
} else if reason.contains(crate::constants::CONFIG_RELATION_REASON_MARKER) {
t!("suggestion.config-relation")
} else if reason.contains(crate::constants::CONFIG_OUT_OF_RANGE_REASON_MARKER) {
t!("suggestion.config-out-of-range")
} else if reason.contains(crate::constants::PRUNE_DIRECTORY_REASON_MARKER) {
t!("suggestion.prune-backups-directory")
} else if reason.contains(crate::constants::SIDE_EFFECT_NO_SIDE_EFFECT_REASON_MARKER) {
t!("suggestion.confirm-side-effect-without-side-effect")
} else if reason.contains(crate::constants::LANGUAGE_MISMATCH_REASON_MARKER) {
t!("suggestion.language-extension-mismatch")
} else if reason.contains(crate::constants::SINGLE_TARGET_REASON_MARKER) {
t!("suggestion.single-target")
} else if reason.contains(crate::constants::READ_MULTI_TARGET_REASON_MARKER) {
t!("suggestion.read-multi-target")
} else if reason.contains(crate::constants::NO_COMMENTS_NEEDS_NUMBER_REASON_MARKER) {
t!("suggestion.read-no-comments-number")
} else {
t!("suggestion.invalid-input")
};
Some(key.to_string())
}
AtomwriteError::PermissionDenied { .. } => {
Some(t!("suggestion.permission-denied").to_string())
}
AtomwriteError::DiskFull { .. } => Some(t!("suggestion.disk-full").to_string()),
AtomwriteError::QuotaExceeded { .. } => Some(t!("suggestion.quota-exceeded").to_string()),
AtomwriteError::CrossDevice { .. } => Some(t!("suggestion.cross-device").to_string()),
AtomwriteError::Io { source } => {
Some(t!("suggestion.io", source = source.to_string()).to_string())
}
AtomwriteError::ConfigInvalid { reason } => {
let key = if reason.contains(crate::constants::FEATURE_MISSING_REASON_MARKER) {
t!("suggestion.feature-missing")
} else {
t!("suggestion.config-invalid")
};
Some(key.to_string())
}
AtomwriteError::StateDrift { .. } => Some(t!("suggestion.state-drift").to_string()),
AtomwriteError::ChecksumVerifyFailed { .. } => {
Some(t!("suggestion.checksum-verify").to_string())
}
AtomwriteError::FileTooLarge { .. } => Some(t!("suggestion.file-too-large").to_string()),
AtomwriteError::WorkspaceJail {
path,
workspace,
content_source,
} => {
if *content_source && ctx.workspace_provided {
Some(
t!(
"suggestion.workspace-jail-content-source",
workspace = workspace.display().to_string()
)
.to_string(),
)
} else if ctx.workspace_provided {
Some(
t!(
"suggestion.workspace-jail-inside",
workspace = workspace.display().to_string()
)
.to_string(),
)
} else {
let root = path
.parent()
.map(|parent| parent.display().to_string())
.unwrap_or_else(|| workspace.display().to_string());
let name = path
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_default();
Some(
t!(
"suggestion.workspace-jail-set",
root = root,
name = name
)
.to_string(),
)
}
}
AtomwriteError::SymlinkBlocked { .. } => Some(t!("suggestion.symlink-blocked").to_string()),
AtomwriteError::FileImmutable { path } => Some(
t!(
"suggestion.immutable-path",
path = path.display().to_string()
)
.to_string(),
),
AtomwriteError::BinaryFile { .. } => Some(t!("suggestion.binary-file").to_string()),
AtomwriteError::FifoDetected { .. } => Some(t!("suggestion.skip-special").to_string()),
AtomwriteError::DeviceFile { .. } => Some(t!("suggestion.skip-special").to_string()),
AtomwriteError::NoMatches => Some(t!("suggestion.no-matches").to_string()),
AtomwriteError::AllSkippedBinary { .. } => {
Some(t!("suggestion.all-skipped-binary").to_string())
}
AtomwriteError::BrokenPipe => None,
AtomwriteError::MatchAmbiguous { .. } => Some(t!("suggestion.match-ambiguous").to_string()),
AtomwriteError::MatchFailed { .. } => {
let key = if ctx.verb == Some("apply") {
t!("suggestion.match-failed-apply")
} else {
t!("suggestion.match-failed")
};
Some(key.to_string())
}
AtomwriteError::Cancelled { .. } => Some(t!("suggestion.cancelled").to_string()),
AtomwriteError::InternalError { .. } => Some(t!("suggestion.internal-error").to_string()),
AtomwriteError::LockTimeout { path, timeout_ms } => Some(
t!(
"suggestion.lock-timeout",
path = path.display().to_string(),
timeout_ms = timeout_ms
)
.to_string(),
),
AtomwriteError::SyntaxError { path, count, .. } => Some(
t!(
"suggestion.syntax-error",
path = path.display().to_string(),
count = count
)
.to_string(),
),
AtomwriteError::ExdevFallbackDisabled { path, .. } => Some(
t!(
"suggestion.exdev-fallback-disabled",
path = path.display().to_string()
)
.to_string(),
),
AtomwriteError::CopyBackBlake3Failed { path } => Some(
t!(
"suggestion.copy-back-blake3-failed",
path = path.display().to_string()
)
.to_string(),
),
AtomwriteError::OrphanJournal { journal, .. } => Some(
t!(
"suggestion.orphan-journal",
journal = journal.display().to_string()
)
.to_string(),
),
AtomwriteError::MergeConflict { count, path } => Some(
t!(
"suggestion.merge-conflict",
count = count,
path = path.display().to_string()
)
.to_string(),
),
AtomwriteError::EditLoopPartial { unmatched, total } => Some(
t!(
"suggestion.edit-loop-partial",
unmatched = unmatched,
total = total
)
.to_string(),
),
AtomwriteError::DoctorChecksFailed { failed, warned } => Some(
t!(
"suggestion.doctor-checks-failed",
failed = failed,
warned = warned
)
.to_string(),
),
AtomwriteError::MissingFlag { flag, .. } => {
Some(t!("suggestion.missing-flag", flag = flag.as_str()).to_string())
}
AtomwriteError::TransactionRollbackFailed { .. } => {
Some(t!("suggestion.transaction-rollback-failed").to_string())
}
AtomwriteError::GrammarUnavailable { language, .. } => Some(
t!("suggestion.grammar-unavailable", language = language.as_str()).to_string(),
),
AtomwriteError::UnsupportedCombination { flag, other, .. } => Some(
t!(
"suggestion.unsupported-combination",
flag = flag.as_str(),
other = other.as_str()
)
.to_string(),
),
}
}