yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
message_list_query.h
Go to the documentation of this file.
1#ifndef YAZE_APP_EDITOR_MESSAGE_MESSAGE_LIST_QUERY_H_
2#define YAZE_APP_EDITOR_MESSAGE_MESSAGE_LIST_QUERY_H_
3
4#include <algorithm>
5#include <cctype>
6#include <cstddef>
7#include <cstdint>
8#include <cstdio>
9#include <optional>
10#include <string>
11#include <string_view>
12
13namespace yaze {
14namespace editor {
15
16// Which bank the message list is showing. Independent of find/replace
17// case-sensitivity: the list filter is always a case-insensitive browse.
19 kAll = 0,
20 kVanilla = 1,
21 kExpanded = 2,
22};
23
24inline char AsciiLower(unsigned char c) {
25 return static_cast<char>(std::tolower(c));
26}
27
28inline bool ContainsCaseInsensitive(std::string_view haystack,
29 std::string_view needle) {
30 if (needle.empty()) {
31 return true;
32 }
33 if (needle.size() > haystack.size()) {
34 return false;
35 }
36 for (size_t i = 0; i + needle.size() <= haystack.size(); ++i) {
37 bool match = true;
38 for (size_t j = 0; j < needle.size(); ++j) {
39 if (AsciiLower(static_cast<unsigned char>(haystack[i + j])) !=
40 AsciiLower(static_cast<unsigned char>(needle[j]))) {
41 match = false;
42 break;
43 }
44 }
45 if (match) {
46 return true;
47 }
48 }
49 return false;
50}
51
52inline std::string_view StripHexPrefix(std::string_view query) {
53 if (query.size() >= 2 && query[0] == '0' &&
54 (query[1] == 'x' || query[1] == 'X')) {
55 return query.substr(2);
56 }
57 if (!query.empty() && (query[0] == '$' || query[0] == '#')) {
58 return query.substr(1);
59 }
60 return query;
61}
62
63inline bool HexDigitsContain(uint32_t value, int width,
64 std::string_view query) {
65 query = StripHexPrefix(query);
66 if (query.empty() || width <= 0 || width >= 16) {
67 return false;
68 }
69 char buf[16];
70 static const char* digits = "0123456789ABCDEF";
71 uint32_t remaining = value;
72 for (int i = width - 1; i >= 0; --i) {
73 buf[i] = digits[remaining & 0xF];
74 remaining >>= 4;
75 }
77 std::string_view(buf, static_cast<size_t>(width)), query);
78}
79
80inline bool DecimalContains(int value, std::string_view query) {
81 char buf[16];
82 const int written = std::snprintf(buf, sizeof(buf), "%d", value);
83 if (written <= 0) {
84 return false;
85 }
87 std::string_view(buf, static_cast<size_t>(written)), query);
88}
89
90// True when a list row should stay visible for the current bank + text filter.
91inline bool MessageListQueryMatches(int display_id, uint32_t address,
92 std::string_view contents, bool expanded,
94 std::string_view query) {
95 if (bank == MessageBankFilter::kVanilla && expanded) {
96 return false;
97 }
98 if (bank == MessageBankFilter::kExpanded && !expanded) {
99 return false;
100 }
101 if (query.empty()) {
102 return true;
103 }
104 if (ContainsCaseInsensitive(contents, query)) {
105 return true;
106 }
107 if (DecimalContains(display_id, query)) {
108 return true;
109 }
110 const int id_width = display_id > 0xFFFF ? 6 : 4;
111 if (HexDigitsContain(static_cast<uint32_t>(display_id), id_width, query)) {
112 return true;
113 }
114 return HexDigitsContain(address, 6, query);
115}
116
117inline bool IsWordChar(std::string_view text, size_t index) {
118 if (index >= text.size()) {
119 return false;
120 }
121 return std::isalnum(static_cast<unsigned char>(text[index])) != 0;
122}
123
124// Next match at or after `start`. Empty needle never matches.
125inline std::optional<size_t> FindNextTextMatch(std::string_view text,
126 std::string_view needle,
127 size_t start,
128 bool case_sensitive,
129 bool whole_word) {
130 if (needle.empty() || start > text.size()) {
131 return std::nullopt;
132 }
133 for (size_t i = start; i + needle.size() <= text.size(); ++i) {
134 bool match = true;
135 for (size_t j = 0; j < needle.size(); ++j) {
136 unsigned char left = static_cast<unsigned char>(text[i + j]);
137 unsigned char right = static_cast<unsigned char>(needle[j]);
138 if (!case_sensitive) {
139 left = static_cast<unsigned char>(AsciiLower(left));
140 right = static_cast<unsigned char>(AsciiLower(right));
141 }
142 if (left != right) {
143 match = false;
144 break;
145 }
146 }
147 if (!match) {
148 continue;
149 }
150 if (whole_word) {
151 const bool start_ok = (i == 0) || !IsWordChar(text, i - 1);
152 const bool end_ok = !IsWordChar(text, i + needle.size());
153 if (!start_ok || !end_ok) {
154 continue;
155 }
156 }
157 return i;
158 }
159 return std::nullopt;
160}
161
162// Last match strictly before `before`. `npos` takes the last match in `text`.
163inline std::optional<size_t> FindPreviousTextMatch(std::string_view text,
164 std::string_view needle,
165 size_t before,
166 bool case_sensitive,
167 bool whole_word) {
168 if (needle.empty() || before == 0) {
169 return std::nullopt;
170 }
171 std::optional<size_t> last;
172 size_t from = 0;
173 while (auto found = FindNextTextMatch(text, needle, from, case_sensitive,
174 whole_word)) {
175 if (before != std::string::npos && *found >= before) {
176 break;
177 }
178 last = found;
179 from = *found + 1;
180 if (from > text.size()) {
181 break;
182 }
183 }
184 return last;
185}
186
187// First physical line, capped, so the message list stays one row tall.
188inline std::string MessageListPreview(std::string_view text,
189 size_t max_chars = 96) {
190 std::string line;
191 line.reserve(std::min(text.size(), max_chars + 3));
192 for (char c : text) {
193 if (c == '\n' || c == '\r') {
194 break;
195 }
196 line.push_back(c);
197 if (line.size() >= max_chars) {
198 line.append("...");
199 break;
200 }
201 }
202 return line;
203}
204
205} // namespace editor
206} // namespace yaze
207
208#endif // YAZE_APP_EDITOR_MESSAGE_MESSAGE_LIST_QUERY_H_
bool IsWordChar(std::string_view text, size_t index)
std::string_view StripHexPrefix(std::string_view query)
bool ContainsCaseInsensitive(std::string_view haystack, std::string_view needle)
char AsciiLower(unsigned char c)
std::string MessageListPreview(std::string_view text, size_t max_chars=96)
bool DecimalContains(int value, std::string_view query)
bool HexDigitsContain(uint32_t value, int width, std::string_view query)
std::optional< size_t > FindNextTextMatch(std::string_view text, std::string_view needle, size_t start, bool case_sensitive, bool whole_word)
std::optional< size_t > FindPreviousTextMatch(std::string_view text, std::string_view needle, size_t before, bool case_sensitive, bool whole_word)
bool MessageListQueryMatches(int display_id, uint32_t address, std::string_view contents, bool expanded, MessageBankFilter bank, std::string_view query)