rpfm_extensions/search/atlas.rs
1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11use getset::{Getters, MutGetters, Setters};
12use serde_derive::{Deserialize, Serialize};
13
14use rpfm_lib::files::atlas::Atlas;
15
16use super::{find_in_string, MatchingMode, replace_match_string, Replaceable, SearchSource, Searchable};
17
18//-------------------------------------------------------------------------------//
19// Enums & Structs
20//-------------------------------------------------------------------------------//
21
22/// This struct represents all the matches of the global search within an Atlas File.
23#[derive(Debug, Clone, Getters, MutGetters, Setters, Serialize, Deserialize)]
24#[getset(get = "pub", get_mut = "pub", set = "pub")]
25pub struct AtlasMatches {
26
27 /// The path of the file.
28 path: String,
29
30 /// The search source that produced these matches.
31 #[serde(default)]
32 source: SearchSource,
33
34 /// The container name (pack file name) this file belongs to.
35 #[serde(default)]
36 container_name: String,
37
38 /// The list of matches within the file.
39 matches: Vec<AtlasMatch>,
40}
41
42/// This struct represents a match within an Atlas File.
43#[derive(Debug, Clone, Eq, PartialEq, Getters, MutGetters, Serialize, Deserialize)]
44#[getset(get = "pub", get_mut = "pub")]
45pub struct AtlasMatch {
46
47 /// The name of the column where the match is.
48 column_name: String,
49
50 /// The logical index of the column where the match is.
51 column_number: u32,
52
53 /// The row number of this match.
54 row_number: i64,
55
56 /// Byte where the match starts.
57 start: usize,
58
59 /// Byte where the match ends.
60 end: usize,
61
62 /// The contents of the matched cell.
63 text: String,
64}
65
66//-------------------------------------------------------------------------------//
67// Implementations
68//-------------------------------------------------------------------------------//
69
70impl Searchable for Atlas {
71 type SearchMatches = AtlasMatches;
72
73 fn search(&self, file_path: &str, pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode) -> AtlasMatches {
74 let mut matches = AtlasMatches::new(file_path);
75
76 match matching_mode {
77 MatchingMode::Regex(regex) => {
78 for (row, entry) in self.entries().iter().enumerate() {
79 for entry_match in regex.find_iter(entry.string1()) {
80 matches.matches.push(
81 AtlasMatch::new(
82 "String1",
83 0,
84 row as i64,
85 entry_match.start(),
86 entry_match.end(),
87 entry.string1(),
88 )
89 );
90 }
91
92 for entry_match in regex.find_iter(entry.string2()) {
93 matches.matches.push(
94 AtlasMatch::new(
95 "String2",
96 0,
97 row as i64,
98 entry_match.start(),
99 entry_match.end(),
100 entry.string2(),
101 )
102 );
103 }
104 }
105 }
106
107 MatchingMode::Pattern(regex) => {
108 let pattern = if case_sensitive || regex.is_some() {
109 pattern.to_owned()
110 } else {
111 pattern.to_lowercase()
112 };
113
114 for (row, entry) in self.entries().iter().enumerate() {
115 for (start, end, _) in &find_in_string(entry.string1(), &pattern, case_sensitive, regex) {
116 matches.matches.push(
117 AtlasMatch::new(
118 "String1",
119 0,
120 row as i64,
121 *start,
122 *end,
123 entry.string1(),
124 )
125 );
126 }
127
128 for (start, end, _) in &find_in_string(entry.string2(), &pattern, case_sensitive, regex) {
129 matches.matches.push(
130 AtlasMatch::new(
131 "String2",
132 1,
133 row as i64,
134 *start,
135 *end,
136 entry.string2(),
137 )
138 );
139 }
140 }
141 }
142 }
143
144 matches
145 }
146}
147
148impl Replaceable for Atlas {
149
150 fn replace(&mut self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, search_matches: &AtlasMatches) -> bool {
151 let mut edited = false;
152
153 // NOTE: Due to changes in index positions, we need to do this in reverse.
154 // Otherwise we may cause one edit to generate invalid indexes for the next matches.
155 for search_match in search_matches.matches().iter().rev() {
156 edited |= search_match.replace(pattern, replace_pattern, case_sensitive, matching_mode, self);
157 }
158
159 edited
160 }
161}
162
163impl AtlasMatches {
164
165 /// This function creates a new `AtlasMatches` for the provided path.
166 pub fn new(path: &str) -> Self {
167 Self {
168 path: path.to_owned(),
169 matches: vec![],
170 source: SearchSource::default(),
171 container_name: String::new(),
172 }
173 }
174}
175
176impl AtlasMatch {
177
178 /// This function creates a new `AtlasMatch` with the provided data.
179 pub fn new(column_name: &str, column_number: u32, row_number: i64, start: usize, end: usize, contents: &str) -> Self {
180 Self {
181 column_name: column_name.to_owned(),
182 column_number,
183 row_number,
184 start,
185 end,
186 text: contents.to_owned(),
187 }
188 }
189
190 /// This function replaces all the matches in the provided data.
191 fn replace(&self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, data: &mut Atlas) -> bool {
192 let mut edited = false;
193
194 if let Some(entry) = data.entries_mut().get_mut(self.row_number as usize) {
195
196 // Get all the previous data and references of data to manipulate here, so we don't duplicate a lot of code per-field in the match mode part.
197 let (previous_data, current_data) = {
198 if self.column_number == 0 {
199 (entry.string1().to_owned(), entry.string1_mut())
200 } else if self.column_number == 1 {
201 (entry.string2().to_owned(), entry.string2_mut())
202 }
203
204 // This is an error.
205 else {
206 return false
207 }
208 };
209
210 edited = replace_match_string(pattern, replace_pattern, case_sensitive, matching_mode, self.start, self.end, &previous_data, current_data);
211 }
212
213 edited
214 }
215}