1use std::borrow::Cow;
7
8use reedline::{Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus};
9
10use crate::session::SessionState;
11
12#[derive(Debug, Clone)]
18pub struct PromptState {
19 pub galaxy_name: String,
20 pub biome_filter: Option<String>,
21 pub planet_count: usize,
22}
23
24impl PromptState {
25 pub fn from_session(session: &SessionState) -> Self {
27 Self {
28 galaxy_name: session.galaxy.name.to_string(),
29 biome_filter: session.biome_filter.map(|b| format!("{b:?}")),
30 planet_count: session.planet_count,
31 }
32 }
33}
34
35pub struct CopilotPrompt {
37 state: PromptState,
38}
39
40impl CopilotPrompt {
41 pub fn new(state: PromptState) -> Self {
42 Self { state }
43 }
44
45 pub fn update(&mut self, state: PromptState) {
47 self.state = state;
48 }
49
50 fn render_left(&self) -> String {
51 let mut parts = vec![self.state.galaxy_name.clone()];
52
53 if let Some(ref biome) = self.state.biome_filter {
54 parts.push(biome.clone());
55 }
56
57 parts.push(format!("{} planets", self.state.planet_count));
58
59 format!("[{}] 🚀", parts.join(" | "))
60 }
61}
62
63impl Prompt for CopilotPrompt {
64 fn render_prompt_left(&self) -> Cow<'_, str> {
65 Cow::Owned(self.render_left())
66 }
67
68 fn render_prompt_right(&self) -> Cow<'_, str> {
69 Cow::Borrowed("")
70 }
71
72 fn render_prompt_indicator(&self, _edit_mode: PromptEditMode) -> Cow<'_, str> {
73 Cow::Borrowed(" ")
74 }
75
76 fn render_prompt_multiline_indicator(&self) -> Cow<'_, str> {
77 Cow::Borrowed("... ")
78 }
79
80 fn render_prompt_history_search_indicator(
81 &self,
82 history_search: PromptHistorySearch,
83 ) -> Cow<'_, str> {
84 let prefix = match history_search.status {
85 PromptHistorySearchStatus::Passing => "",
86 PromptHistorySearchStatus::Failing => "(failed) ",
87 };
88 Cow::Owned(format!("{prefix}(search: {}) ", history_search.term))
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_prompt_basic() {
98 let state = PromptState {
99 galaxy_name: "Euclid".into(),
100 biome_filter: None,
101 planet_count: 644,
102 };
103 let prompt = CopilotPrompt::new(state);
104 let left = prompt.render_prompt_left();
105 assert_eq!(left.as_ref(), "[Euclid | 644 planets] 🚀");
106 }
107
108 #[test]
109 fn test_prompt_with_biome_filter() {
110 let state = PromptState {
111 galaxy_name: "Euclid".into(),
112 biome_filter: Some("Lush".into()),
113 planet_count: 42,
114 };
115 let prompt = CopilotPrompt::new(state);
116 let left = prompt.render_prompt_left();
117 assert_eq!(left.as_ref(), "[Euclid | Lush | 42 planets] 🚀");
118 }
119
120 #[test]
121 fn test_prompt_different_galaxy() {
122 let state = PromptState {
123 galaxy_name: "Hilbert Dimension".into(),
124 biome_filter: None,
125 planet_count: 100,
126 };
127 let prompt = CopilotPrompt::new(state);
128 let left = prompt.render_prompt_left();
129 assert!(left.contains("Hilbert Dimension"));
130 }
131
132 #[test]
133 fn test_prompt_indicator_is_space() {
134 let state = PromptState {
135 galaxy_name: "Euclid".into(),
136 biome_filter: None,
137 planet_count: 0,
138 };
139 let prompt = CopilotPrompt::new(state);
140 assert_eq!(
141 prompt
142 .render_prompt_indicator(PromptEditMode::Default)
143 .as_ref(),
144 " "
145 );
146 }
147
148 #[test]
149 fn test_prompt_update() {
150 let state1 = PromptState {
151 galaxy_name: "Euclid".into(),
152 biome_filter: None,
153 planet_count: 100,
154 };
155 let mut prompt = CopilotPrompt::new(state1);
156 assert!(prompt.render_prompt_left().contains("100 planets"));
157
158 let state2 = PromptState {
159 galaxy_name: "Euclid".into(),
160 biome_filter: Some("Toxic".into()),
161 planet_count: 200,
162 };
163 prompt.update(state2);
164 let left = prompt.render_prompt_left();
165 assert!(left.contains("200 planets"));
166 assert!(left.contains("Toxic"));
167 }
168
169 #[test]
170 fn test_prompt_state_from_session() {
171 let json = r#"{
172 "Version": 4720, "Platform": "Mac|Final", "ActiveContext": "Main",
173 "CommonStateData": {"SaveName": "Test", "TotalPlayTime": 100},
174 "BaseContext": {
175 "GameMode": 1,
176 "PlayerStateData": {
177 "UniverseAddress": {"RealityIndex": 0, "GalacticAddress": {"VoxelX": 0, "VoxelY": 0, "VoxelZ": 0, "SolarSystemIndex": 1, "PlanetIndex": 0}},
178 "Units": 0, "Nanites": 0, "Specials": 0,
179 "PersistentPlayerBases": []
180 }
181 },
182 "ExpeditionContext": {"GameMode": 6, "PlayerStateData": {"UniverseAddress": {"RealityIndex": 0, "GalacticAddress": {"VoxelX": 0, "VoxelY": 0, "VoxelZ": 0, "SolarSystemIndex": 0, "PlanetIndex": 0}}, "Units": 0, "Nanites": 0, "Specials": 0, "PersistentPlayerBases": []}},
183 "DiscoveryManagerData": {"DiscoveryData-v1": {"ReserveStore": 0, "ReserveManaged": 0, "Store": {"Record": [
184 {"DD": {"UA": "0x050003AB8C07", "DT": "SolarSystem", "VP": []}, "DM": {}, "OWS": {"LID":"","UID":"1","USN":"","PTK":"ST","TS":0}, "FL": {"U": 1}}
185 ]}}}
186 }"#;
187 let save = nms_save::parse_save(json.as_bytes()).unwrap();
188 let model = nms_graph::GalaxyModel::from_save(&save);
189 let session = crate::session::SessionState::from_model(&model);
190 let ps = PromptState::from_session(&session);
191 assert_eq!(ps.galaxy_name, "Euclid");
192 assert!(ps.biome_filter.is_none());
193 }
194}