1use std::path::PathBuf;
36use std::sync::Arc;
37
38use crate::WitBoundary;
39use crate::boundary::path_to_wit;
40
41use crate::lattice::plugin_host::types::{
42 ActiveBufferSnapshot as WitActiveBufferSnapshot, AiSessionPayload as WitAiSessionPayload,
43 Annotation as WitAnnotation, AnnotationCustom as WitAnnotationCustom,
44 AnnotationSegment as WitAnnotationSegment, AnnotationStyled as WitAnnotationStyled,
45 ArgDefault as WitArgDefault, ArgKind as WitArgKind, ArgSpec as WitArgSpec,
46 BufferEntry as WitBufferEntry, CommandRef as WitCommandRef, JumpTarget as WitJumpTarget,
47 KeyChord as WitKeyChord, KeyKind as WitKeyKind, Location as WitLocation,
48 LspInstancePayload as WitLspInstancePayload, OpenTarget as WitOpenTarget,
49 PickerContext as WitPickerContext, PickerSourceSpec as WitPickerSourceSpec,
50 PositionEntry as WitPositionEntry, PositionSource as WitPositionSource,
51 ResolveDiffPayload as WitResolveDiffPayload, RoutingPayload as WitRoutingPayload,
52 ShowMessageActionPayload as WitShowMessageActionPayload, SpecialKey as WitSpecialKey,
53 SymbolLocation as WitSymbolLocation,
54};
55
56use lattice_completion::candidate::{
57 Annotation as NativeAnnotation, AnnotationSegment as NativeAnnotationSegment,
58};
59use lattice_grammar::args::{
60 ArgDefault as NativeArgDefault, ArgKind as NativeArgKind, ArgSpec as NativeArgSpec,
61 ArgValue as NativeArgValue,
62};
63use lattice_picker::RoutingPayload as NativeRoutingPayload;
64use lattice_picker::context::{
65 ActiveBufferSnapshot as NativeActiveBufferSnapshot, BufferEntry as NativeBufferEntry,
66 PickerContext as NativePickerContext, PositionEntry as NativePositionEntry,
67 PositionSource as NativePositionSource,
68};
69use lattice_picker::outcome::OpenTarget as NativeOpenTarget;
70use lattice_picker::source::PickerSourceSpec as NativePickerSourceSpec;
71use lattice_protocol::chord::{
72 KeyChord as NativeKeyChord, KeyKind as NativeKeyKind, KeyMods as NativeKeyMods,
73 SpecialKey as NativeSpecialKey,
74};
75
76impl WitBoundary for NativeSpecialKey {
84 type Wit = WitSpecialKey;
85
86 fn to_wit(&self) -> Result<WitSpecialKey, String> {
87 Ok(match self {
88 NativeSpecialKey::Esc => WitSpecialKey::Esc,
89 NativeSpecialKey::Enter => WitSpecialKey::Enter,
90 NativeSpecialKey::Tab => WitSpecialKey::Tab,
91 NativeSpecialKey::Backspace => WitSpecialKey::Backspace,
92 NativeSpecialKey::Space => WitSpecialKey::Space,
93 NativeSpecialKey::Up => WitSpecialKey::Up,
94 NativeSpecialKey::Down => WitSpecialKey::Down,
95 NativeSpecialKey::Left => WitSpecialKey::Left,
96 NativeSpecialKey::Right => WitSpecialKey::Right,
97 NativeSpecialKey::Home => WitSpecialKey::Home,
98 NativeSpecialKey::End => WitSpecialKey::End,
99 NativeSpecialKey::PageUp => WitSpecialKey::PageUp,
100 NativeSpecialKey::PageDown => WitSpecialKey::PageDown,
101 NativeSpecialKey::Insert => WitSpecialKey::Insert,
102 NativeSpecialKey::Delete => WitSpecialKey::Delete,
103 NativeSpecialKey::F(n) => WitSpecialKey::F(*n),
104 })
105 }
106
107 fn from_wit(wit: WitSpecialKey) -> Result<Self, String> {
108 Ok(match wit {
109 WitSpecialKey::Esc => NativeSpecialKey::Esc,
110 WitSpecialKey::Enter => NativeSpecialKey::Enter,
111 WitSpecialKey::Tab => NativeSpecialKey::Tab,
112 WitSpecialKey::Backspace => NativeSpecialKey::Backspace,
113 WitSpecialKey::Space => NativeSpecialKey::Space,
114 WitSpecialKey::Up => NativeSpecialKey::Up,
115 WitSpecialKey::Down => NativeSpecialKey::Down,
116 WitSpecialKey::Left => NativeSpecialKey::Left,
117 WitSpecialKey::Right => NativeSpecialKey::Right,
118 WitSpecialKey::Home => NativeSpecialKey::Home,
119 WitSpecialKey::End => NativeSpecialKey::End,
120 WitSpecialKey::PageUp => NativeSpecialKey::PageUp,
121 WitSpecialKey::PageDown => NativeSpecialKey::PageDown,
122 WitSpecialKey::Insert => NativeSpecialKey::Insert,
123 WitSpecialKey::Delete => NativeSpecialKey::Delete,
124 WitSpecialKey::F(0) => {
127 return Err("special-key f(0) is invalid (function keys are 1..=24)".to_string());
128 }
129 WitSpecialKey::F(n) => NativeSpecialKey::F(n),
130 })
131 }
132}
133
134impl WitBoundary for NativeKeyChord {
135 type Wit = WitKeyChord;
136
137 fn to_wit(&self) -> Result<WitKeyChord, String> {
138 let key = match self.key {
139 NativeKeyKind::Char(c) => WitKeyKind::Char(c),
140 NativeKeyKind::Special(s) => WitKeyKind::Special(s.to_wit()?),
141 };
142 Ok(WitKeyChord {
143 key,
144 mods: self.mods.0,
145 })
146 }
147
148 fn from_wit(wit: WitKeyChord) -> Result<Self, String> {
149 let key = match wit.key {
150 WitKeyKind::Char(c) => NativeKeyKind::Char(c),
151 WitKeyKind::Special(s) => NativeKeyKind::Special(NativeSpecialKey::from_wit(s)?),
152 };
153 Ok(NativeKeyChord {
154 key,
155 mods: NativeKeyMods(wit.mods),
156 })
157 }
158}
159
160impl WitBoundary for NativeAnnotation {
161 type Wit = WitAnnotation;
162
163 fn to_wit(&self) -> Result<WitAnnotation, String> {
164 Ok(match self {
165 NativeAnnotation::Kind(s) => WitAnnotation::Kind(s.to_string()),
166 NativeAnnotation::DocSnippet(s) => WitAnnotation::DocSnippet(s.to_string()),
167 NativeAnnotation::Keybinding(chords) => {
168 WitAnnotation::Keybinding(chords.iter().map(WitBoundary::to_wit).collect::<Result<
169 Vec<_>,
170 String,
171 >>(
172 )?)
173 }
174 NativeAnnotation::Source(s) => WitAnnotation::Source(s.to_string()),
175 NativeAnnotation::Custom { text, slot } => WitAnnotation::Custom(WitAnnotationCustom {
176 text: text.to_string(),
177 slot: slot.to_string(),
178 }),
179 NativeAnnotation::Styled { category, segments } => {
180 WitAnnotation::Styled(WitAnnotationStyled {
181 category: category.to_string(),
182 segments: segments
183 .iter()
184 .map(|seg| WitAnnotationSegment {
185 text: seg.text.to_string(),
186 slot: seg.slot.to_string(),
187 })
188 .collect(),
189 })
190 }
191 })
192 }
193
194 fn from_wit(wit: WitAnnotation) -> Result<Self, String> {
195 Ok(match wit {
196 WitAnnotation::Kind(s) => NativeAnnotation::Kind(Arc::from(s)),
197 WitAnnotation::DocSnippet(s) => NativeAnnotation::DocSnippet(Arc::from(s)),
198 WitAnnotation::Keybinding(chords) => NativeAnnotation::Keybinding(
199 chords
200 .into_iter()
201 .map(NativeKeyChord::from_wit)
202 .collect::<Result<Vec<_>, String>>()?,
203 ),
204 WitAnnotation::Source(s) => NativeAnnotation::Source(Arc::from(s)),
205 WitAnnotation::Custom(c) => NativeAnnotation::Custom {
206 text: Arc::from(c.text),
207 slot: Arc::from(c.slot),
208 },
209 WitAnnotation::Styled(st) => NativeAnnotation::Styled {
210 category: Arc::from(st.category),
211 segments: st
212 .segments
213 .into_iter()
214 .map(|seg| NativeAnnotationSegment {
215 text: Arc::from(seg.text),
216 slot: Arc::from(seg.slot),
217 })
218 .collect(),
219 },
220 })
221 }
222}
223
224impl WitBoundary for NativeArgKind {
227 type Wit = WitArgKind;
228
229 fn to_wit(&self) -> Result<WitArgKind, String> {
230 Ok(match self {
231 NativeArgKind::String => WitArgKind::String,
232 NativeArgKind::Char => WitArgKind::Char,
233 NativeArgKind::Bool => WitArgKind::Bool,
234 NativeArgKind::Int => WitArgKind::Int,
235 NativeArgKind::Pattern => WitArgKind::Pattern,
236 NativeArgKind::Chord => WitArgKind::Chord,
237 NativeArgKind::Body => WitArgKind::Body,
238 NativeArgKind::Raw => WitArgKind::Raw,
239 })
240 }
241
242 fn from_wit(wit: WitArgKind) -> Result<Self, String> {
243 Ok(match wit {
244 WitArgKind::String => NativeArgKind::String,
245 WitArgKind::Char => NativeArgKind::Char,
246 WitArgKind::Bool => NativeArgKind::Bool,
247 WitArgKind::Int => NativeArgKind::Int,
248 WitArgKind::Pattern => NativeArgKind::Pattern,
249 WitArgKind::Chord => NativeArgKind::Chord,
250 WitArgKind::Body => NativeArgKind::Body,
251 WitArgKind::Raw => NativeArgKind::Raw,
252 })
253 }
254}
255
256impl WitBoundary for NativeArgDefault {
257 type Wit = WitArgDefault;
258
259 fn to_wit(&self) -> Result<WitArgDefault, String> {
260 Ok(match self {
261 NativeArgDefault::Required => WitArgDefault::Required,
262 NativeArgDefault::None => WitArgDefault::None,
263 NativeArgDefault::Literal(v) => WitArgDefault::Literal(v.to_wit()?),
264 NativeArgDefault::UseSelection => WitArgDefault::UseSelection,
265 NativeArgDefault::UseCursorWord => WitArgDefault::UseCursorWord,
266 NativeArgDefault::UseLastResponse => WitArgDefault::UseLastResponse,
267 })
268 }
269
270 fn from_wit(wit: WitArgDefault) -> Result<Self, String> {
271 Ok(match wit {
272 WitArgDefault::Required => NativeArgDefault::Required,
273 WitArgDefault::None => NativeArgDefault::None,
274 WitArgDefault::Literal(v) => NativeArgDefault::Literal(NativeArgValue::from_wit(v)?),
275 WitArgDefault::UseSelection => NativeArgDefault::UseSelection,
276 WitArgDefault::UseCursorWord => NativeArgDefault::UseCursorWord,
277 WitArgDefault::UseLastResponse => NativeArgDefault::UseLastResponse,
278 })
279 }
280}
281
282impl WitBoundary for NativeArgSpec {
283 type Wit = WitArgSpec;
284
285 fn to_wit(&self) -> Result<WitArgSpec, String> {
286 Ok(WitArgSpec {
287 name: self.name.to_string(),
288 kind: self.kind.to_wit()?,
289 doc: self.doc.to_string(),
290 prompt: self.prompt.to_string(),
291 default: self.default.to_wit()?,
292 completion: self.completion.as_deref().map(str::to_string),
293 picker: self.picker.as_deref().map(str::to_string),
294 })
295 }
296
297 fn from_wit(wit: WitArgSpec) -> Result<Self, String> {
298 Ok(NativeArgSpec {
302 name: wit.name.into(),
303 kind: NativeArgKind::from_wit(wit.kind)?,
304 doc: wit.doc.into(),
305 prompt: wit.prompt.into(),
306 default: NativeArgDefault::from_wit(wit.default)?,
307 completion: wit.completion.map(Into::into),
308 picker: wit.picker.map(Into::into),
309 })
310 }
311}
312
313impl WitBoundary for NativePickerSourceSpec {
314 type Wit = WitPickerSourceSpec;
315
316 fn to_wit(&self) -> Result<WitPickerSourceSpec, String> {
317 Ok(WitPickerSourceSpec {
318 id: self.id.to_string(),
319 doc: self.doc.to_string(),
320 args_schema: self
321 .args_schema
322 .iter()
323 .map(WitBoundary::to_wit)
324 .collect::<Result<Vec<_>, String>>()?,
325 args_hint: self.args_hint.to_string(),
326 live: self.live,
327 create_label: self.create_label.as_ref().map(|l| l.to_string()),
328 rooted: self.rooted,
329 delete_command: self.delete_command.as_ref().map(|c| c.to_string()),
330 })
331 }
332
333 fn from_wit(wit: WitPickerSourceSpec) -> Result<Self, String> {
334 Ok(NativePickerSourceSpec {
337 id: wit.id.into(),
338 doc: wit.doc.into(),
339 args_schema: wit
340 .args_schema
341 .into_iter()
342 .map(NativeArgSpec::from_wit)
343 .collect::<Result<Vec<_>, String>>()?,
344 args_hint: wit.args_hint.into(),
345 live: wit.live,
346 create_label: wit.create_label.map(Into::into),
349 rooted: wit.rooted,
356 delete_command: wit.delete_command.map(Into::into),
360 help_topic: None,
367 })
368 }
369}
370
371impl WitBoundary for NativeOpenTarget {
374 type Wit = WitOpenTarget;
375
376 fn to_wit(&self) -> Result<WitOpenTarget, String> {
377 Ok(match self {
378 NativeOpenTarget::Default => WitOpenTarget::Default,
379 NativeOpenTarget::Split => WitOpenTarget::Split,
380 NativeOpenTarget::VSplit => WitOpenTarget::Vsplit,
381 NativeOpenTarget::Tab => WitOpenTarget::Tab,
382 })
383 }
384
385 fn from_wit(wit: WitOpenTarget) -> Result<Self, String> {
386 Ok(match wit {
387 WitOpenTarget::Default => NativeOpenTarget::Default,
388 WitOpenTarget::Split => NativeOpenTarget::Split,
389 WitOpenTarget::Vsplit => NativeOpenTarget::VSplit,
390 WitOpenTarget::Tab => NativeOpenTarget::Tab,
391 })
392 }
393}
394
395impl WitBoundary for NativeRoutingPayload {
396 type Wit = WitRoutingPayload;
397
398 fn to_wit(&self) -> Result<WitRoutingPayload, String> {
399 Ok(match self {
400 NativeRoutingPayload::Buffer { id } => WitRoutingPayload::Buffer(*id),
401 NativeRoutingPayload::PaneHistoryEntry { index } => {
402 WitRoutingPayload::PaneHistoryEntry(*index)
403 }
404 NativeRoutingPayload::ResolveDiff { primary, accept } => {
405 WitRoutingPayload::ResolveDiff(WitResolveDiffPayload {
406 primary: *primary,
407 accept: *accept,
408 })
409 }
410 NativeRoutingPayload::LspInstance {
411 server_id,
412 workspace,
413 } => WitRoutingPayload::LspInstance(WitLspInstancePayload {
414 server_id: server_id.clone(),
415 workspace: path_to_wit(workspace)?,
416 }),
417 NativeRoutingPayload::LspLocation { path, line, col } => {
418 WitRoutingPayload::LspLocation(WitLocation {
419 path: path_to_wit(path)?,
420 line: *line,
421 col: *col,
422 })
423 }
424 NativeRoutingPayload::LspCompletion { index } => {
425 WitRoutingPayload::LspCompletion(*index)
426 }
427 NativeRoutingPayload::LspCodeAction { index } => {
428 WitRoutingPayload::LspCodeAction(*index)
429 }
430 NativeRoutingPayload::OpenFile { path } => {
431 WitRoutingPayload::OpenFile(path_to_wit(path)?)
432 }
433 NativeRoutingPayload::JumpInBuffer {
434 buffer_id,
435 line,
436 col,
437 } => WitRoutingPayload::JumpInBuffer(WitJumpTarget {
438 buffer_id: *buffer_id,
439 line: *line,
440 col: *col,
441 }),
442 NativeRoutingPayload::InvokeCommand { id, args } => {
443 WitRoutingPayload::InvokeCommand(WitCommandRef {
444 id: id.clone(),
445 args: args.to_wit()?,
446 })
447 }
448 NativeRoutingPayload::PasteRegister { name } => WitRoutingPayload::PasteRegister(*name),
449 NativeRoutingPayload::JumpToMark { name } => WitRoutingPayload::JumpToMark(*name),
450 NativeRoutingPayload::ExpandSnippet { id } => {
451 WitRoutingPayload::ExpandSnippet(id.clone())
452 }
453 NativeRoutingPayload::AcceptShowMessageAction {
454 request_id,
455 action_index,
456 } => WitRoutingPayload::AcceptShowMessageAction(WitShowMessageActionPayload {
457 request_id: *request_id,
458 action_index: *action_index,
459 }),
460 NativeRoutingPayload::LspCodeLens { index } => WitRoutingPayload::LspCodeLens(*index),
461 NativeRoutingPayload::ColorPresentation { index } => {
462 WitRoutingPayload::ColorPresentation(*index)
463 }
464 NativeRoutingPayload::Colorscheme { name } => {
465 WitRoutingPayload::Colorscheme(name.clone())
466 }
467 NativeRoutingPayload::AiSession { provider, index } => {
468 WitRoutingPayload::AiSession(WitAiSessionPayload {
469 provider: provider.clone(),
470 index: *index,
471 })
472 }
473 NativeRoutingPayload::LoadCommandLine { .. } => {
478 return Err(
479 "load-command-line is a host-internal picker routing payload, not representable over WIT"
480 .into(),
481 );
482 }
483 NativeRoutingPayload::LoadSearchLine { .. } => {
486 return Err(
487 "load-search-line is a host-internal picker routing payload, not representable over WIT"
488 .into(),
489 );
490 }
491 NativeRoutingPayload::BranchBase { .. } => {
494 return Err(
495 "branch-base is a host-internal picker routing payload, not representable over WIT"
496 .into(),
497 );
498 }
499 NativeRoutingPayload::SuppliedValue { .. } => {
503 return Err(
504 "supplied-value is a host-internal picker routing payload, not representable over WIT"
505 .into(),
506 );
507 }
508 NativeRoutingPayload::FileLocation { path, line, col } => {
510 WitRoutingPayload::FileLocation(crate::lattice::plugin_host::types::Location {
511 path: crate::boundary::path_to_wit(path)?,
512 line: *line,
513 col: *col,
514 })
515 }
516 NativeRoutingPayload::Create { query } => WitRoutingPayload::Create(query.clone()),
521 })
522 }
523
524 fn from_wit(wit: WitRoutingPayload) -> Result<Self, String> {
525 Ok(match wit {
526 WitRoutingPayload::Buffer(id) => NativeRoutingPayload::Buffer { id },
527 WitRoutingPayload::FileLocation(loc) => NativeRoutingPayload::FileLocation {
529 path: std::path::PathBuf::from(loc.path),
530 line: loc.line,
531 col: loc.col,
532 },
533 WitRoutingPayload::Create(query) => NativeRoutingPayload::Create { query },
537 WitRoutingPayload::PaneHistoryEntry(index) => {
538 NativeRoutingPayload::PaneHistoryEntry { index }
539 }
540 WitRoutingPayload::ResolveDiff(p) => NativeRoutingPayload::ResolveDiff {
541 primary: p.primary,
542 accept: p.accept,
543 },
544 WitRoutingPayload::LspInstance(p) => NativeRoutingPayload::LspInstance {
545 server_id: p.server_id,
546 workspace: PathBuf::from(p.workspace),
547 },
548 WitRoutingPayload::LspLocation(l) => NativeRoutingPayload::LspLocation {
549 path: PathBuf::from(l.path),
550 line: l.line,
551 col: l.col,
552 },
553 WitRoutingPayload::LspCompletion(index) => {
554 NativeRoutingPayload::LspCompletion { index }
555 }
556 WitRoutingPayload::LspCodeAction(index) => {
557 NativeRoutingPayload::LspCodeAction { index }
558 }
559 WitRoutingPayload::OpenFile(path) => NativeRoutingPayload::OpenFile {
560 path: PathBuf::from(path),
561 },
562 WitRoutingPayload::JumpInBuffer(t) => NativeRoutingPayload::JumpInBuffer {
563 buffer_id: t.buffer_id,
564 line: t.line,
565 col: t.col,
566 },
567 WitRoutingPayload::InvokeCommand(c) => NativeRoutingPayload::InvokeCommand {
568 id: c.id,
569 args: lattice_grammar::args::Args::from_wit(c.args)?,
570 },
571 WitRoutingPayload::PasteRegister(name) => NativeRoutingPayload::PasteRegister { name },
572 WitRoutingPayload::JumpToMark(name) => NativeRoutingPayload::JumpToMark { name },
573 WitRoutingPayload::ExpandSnippet(id) => NativeRoutingPayload::ExpandSnippet { id },
574 WitRoutingPayload::AcceptShowMessageAction(p) => {
575 NativeRoutingPayload::AcceptShowMessageAction {
576 request_id: p.request_id,
577 action_index: p.action_index,
578 }
579 }
580 WitRoutingPayload::LspCodeLens(index) => NativeRoutingPayload::LspCodeLens { index },
581 WitRoutingPayload::ColorPresentation(index) => {
582 NativeRoutingPayload::ColorPresentation { index }
583 }
584 WitRoutingPayload::Colorscheme(name) => NativeRoutingPayload::Colorscheme { name },
585 WitRoutingPayload::AiSession(p) => NativeRoutingPayload::AiSession {
586 provider: p.provider,
587 index: p.index,
588 },
589 })
590 }
591}
592
593fn project_position_source(src: &NativePositionSource) -> WitPositionSource {
596 match src {
597 NativePositionSource::AutoJump => WitPositionSource::AutoJump,
598 NativePositionSource::ExplicitMark => WitPositionSource::ExplicitMark,
599 NativePositionSource::PluginPush => WitPositionSource::PluginPush,
600 NativePositionSource::NamedMark(c) => WitPositionSource::NamedMark(*c),
601 }
602}
603
604fn project_position_entry(e: &NativePositionEntry) -> WitPositionEntry {
605 WitPositionEntry {
606 buffer_id: e.buffer_id,
607 line: e.line,
608 col: e.col,
609 source: project_position_source(&e.source),
610 }
611}
612
613fn project_buffer_entry(e: &NativeBufferEntry) -> Result<WitBufferEntry, String> {
614 Ok(WitBufferEntry {
615 id: e.id,
616 kind_label: e.kind_label.clone(),
617 path: e.path.as_deref().map(path_to_wit).transpose()?,
618 title: e.title.clone(),
619 dirty: e.dirty,
620 })
621}
622
623fn project_active_buffer_snapshot(
624 ab: &NativeActiveBufferSnapshot<'_>,
625) -> Result<WitActiveBufferSnapshot, String> {
626 let selection = match ab.selection {
627 Some((a, b)) => Some((a.to_wit()?, b.to_wit()?)),
628 None => None,
629 };
630 Ok(WitActiveBufferSnapshot {
631 buffer_id: ab.buffer_id,
632 path: ab.path.map(path_to_wit).transpose()?,
633 language: ab.language.map(str::to_string),
634 cursor: ab.cursor.to_wit()?,
635 selection,
636 syntax_symbols: ab
637 .syntax_symbols
638 .iter()
639 .map(|(name, line, col)| WitSymbolLocation {
640 name: name.clone(),
641 line: *line,
642 col: *col,
643 })
644 .collect(),
645 })
646}
647
648pub fn project_picker_context(ctx: &NativePickerContext<'_>) -> Result<WitPickerContext, String> {
653 Ok(WitPickerContext {
654 active_buffer: project_active_buffer_snapshot(&ctx.active_buffer)?,
655 workspace_root: path_to_wit(&ctx.workspace_root)?,
656 recent_files: ctx
657 .recent_files
658 .iter()
659 .map(|p| path_to_wit(p))
660 .collect::<Result<Vec<_>, String>>()?,
661 position_history: ctx
662 .position_history
663 .iter()
664 .map(project_position_entry)
665 .collect(),
666 buffers: ctx
667 .buffers
668 .iter()
669 .map(project_buffer_entry)
670 .collect::<Result<Vec<_>, String>>()?,
671 marks: ctx
672 .marks
673 .iter()
674 .map(|(c, pos)| Ok((*c, pos.to_wit()?)))
675 .collect::<Result<Vec<_>, String>>()?,
676 registers: ctx.registers.clone(),
677 })
678}
679
680#[cfg(test)]
681mod tests {
682 #![allow(clippy::unwrap_used, clippy::panic)]
683
684 use super::*;
685 use lattice_completion::candidate::AnnotationSegment;
686
687 fn assert_chord_round_trips(native: NativeKeyChord) {
690 let back = NativeKeyChord::from_wit(native.to_wit().unwrap()).unwrap();
691 assert_eq!(native, back);
692 }
693
694 #[test]
695 fn key_chord_round_trips_char_special_and_mods() {
696 assert_chord_round_trips(NativeKeyChord {
697 key: NativeKeyKind::Char('a'),
698 mods: NativeKeyMods::NONE,
699 });
700 assert_chord_round_trips(NativeKeyChord {
701 key: NativeKeyKind::Char('x'),
702 mods: NativeKeyMods::CTRL,
703 });
704 assert_chord_round_trips(NativeKeyChord {
705 key: NativeKeyKind::Special(NativeSpecialKey::Tab),
706 mods: NativeKeyMods(NativeKeyMods::CTRL.0 | NativeKeyMods::SHIFT.0),
707 });
708 assert_chord_round_trips(NativeKeyChord {
709 key: NativeKeyKind::Special(NativeSpecialKey::F(12)),
710 mods: NativeKeyMods::ALT,
711 });
712 }
713
714 #[test]
715 fn special_key_f0_is_a_typed_error() {
716 let err = NativeSpecialKey::from_wit(WitSpecialKey::F(0)).expect_err("f(0) must reject");
717 assert!(err.contains("f(0)"), "error names the culprit: {err}");
718 }
719
720 fn assert_annotation_round_trips(native: NativeAnnotation) {
721 let back = NativeAnnotation::from_wit(native.to_wit().unwrap()).unwrap();
722 assert_eq!(native, back);
723 }
724
725 #[test]
726 fn annotation_round_trips_every_variant() {
727 assert_annotation_round_trips(NativeAnnotation::Kind(Arc::from("(file)")));
728 assert_annotation_round_trips(NativeAnnotation::DocSnippet(Arc::from("opens a file")));
729 assert_annotation_round_trips(NativeAnnotation::Keybinding(vec![
730 NativeKeyChord {
731 key: NativeKeyKind::Char('g'),
732 mods: NativeKeyMods::NONE,
733 },
734 NativeKeyChord {
735 key: NativeKeyKind::Special(NativeSpecialKey::Esc),
736 mods: NativeKeyMods::CTRL,
737 },
738 ]));
739 assert_annotation_round_trips(NativeAnnotation::Source(Arc::from("lsp")));
740 assert_annotation_round_trips(NativeAnnotation::Custom {
741 text: Arc::from("42K"),
742 slot: Arc::from("annotation_size"),
743 });
744 assert_annotation_round_trips(NativeAnnotation::Styled {
747 category: Arc::from("perms"),
748 segments: vec![
749 AnnotationSegment {
750 text: Arc::from("d"),
751 slot: Arc::from("perm_dir"),
752 },
753 AnnotationSegment {
754 text: Arc::from("rwx"),
755 slot: Arc::from("perm_owner"),
756 },
757 ],
758 });
759 }
760
761 #[test]
764 fn arg_spec_round_trips_through_intern() {
765 let native = NativeArgSpec {
766 name: "root".into(),
767 kind: NativeArgKind::String,
768 doc: "directory to walk".into(),
769 prompt: "root:".into(),
770 default: NativeArgDefault::Literal(NativeArgValue::String("/tmp".into())),
771 completion: Some("gen:files".into()),
772 picker: None,
773 };
774 let back = NativeArgSpec::from_wit(native.to_wit().unwrap()).unwrap();
775 assert_eq!(back.name, "root");
776 assert_eq!(back.doc, "directory to walk");
777 assert_eq!(back.prompt, "root:");
778 assert_eq!(back.completion.as_deref(), Some("gen:files"));
779 assert!(matches!(back.kind, NativeArgKind::String));
780 assert!(matches!(
781 back.default,
782 NativeArgDefault::Literal(NativeArgValue::String(ref s)) if s == "/tmp"
783 ));
784 }
785
786 #[test]
787 fn picker_source_spec_round_trips() {
788 let native = NativePickerSourceSpec {
789 id: "files".into(),
790 doc: "file picker".into(),
791 args_schema: vec![NativeArgSpec {
792 name: "root".into(),
793 kind: NativeArgKind::String,
794 doc: "dir".into(),
795 prompt: "root:".into(),
796 default: NativeArgDefault::None,
797 completion: Some("gen:files".into()),
798 picker: None,
799 }],
800 args_hint: "[root]".into(),
801 live: false,
802 create_label: Some("Create note: %s".into()),
805 rooted: true,
811 delete_command: Some("project-forget".into()),
816 help_topic: None,
818 };
819 let back = NativePickerSourceSpec::from_wit(native.to_wit().unwrap()).unwrap();
820 assert_eq!(back.id, "files");
821 assert_eq!(back.doc, "file picker");
822 assert_eq!(back.args_hint, "[root]");
823 assert!(!back.live);
824 assert_eq!(back.create_label.as_deref(), Some("Create note: %s"));
825 assert!(
826 back.rooted,
827 "a guest declaring `rooted` must arrive rooted — a picker that \
828 silently stops naming its root reads as the feature not existing"
829 );
830 assert_eq!(
831 back.delete_command.as_deref(),
832 Some("project-forget"),
833 "a guest declaring a delete verb must arrive with it — `<C-d>` is \
834 silent by design when none is declared, so a dropped field is \
835 indistinguishable from a source that never wanted the key"
836 );
837 assert_eq!(back.args_schema.len(), 1);
838 assert_eq!(back.args_schema[0].name, "root");
839 }
840
841 #[test]
844 fn open_target_round_trips_every_variant() {
845 for t in [
846 NativeOpenTarget::Default,
847 NativeOpenTarget::Split,
848 NativeOpenTarget::VSplit,
849 NativeOpenTarget::Tab,
850 ] {
851 let back = NativeOpenTarget::from_wit(t.to_wit().unwrap()).unwrap();
852 assert_eq!(t, back);
853 }
854 }
855
856 fn assert_routing_round_trips(native: NativeRoutingPayload) {
859 let dbg = format!("{native:?}");
860 let back = NativeRoutingPayload::from_wit(native.to_wit().unwrap()).unwrap();
861 assert_eq!(dbg, format!("{back:?}"));
862 }
863
864 #[test]
865 fn routing_payload_round_trips_representative_variants() {
866 assert_routing_round_trips(NativeRoutingPayload::Buffer { id: 7 });
867 assert_routing_round_trips(NativeRoutingPayload::ResolveDiff {
868 primary: 3,
869 accept: true,
870 });
871 assert_routing_round_trips(NativeRoutingPayload::LspInstance {
872 server_id: "rust-analyzer".into(),
873 workspace: PathBuf::from("/home/x/proj"),
874 });
875 assert_routing_round_trips(NativeRoutingPayload::LspLocation {
876 path: PathBuf::from("/a/b.rs"),
877 line: 12,
878 col: 4,
879 });
880 assert_routing_round_trips(NativeRoutingPayload::OpenFile {
881 path: PathBuf::from("/tmp/foo.rs"),
882 });
883 assert_routing_round_trips(NativeRoutingPayload::JumpInBuffer {
884 buffer_id: 2,
885 line: 40,
886 col: 1,
887 });
888 assert_routing_round_trips(NativeRoutingPayload::InvokeCommand {
889 id: "write".into(),
890 args: lattice_grammar::args::Args::None,
891 });
892 assert_routing_round_trips(NativeRoutingPayload::PasteRegister { name: 'a' });
893 assert_routing_round_trips(NativeRoutingPayload::JumpToMark { name: 'z' });
894 assert_routing_round_trips(NativeRoutingPayload::ExpandSnippet { id: "fn".into() });
895 assert_routing_round_trips(NativeRoutingPayload::AcceptShowMessageAction {
896 request_id: 5,
897 action_index: 1,
898 });
899 assert_routing_round_trips(NativeRoutingPayload::LspCodeLens { index: 9 });
900 assert_routing_round_trips(NativeRoutingPayload::ColorPresentation { index: 0 });
901 assert_routing_round_trips(NativeRoutingPayload::Colorscheme {
902 name: "nord".into(),
903 });
904 }
905
906 #[test]
907 #[cfg(unix)]
908 fn routing_payload_non_utf8_path_is_a_typed_error() {
909 use std::os::unix::ffi::OsStrExt;
910 let bad = std::ffi::OsStr::from_bytes(&[0xff, 0xfe]);
911 let native = NativeRoutingPayload::OpenFile {
912 path: PathBuf::from(bad),
913 };
914 let err = native.to_wit().expect_err("non-utf8 path must reject");
915 assert!(err.contains("UTF-8"), "error explains the failure: {err}");
916 }
917}