1use crate::WitBoundary;
30use crate::lattice::plugin_host::types::{
31 ActionContext as WitActionContext, ExCommandContext as WitExCommandContext,
32 LatencyClass as WitLatencyClass, MotionContext as WitMotionContext,
33 MotionResult as WitMotionResult, OperatorContext as WitOperatorContext,
34 SurfaceForm as WitSurfaceForm, TextObjectContext as WitTextObjectContext,
35};
36use lattice_grammar::command::LatencyClass as NativeLatencyClass;
37use lattice_grammar::registry::{
38 ActionContext as NativeActionContext, ExCommandContext as NativeExCommandContext,
39 MotionContext as NativeMotionContext, MotionResult as NativeMotionResult,
40 OperatorContext as NativeOperatorContext, SurfaceForm as NativeSurfaceForm,
41 TextObjectContext as NativeTextObjectContext,
42};
43
44impl WitBoundary for NativeLatencyClass {
46 type Wit = WitLatencyClass;
47
48 fn to_wit(&self) -> Result<WitLatencyClass, String> {
49 Ok(match self {
50 NativeLatencyClass::Reflex => WitLatencyClass::Reflex,
51 NativeLatencyClass::Display => WitLatencyClass::Display,
52 NativeLatencyClass::Background => WitLatencyClass::Background,
53 })
54 }
55
56 fn from_wit(wit: WitLatencyClass) -> Result<Self, String> {
57 Ok(match wit {
58 WitLatencyClass::Reflex => NativeLatencyClass::Reflex,
59 WitLatencyClass::Display => NativeLatencyClass::Display,
60 WitLatencyClass::Background => NativeLatencyClass::Background,
61 })
62 }
63}
64
65impl WitBoundary for NativeSurfaceForm {
66 type Wit = WitSurfaceForm;
67
68 fn to_wit(&self) -> Result<WitSurfaceForm, String> {
69 Ok(match self {
70 NativeSurfaceForm::Keyword => WitSurfaceForm::Keyword,
71 NativeSurfaceForm::Delimiter { hint } => WitSurfaceForm::Delimiter(hint.to_string()),
72 })
73 }
74
75 fn from_wit(wit: WitSurfaceForm) -> Result<Self, String> {
76 Ok(match wit {
77 WitSurfaceForm::Keyword => NativeSurfaceForm::Keyword,
78 WitSurfaceForm::Delimiter(hint) => NativeSurfaceForm::Delimiter { hint: hint.into() },
81 })
82 }
83}
84
85impl WitBoundary for NativeMotionResult {
86 type Wit = WitMotionResult;
87
88 fn to_wit(&self) -> Result<WitMotionResult, String> {
89 Ok(WitMotionResult {
90 target: self.target.to_wit()?,
91 linewise: self.linewise,
92 })
93 }
94
95 fn from_wit(wit: WitMotionResult) -> Result<Self, String> {
96 Ok(NativeMotionResult {
97 target: lattice_protocol::position::Position::from_wit(wit.target)?,
98 linewise: wit.linewise,
99 exclusive: None,
106 notice: None,
107 curswant: None,
115 })
116 }
117}
118
119pub fn project_motion_context(ctx: &NativeMotionContext) -> Result<WitMotionContext, String> {
124 Ok(WitMotionContext {
125 buffer_id: ctx.buffer_id.0,
126 from: ctx.from.to_wit()?,
127 count: ctx.count.get(),
128 has_explicit_count: ctx.has_explicit_count,
129 args: ctx.args.to_wit()?,
130 })
131}
132
133pub fn project_operator_context(ctx: &NativeOperatorContext) -> Result<WitOperatorContext, String> {
136 Ok(WitOperatorContext {
137 buffer_id: ctx.buffer_id.0,
138 range: ctx.range.to_wit()?,
139 linewise: ctx.linewise,
140 register: ctx.register.to_wit()?,
141 count: ctx.count.get(),
142 args: ctx.args.to_wit()?,
143 })
144}
145
146pub fn project_text_object_context(
149 ctx: &NativeTextObjectContext,
150) -> Result<WitTextObjectContext, String> {
151 Ok(WitTextObjectContext {
152 at: ctx.at.to_wit()?,
153 count: ctx.count.get(),
154 args: ctx.args.to_wit()?,
155 })
156}
157
158pub fn project_ex_command_context(
163 ctx: &NativeExCommandContext,
164) -> Result<WitExCommandContext, String> {
165 Ok(WitExCommandContext {
166 bang: ctx.bang,
167 args: ctx.args.to_wit()?,
168 register: ctx.register.to_wit()?,
169 count: ctx.count.get(),
170 cursor: ctx.cursor.to_wit()?,
175 buffer_id: ctx.buffer_id.0,
176 })
177}
178
179pub fn project_action_context(ctx: &NativeActionContext) -> Result<WitActionContext, String> {
184 Ok(WitActionContext {
185 args: ctx.args.to_wit()?,
186 register: ctx.register.to_wit()?,
187 count: ctx.count.get(),
188 cursor: ctx.cursor.to_wit()?,
189 buffer_id: ctx.buffer_id.0,
190 selection: ctx.selection.map(|r| r.to_wit()).transpose()?,
195 })
196}
197
198#[cfg(test)]
199mod tests {
200 #![allow(clippy::unwrap_used, clippy::panic)]
201
202 use super::*;
203 use crate::lattice::plugin_host::types::{Args as WitArgs, Register as WitRegister};
204 use lattice_core::Document;
205 use lattice_core::buffer::Buffer;
206 use lattice_core::buffers::BufferId;
207 use lattice_grammar::CancellationToken;
208 use lattice_grammar::args::{ArgValue, Args};
209 use lattice_grammar::command::Count;
210 use lattice_grammar::register::Register;
211 use lattice_protocol::position::{Position, Range};
212
213 fn pos(line: u32, byte: u32) -> Position {
214 Position { line, byte }
215 }
216
217 #[test]
218 fn latency_class_round_trips_every_arm() {
219 for native in [
220 NativeLatencyClass::Reflex,
221 NativeLatencyClass::Display,
222 NativeLatencyClass::Background,
223 ] {
224 let back = NativeLatencyClass::from_wit(native.to_wit().unwrap()).unwrap();
225 assert_eq!(native, back);
226 }
227 }
228
229 #[test]
230 fn surface_form_round_trips_both_arms() {
231 assert_eq!(
232 NativeSurfaceForm::from_wit(NativeSurfaceForm::Keyword.to_wit().unwrap()).unwrap(),
233 NativeSurfaceForm::Keyword
234 );
235 let delim = NativeSurfaceForm::Delimiter {
236 hint: ":s/pat/repl/".into(),
237 };
238 let back = NativeSurfaceForm::from_wit(delim.to_wit().unwrap()).unwrap();
239 assert_eq!(back, delim);
240 }
241
242 #[test]
243 fn motion_result_round_trips() {
244 let native = NativeMotionResult {
245 curswant: None,
246 target: pos(3, 7),
247 linewise: true,
248 exclusive: Some(true),
253 notice: None,
254 };
255 let back = NativeMotionResult::from_wit(native.to_wit().unwrap()).unwrap();
256 assert_eq!(back.target, native.target);
257 assert_eq!(back.linewise, native.linewise);
258 assert_eq!(
259 back.exclusive, None,
260 "`exclusive` is host-side only: a plugin declares exclusivity on \
261 its MotionSpec, so the WIT mirror carries no field and the decode \
262 must not invent one"
263 );
264 }
265
266 #[test]
267 fn motion_context_projects_owned_scalars() {
268 let buffer = Buffer::from_text("hello world\nsecond line\n");
269 let cancel = CancellationToken::never();
270 let ctx = NativeMotionContext {
271 buffer: &buffer,
272 buffer_id: BufferId(9),
273 from: pos(1, 2),
274 count: Count(4),
275 has_explicit_count: true,
276 args: Args::String("w".into()),
277 cancel: &cancel,
278 scope_resolver: None,
279 path: None,
280 syntax: None,
281 last_find: None,
282 fold_resolver: None,
283 last_search: None,
284 marks: None,
285 viewport: None,
286 nostartofline: false,
287 operator_pending: false,
288 scrolloff: 0,
289 curswant: None,
290 display: None,
291 };
292 let wit = project_motion_context(&ctx).unwrap();
293 assert_eq!(wit.buffer_id, 9);
294 assert_eq!(wit.from.line, 1);
295 assert_eq!(wit.from.byte, 2);
296 assert_eq!(wit.count, 4);
297 assert!(wit.has_explicit_count);
298 assert!(matches!(wit.args, WitArgs::String(ref s) if s == "w"));
299 }
300
301 #[test]
302 fn operator_context_projects_range_and_register() {
303 let mut document = Document::from_text("abc\n");
304 let ctx = NativeOperatorContext {
305 document: &mut document,
306 buffer_id: lattice_core::BufferId(7),
307 range: Range {
308 start: pos(0, 0),
309 end: pos(0, 3),
310 },
311 origin: pos(0, 0),
312 linewise: false,
313 register: Register::Named('a'),
314 count: Count(1),
315 args: Args::None,
316 cancel: &CancellationToken::never(),
317 indent: Default::default(),
318 indent_resolver: None,
319 textwidth: Default::default(),
320 comment_syntax: None,
321 native_format: Default::default(),
322 };
323 let wit = project_operator_context(&ctx).unwrap();
324 assert_eq!(wit.range.start.byte, 0);
325 assert_eq!(wit.range.end.byte, 3);
326 assert!(!wit.linewise);
327 assert!(matches!(wit.register, WitRegister::Named('a')));
328 assert_eq!(wit.count, 1);
329 assert_eq!(wit.buffer_id, 7, "CM.3: the id crosses");
330 }
331
332 #[test]
333 fn text_object_context_projects_cursor_and_count() {
334 let buffer = Buffer::from_text("word\n");
335 let cancel = CancellationToken::never();
336 let ctx = NativeTextObjectContext {
337 buffer: &buffer,
338 at: pos(0, 2),
339 count: Count(2),
340 args: Args::None,
341 cancel: &cancel,
342 scope_resolver: None,
343 comment_syntax: None,
344 path: None,
345 syntax: None,
346 };
347 let wit = project_text_object_context(&ctx).unwrap();
348 assert_eq!(wit.at.byte, 2);
349 assert_eq!(wit.count, 2);
350 }
351
352 #[test]
353 fn ex_command_context_projects_bang_and_args() {
354 let ctx = NativeExCommandContext {
355 bang: true,
356 args: Args::List(vec![ArgValue::String("x".into()), ArgValue::Int(1)]),
357 range: None,
358 register: Register::Unnamed,
359 count: Count(1),
360 buffer_id: lattice_core::BufferId(7),
361 cursor: lattice_protocol::position::Position { line: 3, byte: 5 },
365 buffer: Default::default(),
366 path: None,
367 syntax: None,
368 cancel: CancellationToken::never(),
369 };
370 let wit = project_ex_command_context(&ctx).unwrap();
371 assert!(wit.bang);
372 assert!(matches!(wit.register, WitRegister::Unnamed));
373 assert!(matches!(wit.args, WitArgs::List(ref v) if v.len() == 2));
374 assert_eq!(wit.cursor.line, 3);
375 assert_eq!(wit.buffer_id, 7);
376 }
377
378 #[test]
379 fn action_context_projects_register_count_and_cursor() {
380 let ctx = NativeActionContext {
381 args: Args::None,
382 register: Register::System,
383 count: Count(3),
384 cursor: pos(4, 2),
385 buffer_id: BufferId(7),
386 buffer: Buffer::from_text("hello\nworld\n"),
387 syntax: None,
388 selection: None,
389 cancel: CancellationToken::never(),
390 path: None,
391 };
392 let wit = project_action_context(&ctx).unwrap();
393 assert_eq!(wit.count, 3);
394 assert!(matches!(wit.register, WitRegister::System));
395 assert_eq!(wit.cursor.line, 4);
397 assert_eq!(wit.cursor.byte, 2);
398 assert_eq!(wit.buffer_id, 7);
399 }
400
401 fn action_ctx() -> NativeActionContext {
402 NativeActionContext {
403 args: Args::None,
404 register: Register::Unnamed,
405 count: Count(1),
406 cursor: pos(0, 0),
407 buffer_id: BufferId(1),
408 buffer: Buffer::from_text("one\ntwo\nthree\nfour\nfive\n"),
409 syntax: None,
410 selection: None,
411 cancel: CancellationToken::never(),
412 path: None,
413 }
414 }
415
416 #[test]
421 fn a_visual_selection_is_mirrored_to_the_guest() {
422 let mut ctx = action_ctx();
423 ctx.selection = Some(Range::new(pos(2, 0), pos(4, 7)));
424 let wit = project_action_context(&ctx).unwrap();
425 let sel = wit.selection.expect("selection mirrored");
426 assert_eq!((sel.start.line, sel.start.byte), (2, 0));
427 assert_eq!((sel.end.line, sel.end.byte), (4, 7));
428 }
429
430 #[test]
433 fn no_selection_projects_as_none() {
434 assert!(
435 project_action_context(&action_ctx())
436 .unwrap()
437 .selection
438 .is_none()
439 );
440 }
441}