@bniladridas/cursor 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clang-tidy +28 -0
- package/.dockerignore +56 -0
- package/.env.example +29 -0
- package/.github/CODEOWNERS +2 -0
- package/.github/ISSUE_TEMPLATE/blank.md +27 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/SECURITY.md +24 -0
- package/.github/codeql/codeql-config.yml +8 -0
- package/.github/dependabot.yml +14 -0
- package/.github/labeler.yml +50 -0
- package/.github/packaging/brand-cursor.png +0 -0
- package/.github/packaging/database/init.sql +48 -0
- package/.github/packaging/docker/Dockerfile +111 -0
- package/.github/packaging/docker/docker-compose.yml +56 -0
- package/.github/packaging/scripts/preflight.sh +413 -0
- package/.github/packaging/scripts/prepare-release.sh +141 -0
- package/.github/packaging/scripts/release.sh +22 -0
- package/.github/packaging/scripts/setup-git-hooks.sh +73 -0
- package/.github/pull_request_template.md +31 -0
- package/.github/signed.json +9 -0
- package/.github/workflows/README.md +23 -0
- package/.github/workflows/ci.yml +181 -0
- package/.github/workflows/cla.yml +33 -0
- package/.github/workflows/formula-sha.yml +63 -0
- package/.github/workflows/issue-response.yml +44 -0
- package/.github/workflows/labeler.yml +42 -0
- package/.github/workflows/pr-body.yml +49 -0
- package/.github/workflows/release.yml +176 -0
- package/.github/workflows/security.yml +94 -0
- package/.github/workflows/stale.yml +38 -0
- package/AGENTS.md +49 -0
- package/CHANGELOG.md +3 -0
- package/CMakeLists.txt +646 -0
- package/Formula/cursor.rb +46 -0
- package/LICENSE +201 -0
- package/Makefile +28 -0
- package/README.md +46 -0
- package/cli.js +16 -0
- package/include/agent.h +86 -0
- package/include/agent_mode.h +17 -0
- package/include/memory_manager.h +102 -0
- package/include/services/ai_service.h +31 -0
- package/include/services/auth_service.h +87 -0
- package/include/services/checkpoint_service.h +69 -0
- package/include/services/codebase_service.h +38 -0
- package/include/services/command_service.h +23 -0
- package/include/services/context_service.h +74 -0
- package/include/services/database_service.h +56 -0
- package/include/services/error_service.h +106 -0
- package/include/services/file_service.h +51 -0
- package/include/services/git_service.h +29 -0
- package/include/services/github_service.h +85 -0
- package/include/services/mcp_service.h +85 -0
- package/include/services/multi_file_service.h +93 -0
- package/include/services/sandbox_service.h +96 -0
- package/include/services/theme_service.h +67 -0
- package/include/services/web_service.h +52 -0
- package/include/utils/config.h +68 -0
- package/include/utils/memory_utils.h +79 -0
- package/include/utils/platform.h +56 -0
- package/include/utils/ui.h +43 -0
- package/include/utils/validation.h +63 -0
- package/include/utils/version.h.in +17 -0
- package/install.js +49 -0
- package/package.json +16 -0
- package/release/checksums.txt +3 -0
- package/release/cursor-linux/cursor_v0.1.7_linux_amd64.tar.gz +0 -0
- package/release/cursor-macos/cursor_v0.1.7_darwin_arm64.tar.gz +0 -0
- package/release/cursor-windows/cursor__windows_amd64.zip +0 -0
- package/src/agent.cpp +2026 -0
- package/src/main.cpp +97 -0
- package/src/memory_manager.cpp +814 -0
- package/src/services/ai_service.cpp +366 -0
- package/src/services/auth_service.cpp +779 -0
- package/src/services/checkpoint_service.cpp +465 -0
- package/src/services/codebase_service.cpp +233 -0
- package/src/services/command_service.cpp +82 -0
- package/src/services/context_service.cpp +348 -0
- package/src/services/database_service.cpp +148 -0
- package/src/services/error_service.cpp +438 -0
- package/src/services/file_service.cpp +349 -0
- package/src/services/git_service.cpp +148 -0
- package/src/services/github_service.cpp +435 -0
- package/src/services/mcp_service.cpp +481 -0
- package/src/services/multi_file_service.cpp +591 -0
- package/src/services/sandbox_service.cpp +678 -0
- package/src/services/theme_service.cpp +429 -0
- package/src/services/web_service.cpp +532 -0
- package/src/utils/config.cpp +77 -0
- package/src/utils/memory_utils.cpp +93 -0
- package/src/utils/ui.cpp +307 -0
- package/src/utils/validation.cpp +306 -0
- package/src/utils/version.cpp +175 -0
- package/tests/e2e/docker-compose.yml +195 -0
- package/tests/e2e/run_e2e_tests.sh +70 -0
- package/tests/e2e/run_tests_in_docker.sh +115 -0
- package/tests/main_test.cpp +16 -0
- package/tests/mocks/mock_ollama.py +98 -0
- package/tests/mocks/start_nginx.sh +64 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
#include "services/multi_file_service.h"
|
|
2
|
+
#include "utils/platform.h"
|
|
3
|
+
#include "utils/validation.h"
|
|
4
|
+
#include <algorithm>
|
|
5
|
+
#include <filesystem>
|
|
6
|
+
#include <fstream>
|
|
7
|
+
#include <iostream>
|
|
8
|
+
#include <map>
|
|
9
|
+
#include <regex>
|
|
10
|
+
#include <sstream>
|
|
11
|
+
|
|
12
|
+
namespace Services {
|
|
13
|
+
|
|
14
|
+
std::vector<FileMatch>
|
|
15
|
+
MultiFileService::read_many_files(const std::vector<std::string> &paths,
|
|
16
|
+
const MultiFileOptions &options) {
|
|
17
|
+
std::vector<FileMatch> results;
|
|
18
|
+
size_t total_size = 0;
|
|
19
|
+
|
|
20
|
+
for (const auto &path : paths) {
|
|
21
|
+
if (results.size() >= options.max_files) {
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
if (std::filesystem::is_directory(path)) {
|
|
27
|
+
auto dir_files = read_directory_files(path, options);
|
|
28
|
+
for (auto &file : dir_files) {
|
|
29
|
+
if (results.size() >= options.max_files ||
|
|
30
|
+
total_size >= options.max_total_size) {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
total_size += file.size;
|
|
34
|
+
results.push_back(std::move(file));
|
|
35
|
+
}
|
|
36
|
+
} else if (std::filesystem::is_regular_file(path)) {
|
|
37
|
+
if (should_include_file(path, options)) {
|
|
38
|
+
FileMatch match;
|
|
39
|
+
match.file_path = std::filesystem::path(path).string();
|
|
40
|
+
match.relative_path = std::filesystem::relative(path).string();
|
|
41
|
+
match.file_type = get_file_type(path);
|
|
42
|
+
|
|
43
|
+
// Read file content
|
|
44
|
+
std::ifstream file(path);
|
|
45
|
+
if (file.is_open()) {
|
|
46
|
+
std::ostringstream content;
|
|
47
|
+
content << file.rdbuf();
|
|
48
|
+
match.content = content.str();
|
|
49
|
+
match.size = match.content.size();
|
|
50
|
+
|
|
51
|
+
if (match.size <= options.max_file_size &&
|
|
52
|
+
total_size + match.size <= options.max_total_size) {
|
|
53
|
+
total_size += match.size;
|
|
54
|
+
results.push_back(std::move(match));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch (const std::exception &e) {
|
|
60
|
+
std::cerr << "Error processing path " << path << ": " << e.what()
|
|
61
|
+
<< std::endl;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
std::vector<FileMatch>
|
|
69
|
+
MultiFileService::read_directory_files(const std::string &directory,
|
|
70
|
+
const MultiFileOptions &options) {
|
|
71
|
+
std::vector<FileMatch> results;
|
|
72
|
+
size_t total_size = 0;
|
|
73
|
+
|
|
74
|
+
// Load gitignore patterns if enabled
|
|
75
|
+
std::set<std::string> gitignore_patterns;
|
|
76
|
+
if (options.respect_gitignore) {
|
|
77
|
+
gitignore_patterns = load_gitignore_patterns(directory);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Get Git status filtered files if Git filtering is enabled
|
|
81
|
+
std::vector<std::string> git_filtered_files;
|
|
82
|
+
std::set<std::string> git_files_set;
|
|
83
|
+
if (options.git_filter != GitStatusFilter::ALL) {
|
|
84
|
+
git_filtered_files = get_git_status_files(directory, options.git_filter);
|
|
85
|
+
git_files_set = std::set<std::string>(git_filtered_files.begin(),
|
|
86
|
+
git_filtered_files.end());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
if (options.recursive) {
|
|
91
|
+
for (const auto &entry :
|
|
92
|
+
std::filesystem::recursive_directory_iterator(directory)) {
|
|
93
|
+
if (results.size() >= options.max_files ||
|
|
94
|
+
total_size >= options.max_total_size) {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!entry.is_regular_file()) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
std::string file_path = entry.path().string();
|
|
103
|
+
|
|
104
|
+
// Check gitignore patterns
|
|
105
|
+
if (options.respect_gitignore &&
|
|
106
|
+
matches_gitignore(file_path, gitignore_patterns)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check include/exclude patterns
|
|
111
|
+
if (!should_include_file(file_path, options)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Create file match
|
|
116
|
+
FileMatch match;
|
|
117
|
+
match.file_path = file_path;
|
|
118
|
+
match.relative_path =
|
|
119
|
+
std::filesystem::relative(file_path, directory).string();
|
|
120
|
+
match.file_type = get_file_type(file_path);
|
|
121
|
+
match.size = entry.file_size();
|
|
122
|
+
|
|
123
|
+
// Skip if file is too large
|
|
124
|
+
if (match.size > options.max_file_size) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Read content
|
|
129
|
+
std::ifstream file(file_path);
|
|
130
|
+
if (file.is_open()) {
|
|
131
|
+
std::ostringstream content;
|
|
132
|
+
content << file.rdbuf();
|
|
133
|
+
match.content = content.str();
|
|
134
|
+
|
|
135
|
+
if (total_size + match.size <= options.max_total_size) {
|
|
136
|
+
total_size += match.size;
|
|
137
|
+
results.push_back(std::move(match));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
for (const auto &entry : std::filesystem::directory_iterator(directory)) {
|
|
143
|
+
if (results.size() >= options.max_files ||
|
|
144
|
+
total_size >= options.max_total_size) {
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!entry.is_regular_file()) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
std::string file_path = entry.path().string();
|
|
153
|
+
|
|
154
|
+
// Check gitignore patterns
|
|
155
|
+
if (options.respect_gitignore &&
|
|
156
|
+
matches_gitignore(file_path, gitignore_patterns)) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check include/exclude patterns
|
|
161
|
+
if (!should_include_file(file_path, options)) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Create file match
|
|
166
|
+
FileMatch match;
|
|
167
|
+
match.file_path = file_path;
|
|
168
|
+
match.relative_path =
|
|
169
|
+
std::filesystem::relative(file_path, directory).string();
|
|
170
|
+
match.file_type = get_file_type(file_path);
|
|
171
|
+
match.size = entry.file_size();
|
|
172
|
+
|
|
173
|
+
// Skip if file is too large
|
|
174
|
+
if (match.size > options.max_file_size) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Read content
|
|
179
|
+
std::ifstream file(file_path);
|
|
180
|
+
if (file.is_open()) {
|
|
181
|
+
std::ostringstream content;
|
|
182
|
+
content << file.rdbuf();
|
|
183
|
+
match.content = content.str();
|
|
184
|
+
|
|
185
|
+
if (total_size + match.size <= options.max_total_size) {
|
|
186
|
+
total_size += match.size;
|
|
187
|
+
results.push_back(std::move(match));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} catch (const std::exception &e) {
|
|
193
|
+
std::cerr << "Error reading directory " << directory << ": " << e.what()
|
|
194
|
+
<< std::endl;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return results;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
bool MultiFileService::should_include_file(const std::string &file_path,
|
|
201
|
+
const MultiFileOptions &options) {
|
|
202
|
+
// Check if hidden files should be included
|
|
203
|
+
std::string filename = std::filesystem::path(file_path).filename().string();
|
|
204
|
+
if (!options.include_hidden_files && !filename.empty() &&
|
|
205
|
+
filename[0] == '.') {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check if only text files should be included
|
|
210
|
+
if (options.only_text_files && !is_text_file(file_path)) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check language filters
|
|
215
|
+
if (!matches_language_filter(file_path, options.language_filters)) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Check exclude patterns first
|
|
220
|
+
for (const auto &pattern : options.exclude_patterns) {
|
|
221
|
+
if (matches_pattern(file_path, pattern)) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Check default exclude patterns
|
|
227
|
+
auto default_excludes = get_default_exclude_patterns();
|
|
228
|
+
for (const auto &pattern : default_excludes) {
|
|
229
|
+
if (matches_pattern(file_path, pattern)) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// If no include patterns specified, include by default
|
|
235
|
+
if (options.include_patterns.empty()) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check include patterns
|
|
240
|
+
for (const auto &pattern : options.include_patterns) {
|
|
241
|
+
if (matches_pattern(file_path, pattern)) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
std::string MultiFileService::get_file_type(const std::string &file_path) {
|
|
250
|
+
std::string ext = std::filesystem::path(file_path).extension().string();
|
|
251
|
+
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
|
|
252
|
+
|
|
253
|
+
if (ext == ".cpp" || ext == ".cc" || ext == ".cxx" || ext == ".c" ||
|
|
254
|
+
ext == ".h" || ext == ".hpp" || ext == ".hxx") {
|
|
255
|
+
return "C++";
|
|
256
|
+
} else if (ext == ".py") {
|
|
257
|
+
return "Python";
|
|
258
|
+
} else if (ext == ".js" || ext == ".ts") {
|
|
259
|
+
return "JavaScript/TypeScript";
|
|
260
|
+
} else if (ext == ".java") {
|
|
261
|
+
return "Java";
|
|
262
|
+
} else if (ext == ".rs") {
|
|
263
|
+
return "Rust";
|
|
264
|
+
} else if (ext == ".go") {
|
|
265
|
+
return "Go";
|
|
266
|
+
} else if (ext == ".md") {
|
|
267
|
+
return "Markdown";
|
|
268
|
+
} else if (ext == ".json") {
|
|
269
|
+
return "JSON";
|
|
270
|
+
} else if (ext == ".yaml" || ext == ".yml") {
|
|
271
|
+
return "YAML";
|
|
272
|
+
} else if (ext == ".xml") {
|
|
273
|
+
return "XML";
|
|
274
|
+
} else if (ext == ".txt") {
|
|
275
|
+
return "Text";
|
|
276
|
+
} else if (ext == ".sh" || ext == ".bash") {
|
|
277
|
+
return "Shell Script";
|
|
278
|
+
} else {
|
|
279
|
+
return "Unknown";
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
std::set<std::string>
|
|
284
|
+
MultiFileService::load_gitignore_patterns(const std::string &directory) {
|
|
285
|
+
std::set<std::string> patterns;
|
|
286
|
+
|
|
287
|
+
// Add common patterns
|
|
288
|
+
patterns.insert("node_modules/");
|
|
289
|
+
patterns.insert(".git/");
|
|
290
|
+
patterns.insert("build/");
|
|
291
|
+
patterns.insert("dist/");
|
|
292
|
+
patterns.insert(".cache/");
|
|
293
|
+
patterns.insert("*.log");
|
|
294
|
+
patterns.insert("*.tmp");
|
|
295
|
+
patterns.insert("*.swp");
|
|
296
|
+
patterns.insert(".DS_Store");
|
|
297
|
+
|
|
298
|
+
// Try to read .gitignore file
|
|
299
|
+
std::string gitignore_path = directory + "/.gitignore";
|
|
300
|
+
std::ifstream gitignore_file(gitignore_path);
|
|
301
|
+
|
|
302
|
+
if (gitignore_file.is_open()) {
|
|
303
|
+
std::string line;
|
|
304
|
+
while (std::getline(gitignore_file, line)) {
|
|
305
|
+
// Skip empty lines and comments
|
|
306
|
+
if (line.empty() || line[0] == '#') {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Trim whitespace
|
|
311
|
+
line.erase(0, line.find_first_not_of(" \t"));
|
|
312
|
+
line.erase(line.find_last_not_of(" \t") + 1);
|
|
313
|
+
|
|
314
|
+
if (!line.empty()) {
|
|
315
|
+
patterns.insert(line);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return patterns;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
bool MultiFileService::matches_gitignore(
|
|
324
|
+
const std::string &file_path, const std::set<std::string> &patterns) {
|
|
325
|
+
for (const auto &pattern : patterns) {
|
|
326
|
+
if (matches_pattern(file_path, pattern)) {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
std::string MultiFileService::format_multi_file_content(
|
|
334
|
+
const std::vector<FileMatch> &files,
|
|
335
|
+
const std::string &context_description) {
|
|
336
|
+
std::ostringstream formatted;
|
|
337
|
+
|
|
338
|
+
if (!context_description.empty()) {
|
|
339
|
+
formatted << "[" << context_description << "]\n\n";
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
formatted << "[Multi-file content: " << files.size() << " files]\n\n";
|
|
343
|
+
|
|
344
|
+
// Group files by type
|
|
345
|
+
std::map<std::string, std::vector<const FileMatch *>> files_by_type;
|
|
346
|
+
for (const auto &file : files) {
|
|
347
|
+
files_by_type[file.file_type].push_back(&file);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Output summary
|
|
351
|
+
formatted << "File Summary:\n";
|
|
352
|
+
for (const auto &[type, type_files] : files_by_type) {
|
|
353
|
+
formatted << " " << type << ": " << type_files.size() << " files\n";
|
|
354
|
+
}
|
|
355
|
+
formatted << "\n";
|
|
356
|
+
|
|
357
|
+
// Output file contents
|
|
358
|
+
for (const auto &file : files) {
|
|
359
|
+
formatted << "=== " << file.relative_path << " (" << file.file_type
|
|
360
|
+
<< ") ===\n";
|
|
361
|
+
formatted << file.content;
|
|
362
|
+
if (!file.content.empty() && file.content.back() != '\n') {
|
|
363
|
+
formatted << "\n";
|
|
364
|
+
}
|
|
365
|
+
formatted << "\n";
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return formatted.str();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
std::vector<std::string> MultiFileService::get_default_exclude_patterns() {
|
|
372
|
+
return {
|
|
373
|
+
"*.exe", "*.dll", "*.so", "*.dylib", "*.a", "*.lib",
|
|
374
|
+
"*.obj", "*.o", "*.png", "*.jpg", "*.jpeg", "*.gif",
|
|
375
|
+
"*.bmp", "*.ico", "*.svg", "*.mp3", "*.mp4", "*.avi",
|
|
376
|
+
"*.mov", "*.wav", "*.pdf", "*.zip", "*.tar", "*.gz",
|
|
377
|
+
"*.7z", "*.rar", "*.bin", "node_modules/*", ".git/*", "build/*",
|
|
378
|
+
"dist/*", ".cache/*", "*.log", "*.tmp", "*.swp", ".DS_Store",
|
|
379
|
+
"Thumbs.db"};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
bool MultiFileService::matches_pattern(const std::string &file_path,
|
|
383
|
+
const std::string &pattern) {
|
|
384
|
+
// Simple glob-like pattern matching
|
|
385
|
+
// Convert glob pattern to regex
|
|
386
|
+
std::string regex_pattern = pattern;
|
|
387
|
+
|
|
388
|
+
// Escape special regex characters except * and ?
|
|
389
|
+
std::regex special_chars(R"([\.\+\^\$\(\)\[\]\{\}\|\\])");
|
|
390
|
+
regex_pattern = std::regex_replace(regex_pattern, special_chars, R"(\$&)");
|
|
391
|
+
|
|
392
|
+
// Convert glob wildcards to regex
|
|
393
|
+
std::regex glob_star(R"(\\\*)");
|
|
394
|
+
regex_pattern = std::regex_replace(regex_pattern, glob_star, ".*");
|
|
395
|
+
|
|
396
|
+
std::regex glob_question(R"(\\\?)");
|
|
397
|
+
regex_pattern = std::regex_replace(regex_pattern, glob_question, ".");
|
|
398
|
+
|
|
399
|
+
// Add anchors for full match
|
|
400
|
+
regex_pattern = "^" + regex_pattern + "$";
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
std::regex pattern_regex(regex_pattern, std::regex_constants::icase);
|
|
404
|
+
return std::regex_match(file_path, pattern_regex) ||
|
|
405
|
+
std::regex_match(
|
|
406
|
+
std::filesystem::path(file_path).filename().string(),
|
|
407
|
+
pattern_regex);
|
|
408
|
+
} catch (const std::exception &) {
|
|
409
|
+
// If regex fails, fall back to simple string matching
|
|
410
|
+
return file_path.find(pattern) != std::string::npos;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
std::vector<std::string>
|
|
415
|
+
MultiFileService::get_git_status_files(const std::string &directory,
|
|
416
|
+
GitStatusFilter filter) {
|
|
417
|
+
std::vector<std::string> files;
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
std::string command;
|
|
421
|
+
switch (filter) {
|
|
422
|
+
case GitStatusFilter::TRACKED:
|
|
423
|
+
command = "git ls-files";
|
|
424
|
+
break;
|
|
425
|
+
case GitStatusFilter::UNTRACKED:
|
|
426
|
+
command = "git ls-files --others --exclude-standard";
|
|
427
|
+
break;
|
|
428
|
+
case GitStatusFilter::MODIFIED:
|
|
429
|
+
command = "git diff --name-only HEAD";
|
|
430
|
+
break;
|
|
431
|
+
case GitStatusFilter::STAGED:
|
|
432
|
+
command = "git diff --name-only --cached";
|
|
433
|
+
break;
|
|
434
|
+
case GitStatusFilter::UNSTAGED:
|
|
435
|
+
command = "git diff --name-only";
|
|
436
|
+
break;
|
|
437
|
+
default: // GitStatusFilter::ALL
|
|
438
|
+
command = "git ls-files && git ls-files --others --exclude-standard";
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Change to directory and execute git command
|
|
443
|
+
std::string full_command = "cd \"" + directory + "\" && " + command;
|
|
444
|
+
FILE *pipe = Utils::Platform::open_process(full_command, "r");
|
|
445
|
+
if (!pipe)
|
|
446
|
+
return files;
|
|
447
|
+
|
|
448
|
+
char buffer[256];
|
|
449
|
+
std::string result;
|
|
450
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
451
|
+
result += buffer;
|
|
452
|
+
}
|
|
453
|
+
Utils::Platform::close_process(pipe);
|
|
454
|
+
|
|
455
|
+
// Parse output into file paths
|
|
456
|
+
std::istringstream iss(result);
|
|
457
|
+
std::string line;
|
|
458
|
+
while (std::getline(iss, line)) {
|
|
459
|
+
if (!line.empty()) {
|
|
460
|
+
// Convert to absolute path
|
|
461
|
+
std::string full_path = directory + "/" + line;
|
|
462
|
+
files.push_back(full_path);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
} catch ([[maybe_unused]] const std::exception &e) {
|
|
467
|
+
// If git commands fail, return empty vector
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return files;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
bool MultiFileService::is_text_file(const std::string &file_path) {
|
|
474
|
+
// Check by extension first
|
|
475
|
+
std::string ext = std::filesystem::path(file_path).extension().string();
|
|
476
|
+
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
|
|
477
|
+
|
|
478
|
+
// Common text file extensions
|
|
479
|
+
static const std::set<std::string> text_extensions(
|
|
480
|
+
Utils::Validator::get_text_extensions().begin(),
|
|
481
|
+
Utils::Validator::get_text_extensions().end());
|
|
482
|
+
|
|
483
|
+
if (text_extensions.count(ext)) {
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Check files without extensions or with unknown extensions
|
|
488
|
+
try {
|
|
489
|
+
std::ifstream file(file_path, std::ios::binary);
|
|
490
|
+
if (!file.is_open())
|
|
491
|
+
return false;
|
|
492
|
+
|
|
493
|
+
// Read first 512 bytes to check for binary content
|
|
494
|
+
char buffer[512];
|
|
495
|
+
file.read(buffer, sizeof(buffer));
|
|
496
|
+
std::streamsize bytes_read = file.gcount();
|
|
497
|
+
|
|
498
|
+
// Check for null bytes (common in binary files)
|
|
499
|
+
for (std::streamsize i = 0; i < bytes_read; ++i) {
|
|
500
|
+
if (buffer[i] == '\0') {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return true;
|
|
506
|
+
|
|
507
|
+
} catch ([[maybe_unused]] const std::exception &e) {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
std::string
|
|
513
|
+
MultiFileService::get_language_from_extension(const std::string &file_path) {
|
|
514
|
+
std::string ext = std::filesystem::path(file_path).extension().string();
|
|
515
|
+
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
|
|
516
|
+
|
|
517
|
+
// Map extensions to language names
|
|
518
|
+
static std::map<std::string, std::string> ext_to_lang = {
|
|
519
|
+
{".c", "c"},
|
|
520
|
+
{".h", "c"},
|
|
521
|
+
{".cpp", "cpp"},
|
|
522
|
+
{".cc", "cpp"},
|
|
523
|
+
{".cxx", "cpp"},
|
|
524
|
+
{".hpp", "cpp"},
|
|
525
|
+
{".hxx", "cpp"},
|
|
526
|
+
{".py", "python"},
|
|
527
|
+
{".js", "javascript"},
|
|
528
|
+
{".jsx", "javascript"},
|
|
529
|
+
{".ts", "typescript"},
|
|
530
|
+
{".tsx", "typescript"},
|
|
531
|
+
{".java", "java"},
|
|
532
|
+
{".go", "go"},
|
|
533
|
+
{".rs", "rust"},
|
|
534
|
+
{".rb", "ruby"},
|
|
535
|
+
{".php", "php"},
|
|
536
|
+
{".swift", "swift"},
|
|
537
|
+
{".kt", "kotlin"},
|
|
538
|
+
{".scala", "scala"},
|
|
539
|
+
{".clj", "clojure"},
|
|
540
|
+
{".hs", "haskell"},
|
|
541
|
+
{".ml", "ocaml"},
|
|
542
|
+
{".r", "r"},
|
|
543
|
+
{".m", "matlab"},
|
|
544
|
+
{".pl", "perl"},
|
|
545
|
+
{".sh", "shell"},
|
|
546
|
+
{".bash", "shell"},
|
|
547
|
+
{".zsh", "shell"},
|
|
548
|
+
{".fish", "shell"},
|
|
549
|
+
{".ps1", "powershell"},
|
|
550
|
+
{".sql", "sql"},
|
|
551
|
+
{".html", "html"},
|
|
552
|
+
{".htm", "html"},
|
|
553
|
+
{".css", "css"},
|
|
554
|
+
{".xml", "xml"},
|
|
555
|
+
{".json", "json"},
|
|
556
|
+
{".yaml", "yaml"},
|
|
557
|
+
{".yml", "yaml"},
|
|
558
|
+
{".md", "markdown"},
|
|
559
|
+
{".rst", "restructuredtext"},
|
|
560
|
+
{".tex", "latex"},
|
|
561
|
+
{".dockerfile", "docker"},
|
|
562
|
+
{".makefile", "make"},
|
|
563
|
+
{".cmake", "cmake"}};
|
|
564
|
+
|
|
565
|
+
auto it = ext_to_lang.find(ext);
|
|
566
|
+
return (it != ext_to_lang.end()) ? it->second : "unknown";
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
bool MultiFileService::matches_language_filter(
|
|
570
|
+
const std::string &file_path,
|
|
571
|
+
const std::vector<std::string> &language_filters) {
|
|
572
|
+
if (language_filters.empty()) {
|
|
573
|
+
return true; // No filter means include all
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
std::string file_language = get_language_from_extension(file_path);
|
|
577
|
+
|
|
578
|
+
for (const auto &filter : language_filters) {
|
|
579
|
+
std::string filter_lower = filter;
|
|
580
|
+
std::transform(filter_lower.begin(), filter_lower.end(),
|
|
581
|
+
filter_lower.begin(), ::tolower);
|
|
582
|
+
|
|
583
|
+
if (file_language == filter_lower) {
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
} // namespace Services
|