atmos/os_lib/web_engine/
omnibox.rs1#![allow(dead_code)]
3
4extern crate alloc;
5use alloc::format;
6use alloc::string::String;
7use alloc::vec::Vec;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum OmniboxItemKind {
12 Bookmark,
14 History,
16 Search,
18}
19
20impl OmniboxItemKind {
21 pub fn badge_label(&self) -> &'static str {
22 match self {
23 OmniboxItemKind::Bookmark => "Bookmark",
24 OmniboxItemKind::History => "History",
25 OmniboxItemKind::Search => "DuckDuckGo",
26 }
27 }
28
29 pub fn icon_char(&self) -> &'static str {
30 match self {
31 OmniboxItemKind::Bookmark => "*",
32 OmniboxItemKind::History => ">",
33 OmniboxItemKind::Search => "?",
34 }
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct OmniboxItem {
41 pub title: String,
42 pub url: String,
43 pub kind: OmniboxItemKind,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct Bookmark {
49 pub title: String,
50 pub url: String,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct HistoryEntry {
56 pub title: String,
57 pub url: String,
58 pub visit_count: u32,
59}
60
61#[derive(Debug, Clone)]
63pub struct OmniboxEngine {
64 pub bookmarks: Vec<Bookmark>,
65 pub history: Vec<HistoryEntry>,
66}
67
68impl Default for OmniboxEngine {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl OmniboxEngine {
75 pub fn new() -> Self {
76 let mut engine = Self {
77 bookmarks: Vec::new(),
78 history: Vec::new(),
79 };
80
81 engine.add_bookmark("AtmOS Home", "http://localhost/home");
83 engine.add_bookmark("DuckDuckGo", "https://duckduckgo.com/");
84 engine.add_bookmark("Sugi Lab (Nagoya Univ)", "https://www.sugi-lab.net/");
85 engine.add_bookmark("YouTube", "https://www.youtube.com/");
86 engine.add_bookmark("AtmOS GitHub", "https://github.com/sugi-lab/AtmOS");
87
88 engine
89 }
90
91 pub fn add_bookmark(&mut self, title: &str, url: &str) -> bool {
93 let clean_url = url.trim();
94 if clean_url.is_empty() {
95 return false;
96 }
97
98 if let Some(existing) = self.bookmarks.iter_mut().find(|b| b.url == clean_url) {
99 existing.title = title.trim().into();
100 return false;
101 }
102
103 self.bookmarks.push(Bookmark {
104 title: title.trim().into(),
105 url: clean_url.into(),
106 });
107 true
108 }
109
110 pub fn remove_bookmark(&mut self, url: &str) -> bool {
112 let clean_url = url.trim();
113 let initial_len = self.bookmarks.len();
114 self.bookmarks.retain(|b| b.url != clean_url);
115 self.bookmarks.len() < initial_len
116 }
117
118 pub fn is_bookmarked(&self, url: &str) -> bool {
120 let clean = url.trim();
121 self.bookmarks.iter().any(|b| b.url == clean)
122 }
123
124 pub fn toggle_bookmark(&mut self, title: &str, url: &str) -> bool {
126 if self.is_bookmarked(url) {
127 self.remove_bookmark(url);
128 false
129 } else {
130 self.add_bookmark(title, url);
131 true
132 }
133 }
134
135 pub fn record_visit(&mut self, title: &str, url: &str) {
137 let clean_url = url.trim();
138 if clean_url.is_empty() || clean_url == "about:blank" {
139 return;
140 }
141
142 if let Some(pos) = self.history.iter().position(|h| h.url == clean_url) {
144 let mut entry = self.history.remove(pos);
145 entry.visit_count = entry.visit_count.saturating_add(1);
146 if !title.trim().is_empty() {
147 entry.title = title.trim().into();
148 }
149 self.history.insert(0, entry);
150 return;
151 }
152
153 self.history.insert(0, HistoryEntry {
155 title: title.trim().into(),
156 url: clean_url.into(),
157 visit_count: 1,
158 });
159
160 if self.history.len() > 100 {
162 self.history.truncate(100);
163 }
164 }
165
166 pub fn get_suggestions(
173 &self,
174 query: &str,
175 search_suggestions: &[String],
176 max_items: usize,
177 ) -> Vec<OmniboxItem> {
178 let q = query.trim().to_ascii_lowercase();
179 let mut results = Vec::new();
180
181 if q.is_empty() {
182 for b in self.bookmarks.iter().take(max_items) {
184 results.push(OmniboxItem {
185 title: b.title.clone(),
186 url: b.url.clone(),
187 kind: OmniboxItemKind::Bookmark,
188 });
189 }
190 return results;
191 }
192
193 for b in &self.bookmarks {
195 let t_lower = b.title.to_ascii_lowercase();
196 let u_lower = b.url.to_ascii_lowercase();
197 if t_lower.contains(&q) || u_lower.contains(&q) {
198 results.push(OmniboxItem {
199 title: b.title.clone(),
200 url: b.url.clone(),
201 kind: OmniboxItemKind::Bookmark,
202 });
203 if results.len() >= max_items {
204 return results;
205 }
206 }
207 }
208
209 for h in &self.history {
211 let t_lower = h.title.to_ascii_lowercase();
212 let u_lower = h.url.to_ascii_lowercase();
213 if t_lower.contains(&q) || u_lower.contains(&q) {
214 if !results.iter().any(|r| r.url == h.url) {
215 let disp_title = if h.title.is_empty() {
216 h.url.clone()
217 } else {
218 h.title.clone()
219 };
220 results.push(OmniboxItem {
221 title: disp_title,
222 url: h.url.clone(),
223 kind: OmniboxItemKind::History,
224 });
225 if results.len() >= max_items {
226 return results;
227 }
228 }
229 }
230 }
231
232 for s in search_suggestions {
234 if !results.iter().any(|r| r.title.eq_ignore_ascii_case(s)) {
235 let search_url = format!("https://duckduckgo.com/?q={}", super::duckduckgo::url_encode(s));
236 results.push(OmniboxItem {
237 title: s.clone(),
238 url: search_url,
239 kind: OmniboxItemKind::Search,
240 });
241 if results.len() >= max_items {
242 return results;
243 }
244 }
245 }
246
247 results
248 }
249}