Compare commits
2
Commits
58e55cf35a
...
6505f35669
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6505f35669
|
||
|
|
92b515cbf8
|
No files matched your search
@@ -2,6 +2,9 @@
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
/// This build has a system print panel.
|
||||
pub const BUILT: bool = cfg!(target_os = "macos");
|
||||
|
||||
/// Shows the print panel for `pdf`; false when cancelled.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn print_pdf(pdf: &Path) -> anyhow::Result<bool> {
|
||||
@@ -85,8 +88,8 @@ fn paper_for(sizes: &[(f64, f64)]) -> Option<(f64, f64)> {
|
||||
|
||||
/// Printing needs a system panel this platform build lacks.
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn print_pdf(pdf: &Path) -> anyhow::Result<bool> {
|
||||
anyhow::bail!("printing {} is not built on this platform", pdf.display())
|
||||
pub fn print_pdf(_: &Path) -> anyhow::Result<bool> {
|
||||
anyhow::bail!("printing is not built on this platform")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -131,6 +131,9 @@ fn label(command: &Command, cx: &App) -> SharedString {
|
||||
|
||||
/// Why `id` cannot run; a document lifts the no-document block.
|
||||
pub fn blocked(id: &str, document: bool, cx: &App) -> Option<SharedString> {
|
||||
if id == PRINT.id && !ely_platform::print::BUILT {
|
||||
return Some(i18n::text(cx, "command.blocked.unbuilt", &[]));
|
||||
}
|
||||
let command = RENAMING.iter().find(|command| command.id == id)?;
|
||||
let key = command.blocked.filter(|_| !document)?;
|
||||
Some(i18n::text(cx, key, &[]))
|
||||
@@ -187,6 +190,10 @@ pub fn menus(
|
||||
}
|
||||
|
||||
pub fn bind_keys(cx: &mut App) {
|
||||
// Without a print panel, Print stays off the keyboard.
|
||||
if ely_platform::print::BUILT {
|
||||
cx.bind_keys([KeyBinding::new(PRINT.keys, Print, Some(CONTEXT))]);
|
||||
}
|
||||
cx.bind_keys([
|
||||
KeyBinding::new(NEW.keys, New, Some(CONTEXT)),
|
||||
KeyBinding::new(OPEN.keys, Open, Some(CONTEXT)),
|
||||
@@ -194,7 +201,6 @@ pub fn bind_keys(cx: &mut App) {
|
||||
KeyBinding::new(SAVE_AS.keys, SaveAs, Some(CONTEXT)),
|
||||
KeyBinding::new(RENAME.keys, Rename, Some(CONTEXT)),
|
||||
KeyBinding::new(EXPORT.keys, Export, Some(CONTEXT)),
|
||||
KeyBinding::new(PRINT.keys, Print, Some(CONTEXT)),
|
||||
KeyBinding::new("secondary-w", CloseTab, Some(CONTEXT)),
|
||||
KeyBinding::new(TOGGLE_PALETTE, ToggleCommandPalette, Some(CONTEXT)),
|
||||
KeyBinding::new("secondary-q", Quit, None),
|
||||
|
||||
@@ -41,6 +41,7 @@ const EN: &[(&str, &str)] = &[
|
||||
("command.file.export", "Export…"),
|
||||
("command.file.print", "Print…"),
|
||||
("command.blocked.no_document", "No open document"),
|
||||
("command.blocked.unbuilt", "Not built for this platform yet"),
|
||||
("palette.group.file", "File"),
|
||||
("gallery.ready", "Ready"),
|
||||
("gallery.nav", "Navigation"),
|
||||
@@ -96,6 +97,7 @@ const ZH_CN: &[(&str, &str)] = &[
|
||||
("command.file.export", "导出…"),
|
||||
("command.file.print", "打印…"),
|
||||
("command.blocked.no_document", "没有打开的文档"),
|
||||
("command.blocked.unbuilt", "此平台尚未构建"),
|
||||
("palette.group.file", "文件"),
|
||||
("gallery.ready", "就绪"),
|
||||
("gallery.nav", "导航"),
|
||||
|
||||
@@ -157,16 +157,30 @@ impl Root {
|
||||
}
|
||||
|
||||
fn attempt(&mut self, id: &'static str, cx: &mut Context<Self>) {
|
||||
if let Some(reason) = commands::blocked(id, self.document, cx) {
|
||||
log::info!("command: {id} blocked: {reason}");
|
||||
self.last = Some(format!("{id} blocked").into());
|
||||
self.toaster.update(cx, |toaster, cx| {
|
||||
toaster.push(Toast::new(reason).severity(Severity::Warning), cx);
|
||||
});
|
||||
cx.notify();
|
||||
return;
|
||||
if !self.refused(id, cx) {
|
||||
self.ran(id, cx);
|
||||
}
|
||||
self.ran(id, cx);
|
||||
}
|
||||
|
||||
/// Runs a palette pick; views act first, so check here.
|
||||
pub fn run_from_palette(&mut self, id: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if !self.refused(id, cx) {
|
||||
commands::run(id, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `id` is blocked here; the reason shows.
|
||||
fn refused(&mut self, id: &str, cx: &mut Context<Self>) -> bool {
|
||||
let Some(reason) = commands::blocked(id, self.document, cx) else {
|
||||
return false;
|
||||
};
|
||||
log::info!("command: {id} blocked: {reason}");
|
||||
self.last = Some(format!("{id} blocked").into());
|
||||
self.toaster.update(cx, |toaster, cx| {
|
||||
toaster.push(Toast::new(reason).severity(Severity::Warning), cx);
|
||||
});
|
||||
cx.notify();
|
||||
true
|
||||
}
|
||||
|
||||
/// Shows `text` in the window's toaster.
|
||||
@@ -305,6 +319,7 @@ impl Render for Root {
|
||||
if !self.palette {
|
||||
return frame.into_any_element();
|
||||
}
|
||||
let run = cx.entity();
|
||||
let palette = CommandPalette::new("command-palette", move |_, cx| {
|
||||
this.update(cx, |this, cx| this.close_palette(cx));
|
||||
})
|
||||
@@ -319,7 +334,7 @@ impl Render for Root {
|
||||
}),
|
||||
)
|
||||
.recent(self.recent.clone())
|
||||
.on_run(move |value, window, cx| commands::run(value, window, cx));
|
||||
.on_run(move |value, window, cx| run.update(cx, |root, cx| root.run_from_palette(value, window, cx)));
|
||||
frame.child(raise("palette", palette)).into_any_element()
|
||||
}
|
||||
}
|
||||
@@ -332,3 +347,44 @@ fn view(path: &Path) -> std::io::Result<()> {
|
||||
}
|
||||
ely_platform::apps::open_default(path)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::{FocusHandle, InteractiveElement as _, IntoElement, Render, TestAppContext, div};
|
||||
|
||||
/// A focused view that counts the saves it gets.
|
||||
struct Saves(FocusHandle, usize);
|
||||
|
||||
impl Render for Saves {
|
||||
fn render(&mut self, _: &mut gpui::Window, cx: &mut gpui::Context<Self>) -> impl IntoElement {
|
||||
div().track_focus(&self.0).on_action(cx.listener(|saves, _: &crate::commands::Save, _, _| saves.1 += 1))
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_blocked_palette_pick_never_reaches_the_view(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
ely_gpui_component::init_for_tests(cx);
|
||||
crate::settings::install_defaults(cx);
|
||||
crate::i18n::install(cx);
|
||||
});
|
||||
let window = cx.add_window(|_, cx| Saves(cx.focus_handle(), 0));
|
||||
let (saves, root) = window
|
||||
.update(cx, |view, window, cx| {
|
||||
let saves = cx.entity();
|
||||
let root = super::tool_root(ely_platform::AppId::Tree, saves.clone().into(), Vec::new(), window, cx);
|
||||
// The root takes focus as it builds; give it back.
|
||||
window.focus(&view.0, cx);
|
||||
(saves, root)
|
||||
})
|
||||
.expect("the window lives");
|
||||
cx.run_until_parked();
|
||||
// Save without a document is blocked; so is Print unbuilt.
|
||||
window
|
||||
.update(cx, |_, window, cx| root.update(cx, |root, cx| root.run_from_palette("file.save", window, cx)))
|
||||
.expect("the window lives");
|
||||
cx.run_until_parked();
|
||||
assert_eq!(cx.update(|cx| saves.read(cx).1), 0, "the view never gets it");
|
||||
assert_eq!(cx.update(|cx| root.read(cx).toasts(cx).len()), 1, "the reason shows");
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -54,7 +54,7 @@
|
||||
- [x] D-05 邮件合并某条记录失败时内核文档泄漏 — 失败的记录照样关闭内核文档,再传出首个错误
|
||||
- [x] D-06 导航栏大纲点击按当前列表取下标,后台重读后可能越界 — 点击按绘制时的列表跳转;补点击路径的回归测试
|
||||
- [x] D-07 导航栏隐藏时仍随每次光标移动重读大纲与书签 — 隐藏时不跟随标签、不读;再显示时读一次
|
||||
- [ ] D-08 Linux、Windows 上“打印…”可用,导出 PDF 后才报“此平台未构建”
|
||||
- [x] D-08 Linux、Windows 上“打印…”可用,导出 PDF 后才报“此平台未构建” — 未构建平台禁用打印、不绑快捷键、错误不带路径;命令面板先查禁用
|
||||
- [x] M-01 键入期间外部以相同内容重写文件,丢掉上次停顿之后键入的全部文字 — 先等页面送出未送的键;同文只接过文件与字节、标签为已保存;磁盘回到上次保存的文字时保留编辑并恢复自动保存;R1–R4 字节、自动保存、在途写入、已保存基线;R5 PASS
|
||||
- [ ] M-02 `refresh` 归零版本计数却不换代,在途的一键让标签一直“未落定”
|
||||
- [ ] M-03 干净标签接过磁盘文字后关闭仍问“是否保存”
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
FAIL
|
||||
|
||||
crates/ely-ui/src/commands.rs:213 — 命令面板仍可绕过打印的平台禁用检查。`shell/root.rs:313-322` 将 `file.print` 放入命令面板,选择后直接调用 `commands::run`;这里直接分发 `Print`,文档视图的处理器先接收动作,外层 `Root::attempt` 的 `blocked()` 检查来不及拦截。具体场景:Linux/Windows 打开文档,打开命令面板,搜索并执行“打印…”;Doc/Show 仍进入打印范围流程,Table/PDF/Mark 仍生成打印副本,最后才收到平台未构建错误,违反 D-08 与 X-13 打印入口禁用要求。复现证据:在独立源码副本中仅将 commands.rs 的两处 `BUILT` 引用设为 false,经真实 GPUI 命令面板键入 `Print` 并回车,菜单已禁用、打印快捷键未绑定,文档打印处理器仍被调用 1 次;加入分发前检查后为 0 次,恢复后再次为 1 次(`/tmp/ely-d08-r1-probe.log`、`/tmp/ely-d08-r1-guard-control.log`)。原有平台/UI 单元测试 84 项通过,实际 Windows 打印分支独立编译通过;完整 Windows 平台库检查因缺少 Windows C 头文件停在 ring。修复方向:命令面板执行打印前统一检查平台能力并显示禁用原因,补充 `BUILT=false` 时从命令面板执行打印的回归测试。探针源码与其构建目录已删除。
|
||||
@@ -0,0 +1,3 @@
|
||||
FAIL
|
||||
|
||||
crates/ely-ui/src/shell/root.rs:375 — 回归测试的 Save 计数存在假通过:紧接着的 `tool_root` 会在 `build` 中把焦点移到 Root,计数视图随即失焦。因此,`a_blocked_palette_pick_never_reaches_the_view` 的零次 Save 断言无法验证拦截。具体复现:在独立副本中把 `run_from_palette` 改为先调用 `self.refused(id, cx)` 显示提示,再无条件调用 `commands::run(id, window, cx)`;该错误实现仍通过现有回归测试(Save 为 0、提示为 1)。仅将测试中的 `window.focus(&view.0, cx)` 移到 `tool_root` 创建之后,同一错误实现即收到 1 次 Save,并在动作计数断言处失败。直接移除检查时,原测试仅因提示数量为 0 而失败。证据:`/tmp/ely-d08-r2-dispatch-despite-toast.log`、`/tmp/ely-d08-r2-focus-control.log`、`/tmp/ely-d08-r2-guard-removed-a_blocked_palette_pick.log`。修复方向:创建 tool root 后重新聚焦计数视图,并用保留提示但继续分发的变异验证 Save 断言确实失败。当前实际命令面板的打印拦截已通过独立复现,ely-ui 的 35 项单测及平台测试、注释检查通过;探针源码与独立构建目录已删除。
|
||||
@@ -0,0 +1 @@
|
||||
PASS
|
||||
Reference in new issue
Block a user