Skip to main content

rpfm_extensions/search/
unit_variant.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::unit_variant::UnitVariant;
15
16use super::{find_in_string, MatchingMode, Replaceable, SearchSource, Searchable, replace_match_string};
17
18//-------------------------------------------------------------------------------//
19//                              Enums & Structs
20//-------------------------------------------------------------------------------//
21
22/// This struct represents all the matches of the global search within an UnitVariant File.
23#[derive(Debug, Clone, Getters, MutGetters, Setters, Serialize, Deserialize)]
24#[getset(get = "pub", get_mut = "pub", set = "pub")]
25pub struct UnitVariantMatches {
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<UnitVariantMatch>,
40}
41
42/// This struct represents a match within an UnitVariant File.
43#[derive(Debug, Clone, Eq, PartialEq, Getters, MutGetters, Serialize, Deserialize)]
44#[getset(get = "pub", get_mut = "pub")]
45pub struct UnitVariantMatch {
46
47    /// The index of the entry in question in the UnitVariant file. Not sure if the ids are unique, so we use the index.
48    entry: usize,
49
50    /// If the match corresponds to the name.
51    name: bool,
52
53    /// If the match corresponds to a variant value. We have their index and a bool for each value.
54    variant: Option<(usize, bool, bool)>,
55
56    /// Byte where the match starts.
57    start: usize,
58
59    /// Byte where the match ends.
60    end: usize,
61
62    /// Matched data.
63    text: String,
64}
65
66//-------------------------------------------------------------------------------//
67//                             Implementations
68//-------------------------------------------------------------------------------//
69
70impl Searchable for UnitVariant {
71    type SearchMatches = UnitVariantMatches;
72
73    fn search(&self, file_path: &str, pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode) -> UnitVariantMatches {
74        let mut matches = UnitVariantMatches::new(file_path);
75
76        match matching_mode {
77            MatchingMode::Regex(regex) => {
78                for (index, data) in self.categories().iter().enumerate() {
79                    for entry_match in regex.find_iter(data.name()) {
80                        matches.matches.push(
81                            UnitVariantMatch::new(
82                                index,
83                                true,
84                                None,
85                                entry_match.start(),
86                                entry_match.end(),
87                                data.name().to_owned()
88                            )
89                        );
90                    }
91
92                    for (vindex, variant) in data.variants().iter().enumerate() {
93                        for entry_match in regex.find_iter(variant.mesh_file()) {
94                            matches.matches.push(
95                                UnitVariantMatch::new(
96                                    index,
97                                    false,
98                                    Some((vindex, true, false)),
99                                    entry_match.start(),
100                                    entry_match.end(),
101                                    variant.mesh_file().to_owned()
102                                )
103                            );
104                        }
105
106                        for entry_match in regex.find_iter(variant.texture_folder()) {
107                            matches.matches.push(
108                                UnitVariantMatch::new(
109                                    index,
110                                    false,
111                                    Some((vindex, false, true)),
112                                    entry_match.start(),
113                                    entry_match.end(),
114                                    variant.texture_folder().to_owned()
115                                )
116                            );
117                        }
118                    }
119                }
120            }
121
122            MatchingMode::Pattern(regex) => {
123                for (index, data) in self.categories().iter().enumerate() {
124                    for (start, end, _) in &find_in_string(data.name(), pattern, case_sensitive, regex) {
125                        matches.matches.push(
126                            UnitVariantMatch::new(
127                                index,
128                                true,
129                                None,
130                                *start,
131                                *end,
132                                data.name().to_owned()
133                            )
134                        );
135                    }
136
137                    for (vindex, variant) in data.variants().iter().enumerate() {
138                        for (start, end, _) in &find_in_string(variant.mesh_file(), pattern, case_sensitive, regex) {
139                            matches.matches.push(
140                                UnitVariantMatch::new(
141                                    index,
142                                    false,
143                                    Some((vindex, true, false)),
144                                    *start,
145                                    *end,
146                                    variant.mesh_file().to_owned()
147                                )
148                            );
149                        }
150
151                        for (start, end, _) in &find_in_string(variant.texture_folder(), pattern, case_sensitive, regex) {
152                            matches.matches.push(
153                                UnitVariantMatch::new(
154                                    index,
155                                    false,
156                                    Some((vindex, false, true)),
157                                    *start,
158                                    *end,
159                                    variant.texture_folder().to_owned()
160                                )
161                            );
162                        }
163                    }
164                }
165            }
166        }
167
168        matches
169    }
170}
171
172impl Replaceable for UnitVariant {
173
174    fn replace(&mut self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, search_matches: &UnitVariantMatches) -> bool {
175        let mut edited = false;
176
177        // NOTE: Due to changes in index positions, we need to do this in reverse.
178        // Otherwise we may cause one edit to generate invalid indexes for the next matches.
179        for search_match in search_matches.matches().iter().rev() {
180            edited |= search_match.replace(pattern, replace_pattern, case_sensitive, matching_mode, self);
181        }
182
183        edited
184    }
185}
186
187impl UnitVariantMatches {
188
189    /// This function creates a new `UnitVariantMatches` for the provided path.
190    pub fn new(path: &str) -> Self {
191        Self {
192            path: path.to_owned(),
193            matches: vec![],
194            source: SearchSource::default(),
195            container_name: String::new(),
196        }
197    }
198}
199
200impl UnitVariantMatch {
201
202    /// This function creates a new `UnitVariantMatch` with the provided data.
203    pub fn new(entry: usize, name: bool, variant: Option<(usize, bool, bool)>, start: usize, end: usize, text: String) -> Self {
204        Self {
205            entry,
206            name,
207            variant,
208            start,
209            end,
210            text
211        }
212    }
213
214    /// This function replaces all the matches in the provided data.
215    fn replace(&self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, data: &mut UnitVariant) -> bool {
216        let mut edited = false;
217
218        if let Some(entry) = data.categories_mut().get_mut(self.entry) {
219
220            // 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.
221            let (previous_data, current_data) = {
222                if self.name {
223                    (entry.name().to_owned(), entry.name_mut())
224                } else if let Some((vindex, mesh_file, texture_folder)) = self.variant {
225                    match entry.variants_mut().get_mut(vindex) {
226                        Some(variant) => {
227                            if mesh_file {
228                                (variant.mesh_file().to_owned(), variant.mesh_file_mut())
229                            } else if texture_folder {
230                                (variant.texture_folder().to_owned(), variant.texture_folder_mut())
231                            } else {
232                                return false;
233                            }
234                        }
235                        None => return false,
236                    }
237                }
238
239                // This is an error.
240                else {
241                    return false
242                }
243            };
244
245            edited = replace_match_string(pattern, replace_pattern, case_sensitive, matching_mode, self.start, self.end, &previous_data, current_data);
246        }
247
248        edited
249    }
250}