R6.13 round 1 fix: notches per platform, the worker decides arrows

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-15 20:22:23 -04:00
1 parent c2897aa21a
commit d427d05b96
15 files changed
+196 -73

No files matched your search

+1 -1
View File
@@ -305,4 +305,4 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely
- 2026-10-15(用户报:ElyMark 源码模式“测”后的插入点画在“测”的中间):组件库 `CodeEditor` 原按“列 × m 的宽”放插入点、选区、按下处与输入法框,中日韩字与表情约宽两格。组件库 9fb607b 起按 gpui 排好的这一行定位(`editor::measure`,含着色带来的字重),块状与下划线插入点取所在字的宽;标尺与缩进参考线仍按列(R6.8)。上下移动仍按字数记目标列。cd61653:按下处取最近的字首或行末(gpui 的 `closest_index_for_x` 越过末字字首即给行末);上次绘制得出的括号与配对位置只属当时的文字(`Buffer` 每次改动换一个戳),输入法改字后、下次绘制前的测量不用它们(原先把新字切进不同字体段,DirectWrite 崩溃)。
- 2026-10-15(用户报:ElyMark 三、四级标题不显眼):组件库 5954132 起标题字号随正文按 Typora 的倍数(一至六级 2.25、1.75、1.5、1.25、1、1),一律加粗,六级为次要色,行高为字号的 1.3 倍(原为固定的 24、20、16、14 像素,正文 13,三、四级只是中等字重)(R6.9)。90bd37b:粗体与标题同一字重(原粗体为半粗,标题里的粗体反而更细,正文粗体随之由半粗改为粗,同 Typora);标题行高按自身字号取 1.3 倍。Windows 真窗口核对(`.work/r69`):可视模式各级标题分明、“你好↵测试”两行,源码模式“测”后的插入点落在两字之间。
- 2026-10-15(用户报:macOS 打开一个文档,程序坞里有两个同样的图标):LOK worker 在应用包的 `Contents/MacOS` 里启动 AppKit,系统按包的 Info.plist 把它也登记为常规应用(`lsappinfo` 里应用与 worker 都是 Foreground),程序坞多一个图标;内核自己从不设激活策略。worker 在内核启动 AppKit 之前(`Lok::hook` 打开内核之后、调钩子之前)把本进程主包信息里的 `LSUIElement` 设为真,AppKit 登记时即为辅助应用(无程序坞图标、无菜单栏,仍可建屏外窗口);钩子之后核对激活策略是辅助,不是即报错、不启动(`ffi/accessory.rs`);包外的 worker(`cargo run`、单测)无包可登记,策略为禁止,此时改设辅助(R6.12 第 2 轮)。原先在登记之后才改策略,审查实测 15 次启动有 11 次先登记为 Foreground 约 30 毫秒(R6.12 第 1 轮)。`tests/scripts/dock-macos.sh` 连续启动三个应用与包外的开发构建,从 worker 启动起采样其类型到首块图块,见 Foreground 即失败(R6.12)。
- 2026-10-15(用户报:ElyShow 编辑区滚轮与上下键不换页,三端):内核在未选中对象时把上下键当作滚动窗口(`fupoor.cxx`),Ely 的画布整页适合、无处可滚,滚轮同样。`LokView::turn_parts`(ElyShow 首次适合时调用)之后:整页可见时滚轮换页(`view/turn.rs`:每格一页;触控板一次手势只换一页,惯性不算,静止 250 毫秒或新手势再计),未选中对象、未编辑文字时上下键换页;视图发 `Turn`,ElyShow 从视图所示的页起算(连续换页累加),经缩略图条同一路径 `show_part`,到首末页即停;后台读回的当前页只在视图已无待落地输入时采用,排队的换页不被先前的读取拉回(R6.13)。
- 2026-10-15(用户报:ElyShow 编辑区滚轮与上下键不换页,三端):内核在未选中对象时把上下键当作滚动窗口(`fupoor.cxx`),Ely 的画布整页适合、无处可滚,滚轮同样。`LokView::turn_parts`(ElyShow 首次适合时调用)之后:整页可见时滚轮换页(`view/turn.rs`):滚轮按本平台每格的行数(`ely_lok::notch`:macOS 1,Windows 取系统设置,X11 与 Wayland 为 3)计格,每格一页;不足一格的行(X11 与 Windows 的触控板)与像素(macOS、Wayland 的触控板)一次手势只换一页,惯性不算;有起止的手势(macOS)只到结束才算完,没有的静止 250 毫秒再计。上下键经有序输入队列交 worker(`TURN_KEY`),worker 按之前各输入回报后的状态判定:无选区、无选中对象、无插入点即换页,否则照常交内核(Tab 之后紧接的 Down 移动所选对象,不换页);视图发 `Turn`,ElyShow 从视图所示的页起算(连续换页累加),经缩略图条同一路径 `show_part`,到首末页即停;后台读回的当前页只在视图已无待落地输入时采用,排队的换页不被先前的读取拉回(R6.13)。
+8 -3
View File
@@ -2,7 +2,7 @@ use ely_lok::LokView;
use ely_office::prelude::*;
use gpui::{Entity, ScrollDelta, ScrollWheelEvent, TestAppContext, VisualTestContext, point};
use super::objects::blank;
use super::objects::{blank, selection};
use super::{counts, quiet, settle, update};
use crate::deck::Deck;
@@ -22,9 +22,10 @@ fn current(shown: &mut VisualTestContext, deck: &Entity<Deck>) -> usize {
shown.update(|_, app| deck.read(app).active_tab().map(|tab| tab.extra.slides.current).expect("a tab"))
}
fn wheel(shown: &mut VisualTestContext, lines: f32) {
/// Whole notches, in this platform's lines.
fn wheel(shown: &mut VisualTestContext, notches: f32) {
let middle = shown.update(|window, _| window.viewport_size()).center();
let delta = ScrollDelta::Lines(point(0.0, lines));
let delta = ScrollDelta::Lines(point(0.0, notches * ely_lok::notch()));
shown.simulate_event(ScrollWheelEvent { position: middle.into(), delta, ..Default::default() });
}
@@ -59,4 +60,8 @@ fn the_wheel_and_up_down_turn_slides() {
assert_eq!(current(&mut shown, &deck), 1, "Up turns back");
wheel(&mut shown, 1.0);
assert_eq!(current(&mut shown, &deck), 0, "the wheel turns up");
// Down queued behind Tab moves the object Tab picks.
shown.simulate_keystrokes("tab down");
assert_eq!(current(&mut shown, &deck), 0, "Down after Tab moves, not turns");
assert!(shown.update(|_, app| selection(app, &deck)).is_some(), "Tab picked an object");
}
+8
View File
@@ -237,6 +237,14 @@ impl Worker {
Ok(())
}
/// Up or Down: whether it turned, not reaching the kernel.
pub fn turn_key(&mut self, doc: &Doc, char_code: u32, key_code: i32) -> io::Result<bool> {
let doc_id = self.doc_id(doc)?;
let payload = Pack::new().u64(doc_id).i64(char_code as i64).i64(key_code as i64).bytes();
let reply = self.request(tag::TURN_KEY, &payload)?;
Ok(Unpack::new(&reply).u64()? == 1)
}
/// Drags a selected object's handle, start then end.
pub fn drag_graphic(&mut self, doc: &Doc, from: (i32, i32), to: (i32, i32)) -> io::Result<()> {
for (kind, (x, y)) in [(0, from), (1, to)] {
+9 -11
View File
@@ -362,11 +362,14 @@ pub fn stop_all(docs: &HashMap<u64, Document>) {
/// The current generation of `id`.
pub fn generation(id: u64) -> u64 {
slots()
.lock()
.expect("the events lock")
.get(&id)
.map_or(0, |slot| slot.batch.generation)
slots().lock().expect("the events lock").get(&id).map_or(0, |slot| slot.batch.generation)
}
/// Whether `id` shows a selection, object or caret.
pub fn picked(id: u64) -> bool {
let slots = slots().lock().expect("the events lock");
let Some(batch) = slots.get(&id).map(|slot| &slot.batch) else { return false };
batch.graphic.is_some() || !batch.selection.is_empty() || batch.cursor_visible && batch.cursor.is_some()
}
/// Blocks until documents have unpushed events.
@@ -399,12 +402,7 @@ pub fn clear_result(id: u64) {
/// The completion the last dispatch reported.
pub fn take_result(id: u64) -> Option<(String, bool)> {
slots()
.lock()
.expect("the events lock")
.get_mut(&id)?
.result
.take()
slots().lock().expect("the events lock").get_mut(&id)?.result.take()
}
/// The bridged a11y text now.
+2
View File
@@ -88,6 +88,8 @@ pub mod tag {
pub const REVISION: u8 = 34;
/// One layer's pixels from the last `SLIDE_LAYERS`.
pub const SLIDE_LAYER: u8 = 35;
/// Up or Down: turns the part when nothing is picked.
pub const TURN_KEY: u8 = 36;
pub const OK: u8 = 100;
pub const ERR: u8 = 101;
}
+1 -1
View File
@@ -20,7 +20,7 @@ pub use client::{
};
#[cfg(feature = "view")]
pub use view::{
CONTEXT, Copy, Cut, Geom, Layout, LokError, LokView, Paste, Reported, Turn, ZOOM_STEPS,
CONTEXT, Copy, Cut, Geom, Layout, LokError, LokView, Paste, Reported, Turn, ZOOM_STEPS, notch,
Zoomed, init, layout, paper, read_off, scheme,
};
#[cfg(feature = "view")]
+1 -1
View File
@@ -34,7 +34,7 @@ pub use input::{CONTEXT, Copy, Cut, Paste, init};
pub use off::read_off;
pub use render::{paper, scheme};
mod turn;
pub use turn::Turn;
pub use turn::{Turn, notch};
pub(crate) use crate::{TILE_TWIPS, TWIPS_PER_PIXEL};
+22 -6
View File
@@ -64,6 +64,18 @@ pub(super) enum Input {
Visible(Rect, (u32, i32)),
/// Shows sheet or slide `part`, from 0.
Part(usize),
/// Up or Down; turns `step` parts when nothing is picked.
Turn {
char_code: u32,
key_code: i32,
step: i32,
},
}
/// What an input gives back.
pub(super) enum Back {
Copied(String),
Turned(i32),
}
impl Input {
@@ -73,6 +85,7 @@ impl Input {
// Arrows, Home, End, Page keys, Escape.
Input::Key { key_code, .. } => !matches!(key_code & 0x0FFF, 1024..=1031 | 1281),
Input::Mouse(_)
| Input::Turn { .. }
| Input::Copy { cut: false }
| Input::Visible(..)
| Input::Part(_)
@@ -169,7 +182,7 @@ fn lok_button(button: MouseButton) -> Option<i32> {
}
/// Runs one input; gives a copy's mark and the revision.
pub(super) fn run(worker: &Mutex<Worker>, doc: &Doc, input: Input) -> (io::Result<Option<String>>, Option<u64>) {
pub(super) fn run(worker: &Mutex<Worker>, doc: &Doc, input: Input) -> (io::Result<Option<Back>>, Option<u64>) {
let mut worker = worker.lock().expect("the worker lock");
let counts = !matches!(input, Input::Visible(..));
let copied = apply(&mut worker, doc, input);
@@ -181,12 +194,16 @@ pub(super) fn run(worker: &Mutex<Worker>, doc: &Doc, input: Input) -> (io::Resul
(copied, now)
}
fn apply(worker: &mut Worker, doc: &Doc, input: Input) -> io::Result<Option<String>> {
fn apply(worker: &mut Worker, doc: &Doc, input: Input) -> io::Result<Option<Back>> {
let done = match input {
Input::Key {
char_code,
key_code,
} => worker.post_key(doc, char_code, key_code),
Input::Turn { char_code, key_code, step } => {
let turned = worker.turn_key(doc, char_code, key_code)?;
return Ok(turned.then_some(Back::Turned(step)));
}
Input::Mouse(event) => worker.post_mouse(doc, event),
Input::Drag { from, to } => worker.drag_graphic(doc, from, to),
Input::Visible(area, scale) => doc.visible_area(worker, area, scale),
@@ -195,9 +212,8 @@ fn apply(worker: &mut Worker, doc: &Doc, input: Input) -> io::Result<Option<Stri
Input::Theme(scheme) => worker.run(doc, "view.theme", &[scheme]),
Input::Text { commit, text } => worker.post_text(doc, commit, &text),
Input::Copy { cut } => {
return clip::copy_out(worker, doc, cut, |clip| {
clipboard::write(clip).map_err(io::Error::other)
});
let copied = clip::copy_out(worker, doc, cut, |clip| clipboard::write(clip).map_err(io::Error::other));
return copied.map(|copied| copied.map(Back::Copied));
}
Input::Run { commands, at } => {
let values: Vec<Vec<&str>> =
@@ -299,7 +315,7 @@ impl LokView {
cx: &mut Context<Self>,
) {
if self.walk(&event.keystroke, cx)
|| self.turn_key(&event.keystroke, cx)
|| self.turn_key(&event.keystroke)
|| self.key_down(&event.keystroke)
{
cx.stop_propagation();
+5 -3
View File
@@ -9,7 +9,8 @@ use std::{
use futures::{StreamExt, channel::mpsc, select_biased};
use gpui::Context;
use super::{Geom, LokError, LokView, Reported, TILE_TWIPS, input::Input};
use super::input::{Back, Input};
use super::{Geom, LokError, LokView, Reported, TILE_TWIPS, Turn};
use crate::{Doc, Tile, Worker};
/// An input slower than this is logged as a warning.
@@ -24,7 +25,7 @@ pub(super) struct Job {
/// What came back from the lane.
enum Landed {
/// Sequence, whether it edits, whether it switched part, outcome.
Input(u64, bool, bool, (io::Result<Option<String>>, Option<u64>)),
Input(u64, bool, bool, (io::Result<Option<Back>>, Option<u64>)),
Tile(Job, io::Result<Tile>),
}
@@ -111,7 +112,8 @@ impl LokView {
}
self.seen_revision(now.unwrap_or(0));
match outcome {
Ok(Some(copied)) => self.copied = Some(copied),
Ok(Some(Back::Copied(copied))) => self.copied = Some(copied),
Ok(Some(Back::Turned(step))) => cx.emit(Turn(step)),
Ok(None) => {}
Err(error) => {
log::error!("lok: input failed ({:?})", error.kind());
+113 -45
View File
@@ -5,6 +5,7 @@ use std::time::{Duration, Instant};
use gpui::{Context, EventEmitter, Keystroke, Modifiers, ScrollDelta, ScrollWheelEvent, TouchPhase};
use super::LokView;
use super::input::{Input, vcl_key};
/// Asks for the part `step` away from the shown one.
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -14,53 +15,90 @@ impl EventEmitter<Turn> for LokView {}
/// Trackpad pixels one turn takes.
const SWIPE: f32 = 50.0;
/// A pause this long ends a trackpad gesture.
/// A pause this long ends a gesture without phases.
const PAUSE: Duration = Duration::from_millis(250);
/// Lines one wheel notch scrolls on this platform.
pub fn notch() -> f32 {
#[cfg(windows)]
{
use windows_sys::Win32::UI::WindowsAndMessaging::{SPI_GETWHEELSCROLLLINES, SystemParametersInfoW};
let mut lines = 3u32;
// Same setting gpui scales the wheel by.
unsafe { SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, (&raw mut lines).cast(), 0) };
lines as f32
}
#[cfg(target_os = "macos")]
{
1.0
}
// gpui's X11 and Wayland scroll three lines a notch.
#[cfg(not(any(windows, target_os = "macos")))]
{
3.0
}
}
/// The wheel's progress towards a turn.
#[derive(Default)]
pub(super) struct Turning {
lines: f32,
pixels: f32,
/// This gesture's travel, in turns.
travel: f32,
/// This gesture already turned.
spent: bool,
/// The platform began a gesture it has not ended.
held: bool,
/// Fractional lines: a smooth touchpad, not a wheel.
smooth: bool,
last: Option<Instant>,
}
impl Turning {
/// The turn a wheel event makes; 0 for none.
pub(super) fn wheel(&mut self, delta: ScrollDelta, phase: TouchPhase, now: Instant) -> i32 {
pub(super) fn wheel(&mut self, delta: ScrollDelta, phase: TouchPhase, now: Instant, notch: f32) -> i32 {
let paused = self.last.is_none_or(|last| now.duration_since(last) >= PAUSE);
self.last = Some(now);
let ahead = match delta {
// Each notch turns once.
let travel = match delta {
ScrollDelta::Lines(_) if notch <= 0.0 => return 0,
ScrollDelta::Lines(lines) => {
if paused {
self.lines = 0.0;
let notches = lines.y / notch;
let whole = notches.round();
// Whole notches outside a smooth stream: each turns once.
if whole != 0.0 && (notches - whole).abs() < 1e-3 && (paused || !self.smooth) {
(self.travel, self.spent, self.smooth) = (0.0, false, false);
return -(whole as i32);
}
self.lines += lines.y;
let whole = self.lines.trunc();
self.lines -= whole;
whole
if paused || !self.smooth {
(self.travel, self.spent) = (0.0, false);
}
self.smooth = true;
notches
}
// One trackpad gesture turns once, momentum included.
ScrollDelta::Pixels(pixels) => {
if paused || phase == TouchPhase::Started {
(self.pixels, self.spent) = (0.0, false);
self.smooth = false;
// Without phases, only a pause ends a gesture.
if phase == TouchPhase::Started || paused && !self.held {
(self.travel, self.spent) = (0.0, false);
}
if self.spent {
return 0;
}
self.pixels += f32::from(pixels.y);
if self.pixels.abs() < SWIPE {
return 0;
}
self.spent = true;
self.pixels.signum()
self.held = match phase {
TouchPhase::Started => true,
TouchPhase::Ended | TouchPhase::Cancelled => false,
TouchPhase::Moved => self.held,
};
f32::from(pixels.y) / SWIPE
}
};
// One gesture turns once, momentum included.
if self.spent {
return 0;
}
self.travel += travel;
if self.travel.abs() < 1.0 {
return 0;
}
self.spent = true;
// Content moving down shows the part before.
-(ahead as i32)
-(self.travel.signum() as i32)
}
}
@@ -75,10 +113,9 @@ impl LokView {
self.part
}
/// Up and Down turn while nothing is picked or edited.
pub(super) fn turn_key(&mut self, keystroke: &Keystroke, cx: &mut Context<Self>) -> bool {
let idle = !self.caret() && self.graphic.is_none() && self.selection.is_empty();
if self.turning.is_none() || !idle || keystroke.modifiers != Modifiers::none() {
/// Queues Up, Down; the worker turns if nothing is picked.
pub(super) fn turn_key(&mut self, keystroke: &Keystroke) -> bool {
if self.turning.is_none() || keystroke.modifiers != Modifiers::none() {
return false;
}
let step = match keystroke.key.as_str() {
@@ -86,7 +123,10 @@ impl LokView {
"up" => -1,
_ => return false,
};
cx.emit(Turn(step));
let Some((char_code, key_code)) = vcl_key(keystroke) else {
return false;
};
self.send(Input::Turn { char_code, key_code, step });
true
}
@@ -99,7 +139,7 @@ impl LokView {
let Some(turning) = self.turning.as_mut().filter(|_| whole) else {
return false;
};
let step = turning.wheel(event.delta, event.touch_phase, Instant::now());
let step = turning.wheel(event.delta, event.touch_phase, Instant::now(), notch());
if step != 0 {
cx.emit(Turn(step));
}
@@ -113,30 +153,58 @@ mod tests {
use super::{PAUSE, Turning};
const NOTCH: f32 = 3.0;
fn lines(y: f32) -> ScrollDelta {
ScrollDelta::Lines(point(0.0, y))
}
fn pixels(y: f32) -> ScrollDelta {
ScrollDelta::Pixels(point(px(0.0), px(y)))
}
#[test]
fn each_notch_turns_once() {
let (mut turning, now) = (Turning::default(), std::time::Instant::now());
let notch = |y| ScrollDelta::Lines(point(0.0, y));
assert_eq!(turning.wheel(notch(-1.0), TouchPhase::Moved, now), 1);
assert_eq!(turning.wheel(notch(-1.0), TouchPhase::Moved, now), 1);
assert_eq!(turning.wheel(notch(1.0), TouchPhase::Moved, now), -1);
assert_eq!(turning.wheel(notch(0.5), TouchPhase::Moved, now), 0);
assert_eq!(turning.wheel(notch(0.5), TouchPhase::Moved, now), -1);
let mut wheel = |y| turning.wheel(lines(y), TouchPhase::Moved, now, NOTCH);
assert_eq!(wheel(-3.0), 1, "Windows and Linux scroll three lines a notch");
assert_eq!(wheel(-3.0), 1);
assert_eq!(wheel(3.0), -1);
assert_eq!(wheel(-6.0), 2, "two notches in one event");
let mut mac = Turning::default();
assert_eq!(mac.wheel(lines(-1.0), TouchPhase::Moved, now, 1.0), 1);
}
#[test]
fn smooth_lines_turn_once_a_gesture() {
let (mut turning, now) = (Turning::default(), std::time::Instant::now());
// An X11 or Windows touchpad: fractions, no phases.
let turned: i32 = (0..30).map(|_| turning.wheel(lines(-0.4), TouchPhase::Moved, now, NOTCH)).sum();
assert_eq!(turned, 1, "one swipe turns once");
assert_eq!(turning.wheel(lines(-3.0), TouchPhase::Moved, now, NOTCH), 0, "a whole step mid-swipe");
let later = now + PAUSE;
assert_eq!(turning.wheel(lines(-3.0), TouchPhase::Moved, later, NOTCH), 1, "a pause ends the swipe");
}
#[test]
fn a_swipe_turns_once_until_the_next() {
let (mut turning, now) = (Turning::default(), std::time::Instant::now());
let swipe = |y| ScrollDelta::Pixels(point(px(0.0), px(y)));
assert_eq!(turning.wheel(swipe(-30.0), TouchPhase::Started, now), 0);
assert_eq!(turning.wheel(swipe(-30.0), TouchPhase::Moved, now), 1);
let mut swipe = |y, phase, at| turning.wheel(pixels(y), phase, at, NOTCH);
assert_eq!(swipe(-30.0, TouchPhase::Started, now), 0);
assert_eq!(swipe(-30.0, TouchPhase::Moved, now), 1);
// A pause with fingers down is the same gesture.
assert_eq!(swipe(-60.0, TouchPhase::Moved, now + PAUSE * 2), 0);
assert_eq!(swipe(0.0, TouchPhase::Ended, now + PAUSE * 2), 0);
// Momentum after the turn turns nothing more.
for _ in 0..20 {
assert_eq!(turning.wheel(swipe(-80.0), TouchPhase::Moved, now), 0);
assert_eq!(swipe(-80.0, TouchPhase::Moved, now + PAUSE * 2), 0);
}
assert_eq!(turning.wheel(swipe(60.0), TouchPhase::Started, now), -1);
// A pause ends a gesture without phases.
let later = now + PAUSE;
assert_eq!(turning.wheel(swipe(60.0), TouchPhase::Moved, later), -1);
assert_eq!(swipe(60.0, TouchPhase::Started, now + PAUSE * 2), -1);
assert_eq!(swipe(0.0, TouchPhase::Ended, now + PAUSE * 2), 0);
// Without phases a pause ends a gesture (Wayland).
let later = now + PAUSE * 4;
assert_eq!(swipe(60.0, TouchPhase::Moved, later), -1);
assert_eq!(swipe(60.0, TouchPhase::Moved, later), 0);
assert_eq!(swipe(60.0, TouchPhase::Moved, later + PAUSE), -1);
}
}
+1
View File
@@ -218,6 +218,7 @@ fn answer(lok: &Lok, tag: u8, id: u64, payload: &[u8]) {
}
}),
tag::POST_KEY => input::key(unpack),
tag::TURN_KEY => input::turn_key(unpack),
tag::POST_MOUSE => input::mouse(unpack),
tag::GRAPHIC => input::graphic(unpack),
tag::POST_TEXT => input::text(unpack),
+14
View File
@@ -99,6 +99,20 @@ pub(super) fn key(unpack: &mut Unpack) -> io::Result<Vec<u8>> {
Ok(Vec::new())
}
/// Up or Down after earlier input: 1 if it turns.
pub(super) fn turn_key(unpack: &mut Unpack) -> io::Result<Vec<u8>> {
let (id, char_code, key_code) = (unpack.u64()?, unpack.i64()?, unpack.i64()?);
// Earlier input's callbacks already landed here.
if !crate::events::picked(id) {
return Ok(Pack::new().u64(1).bytes());
}
with_doc(id, |doc| {
doc.post_key(0, char_code as i32, key_code as i32);
doc.post_key(1, char_code as i32, key_code as i32);
})?;
Ok(Pack::new().u64(0).bytes())
}
/// One mouse event: kind, x, y, count, buttons, modifiers.
pub(super) fn mouse(unpack: &mut Unpack) -> io::Result<Vec<u8>> {
let id = unpack.u64()?;
+1 -1
View File
@@ -51,7 +51,7 @@ pub fn expect(unpack: &mut Unpack) -> io::Result<Vec<u8>> {
pub fn bump(request: u8, payload: &[u8]) {
let changes = matches!(
request,
tag::POST_KEY | tag::POST_MOUSE | tag::GRAPHIC | tag::POST_TEXT | tag::POST_UNO | tag::PASTE | tag::CUT | tag::COMPLETE_FUNCTION
tag::POST_KEY | tag::TURN_KEY | tag::POST_MOUSE | tag::GRAPHIC | tag::POST_TEXT | tag::POST_UNO | tag::PASTE | tag::CUT | tag::COMPLETE_FUNCTION
);
let mut unpack = Unpack::new(payload);
let Ok(id) = unpack.u64() else {
+1 -1
View File
@@ -271,5 +271,5 @@
- [x] R6.11 Windows 升级后旧文件仍在(R6.10 本机重装查出):安装程序只覆盖不删除,旧版 ElyMark 的 `git` 与 CEF 文件留在安装目录;安装程序记下所装的文件,升级与卸载只移去这些;文件被占用即什么也不改,失败时放回旧版。第 3 轮后(最后一轮,修完不再审查):备份按清单次序编号,近 260 字的路径照样升级与卸载;卸载先查清单本身,删不掉即放回全部
- [x] R6.10 重发 0.1.5(用户定:仍为 0.1.5):删去 v0.1.5 的全部文件,标签移到修复的提交,三端全部包重建并上传,本机重装(标签在 53630ce;推标签即起发布流程,其 Windows 与 Linux 任务建出并上传,与本机同时起的 Windows 构建撞上,本机那份未上传;macOS 任务未上传,dmg 由 Mac mini 本机建、公证、钉票;44 个文件,同名的源码包只留 Linux 的一份,见 P3-16;下载回的 ElyMark 安装程序与清单的散列与构建相同;本机六个应用 0.1.5 经新安装程序升级,ElyMark 目录无 `git`,冒烟启动均正常退出、日志无警告)
- [x] R6.12 macOS 打开一个文档,程序坞里 ElyDoc、ElyTable、ElyShow 各出现两个图标(用户 2026-10-15 报):LOK worker 在应用包内启动 AppKit,系统按包的 Info.plist 把它也登记为常规应用(`lsappinfo` 两个 Foreground);worker 在内核启动 AppKit 前把本进程主包信息的 `LSUIElement` 设为真,登记即为辅助应用,钩子后核对策略(第 1 轮:原先登记后才改,15 次有 11 次先为 Foreground);`tests/scripts/dock-macos.sh` 在 Mac mini 上 30 次启动全为 UIElement,旧 worker 即失败;第 2 轮:包外的 worker(`cargo run`、单测)策略为禁止,原核对即退出,改设辅助,测试另启包外的开发构建(40 次全过);第 3 轮通过
- [ ] R6.13 ElyShow 编辑区的滚轮与上下键不换页(用户报,三端;放映时正常):内核在未选中对象时把上下键当作滚动窗口,Ely 的画布整页适合、无处可滚;整页可见时滚轮换页(每格一页,触控板一次手势一页),未选中对象、未编辑文字时上下键换页,同缩略图条的路径,到首末页即停
- [ ] R6.13 ElyShow 编辑区的滚轮与上下键不换页(用户报,三端;放映时正常):内核在未选中对象时把上下键当作滚动窗口,Ely 的画布整页适合、无处可滚;整页可见时滚轮换页(每格一页,触控板一次手势一页),未选中对象、未编辑文字时上下键换页,同缩略图条的路径,到首末页即停;第 1 轮:Windows 一格三行原换三页,X11 触控板的小数行一次滑动换多页,按住手指停顿后再滑又换一页,Tab 后紧接的 Down 按旧状态换页;改为按平台每格行数计格、小数行与像素按手势计、有起止的手势只到结束才算完、上下键由 worker 按输入次序判定
- [ ] R6.14 重发 0.1.5(用户定:仍为 0.1.5,全部重建并重传):删去 v0.1.5 的全部文件,标签移到 R6.12、R6.13 的提交,三端全部包重建并上传,本机重装
+9
View File
@@ -0,0 +1,9 @@
FAIL
- crates/ely-lok/src/view/turn.rs:41 — Logical scroll lines are counted as physical wheel notches. Windows multiplies each notch by the system scroll-line setting (`vendor/gpui-pre-windows/src/events.rs:589`); with three lines configured, one downward notch produces `Lines(0, -3)`. Replaying that event through ElyShow and the rebuilt LOK worker moved from slide 1 to slide 4, rather than slide 2 (`.work/R6.13-r1/probe-explicit.log`). The pinned Linux X11 backend also uses three lines per notch. Normalize platform wheel units before counting slide changes, and cover realistic platform deltas; the existing tests only exercise unit notches.
- crates/ely-lok/src/view/turn.rs:37 — The line-delta branch never applies the one-turn-per-gesture limit, but the pinned GPUI X11 backend also represents smooth touchpad scrolling as `ScrollDelta::Lines` (`gpui-pre-linux-0.3.8`, `x11/client.rs`, `get_scroll_delta_and_update_state` and `make_scroll_wheel_event`). Replaying ten consecutive `-0.4` line deltas in one UI update through ElyShow/LOK moved from slide 1 to slide 5 instead of slide 2 (`.work/R6.13-r1/probe-extra.log`). Preserve the distinction between discrete wheel steps and smooth XInput gestures in the input adapter, and apply gesture limiting to the latter; clamping each event individually would still turn repeatedly during one swipe.
- crates/ely-lok/src/view/turn.rs:48 — A 250 ms pause resets `spent` even when the platform reports continuation of the same gesture. With a fitted deck, a `Started` event of -60 pixels turns one slide; keeping fingers down, pausing 300 ms, then sending a `Moved` event of -60 pixels turns another. This reproduced through ElyShow/LOK with slide index 2 instead of 1 (`.work/R6.13-r1/probe-explicit.log`). Track explicit gesture lifetime when phases are available; elapsed time must not reopen an explicitly ongoing gesture. Reserve timeout inference for streams without gesture boundaries and test continuation after a pause.
- crates/ely-lok/src/view/turn.rs:80 — The arrow-key decision uses the last selection callback without accounting for earlier queued input. On a title slide, press Tab to select its placeholder and Down before the worker's selection callback arrives: Down emits `Turn`, so the queued Tab selects the object and the following part change leaves the slide instead of moving that object. The integration probe changed slide index 0 to 1; its control case, waiting for Tab's selection before Down, stayed on slide 0 (`.work/R6.13-r1/probe-extra.log`). Resolve the arrow's action against the selection/editing state after preceding inputs and their callbacks have landed, while preserving input order, and add this queued-input regression.