ElyMark: R4.9 round 1 fixes
Menus act on an open block's selection (component 6d90351), clipboard keys reach a focused field outside the document, Strike binds per platform, platform-correct key tests, Delete Table in the table menu (component 24ffc45), short comments. Pin 6b033f3 -> 24ffc45. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
cb513d49de
commit
f84f990f90
13 files changed
+205
-46
No files matched your search
Generated
+1
-1
@@ -1923,7 +1923,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ely-gpui-component"
|
||||
version = "0.1.1"
|
||||
source = "git+https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components?rev=6b033f3eb05af53c3dd855057608e9e0cd7c8f4b#6b033f3eb05af53c3dd855057608e9e0cd7c8f4b"
|
||||
source = "git+https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components?rev=24ffc45c6bdb89adee82149873a6413625cb73da#24ffc45c6bdb89adee82149873a6413625cb73da"
|
||||
dependencies = [
|
||||
"alacritty_terminal",
|
||||
"anyhow",
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ jiff = "0.2"
|
||||
anyhow = "1"
|
||||
directories = "6"
|
||||
dunce = "1"
|
||||
ely-gpui-component = { git = "https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components", rev = "6b033f3eb05af53c3dd855057608e9e0cd7c8f4b" }
|
||||
ely-gpui-component = { git = "https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components", rev = "24ffc45c6bdb89adee82149873a6413625cb73da" }
|
||||
env_logger = "0.11"
|
||||
gpui = { package = "gpui-pre", version = "=0.3.8", default-features = false, features = ["stacker"] }
|
||||
gpui_platform = { package = "gpui-pre-platform", version = "=0.3.8", features = ["font-kit", "wayland", "x11"] }
|
||||
|
||||
@@ -40,7 +40,7 @@ site/ 官网(Vite 8、pnpm、TypeScript,无框架;等轴 SVG 积木
|
||||
|
||||
## 组件映射
|
||||
|
||||
依赖 `ely-gpui-component`(pin 6b033f3,gpui 为 crates.io 的 `gpui-pre` =0.3.8)。名字取自 `reference/Ely-GPUI-Components/src`。gpui 用法规则见该仓库 `AGENTS.md`。`tests/scripts/agents_md.py` 核对下表每个名字都是组件库公开项。
|
||||
依赖 `ely-gpui-component`(pin 24ffc45,gpui 为 crates.io 的 `gpui-pre` =0.3.8)。名字取自 `reference/Ely-GPUI-Components/src`。gpui 用法规则见该仓库 `AGENTS.md`。`tests/scripts/agents_md.py` 核对下表每个名字都是组件库公开项。
|
||||
|
||||
| 需要 | 用 |
|
||||
| --- | --- |
|
||||
@@ -291,4 +291,4 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely
|
||||
- 2026-10-14(用户要求核对 ElyTable 与 ElyShow):ElyTable 的右键(R5.5)。菜单机制移入 `ely_office::menu`(取内核这次给的菜单、开在所点之处、剪贴板图标行、子菜单可否随其中各项),ElyDoc 与 ElyTable 共用;`ely_lok::menu` 的目标另有单元格(有 `.uno:InsertCell`)与 Writer 之外的绘图对象(有 `.uno:ArrangeMenu`)。网格照 Excel:插入 ▸、删除 ▸、清除内容(`.uno:ClearContents`,同 Delete 键,不弹内核的删除内容对话框)、合并、拆分、设置单元格格式(打开格式面板);对象为置于顶层、置于底层;编辑单元格时只有剪贴板行。行列表头由 Ely 自画,内核不给菜单:右键先选中该行或列(已选的整行整列含它即不动,同 Excel)。选中对象或正编辑图形里的文字时,绘图外壳取代了单元格外壳,`.uno:SelectRow`/`.uno:SelectColumn` 原无处执行而被丢弃;补丁 `0046-lok-calc-headers-leave-drawings.patch` 让 LOK 下视图外壳接下这两条:先结束图形编辑(键入的文字留下)并取消选中,再选行列,同 Calc 自己的表头(`DrawDeselectAll`),菜单为插入、删除、清除内容、行高或列宽、隐藏、显示。ElyTable 每一项的可否都取自内核对它所发的那条命令此刻的状态(内核的菜单只定目标):补丁 `0045-lok-command-states.patch` 加 `getCommandValues(".uno:CommandStates?commands=…")`,先冲出调度器、再逐条问调度器(同菜单栏),未知的命令即报错(`ely_lok::menu::runs`);LOK 推送的状态只有固定的一组,且在空闲时才更新。因此受保护而许插入行列的工作表照样可插;行高、列宽只在文档只读时禁用(内核的规则),受保护时由内核拒绝并报出其错误。ElyTable 无批注界面,不列批注。
|
||||
- 2026-10-14:ElyShow 的右键(R5.6)。画布右键照 PowerPoint 按目标出 Ely 的菜单(`apps/ely-show/src/menu.rs`,共用 `ely_office::menu`);`ely_lok::menu` 的目标另有幻灯片(有 `.uno:SlideLayoutMenu`)与 Writer 之外的图片(有 `.uno:ChangePicture`)。幻灯片为版式 ▸、新建批注;编辑文字为项目符号、编号、新建批注;图形与文本框为组合 ▸、置于顶层 ▸、置于底层 ▸、新建批注;图片另有替换…、裁剪;表格为插入 ▸、删除 ▸、合并单元格、新建批注;剪贴板同为图标行。每项可否取自内核对其命令此刻的状态(补丁 0045):整表选中时可插入行列,删除与合并要光标进单元格(内核的规则)。Impress 的 `LOK_CALLBACK_TABLE_SELECTED` 由 boost 属性树写出,数值与真假都是文字,空列表是空串,`table::read` 照收;拖动表格边线走 R5.3 的同一路径(`.uno:TableChangeCurrentBorderPosition`,偏移为 twips,内核换算)。补丁 `0047-lok-impress-table-edges.patch`:Impress 原只给内侧边线的上下限,行的下限是布局器写死的 600(上游待办),参考线与落点不符;补丁让两轴都带首尾边线的上下限(`leftMin`、`leftMax`、`rightMin`、`rightMax`,列只限从左到右),内侧行线取行自身的最小高度,`table::Lines::ends` 收下,有则按它夹住(内侧线也直接用内核的上限),Writer 不给时照旧留 40 twips;已在界外的线从不逆着拖动方向移动,原地按下松开不发命令;最左列边线原什么也不做却记一步撤销(上游待办),LOK 下改为改首列宽并随边线移动表格,同一步撤销,桌面的拖动手柄不变。正编辑的单元格按编辑器量最小高度,略大于内核真正允许的(约 2 像素),新表的行因此不能再缩,参考线与落点仍一致。正编辑表格单元格时在幻灯片空白处右键,内核只结束编辑、不给菜单,再按一次才有(BUGS P3-11)。
|
||||
- 2026-10-14(Gitea #1,Linux Wayland 报):ElyShow 左侧缩略图条滚轮不滚。根因:列表包在 `ContextMenu` 宿主里,宿主无尺寸,列表随内容长高、从不溢出,滚轮无处可滚(右栏正常);宿主改为 `size_full`(同 ElyPDF 的三处),`tests/strip.rs` 以 24 张幻灯片核对滚轮滚到最后一张。各应用其余 `ContextMenu` 宿主已核:ElyPDF 三处本有尺寸,ElyTable 的工作表标签条与下拉选项不是滚动列表。
|
||||
- 2026-10-14:ElyMark 的菜单与快捷键(R4.9,MARK-15)。项与键取自 Typora 1.14.10 主进程字节码的字符串表(`.work/typora-main-menus.txt`)与其 `zh-Hans.lproj/Menu.json`;实际大小在字节码里有 Ctrl+Shift+9 与 Ctrl+Shift+0 两串,照用户所定用 Ctrl+Shift+0,R4.11 在真窗口核对。表格是“段落”的子菜单(照 Typora),顶层不再有“表格”。原 Ctrl+1/2/3 切模式、Ctrl+=/-/0 字号、Ctrl+Shift+F 专注让给 Typora 的键;可视、源码、预览留在“视图”菜单,无键。Ctrl+/ 另绑在 `ElyEditor` 上(gpui 同深度后绑者胜),盖过代码编辑器的 `//` 注释切换(Markdown 没有这种注释);`MarkdownDocument` 的键上下文只在可视模式有。带 Shift 的标点:Windows 写键名(`ctrl-shift-[`),macOS 与 Linux 写字符(`cmd-{`),见 `blocks::shifted`。Ctrl+Enter 在打开的块里原是输入框的 Submit(文档不处理),改绑为下方插入行。拒绝一律成信息提示:当场的由 `Desk::edit` 说,后台读出的经 `DocumentEvent::EditRefused`、`view::Refused` 到宿主。Typora 有而组件库暂缺的不进菜单:行内公式、上下标、注释、提示块子菜单、任务状态、列表缩进 Ctrl+[/]、代码工具。
|
||||
- 2026-10-14:ElyMark 的菜单与快捷键(R4.9,MARK-15)。项与键取自 Typora 1.14.10 主进程字节码的字符串表(`.work/typora-main-menus.txt`)与其 `zh-Hans.lproj/Menu.json`;实际大小在字节码里有 Ctrl+Shift+9 与 Ctrl+Shift+0 两串,照用户所定用 Ctrl+Shift+0,R4.11 在真窗口核对。表格是“段落”的子菜单(照 Typora),顶层不再有“表格”。原 Ctrl+1/2/3 切模式、Ctrl+=/-/0 字号、Ctrl+Shift+F 专注让给 Typora 的键;可视、源码、预览留在“视图”菜单,无键。Ctrl+/ 另绑在 `ElyEditor` 上(gpui 同深度后绑者胜),盖过代码编辑器的 `//` 注释切换(Markdown 没有这种注释);`MarkdownDocument` 的键上下文只在可视模式有。带 Shift 的标点:Windows 写键名(`ctrl-shift-[`),macOS 与 Linux 写字符(`cmd-{`),见 `blocks::shifted`。Ctrl+Enter 在打开的块里原是输入框的 Submit(文档不处理),改绑为下方插入行。拒绝一律成信息提示:当场的由 `Desk::edit` 说,后台读出的经 `DocumentEvent::EditRefused`、`view::Refused` 到宿主。菜单打开时,打开的块把焦点借给菜单,块与选区都留着,菜单命令照样作用于所选(组件 6d90351)。复制为 Markdown 与粘贴为纯文本只作用于文档:焦点在文档外的输入框(查找框、对话框)时交给该框自己的复制、粘贴。表格子菜单末项为删除表格(组件 `TableEdit::Delete`,一步撤销,插入点不在表格里即拒绝)。Typora 有而组件库暂缺的不进菜单:行内公式、上下标、注释、提示块子菜单、任务状态、列表缩进 Ctrl+[/]、代码工具。
|
||||
@@ -50,10 +50,10 @@ actions!(
|
||||
);
|
||||
|
||||
/// Shift with punctuation: Windows names the key, others the character.
|
||||
pub(crate) fn shifted(key: char, typed: char) -> String {
|
||||
pub(crate) fn shifted(mods: &str, key: char, typed: char) -> String {
|
||||
match cfg!(target_os = "windows") {
|
||||
true => format!("secondary-shift-{key}"),
|
||||
false => format!("secondary-{typed}"),
|
||||
true => format!("{mods}-shift-{key}"),
|
||||
false => format!("{mods}-{typed}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,14 +72,14 @@ pub fn bind(cx: &mut App) {
|
||||
KeyBinding::new("secondary-shift-m", MathBlock, at),
|
||||
KeyBinding::new("secondary-shift-k", CodeFences, at),
|
||||
KeyBinding::new("secondary-shift-q", Quote, at),
|
||||
KeyBinding::new(&shifted('[', '{'), OrderedList, at),
|
||||
KeyBinding::new(&shifted(']', '}'), BulletList, at),
|
||||
KeyBinding::new(&shifted("secondary", '[', '{'), OrderedList, at),
|
||||
KeyBinding::new(&shifted("secondary", ']', '}'), BulletList, at),
|
||||
KeyBinding::new("secondary-shift-x", TaskList, at),
|
||||
KeyBinding::new("secondary-b", Strong, at),
|
||||
KeyBinding::new("secondary-i", Emphasis, at),
|
||||
KeyBinding::new("secondary-u", Underline, at),
|
||||
KeyBinding::new(&shifted('`', '~'), InlineCode, at),
|
||||
KeyBinding::new("alt-shift-5", Strike, at),
|
||||
KeyBinding::new(&shifted("secondary", '`', '~'), InlineCode, at),
|
||||
KeyBinding::new(&shifted("alt", '5', '%'), Strike, at),
|
||||
KeyBinding::new("secondary-k", Hyperlink, at),
|
||||
KeyBinding::new("secondary-shift-i", InsertImage, at),
|
||||
KeyBinding::new("secondary-\\", ClearFormat, at),
|
||||
@@ -230,5 +230,7 @@ impl Desk {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod menus;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,78 @@
|
||||
use ely_gpui_component::theme::{ActiveTheme as _, ControlSize};
|
||||
use gpui::{
|
||||
App, Entity, KeyUpEvent, Keystroke, Menu, Modifiers, TestAppContext, VisualTestContext, point,
|
||||
px,
|
||||
};
|
||||
|
||||
use super::tests::{current, document, opened, press};
|
||||
use crate::{desk::Desk, view::Mode};
|
||||
|
||||
/// Selects `alpha` in the first block, opened.
|
||||
fn select_alpha(desk: &Entity<Desk>, cx: &mut VisualTestContext) {
|
||||
let bounds = cx.debug_bounds("markdown-block-0").expect("a block");
|
||||
let at = point(bounds.left() + px(4.), bounds.top() + px(4.));
|
||||
cx.simulate_click(at, Modifiers::none());
|
||||
press(
|
||||
"home shift-right shift-right shift-right shift-right shift-right",
|
||||
cx,
|
||||
);
|
||||
let selected = document(desk, cx).read_with(cx, |document, cx| document.selected(cx));
|
||||
assert_eq!(selected, "alpha");
|
||||
}
|
||||
|
||||
/// Runs `menu`'s row `row` from the title bar.
|
||||
fn choose(menu: fn(&App) -> Menu, row: usize, cx: &mut VisualTestContext) {
|
||||
cx.update(|window, cx| {
|
||||
cx.set_menus(vec![menu(cx)]);
|
||||
window.refresh();
|
||||
});
|
||||
cx.run_until_parked();
|
||||
let at = cx.update(|window, cx| {
|
||||
let rem = window.rem_size();
|
||||
let bar = cx.theme().titlebar_height().to_pixels(rem);
|
||||
let title = cx.theme().control_height(ControlSize::Sm).to_pixels(rem);
|
||||
point(px(12.), bar + title / 2.)
|
||||
});
|
||||
cx.simulate_click(at, Modifiers::none());
|
||||
cx.run_until_parked();
|
||||
press(&format!("{} enter", vec!["down"; row].join(" ")), cx);
|
||||
let enter = Keystroke::parse("enter").expect("a key");
|
||||
cx.simulate_event(KeyUpEvent { keystroke: enter });
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
fn edit_menu(cx: &App) -> Menu {
|
||||
let look = crate::look::Look::default();
|
||||
crate::keys::menus(&look, cx).remove(0)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_menu_formats_the_open_blocks_selection(cx: &mut TestAppContext) {
|
||||
let (desk, cx, _files) = opened("alpha beta\n", cx);
|
||||
select_alpha(&desk, cx);
|
||||
choose(super::format_menu, 1, cx);
|
||||
assert_eq!(current(&desk, cx), "**alpha** beta\n");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_menu_copies_the_open_blocks_selection(cx: &mut TestAppContext) {
|
||||
let (desk, cx, _files) = opened("alpha beta\n", cx);
|
||||
select_alpha(&desk, cx);
|
||||
choose(edit_menu, 1, cx);
|
||||
let copied = cx.read_from_clipboard().and_then(|item| item.text());
|
||||
assert_eq!(copied.as_deref(), Some("alpha"));
|
||||
}
|
||||
|
||||
const TABLED: &str = "before\n\n| A |\n| - |\n| 1 |\n\nafter\n";
|
||||
|
||||
#[gpui::test]
|
||||
fn delete_table_from_the_menu_is_one_undo_step(cx: &mut TestAppContext) {
|
||||
let (desk, cx, _files) = opened(TABLED, cx);
|
||||
desk.update_in(cx, |desk, window, cx| desk.switch(Mode::Source, window, cx));
|
||||
let document = document(&desk, cx);
|
||||
document.update(cx, |document, cx| document.set_caret(4, 2, cx));
|
||||
choose(crate::tools::table_menu, 13, cx);
|
||||
assert_eq!(current(&desk, cx), "before\n\nafter\n");
|
||||
press("secondary-z", cx);
|
||||
assert_eq!(current(&desk, cx), TABLED);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use gpui::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
blocks::shifted,
|
||||
desk::{
|
||||
Desk,
|
||||
tests::{open, start},
|
||||
@@ -12,7 +13,7 @@ use crate::{
|
||||
view::Mode,
|
||||
};
|
||||
|
||||
fn opened(
|
||||
pub(super) fn opened(
|
||||
text: &str,
|
||||
cx: &mut TestAppContext,
|
||||
) -> (
|
||||
@@ -33,14 +34,17 @@ fn opened(
|
||||
(desk, cx, [data, files])
|
||||
}
|
||||
|
||||
fn document(desk: &Entity<Desk>, cx: &mut VisualTestContext) -> Entity<MarkdownDocument> {
|
||||
pub(super) fn document(
|
||||
desk: &Entity<Desk>,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> Entity<MarkdownDocument> {
|
||||
desk.read_with(cx, |desk, cx| {
|
||||
let view = desk.active_view().expect("a tab");
|
||||
view.read(cx).document().clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn current(desk: &Entity<Desk>, cx: &mut VisualTestContext) -> String {
|
||||
pub(super) fn current(desk: &Entity<Desk>, cx: &mut VisualTestContext) -> String {
|
||||
let document = document(desk, cx);
|
||||
document.read_with(cx, |document, cx| document.text(cx))
|
||||
}
|
||||
@@ -52,7 +56,7 @@ fn mode(desk: &Entity<Desk>, cx: &mut VisualTestContext) -> Mode {
|
||||
}
|
||||
|
||||
/// `keys`, then the background reads.
|
||||
fn press(keys: &str, cx: &mut VisualTestContext) {
|
||||
pub(super) fn press(keys: &str, cx: &mut VisualTestContext) {
|
||||
cx.simulate_keystrokes(keys);
|
||||
cx.run_until_parked();
|
||||
}
|
||||
@@ -70,19 +74,19 @@ fn toasted(desk: &Entity<Desk>, words: &str, cx: &mut VisualTestContext) -> usiz
|
||||
#[gpui::test]
|
||||
fn paragraph_keys_retype_the_caret_block(cx: &mut TestAppContext) {
|
||||
let (desk, cx, _files) = opened("Intro\n", cx);
|
||||
press("ctrl-1", cx);
|
||||
press("secondary-1", cx);
|
||||
assert_eq!(current(&desk, cx), "# Intro\n");
|
||||
press("ctrl--", cx);
|
||||
press("secondary--", cx);
|
||||
assert_eq!(current(&desk, cx), "## Intro\n");
|
||||
press("ctrl-=", cx);
|
||||
press("secondary-=", cx);
|
||||
assert_eq!(current(&desk, cx), "# Intro\n");
|
||||
press("ctrl-0", cx);
|
||||
press("secondary-0", cx);
|
||||
assert_eq!(current(&desk, cx), "Intro\n");
|
||||
press("ctrl-shift-[", cx);
|
||||
press(&shifted("secondary", '[', '{'), cx);
|
||||
assert_eq!(current(&desk, cx), "1. Intro\n");
|
||||
press("ctrl-shift-]", cx);
|
||||
press(&shifted("secondary", ']', '}'), cx);
|
||||
assert_eq!(current(&desk, cx), "- Intro\n");
|
||||
press("ctrl-shift-q", cx);
|
||||
press("secondary-shift-q", cx);
|
||||
assert_eq!(
|
||||
current(&desk, cx),
|
||||
"- > Intro\n",
|
||||
@@ -93,23 +97,23 @@ fn paragraph_keys_retype_the_caret_block(cx: &mut TestAppContext) {
|
||||
#[gpui::test]
|
||||
fn format_keys_wrap_the_caret_word(cx: &mut TestAppContext) {
|
||||
let (desk, cx, _files) = opened("word\n", cx);
|
||||
press("ctrl-b", cx);
|
||||
press("secondary-b", cx);
|
||||
assert_eq!(current(&desk, cx), "**word**\n");
|
||||
press("ctrl-\\", cx);
|
||||
press("secondary-\\", cx);
|
||||
assert_eq!(current(&desk, cx), "word\n");
|
||||
press("alt-shift-5", cx);
|
||||
press(&shifted("alt", '5', '%'), cx);
|
||||
assert_eq!(current(&desk, cx), "~~word~~\n");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn ctrl_slash_toggles_source_inside_the_code_editor(cx: &mut TestAppContext) {
|
||||
let (desk, cx, _files) = opened("a\n", cx);
|
||||
press("ctrl-/", cx);
|
||||
press("secondary-/", cx);
|
||||
assert_eq!(mode(&desk, cx), Mode::Source);
|
||||
let editor = document(&desk, cx).read_with(cx, |document, _| document.editor().clone());
|
||||
let focus = editor.read_with(cx, |editor, cx| editor.focus_handle(cx));
|
||||
cx.update(|window, cx| window.focus(&focus, cx));
|
||||
press("ctrl-/", cx);
|
||||
press("secondary-/", cx);
|
||||
assert_eq!(mode(&desk, cx), Mode::Visual);
|
||||
assert_eq!(current(&desk, cx), "a\n", "no comment marks");
|
||||
}
|
||||
@@ -117,7 +121,7 @@ fn ctrl_slash_toggles_source_inside_the_code_editor(cx: &mut TestAppContext) {
|
||||
#[gpui::test]
|
||||
fn a_refused_command_says_why(cx: &mut TestAppContext) {
|
||||
let (desk, cx, _files) = opened("```\ncode\n```\n", cx);
|
||||
press("ctrl-1", cx);
|
||||
press("secondary-1", cx);
|
||||
assert_eq!(current(&desk, cx), "```\ncode\n```\n");
|
||||
assert_eq!(toasted(&desk, "inside code", cx), 1);
|
||||
}
|
||||
@@ -125,7 +129,7 @@ fn a_refused_command_says_why(cx: &mut TestAppContext) {
|
||||
#[gpui::test]
|
||||
fn alt_arrows_move_a_table_row(cx: &mut TestAppContext) {
|
||||
let (desk, cx, _files) = opened("| A | B |\n| - | - |\n| 1 | 2 |\n", cx);
|
||||
press("ctrl-/", cx);
|
||||
press("secondary-/", cx);
|
||||
document(&desk, cx).update(cx, |document, cx| document.set_caret(2, 2, cx));
|
||||
press("alt-up", cx);
|
||||
assert_eq!(
|
||||
@@ -139,26 +143,26 @@ fn alt_arrows_move_a_table_row(cx: &mut TestAppContext) {
|
||||
#[gpui::test]
|
||||
fn view_keys_follow_typora(cx: &mut TestAppContext) {
|
||||
let (desk, cx, _files) = opened("a\n", cx);
|
||||
press("f8 f9 ctrl-shift-=", cx);
|
||||
press(&format!("f8 f9 {}", shifted("secondary", '=', '+')), cx);
|
||||
let look = desk.read_with(cx, |desk, _| desk.look.clone());
|
||||
assert!(look.focus && look.typewriter && look.size > 100, "{look:?}");
|
||||
press("ctrl-shift-0", cx);
|
||||
press(&shifted("secondary", '0', ')'), cx);
|
||||
assert_eq!(desk.read_with(cx, |desk, _| desk.look.size), 100);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn copy_as_markdown_and_paste_as_plain_text(cx: &mut TestAppContext) {
|
||||
let (desk, cx, _files) = opened("a **b**\n", cx);
|
||||
press("ctrl-shift-c", cx);
|
||||
press("secondary-shift-c", cx);
|
||||
let copied = cx.read_from_clipboard().and_then(|item| item.text());
|
||||
assert_eq!(copied.as_deref(), Some("a **b**\n"));
|
||||
cx.write_to_clipboard(ClipboardItem::new_string("*x*".into()));
|
||||
press("ctrl-/", cx);
|
||||
press("ctrl-shift-v", cx);
|
||||
press("secondary-/", cx);
|
||||
press("secondary-shift-v", cx);
|
||||
assert_eq!(current(&desk, cx), "*x*a **b**\n");
|
||||
}
|
||||
|
||||
/// Each row's name, `-` for a line, `▸` before a submenu.
|
||||
/// Row names; `-` a line, `▸` a submenu.
|
||||
fn rows(menu: &Menu) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
for item in &menu.items {
|
||||
@@ -198,6 +202,8 @@ fn the_paragraph_and_format_menus_follow_typora(cx: &mut TestAppContext) {
|
||||
"Delete Column",
|
||||
"-",
|
||||
"Prettify Source Code",
|
||||
"-",
|
||||
"Delete Table",
|
||||
];
|
||||
let mut paragraph: Vec<String> = [
|
||||
"Heading 1",
|
||||
@@ -248,6 +254,7 @@ fn the_paragraph_and_format_menus_follow_typora(cx: &mut TestAppContext) {
|
||||
}
|
||||
|
||||
/// The keys a menu row shows for `action`.
|
||||
#[cfg(target_os = "windows")]
|
||||
fn keys(action: &dyn Action, cx: &mut VisualTestContext) -> String {
|
||||
cx.update(|window, _| {
|
||||
let binding = window
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
//! Copy as Markdown and paste as plain text, as Typora's Edit menu.
|
||||
//! Typora's Copy as Markdown and Paste as Plain Text.
|
||||
|
||||
use ely_gpui_component::primitives::Severity;
|
||||
use ely_office::prelude::*;
|
||||
use ely_ui::{commands, i18n::text};
|
||||
use gpui::{App, ClipboardItem, Context, InteractiveElement as _, KeyBinding, actions};
|
||||
use gpui::{
|
||||
App, ClipboardItem, Context, Focusable as _, InteractiveElement as _, KeyBinding, Window,
|
||||
actions,
|
||||
};
|
||||
|
||||
use crate::desk::Desk;
|
||||
|
||||
@@ -18,8 +21,31 @@ pub fn bind(cx: &mut App) {
|
||||
}
|
||||
|
||||
impl Desk {
|
||||
/// Whether the document or nothing else holds focus.
|
||||
fn on_document(&self, window: &Window, cx: &App) -> bool {
|
||||
let Some(focused) = window.focused(cx) else {
|
||||
return true;
|
||||
};
|
||||
let held = (self.active_view()).is_some_and(|view| {
|
||||
let document = view.read(cx).document().read(cx);
|
||||
document.focus_handle(cx).contains_focused(window, cx)
|
||||
});
|
||||
held || focused == self.core.focus
|
||||
}
|
||||
|
||||
/// Hands a field outside the document its own `name` action.
|
||||
fn to_field(name: &str, window: &mut Window, cx: &mut App) {
|
||||
match cx.build_action(name, None) {
|
||||
Ok(action) => window.dispatch_action(action, cx),
|
||||
Err(error) => log::error!("mark: no action {name}: {error}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The selected Markdown, or the whole text, as written.
|
||||
fn copy_markdown(&mut self, cx: &mut Context<Self>) {
|
||||
fn copy_markdown(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if !self.on_document(window, cx) {
|
||||
return Self::to_field("ely_input::Copy", window, cx);
|
||||
}
|
||||
let Some(view) = self.active_view() else {
|
||||
return self.no_document(cx);
|
||||
};
|
||||
@@ -34,7 +60,10 @@ impl Desk {
|
||||
}
|
||||
|
||||
/// The clipboard's text at the caret, pictures left out.
|
||||
fn paste_plain(&mut self, cx: &mut Context<Self>) {
|
||||
fn paste_plain(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if !self.on_document(window, cx) {
|
||||
return Self::to_field("ely_input::Paste", window, cx);
|
||||
}
|
||||
let Some(view) = self.active_view() else {
|
||||
return self.no_document(cx);
|
||||
};
|
||||
@@ -50,7 +79,9 @@ impl Desk {
|
||||
}
|
||||
|
||||
pub fn clip_actions(root: gpui::Div, cx: &mut Context<Self>) -> gpui::Div {
|
||||
root.on_action(cx.listener(|desk, _: &CopyMarkdown, _, cx| desk.copy_markdown(cx)))
|
||||
.on_action(cx.listener(|desk, _: &PastePlain, _, cx| desk.paste_plain(cx)))
|
||||
root.on_action(
|
||||
cx.listener(|desk, _: &CopyMarkdown, window, cx| desk.copy_markdown(window, cx)),
|
||||
)
|
||||
.on_action(cx.listener(|desk, _: &PastePlain, window, cx| desk.paste_plain(window, cx)))
|
||||
}
|
||||
}
|
||||
@@ -176,3 +176,25 @@ fn preview_keeps_its_text_from_tables_and_replacement(cx: &mut TestAppContext) {
|
||||
replace_one(&desk, "x", cx);
|
||||
assert_eq!(current_text(&desk, cx), "a\n");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn clipboard_keys_in_find_reach_the_field(cx: &mut TestAppContext) {
|
||||
let (desk, cx, _files) = opened("alpha beta\n", cx);
|
||||
query(&desk, "beta", cx);
|
||||
let field = desk.read_with(cx, |desk, _| {
|
||||
desk.finding.as_ref().expect("open").query.clone()
|
||||
});
|
||||
field.update(cx, |field, cx| field.select(0..4, cx));
|
||||
cx.simulate_keystrokes("secondary-shift-c");
|
||||
cx.run_until_parked();
|
||||
let copied = cx.read_from_clipboard().and_then(|item| item.text());
|
||||
assert_eq!(copied.as_deref(), Some("beta"));
|
||||
cx.write_to_clipboard(gpui::ClipboardItem::new_string("alpha".into()));
|
||||
cx.simulate_keystrokes("secondary-shift-v");
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
field.read_with(cx, |field, _| field.text().to_string()),
|
||||
"alpha"
|
||||
);
|
||||
assert_eq!(current_text(&desk, cx), "alpha beta\n");
|
||||
}
|
||||
@@ -18,9 +18,9 @@ pub fn bind(cx: &mut App) {
|
||||
KeyBinding::new("secondary-/", ToggleSource, Some("ElyEditor")),
|
||||
KeyBinding::new("f8", crate::look::FocusMode, at),
|
||||
KeyBinding::new("f9", crate::look::Typewriter, at),
|
||||
KeyBinding::new(&shifted('=', '+'), crate::look::Larger, at),
|
||||
KeyBinding::new(&shifted('-', '_'), crate::look::Smaller, at),
|
||||
KeyBinding::new(&shifted('0', ')'), crate::look::ActualSize, at),
|
||||
KeyBinding::new(&shifted("secondary", '=', '+'), crate::look::Larger, at),
|
||||
KeyBinding::new(&shifted("secondary", '-', '_'), crate::look::Smaller, at),
|
||||
KeyBinding::new(&shifted("secondary", '0', ')'), crate::look::ActualSize, at),
|
||||
KeyBinding::new("secondary-f", ely_office::FindReplace, at),
|
||||
]);
|
||||
crate::blocks::bind(cx);
|
||||
|
||||
@@ -20,7 +20,8 @@ actions!(
|
||||
MoveColumnRight,
|
||||
DeleteRow,
|
||||
DeleteColumn,
|
||||
TidyTable
|
||||
TidyTable,
|
||||
DeleteTable
|
||||
]
|
||||
);
|
||||
|
||||
@@ -60,6 +61,8 @@ pub fn table_menu(cx: &App) -> Menu {
|
||||
item("mark.table.delete_column", Box::new(DeleteColumn)),
|
||||
MenuItem::separator(),
|
||||
item("mark.table.tidy", Box::new(TidyTable)),
|
||||
MenuItem::separator(),
|
||||
item("mark.table.delete", Box::new(DeleteTable)),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -79,6 +82,7 @@ impl Desk {
|
||||
DeleteRow => |d, cx| d.edit_table(T::DeleteRow, cx),
|
||||
DeleteColumn => |d, cx| d.edit_table(T::DeleteColumn, cx),
|
||||
TidyTable => |d, cx| d.edit_table(T::Tidy, cx),
|
||||
DeleteTable => |d, cx| d.edit_table(T::Delete, cx),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ pub(super) const EN: &[(&str, &str)] = &[
|
||||
("mark.table.column_right", "Add Column After"),
|
||||
("mark.table.delete_row", "Delete Row"),
|
||||
("mark.table.delete_column", "Delete Column"),
|
||||
("mark.table.delete", "Delete Table"),
|
||||
("mark.table.none", "Put the caret in a table first"),
|
||||
("mark.table.kept", "A table keeps its head row and one column"),
|
||||
("mark.table.beyond", "This cell is past the head's columns"),
|
||||
@@ -119,6 +120,7 @@ pub(super) const ZH_CN: &[(&str, &str)] = &[
|
||||
("mark.table.column_right", "右侧插入列"),
|
||||
("mark.table.delete_row", "删除行"),
|
||||
("mark.table.delete_column", "删除列"),
|
||||
("mark.table.delete", "删除表格"),
|
||||
("mark.table.none", "请先把插入点放进表格"),
|
||||
("mark.table.kept", "表格保留表头行和至少一列"),
|
||||
("mark.table.beyond", "此单元格在表头的列之外"),
|
||||
|
||||
@@ -36,7 +36,7 @@ fetch() { # name url rev [sparse paths...]
|
||||
echo "fetch $name $rev"
|
||||
}
|
||||
|
||||
fetch Ely-GPUI-Components https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components.git 6b033f3eb05af53c3dd855057608e9e0cd7c8f4b
|
||||
fetch Ely-GPUI-Components https://git.zacharyzhang.com/ZacharyZhang-NY/Ely-GPUI-Components.git 24ffc45c6bdb89adee82149873a6413625cb73da
|
||||
fetch zed https://github.com/zed-industries/zed.git 1a28cff4b409169bac058bca40dfbfeb7621d19b
|
||||
fetch libreoffice-core https://github.com/LibreOffice/core.git 8221e31b3ac356a1623c672912a3d2b492f7e3d1 \
|
||||
include/LibreOfficeKit libreofficekit desktop/source/lib desktop/inc \
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
FAIL
|
||||
|
||||
1. `apps/ely-mark/src/blocks.rs:126` — The new menus do not execute while a visual block is being edited. On Windows and Linux, open `alpha beta`, select `alpha` in its block, then choose Format → Strong: the text remains unchanged. Copy as Markdown from the Edit menu likewise leaves the clipboard unchanged. Opening MenuBar blurs and commits the block, discarding its selection and input entity; closing the menu restores that removed input's focus, so action dispatch never reaches Desk. The same menu operation succeeds in Source mode, and the shortcut succeeds in the open visual block. Preserve the document selection and a live dispatch target across the shared menu/document focus handoff, and test actual menu interaction rather than only menu models and root-focused shortcuts. Evidence: `.work/R4.9-r1/full-menu.log`, `probes.log`, `linux-probes.log`, and `probe-source.log`.
|
||||
|
||||
2. `apps/ely-mark/src/clip.rs:16` — Paste as Plain Text is bound throughout the `office` context, but its handler always inserts into the active document, regardless of the focused input. Open `alpha beta`, press Ctrl+F, put `query` on the clipboard, and press Ctrl+Shift+V in the Find field: the document becomes `queryalpha beta`, while the search field receives nothing. This reproduces on Windows and Linux. Scope document paste to document focus and let other focused text fields receive their own paste; cover Find and dialog inputs. Evidence: `plain_paste_in_find_does_not_edit_document` in `.work/R4.9-r1/probes.log` and `linux-probes.log`.
|
||||
|
||||
3. `apps/ely-mark/src/blocks.rs:82` — Strike skips the platform-specific punctuation handling used by the other bindings. On a US Linux layout, GPUI converts Alt+Shift+5 to `alt-%` and clears Shift for the symbol; the registered `alt-shift-5` cannot match it. Sending that normalized event leaves `alpha` unchanged instead of producing `~~alpha~~`. The existing test passes only because it injects the Windows-shaped event. Bind Strike using each platform's actual punctuation representation and test the normalized event. Evidence: `.work/R4.9-r1/linux-probes.log` (`native_linux_strike_key`; the adjacent list/zoom controls pass), and the pinned GPUI Linux `keystroke_from_xkb` implementation.
|
||||
|
||||
4. `apps/ely-mark/src/blocks/tests.rs:81` — The new test module breaks the non-Windows gates. On Linux, `paragraph_keys_retype_the_caret_block` fails at line 82 because `ctrl-shift-[` does not match `ctrl-{`; `view_keys_follow_typora` fails at line 144 because `ctrl-shift-=` does not match `ctrl-+`. The literal `ctrl` chords also ignore macOS's `secondary` mapping. Separately, the unconditional `keys` helper at line 251 has only a Windows-gated caller, so Linux Clippy with `-D warnings` fails on dead code. Make the interaction events platform-correct and compile the helper on the same platforms as its callers. Evidence: `.work/R4.9-r1/linux-tests.log` (6 passed, 2 failed) and `linux-clippy.log`.
|
||||
|
||||
5. `apps/ely-mark/src/tools.rs:62` — Paragraph → Table ends at Prettify Source Code and omits Typora's Delete Table item. This is not covered by the decision to omit unsupported component operations: the pinned component's public `block(BlockCommand::Delete)` already removes the table at the caret. A probe with a table between `before` and `after` successfully removes just that table. Users currently cannot invoke this supported Typora menu operation, contrary to MARK-15's matching-items requirement. Add the table-specific action with an outside-table refusal, using the existing block command, and cover its menu dispatch and undo. Evidence: `.work/R4.9/typora-main-menus.txt` and `component_can_delete_table` in `.work/R4.9-r1/linux-probes.log`.
|
||||
|
||||
6. `apps/ely-mark/src/clip.rs:1` — This new comment has 12 words; the new comment at `apps/ely-mark/src/blocks/tests.rs:161` has 11. Both exceed the explicit ten-word limit, and `python tests/scripts/comments.py` fails on exactly these two lines. Shorten or remove them. Evidence: `.work/R4.9-r1/comments.log`.
|
||||
Reference in new issue
Block a user