Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion native/deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
version = 2
yanked = "deny"
unmaintained = "workspace"
unsound = "all"
ignore = []

[bans]
Expand Down
109 changes: 87 additions & 22 deletions native/unterm/examples/diff_smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ fn main() {
index.write().unwrap();
let tree = repo.find_tree(index.write_tree().unwrap()).unwrap();
let sig = git2::Signature::now("t", "t@t").unwrap();
repo.commit(Some("HEAD"), &sig, &sig, "init", &tree, &[]).unwrap();
repo.commit(Some("HEAD"), &sig, &sig, "init", &tree, &[])
.unwrap();

let id = unterm_editor_create(900, 500, 2.0);
assert!(id != 0, "editor create failed");
Expand Down Expand Up @@ -85,13 +86,22 @@ fn main() {
// then render — exercises line_at_y / hunk lookup / tooltip overlay without panic.
// scale 2.0 → line_height 40, pad 12: line 1 sits around y≈52..92.
let shown = unterm_editor_hover(id, 6.0, 72.0);
assert!(shown, "hover over a modified line's gutter marker should show the tooltip");
assert!(
shown,
"hover over a modified line's gutter marker should show the tooltip"
);
unterm_editor_render(id);
println!("gutter-marker hover + tooltip render OK");
// Moving off the marker hides the tooltip: the first away-hover returns true (a
// repaint is needed to clear it), and a second one returns false (nothing shown).
assert!(unterm_editor_hover(id, 400.0, 300.0), "away-hover should request a clear repaint");
assert!(!unterm_editor_hover(id, 400.0, 300.0), "tooltip should now be hidden");
assert!(
unterm_editor_hover(id, 400.0, 300.0),
"away-hover should request a clear repaint"
);
assert!(
!unterm_editor_hover(id, 400.0, 300.0),
"tooltip should now be hidden"
);
unterm_editor_render(id);
println!("tooltip hide (hover away) + render OK");

Expand All @@ -101,23 +111,42 @@ fn main() {
unterm_editor_refresh_diff(id);
let deadline = Instant::now() + Duration::from_millis(1500);
while Instant::now() < deadline {
assert!(!unterm_editor_poll_diff(id), "unchanged refresh must not report a change");
assert!(
!unterm_editor_poll_diff(id),
"unchanged refresh must not report a change"
);
std::thread::sleep(Duration::from_millis(10));
}
unterm_editor_render(id);
println!("refresh with unchanged texts is a no-op OK");

// --- hunk_at + STAGE (HEAD base: the marker must SURVIVE staging, hollow) ---
let hi = unterm_editor_hunk_at(id, 6.0, 72.0);
assert!(hi >= 0, "hunk_at should find the modified hunk in the gutter");
assert!(!unterm_editor_hunk_staged(id, hi as u32), "hunk should start unstaged");
assert!(
hi >= 0,
"hunk_at should find the modified hunk in the gutter"
);
assert!(
!unterm_editor_hunk_staged(id, hi as u32),
"hunk should start unstaged"
);
println!("hunk_at found hunk {hi} (unstaged)");
assert!(unterm_editor_stage_hunk(id, hi as u32), "stage_hunk should succeed");
assert!(
unterm_editor_stage_hunk(id, hi as u32),
"stage_hunk should succeed"
);
// Only hunk here == the whole change, so the index blob should now equal the buffer.
let repo2 = git2::Repository::discover(&dir).unwrap();
let entry = repo2.index().unwrap().get_path(Path::new("foo.cs"), 0).unwrap();
let entry = repo2
.index()
.unwrap()
.get_path(Path::new("foo.cs"), 0)
.unwrap();
let staged = String::from_utf8(repo2.find_blob(entry.id).unwrap().content().to_vec()).unwrap();
assert_eq!(staged, "class A {\n int x = 1;\n int z;\n}\n", "index updated by stage_hunk");
assert_eq!(
staged, "class A {\n int x = 1;\n int z;\n}\n",
"index updated by stage_hunk"
);
println!("stage_hunk updated the index OK");

// Pick up the refreshed git texts: the hunk is still there (buffer != HEAD) but
Expand All @@ -126,41 +155,73 @@ fn main() {
unterm_editor_render(id);
let hi2 = unterm_editor_hunk_at(id, 6.0, 72.0);
assert!(hi2 >= 0, "marker must survive staging (HEAD base)");
assert!(unterm_editor_hunk_staged(id, hi2 as u32), "hunk should now read staged");
assert!(
unterm_editor_hunk_staged(id, hi2 as u32),
"hunk should now read staged"
);
println!("marker survives staging and reads staged OK");

// --- UNSTAGE: the index goes back to HEAD, the hunk reads unstaged again ---
assert!(unterm_editor_unstage_hunk(id, hi2 as u32), "unstage_hunk should succeed");
assert!(
unterm_editor_unstage_hunk(id, hi2 as u32),
"unstage_hunk should succeed"
);
let entry = {
let repo3 = git2::Repository::discover(&dir).unwrap();
repo3.index().unwrap().get_path(Path::new("foo.cs"), 0).unwrap()
repo3
.index()
.unwrap()
.get_path(Path::new("foo.cs"), 0)
.unwrap()
};
let repo3 = git2::Repository::discover(&dir).unwrap();
let unstaged = String::from_utf8(repo3.find_blob(entry.id).unwrap().content().to_vec()).unwrap();
assert_eq!(unstaged, "class A {\n int x;\n int y;\n}\n", "index restored to HEAD");
let unstaged =
String::from_utf8(repo3.find_blob(entry.id).unwrap().content().to_vec()).unwrap();
assert_eq!(
unstaged, "class A {\n int x;\n int y;\n}\n",
"index restored to HEAD"
);
wait_diff(id);
unterm_editor_render(id);
let hi3 = unterm_editor_hunk_at(id, 6.0, 72.0);
assert!(hi3 >= 0 && !unterm_editor_hunk_staged(id, hi3 as u32), "hunk reads unstaged again");
assert!(
hi3 >= 0 && !unterm_editor_hunk_staged(id, hi3 as u32),
"hunk reads unstaged again"
);
println!("unstage_hunk restored the index OK");

// --- STAGED-ONLY: stage, then revert the buffer back to HEAD. The change now
// lives only in the index (`git diff --cached` shows it) — the editor must keep
// showing a (hollow, staged) hunk there and allow unstaging it.
assert!(unterm_editor_stage_hunk(id, hi3 as u32), "re-stage should succeed");
assert!(
unterm_editor_stage_hunk(id, hi3 as u32),
"re-stage should succeed"
);
wait_diff(id);
let head_buf = CString::new("class A {\n int x;\n int y;\n}\n").unwrap();
unsafe { unterm_editor_set_text(id, head_buf.as_ptr()) }; // buffer back at HEAD
unterm_editor_render(id);
let so = unterm_editor_hunk_at(id, 6.0, 72.0);
assert!(so >= 0, "staged-only hunk must still show a marker");
assert!(unterm_editor_hunk_staged(id, so as u32), "staged-only hunk reads staged");
assert!(unterm_editor_hover(id, 6.0, 72.0), "staged-only hunk is peekable");
assert!(
unterm_editor_hunk_staged(id, so as u32),
"staged-only hunk reads staged"
);
assert!(
unterm_editor_hover(id, 6.0, 72.0),
"staged-only hunk is peekable"
);
unterm_editor_render(id);
assert!(unterm_editor_unstage_hunk(id, so as u32), "unstage staged-only should succeed");
assert!(
unterm_editor_unstage_hunk(id, so as u32),
"unstage staged-only should succeed"
);
wait_diff(id);
unterm_editor_render(id);
assert!(unterm_editor_hunk_at(id, 6.0, 72.0) < 0, "everything clean → no markers");
assert!(
unterm_editor_hunk_at(id, 6.0, 72.0) < 0,
"everything clean → no markers"
);
println!("staged-only hunk shown, peeked, and unstaged OK");

unterm_editor_destroy(id);
Expand Down Expand Up @@ -189,7 +250,11 @@ fn main() {
let hi2 = unterm_editor_hunk_at(id2, 6.0, 72.0);
assert!(hi2 >= 0, "hunk_at should find the modified hunk (revert)");
unterm_editor_revert_hunk(id2, hi2 as u32);
assert_eq!(editor_text(id2), "class B {\n int p;\n}\n", "revert restored the base content");
assert_eq!(
editor_text(id2),
"class B {\n int p;\n}\n",
"revert restored the base content"
);
println!("revert_hunk restored the base content OK");

// --- pure ADDITION is peekable (VS Code parity: its peek shows the + lines) ---
Expand Down
37 changes: 30 additions & 7 deletions native/unterm/examples/dump_debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ fn main() {
let mut conn = match sdb::connect_editor(&root) {
Ok(c) => c,
Err(e) => {
eprintln!("connect failed: {e} (is the Unity editor open under {}?)", root.display());
eprintln!(
"connect failed: {e} (is the Unity editor open under {}?)",
root.display()
);
std::process::exit(1);
}
};
Expand All @@ -36,7 +39,9 @@ fn main() {

// Arm: subscribe to TYPE_LOAD for the target file. Types already loaded resolve
// immediately; everything else resolves when the play-mode domain loads them.
let watch = conn.watch_source_files(&[file.clone()]).expect("watch source");
let watch = conn
.watch_source_files(&[file.clone()])
.expect("watch source");
println!("watching TYPE_LOAD for {file} (request {watch})");
let mut armed = false;
if let Ok(types) = conn.types_for_source_file(&file, true) {
Expand Down Expand Up @@ -113,7 +118,9 @@ fn try_arm(conn: &mut sdb::Connection, types: &[u32], file: &str, line: i32) ->
let name = conn.method_name(method).unwrap_or_default();
match conn.set_breakpoint(method, il) {
Ok(req) => {
println!("armed breakpoint at {file}:{line} -> {name}+0x{il:x} (request {req})");
println!(
"armed breakpoint at {file}:{line} -> {name}+0x{il:x} (request {req})"
);
true
}
Err(e) => {
Expand All @@ -123,7 +130,10 @@ fn try_arm(conn: &mut sdb::Connection, types: &[u32], file: &str, line: i32) ->
}
}
None => {
println!("could not resolve {file}:{line} in {} method(s) yet", candidates.len());
println!(
"could not resolve {file}:{line} in {} method(s) yet",
candidates.len()
);
false
}
}
Expand Down Expand Up @@ -170,7 +180,12 @@ fn dump_stop(conn: &mut sdb::Connection, thread: u32) {
.enumerate()
.filter(|(_, l)| top.il_offset >= l.live_start && top.il_offset < l.live_end)
.collect();
println!("locals ({}/{} in scope at il 0x{:x}):", in_scope.len(), locals.len(), top.il_offset);
println!(
"locals ({}/{} in scope at il 0x{:x}):",
in_scope.len(),
locals.len(),
top.il_offset
);
if in_scope.is_empty() {
return;
}
Expand All @@ -193,8 +208,16 @@ fn il_to_source(info: &DebugInfo, il: i32) -> Option<String> {
.iter()
.filter(|s| !s.is_hidden() && s.il_offset <= il)
.max_by_key(|s| s.il_offset)?;
let src = info.sources.get(sp.source_idx as usize).cloned().unwrap_or_default();
Some(format!("{}:{}", src.rsplit(['/', '\\']).next().unwrap_or(&src), sp.line))
let src = info
.sources
.get(sp.source_idx as usize)
.cloned()
.unwrap_or_default();
Some(format!(
"{}:{}",
src.rsplit(['/', '\\']).next().unwrap_or(&src),
sp.line
))
}

/// Sanity-check the GET_DEBUG_INFO decoder against a loaded method (mscorlib is
Expand Down
27 changes: 22 additions & 5 deletions native/unterm/examples/dump_selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ use unterm::*;
fn main() {
env_logger::try_init().ok();

let cwd = CString::new(std::env::current_dir().unwrap().to_string_lossy().to_string()).unwrap();
let cwd = CString::new(
std::env::current_dir()
.unwrap()
.to_string_lossy()
.to_string(),
)
.unwrap();
let id = unsafe { unterm_create(1000, 600, 2.0, cwd.as_ptr()) };
assert!(id != 0, "create failed");

Expand All @@ -34,8 +40,13 @@ fn main() {
let mut len = 0usize;
let ptr = unsafe { unterm_selection_text(id, &mut len as *mut usize) };
assert!(!ptr.is_null() && len > 0, "no selection text");
let text = unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned();
assert!(text.contains("SELECTME"), "selection missing token; got:\n{text}");
let text = unsafe { CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned();
assert!(
text.contains("SELECTME"),
"selection missing token; got:\n{text}"
);
println!("selection round-trip OK ({len} bytes), contains SELECTME");

// Render the highlighted frame for visual inspection.
Expand All @@ -47,8 +58,14 @@ fn main() {
let mut h = 0u32;
unsafe { unterm_size(id, &mut w as *mut u32, &mut h as *mut u32) };
let data = unsafe { std::slice::from_raw_parts(px, plen) };
image::save_buffer("unterm_sel.png", data, w, h, image::ExtendedColorType::Rgba8)
.expect("png save");
image::save_buffer(
"unterm_sel.png",
data,
w,
h,
image::ExtendedColorType::Rgba8,
)
.expect("png save");
println!("wrote unterm_sel.png ({w}x{h})");

// Clearing drops the highlight (selection text becomes empty).
Expand Down
13 changes: 11 additions & 2 deletions native/unterm/examples/dump_term.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ use unterm::*;
fn main() {
env_logger::try_init().ok();

let cwd = CString::new(std::env::current_dir().unwrap().to_string_lossy().to_string()).unwrap();
let cwd = CString::new(
std::env::current_dir()
.unwrap()
.to_string_lossy()
.to_string(),
)
.unwrap();
let id = unsafe { unterm_create(1000, 600, 2.0, cwd.as_ptr()) };
assert!(id != 0, "create failed");

Expand Down Expand Up @@ -44,7 +50,10 @@ fn main() {
unsafe { unterm_size(id, &mut w as *mut u32, &mut h as *mut u32) };
let raw = unsafe { unterm_raw_texture(id) };
println!("rendered {w}x{h}; IOSurface MTLTexture ptr = {raw:?}");
assert!(!raw.is_null(), "IOSurface texture was null (zero-copy target failed)");
assert!(
!raw.is_null(),
"IOSurface texture was null (zero-copy target failed)"
);

unterm_destroy(id);
println!("OK: render pipeline ran on wgpu 29 without panicking");
Expand Down
Loading