bool SearchContext::searchFile(int id, const std::string &filename, RegexList &filespecRegexes, pcre *matchRegex) { bool matchesOneFilespec = false; for(RegexList::iterator it = filespecRegexes.begin(); it != filespecRegexes.end(); ++it) { pcre *regex = *it; if(pcre_exec(regex, NULL, filename.c_str(), filename.length(), 0, 0, NULL, 0) >= 0) { matchesOneFilespec = true; break; } } if(!matchesOneFilespec) return false; std::string contents; if(!readEntireFile(filename, contents, params_.maxFileSize)) return false; std::string workBuffer = contents; std::string updatedContents; bool atLeastOneMatch = false; int lineNumber = 1; char *p = &workBuffer[0]; char *line; while((line = nextToken(&p, '\n')) != NULL) { char *originalLine = line; std::string replacedLine; SearchEntry entry; int ovector[100]; do { bool matches = false; int matchPos; int matchLen; if(matchRegex) { int rc; if(rc = pcre_exec(matchRegex, 0, line, strlen(line), 0, 0, ovector, sizeof(ovector)) >= 0) { matches = true; matchPos = ovector[0]; matchLen = ovector[1] - ovector[0]; } } else { char *match; if(params_.flags & SF_MATCH_CASE_SENSITIVE) match = strstr(line, params_.match.c_str()); else match = strstri(line, params_.match.c_str()); if(match != NULL) { matches = true; matchPos = match - line; matchLen = params_.match.length(); } } if(params_.flags & SF_REPLACE) { if(matches) { replacedLine.append(line, matchPos); entry.highlights_.push_back(Highlight(replacedLine.length(), params_.replace.length())); replacedLine.append(params_.replace); line += matchPos + matchLen; } else { break; } } else { if(matches) { entry.filename_ = filename; entry.match_ = originalLine; entry.line_ = lineNumber; entry.highlights_.push_back(Highlight(matchPos + (line - originalLine), matchLen)); line += matchPos + matchLen; } else { break; } } if(matches) hits_++; } while(*line); if(!entry.filename_.empty()) { linesWithHits_++; atLeastOneMatch = true; } if(params_.flags & SF_REPLACE) { if(line && *line) replacedLine += line; if(replacedLine != originalLine) { entry.filename_ = filename; entry.match_ = replacedLine; entry.line_ = lineNumber; append(id, entry); } replacedLine += "\n"; updatedContents += replacedLine; } else { if(!entry.filename_.empty()) append(id, entry); } lineNumber++; } if(atLeastOneMatch) filesWithHits_++; if(params_.flags & SF_REPLACE) { if((contents != updatedContents)) { bool overwriteFile = true; if(params_.flags & SF_BACKUP) { std::string backupFilename = filename; backupFilename += "."; backupFilename += params_.backupExtension; if(!writeEntireFile(backupFilename, contents)) { std::string err = "WARNING: Couldn't write backup file (skipping replacement): "; err += backupFilename; err += "\n"; poke(id, err.c_str(), HighlightList(), 0, false); overwriteFile = false; } } if(overwriteFile) { if(writeEntireFile(filename, updatedContents)) { return true; } else { std::string err = "WARNING: Couldn't write to file: "; err += filename; err += "\n"; poke(id, err.c_str(), HighlightList(), 0, false); } } } return false; } return true; }
void SearchContext::searchProc() { int id = searchID_; HANDLE findHandle = INVALID_HANDLE_VALUE; StringList paths; paths = params_.paths; RegexList filespecRegexes; pcre *matchRegex = NULL; directoriesSearched_ = 0; directoriesSkipped_ = 0; filesSearched_ = 0; filesSkipped_ = 0; filesWithHits_ = 0; linesWithHits_ = 0; hits_ = 0; unsigned int startTick = GetTickCount(); bool filespecUsesRegexes = ((params_.flags & SF_FILESPEC_REGEXES) != 0); bool matchUsesRegexes = ((params_.flags & SF_MATCH_REGEXES) != 0); delete pokeData_; pokeData_ = new PokeData; if(matchUsesRegexes) { const char *error; int erroffset; int flags = 0; if(!(params_.flags & SF_MATCH_CASE_SENSITIVE)) flags |= PCRE_CASELESS; matchRegex = pcre_compile(params_.match.c_str(), flags, &error, &erroffset, NULL); if(!matchRegex) { MessageBox(window_, error, "Match Regex Error", MB_OK); goto cleanup; } } for(StringList::iterator it = params_.filespecs.begin(); it != params_.filespecs.end(); ++it) { std::string regexString = it->c_str(); if(!filespecUsesRegexes) convertWildcard(regexString); int flags = 0; if(!(params_.flags & SF_FILESPEC_CASE_SENSITIVE)) flags |= PCRE_CASELESS; const char *error; int erroffset; pcre *regex = pcre_compile(regexString.c_str(), flags, &error, &erroffset, NULL); if(regex) filespecRegexes.push_back(regex); else { MessageBox(window_, error, "Filespec Regex Error", MB_OK); goto cleanup; } } PostMessage(window_, WM_SEARCHCONTEXT_STATE, 1, 0); while(!paths.empty()) { directoriesSearched_++; stopCheck(); std::string currentSearchPath = paths.back(); std::string currentSearchWildcard = currentSearchPath + "\\*"; paths.pop_back(); WIN32_FIND_DATA wfd; findHandle = FindFirstFile(currentSearchWildcard.c_str(), &wfd); if(findHandle == INVALID_HANDLE_VALUE) continue; while(FindNextFile(findHandle, &wfd)) { stopCheck(); bool isDirectory = ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); if((wfd.cFileName[0] == '.') || (wfd.cFileName[0] == 0)) { if(isDirectory) directoriesSkipped_++; else filesSkipped_++; continue; } std::string filename = currentSearchPath; filename += "\\"; filename += wfd.cFileName; if(isDirectory) { if(params_.flags & SF_RECURSIVE) paths.push_back(filename); } else { if(searchFile(id, filename, filespecRegexes, matchRegex)) { filesSearched_++; } else { filesSkipped_++; } poke(id, "", HighlightList(), 0, false); } } if(findHandle != INVALID_HANDLE_VALUE) { FindClose(findHandle); } } cleanup: for(RegexList::iterator it = filespecRegexes.begin(); it != filespecRegexes.end(); ++it) { pcre_free(*it); } if(matchRegex) pcre_free(matchRegex); filespecRegexes.clear(); if(!stop_) { unsigned int endTick = GetTickCount(); char buffer[512]; float sec = (endTick - startTick) / 1000.0f; const char *verb = "searched"; if(params_.flags & SF_REPLACE) verb = "updated"; sprintf(buffer, "\n%d hits in %d lines across %d files.\n%d directories scanned, %d files %s, %d files skipped (%3.3f sec)", hits_, linesWithHits_, filesWithHits_, directoriesSearched_, filesSearched_, verb, filesSkipped_, sec); poke(id, buffer, HighlightList(), 0, true); } delete pokeData_; pokeData_ = NULL; PostMessage(window_, WM_SEARCHCONTEXT_STATE, 0, 0); }
bool SearchContext::searchFile(int id, const std::string &filename, RegexList &filespecRegexes, pcre *matchRegex) { bool matchesOneFilespec = false; for(RegexList::iterator it = filespecRegexes.begin(); it != filespecRegexes.end(); ++it) { pcre *regex = *it; if(pcre_exec(regex, NULL, filename.c_str(), filename.length(), 0, 0, NULL, 0) >= 0) { matchesOneFilespec = true; break; } } if(!matchesOneFilespec) return false; std::string contents; if(!readEntireFile(filename, contents, params_.maxFileSize)) return false; std::string workBuffer = contents; std::string updatedContents; std::deque<char *> contextLines; bool atLeastOneMatch = false; int trailingContextLines = 0; int lineNumber = 1; char *p = &workBuffer[0]; char *line; while((line = nextToken(&p, '\n')) != NULL) { char *originalLine = line; std::string replacedLine; SearchEntry entry; int ovector[100]; // Strip newline, but remember exactly what kind it was bool hasCarriageReturn = false; int lineLen = strlen(line); if(lineLen && (line[lineLen - 1] == '\r')) { line[lineLen - 1] = 0; hasCarriageReturn = true; } // Matching loop (we might find our string a few times on a single line) bool lineMatched = false; do { bool matches = false; int matchPos; int matchLen; // The actual match. Either invoke PCRE or do a boring strstr if(matchRegex) { int rc; if(rc = pcre_exec(matchRegex, 0, line, strlen(line), 0, 0, ovector, sizeof(ovector)) >= 0) { matches = true; matchPos = ovector[0]; matchLen = ovector[1] - ovector[0]; } } else { char *match; if(params_.flags & SF_MATCH_CASE_SENSITIVE) match = strstr(line, params_.match.c_str()); else match = strstri(line, params_.match.c_str()); if(match != NULL) { matches = true; matchPos = match - line; matchLen = params_.match.length(); } } if(matches) lineMatched = true; // Handle the match. For replace or find, we: // * Add output explaining the match // * Advance the line pointer for another match attempt // ... or ... // * "break", which leaves the matching loop if(params_.flags & SF_REPLACE) { if(matches) { std::string temp(line, matchPos); replacedLine.append(temp); replacedLine.append(params_.replace); entry.textBlocks.addBlock(temp, config_.textColor_); entry.textBlocks.addBlock(params_.replace, config_.highlightColor_, true); line += matchPos + matchLen; } else { break; } } else { if(matches) { entry.textBlocks.addHighlightedBlock(originalLine, matchPos + (line - originalLine), matchLen, config_.textColor_, config_.highlightColor_, true); line += matchPos + matchLen; } else { break; } } if(matches) hits_++; } while(*line); // end of matching loop // If we're doing a replace, finish the line and append to the final updated contents if(params_.flags & SF_REPLACE) { if(line && *line) { replacedLine += line; entry.textBlocks.addBlock(line, config_.textColor_); } if(hasCarriageReturn) { replacedLine += "\r"; } replacedLine += "\n"; updatedContents += replacedLine; } bool outputMatch = false; if(lineMatched) { // keep stats linesWithHits_++; atLeastOneMatch = true; // If we matched, consider notifying the user. We'll always say something // unless the replaced text doesn't actually change the line. outputMatch = ( !(params_.flags & SF_REPLACE) ) || (replacedLine != originalLine); if(outputMatch) { entry.filename_ = filename; entry.line_ = lineNumber; // output all existing context lines if(contextLines.size()) { SearchEntry contextEntry; contextEntry.filename_ = entry.filename_; contextEntry.contextOnly_ = true; int currLine = entry.line_ - contextLines.size(); for(std::deque<char *>::iterator it = contextLines.begin(); it != contextLines.end(); ++it) { TextBlockList textBlocks; textBlocks.addBlock(*it, config_.textColor_); contextEntry.textBlocks.swap(textBlocks); contextEntry.line_ = currLine++; append(id, contextEntry); } contextLines.clear(); } append(id, entry); } // Remember that we'd like the next few lines, even if they don't match trailingContextLines = config_.contextLines_; } if(!outputMatch) { // Didn't output a match. Keep track or output the line anyway for contextual reasons. if(trailingContextLines > 0) { // A recent match wants to see this line in the output anyway SearchEntry trailingEntry; trailingEntry.filename_ = filename; trailingEntry.line_ = lineNumber; trailingEntry.contextOnly_ = true; trailingEntry.textBlocks.addBlock(originalLine, config_.textColor_); append(id, trailingEntry); trailingContextLines--; } else { // didn't output a match, and wasn't output as context. stash it in contextLines contextLines.push_back(originalLine); if((int)contextLines.size() > config_.contextLines_) contextLines.pop_front(); } } lineNumber++; } // end of line loop (done reading file) if(atLeastOneMatch) filesWithHits_++; if(params_.flags & SF_REPLACE) { if((contents != updatedContents)) { bool overwriteFile = true; if(params_.flags & SF_BACKUP) { std::string backupFilename = filename; backupFilename += "."; backupFilename += params_.backupExtension; if(!writeEntireFile(backupFilename, contents)) { std::string err = "WARNING: Couldn't write backup file (skipping replacement): "; err += backupFilename; err += "\n"; sendError(id, err); overwriteFile = false; } } if(overwriteFile) { if(writeEntireFile(filename, updatedContents)) { return true; } else { std::string err = "WARNING: Couldn't write to file: "; err += filename; err += "\n"; sendError(id, err); } } } return false; } return true; }