mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 16:02:24 +00:00
Merge branch 'fix/io-error-messages' into integration/polish
This commit is contained in:
+12
-1
@@ -623,7 +623,18 @@ impl Tty7App {
|
||||
Ok(mtime) => {
|
||||
f.disk_mtime = mtime;
|
||||
}
|
||||
Err(e) => HostOps::notify_err(window, cx, t(L10nKey::EditorSaveFailed), &e),
|
||||
Err(e) => {
|
||||
// "Save failed" did not say which file, and with more
|
||||
// than one editor tab open that is the first thing you
|
||||
// need to know.
|
||||
let name = f
|
||||
.path
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| f.path.display().to_string());
|
||||
let context = t_fmt(L10nKey::EditorSaveFailed, &[("name", &name)]);
|
||||
HostOps::notify_err(window, cx, &context, &e);
|
||||
}
|
||||
}
|
||||
if landing.clean {
|
||||
f.dirty = false;
|
||||
|
||||
+44
-1
@@ -205,13 +205,31 @@ impl HostOps {
|
||||
window.push_notification(
|
||||
t_fmt(
|
||||
L10nKey::HostOpsError,
|
||||
&[("context", context), ("error", &err.to_string())],
|
||||
&[("context", context), ("error", &explain_io(err))],
|
||||
),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A sentence for the failures someone can act on, and the raw error for the
|
||||
/// rest. `Display` on an `io::Error` answers "what happened" for a developer
|
||||
/// reading a log; it does not answer "what now" for the person who just lost
|
||||
/// a save, and "Permission denied (os error 13)" is the shape of that gap.
|
||||
pub fn explain_io(err: &std::io::Error) -> String {
|
||||
use std::io::ErrorKind;
|
||||
let key = match err.kind() {
|
||||
ErrorKind::PermissionDenied => L10nKey::IoDenied,
|
||||
ErrorKind::NotFound => L10nKey::IoGone,
|
||||
ErrorKind::StorageFull => L10nKey::IoNoSpace,
|
||||
ErrorKind::ReadOnlyFilesystem => L10nKey::IoReadOnly,
|
||||
ErrorKind::ResourceBusy => L10nKey::IoBusy,
|
||||
ErrorKind::TimedOut => L10nKey::IoTimedOut,
|
||||
_ => return err.to_string(),
|
||||
};
|
||||
crate::ui::i18n::t(key).to_string()
|
||||
}
|
||||
|
||||
pub struct InFlight<K: Eq + Hash + Clone> {
|
||||
in_flight: HashSet<K>,
|
||||
stale: HashSet<K>,
|
||||
@@ -322,6 +340,31 @@ impl<K: Eq + Hash, V> ByHost<K, V> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn the_failures_people_can_act_on_get_a_sentence() {
|
||||
use std::io::{Error, ErrorKind};
|
||||
crate::ui::i18n::set_locale("en");
|
||||
|
||||
// The kinds that change what you would do next.
|
||||
for kind in [
|
||||
ErrorKind::PermissionDenied,
|
||||
ErrorKind::StorageFull,
|
||||
ErrorKind::ReadOnlyFilesystem,
|
||||
ErrorKind::NotFound,
|
||||
] {
|
||||
let text = explain_io(&Error::new(kind, "os error 13"));
|
||||
assert!(!text.contains("os error"), "{kind:?}: {text}");
|
||||
assert!(text.ends_with('.'), "{kind:?}: {text}");
|
||||
}
|
||||
|
||||
// Anything unclassified keeps its detail rather than losing it to a
|
||||
// vague house sentence.
|
||||
assert_eq!(
|
||||
explain_io(&Error::other("the widget frobnicated")),
|
||||
"the widget frobnicated"
|
||||
);
|
||||
}
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[test]
|
||||
|
||||
+7
-1
@@ -806,7 +806,7 @@ pub fn translate_en(key: L10nKey) -> &'static str {
|
||||
L10nKey::EditorCantOpen => "Can't open {path}: {e}",
|
||||
L10nKey::EditorCantRead => "Can't read {path}: {e}",
|
||||
L10nKey::EditorNotUtf8 => "\"{path}\" is not valid UTF-8",
|
||||
L10nKey::EditorSaveFailed => "Save failed",
|
||||
L10nKey::EditorSaveFailed => "Could not save {name}",
|
||||
L10nKey::EditorUnsavedChanges => "\"{name}\" has unsaved changes",
|
||||
L10nKey::EditorDiscard => "Discard",
|
||||
L10nKey::EditorNoFileOpen => "No file open",
|
||||
@@ -1048,6 +1048,12 @@ pub fn translate_en(key: L10nKey) -> &'static str {
|
||||
L10nKey::SshPromptUnlock => "Unlock",
|
||||
L10nKey::SshPromptSubmit => "Submit",
|
||||
L10nKey::HostOpsError => "{context}: {error}",
|
||||
L10nKey::IoDenied => "You do not have permission.",
|
||||
L10nKey::IoGone => "It is not there any more.",
|
||||
L10nKey::IoNoSpace => "There is no space left on the disk.",
|
||||
L10nKey::IoReadOnly => "That location is read-only.",
|
||||
L10nKey::IoBusy => "Something else has it open.",
|
||||
L10nKey::IoTimedOut => "The machine did not answer in time.",
|
||||
L10nKey::TreeWindowOpenedEmpty => {
|
||||
"This window's server never handed over its tabs, so it opened empty. Nothing was lost — they come back when it answers. If it doesn't, run \"Restart Server\" from the command palette."
|
||||
}
|
||||
|
||||
+7
-1
@@ -847,7 +847,7 @@ pub fn translate_ja(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::EditorCantOpen => "{path} を開けません: {e}",
|
||||
L10nKey::EditorCantRead => "{path} を読み取れません: {e}",
|
||||
L10nKey::EditorNotUtf8 => "「{path}」は有効な UTF-8 ではありません",
|
||||
L10nKey::EditorSaveFailed => "保存に失敗しました",
|
||||
L10nKey::EditorSaveFailed => "{name} を保存できませんでした",
|
||||
L10nKey::EditorUnsavedChanges => "「{name}」には保存されていない変更があります",
|
||||
L10nKey::EditorDiscard => "破棄",
|
||||
L10nKey::EditorNoFileOpen => "開かれているファイルはありません",
|
||||
@@ -1078,6 +1078,12 @@ pub fn translate_ja(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::SshPromptUnlock => "ロック解除",
|
||||
L10nKey::SshPromptSubmit => "送信",
|
||||
L10nKey::HostOpsError => "{context}: {error}",
|
||||
L10nKey::IoDenied => "権限がありません。",
|
||||
L10nKey::IoGone => "もう存在しません。",
|
||||
L10nKey::IoNoSpace => "ディスクに空き容量がありません。",
|
||||
L10nKey::IoReadOnly => "その場所は読み取り専用です。",
|
||||
L10nKey::IoBusy => "他のプログラムが使用中です。",
|
||||
L10nKey::IoTimedOut => "時間内に応答がありませんでした。",
|
||||
L10nKey::TreeWindowOpenedEmpty => {
|
||||
"このウィンドウのサーバーがタブを渡さなかったため、空のまま開きました。失われたものはなく、応答すれば戻ります。戻らない場合はコマンドパレットの「サーバーを再起動」を実行してください"
|
||||
}
|
||||
|
||||
@@ -877,6 +877,12 @@ l10n_keys! {
|
||||
SshPromptUnlock,
|
||||
SshPromptSubmit,
|
||||
HostOpsError,
|
||||
IoDenied,
|
||||
IoGone,
|
||||
IoNoSpace,
|
||||
IoReadOnly,
|
||||
IoBusy,
|
||||
IoTimedOut,
|
||||
TreeWindowOpenedEmpty,
|
||||
CmdGroupTabsPanes,
|
||||
CmdGroupWorkspaces,
|
||||
@@ -1740,6 +1746,12 @@ mod tests {
|
||||
L10nKey::SshPromptUnlock,
|
||||
L10nKey::SshPromptSubmit,
|
||||
L10nKey::HostOpsError,
|
||||
L10nKey::IoDenied,
|
||||
L10nKey::IoGone,
|
||||
L10nKey::IoNoSpace,
|
||||
L10nKey::IoReadOnly,
|
||||
L10nKey::IoBusy,
|
||||
L10nKey::IoTimedOut,
|
||||
L10nKey::TreeWindowOpenedEmpty,
|
||||
L10nKey::CmdGroupTabsPanes,
|
||||
L10nKey::CmdGroupWorkspaces,
|
||||
|
||||
+7
-1
@@ -772,7 +772,7 @@ pub fn translate_zh(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::EditorCantOpen => "无法打开 {path}:{e}",
|
||||
L10nKey::EditorCantRead => "无法读取 {path}:{e}",
|
||||
L10nKey::EditorNotUtf8 => "\"{path}\" 不是有效的 UTF-8",
|
||||
L10nKey::EditorSaveFailed => "保存失败",
|
||||
L10nKey::EditorSaveFailed => "保存 {name} 失败",
|
||||
L10nKey::EditorUnsavedChanges => "\"{name}\" 有未保存的更改",
|
||||
L10nKey::EditorDiscard => "放弃",
|
||||
L10nKey::EditorNoFileOpen => "没有打开的文件",
|
||||
@@ -986,6 +986,12 @@ pub fn translate_zh(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::SshPromptUnlock => "解锁",
|
||||
L10nKey::SshPromptSubmit => "提交",
|
||||
L10nKey::HostOpsError => "{context}:{error}",
|
||||
L10nKey::IoDenied => "没有权限。",
|
||||
L10nKey::IoGone => "它已经不在了。",
|
||||
L10nKey::IoNoSpace => "磁盘没有空间了。",
|
||||
L10nKey::IoReadOnly => "那个位置是只读的。",
|
||||
L10nKey::IoBusy => "有别的程序正占着它。",
|
||||
L10nKey::IoTimedOut => "对方没有在规定时间内响应。",
|
||||
L10nKey::TreeWindowOpenedEmpty => {
|
||||
"这个窗口的服务器没有交出标签页,所以窗口是空的。什么都没丢,它一响应就会回来。如果一直不回来,在命令面板里执行「重启服务器」。"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user