TextSearch: use BString-based searching instead of Grep-based.

Huge performance improvement. Fixes #9529.
This commit is contained in:
Augustin Cavalier 2015-03-31 19:56:18 -04:00
parent 1b1789a711
commit 1e9f5f5be4
6 changed files with 69 additions and 211 deletions

View File

@ -90,7 +90,6 @@ GrepWindow::GrepWindow(BMessage* message)
fRecurseDirs(NULL), fRecurseDirs(NULL),
fSkipDotDirs(NULL), fSkipDotDirs(NULL),
fCaseSensitive(NULL), fCaseSensitive(NULL),
fEscapeText(NULL),
fTextOnly(NULL), fTextOnly(NULL),
fInvokePe(NULL), fInvokePe(NULL),
fHistoryMenu(NULL), fHistoryMenu(NULL),
@ -211,10 +210,6 @@ void GrepWindow::MessageReceived(BMessage* message)
_OnCaseSensitive(); _OnCaseSensitive();
break; break;
case MSG_ESCAPE_TEXT:
_OnEscapeText();
break;
case MSG_TEXT_ONLY: case MSG_TEXT_ONLY:
_OnTextOnly(); _OnTextOnly();
break; break;
@ -259,10 +254,6 @@ void GrepWindow::MessageReceived(BMessage* message)
_OnReportResult(message); _OnReportResult(message);
break; break;
case MSG_REPORT_ERROR:
_OnReportError(message);
break;
case MSG_SELECT_ALL: case MSG_SELECT_ALL:
_OnSelectAll(message); _OnSelectAll(message);
break; break;
@ -452,9 +443,6 @@ GrepWindow::_CreateMenus()
fCaseSensitive = new BMenuItem( fCaseSensitive = new BMenuItem(
B_TRANSLATE("Case-sensitive"), new BMessage(MSG_CASE_SENSITIVE)); B_TRANSLATE("Case-sensitive"), new BMessage(MSG_CASE_SENSITIVE));
fEscapeText = new BMenuItem(
B_TRANSLATE("Escape search text"), new BMessage(MSG_ESCAPE_TEXT));
fTextOnly = new BMenuItem( fTextOnly = new BMenuItem(
B_TRANSLATE("Text files only"), new BMessage(MSG_TEXT_ONLY)); B_TRANSLATE("Text files only"), new BMessage(MSG_TEXT_ONLY));
@ -486,7 +474,6 @@ GrepWindow::_CreateMenus()
fPreferencesMenu->AddItem(fRecurseDirs); fPreferencesMenu->AddItem(fRecurseDirs);
fPreferencesMenu->AddItem(fSkipDotDirs); fPreferencesMenu->AddItem(fSkipDotDirs);
fPreferencesMenu->AddItem(fCaseSensitive); fPreferencesMenu->AddItem(fCaseSensitive);
fPreferencesMenu->AddItem(fEscapeText);
fPreferencesMenu->AddItem(fTextOnly); fPreferencesMenu->AddItem(fTextOnly);
fPreferencesMenu->AddItem(fInvokePe); fPreferencesMenu->AddItem(fInvokePe);
@ -607,7 +594,6 @@ GrepWindow::_LoadPrefs()
fRecurseLinks->SetMarked(fModel->fRecurseLinks); fRecurseLinks->SetMarked(fModel->fRecurseLinks);
fSkipDotDirs->SetMarked(fModel->fSkipDotDirs); fSkipDotDirs->SetMarked(fModel->fSkipDotDirs);
fCaseSensitive->SetMarked(fModel->fCaseSensitive); fCaseSensitive->SetMarked(fModel->fCaseSensitive);
fEscapeText->SetMarked(fModel->fEscapeText);
fTextOnly->SetMarked(fModel->fTextOnly); fTextOnly->SetMarked(fModel->fTextOnly);
fInvokePe->SetMarked(fModel->fInvokePe); fInvokePe->SetMarked(fModel->fInvokePe);
@ -1019,15 +1005,6 @@ GrepWindow::_OnReportResult(BMessage* message)
} }
void
GrepWindow::_OnReportError(BMessage* message)
{
const char* buf;
if (message->FindString("error", &buf) == B_OK)
fSearchResults->AddItem(new BStringItem(buf));
}
void void
GrepWindow::_OnRecurseLinks() GrepWindow::_OnRecurseLinks()
{ {
@ -1055,15 +1032,6 @@ GrepWindow::_OnSkipDotDirs()
} }
void
GrepWindow::_OnEscapeText()
{
fModel->fEscapeText = !fModel->fEscapeText;
fEscapeText->SetMarked(fModel->fEscapeText);
_ModelChanged();
}
void void
GrepWindow::_OnCaseSensitive() GrepWindow::_OnCaseSensitive()
{ {

View File

@ -50,11 +50,9 @@ private:
void _OnNodeMonitorPulse(); void _OnNodeMonitorPulse();
void _OnReportFileName(BMessage* message); void _OnReportFileName(BMessage* message);
void _OnReportResult(BMessage* message); void _OnReportResult(BMessage* message);
void _OnReportError(BMessage* message);
void _OnRecurseLinks(); void _OnRecurseLinks();
void _OnRecurseDirs(); void _OnRecurseDirs();
void _OnSkipDotDirs(); void _OnSkipDotDirs();
void _OnEscapeText();
void _OnCaseSensitive(); void _OnCaseSensitive();
void _OnTextOnly(); void _OnTextOnly();
void _OnInvokePe(); void _OnInvokePe();
@ -103,7 +101,6 @@ private:
BMenuItem* fRecurseDirs; BMenuItem* fRecurseDirs;
BMenuItem* fSkipDotDirs; BMenuItem* fSkipDotDirs;
BMenuItem* fCaseSensitive; BMenuItem* fCaseSensitive;
BMenuItem* fEscapeText;
BMenuItem* fTextOnly; BMenuItem* fTextOnly;
BMenuItem* fInvokePe; BMenuItem* fInvokePe;
BMenu* fHistoryMenu; BMenu* fHistoryMenu;

View File

@ -17,6 +17,7 @@
#include <Locale.h> #include <Locale.h>
#include <NodeInfo.h> #include <NodeInfo.h>
#include <Path.h> #include <Path.h>
#include <String.h>
#include <UTF8.h> #include <UTF8.h>
#include "FileIterator.h" #include "FileIterator.h"
@ -59,33 +60,10 @@ strdup_to_utf8(uint32 encode, const char* src, int32 length)
} }
char* Grepper::Grepper(BString pattern, const Model* model,
strdup_from_utf8(uint32 encode, const char* src, int32 length)
{
int32 srcLen = length;
int32 dstLen = srcLen;
char* dst = new (nothrow) char[dstLen + 1];
if (dst == NULL)
return NULL;
int32 cookie = 0;
convert_from_utf8(encode, src, &srcLen, dst, &dstLen, &cookie);
// TODO: See above.
dst[dstLen] = '\0';
char* dup = strdup(dst);
delete[] dst;
if (srcLen != length) {
fprintf(stderr, "strdup_from_utf8(%" B_PRId32 ", %" B_PRId32
") dst allocate smalled(%" B_PRId32 ")\n", encode, length, dstLen);
}
return dup;
}
Grepper::Grepper(const char* pattern, const Model* model,
const BHandler* target, FileIterator* iterator) const BHandler* target, FileIterator* iterator)
: fPattern(NULL), : fPattern(pattern),
fTarget(target), fTarget(target),
fEscapeText(model->fEscapeText),
fCaseSensitive(model->fCaseSensitive), fCaseSensitive(model->fCaseSensitive),
fEncoding(model->fEncoding), fEncoding(model->fEncoding),
@ -93,19 +71,12 @@ Grepper::Grepper(const char* pattern, const Model* model,
fThreadId(-1), fThreadId(-1),
fMustQuit(false) fMustQuit(false)
{ {
if (fEncoding > 0) {
char* src = strdup_from_utf8(fEncoding, pattern, strlen(pattern));
_SetPattern(src);
free(src);
} else
_SetPattern(pattern);
} }
Grepper::~Grepper() Grepper::~Grepper()
{ {
Cancel(); Cancel();
free(fPattern);
delete fIterator; delete fIterator;
} }
@ -160,17 +131,9 @@ int32
Grepper::_GrepperThread() Grepper::_GrepperThread()
{ {
BMessage message; BMessage message;
char fileName[B_PATH_NAME_LENGTH]; char fileName[B_PATH_NAME_LENGTH];
char tempString[B_PATH_NAME_LENGTH];
char command[B_PATH_NAME_LENGTH + 32];
BPath tempFile;
sprintf(fileName, "/tmp/SearchText%" B_PRId32, fThreadId);
tempFile.SetTo(fileName);
while (!fMustQuit && fIterator->GetNextName(fileName)) { while (!fMustQuit && fIterator->GetNextName(fileName)) {
message.MakeEmpty(); message.MakeEmpty();
message.what = MSG_REPORT_FILE_NAME; message.what = MSG_REPORT_FILE_NAME;
message.AddString("filename", fileName); message.AddString("filename", fileName);
@ -191,58 +154,44 @@ Grepper::_GrepperThread()
continue; continue;
} }
if (!_EscapeSpecialChars(fileName, B_PATH_NAME_LENGTH)) { BString contents;
sprintf(tempString, B_TRANSLATE("%s: Not enough room to escape "
"the filename."), fileName);
message.MakeEmpty(); BFile file(&ref, B_READ_WRITE);
message.what = MSG_REPORT_ERROR; off_t size;
message.AddString("error", tempString); file.GetSize(&size);
file.Seek(0, SEEK_SET);
char* buffer = new char[size];
file.Read(buffer, size);
if (fEncoding > 0) {
char* temp = strdup_to_utf8(fEncoding, buffer, size);
contents.SetTo(temp, size);
free(temp);
} else
contents.SetTo(buffer, size);
delete[] buffer;
int32 index = 0, lines = 1; // First line is 1 not 0
while (true) {
int32 newPos;
if (fCaseSensitive)
newPos = contents.FindFirst(fPattern, index);
else
newPos = contents.IFindFirst(fPattern, index);
if (newPos == B_ERROR)
break;
lines += _CountLines(contents, index, newPos);
BString linenoAndLine;
linenoAndLine.SetToFormat("%" B_PRId32 ":%s", lines, _GetLine(contents, newPos).String());
message.AddString("text", linenoAndLine);
index = newPos + 1;
}
if (message.HasString("text") || fIterator->NotifyNegatives())
fTarget.SendMessage(&message); fTarget.SendMessage(&message);
continue;
}
sprintf(command, "grep -hn %s %s \"%s\" > \"%s\"",
fCaseSensitive ? "" : "-i", fPattern, fileName, tempFile.Path());
int res = system(command);
if (res == 0 || res == 1) {
FILE *results = fopen(tempFile.Path(), "r");
if (results != NULL) {
while (fgets(tempString, B_PATH_NAME_LENGTH, results) != 0) {
if (fEncoding > 0) {
char* tempdup = strdup_to_utf8(fEncoding, tempString,
strlen(tempString));
message.AddString("text", tempdup);
free(tempdup);
} else
message.AddString("text", tempString);
}
if (message.HasString("text") || fIterator->NotifyNegatives())
fTarget.SendMessage(&message);
fclose(results);
continue;
}
}
sprintf(tempString, B_TRANSLATE("%s: There was a problem running grep."), fileName);
message.MakeEmpty();
message.what = MSG_REPORT_ERROR;
message.AddString("error", tempString);
fTarget.SendMessage(&message);
} }
// We wait with removing the temporary file until after the
// entire search has finished, to prevent a lot of flickering
// if the Tracker window for /tmp/ might be open.
remove(tempFile.Path());
message.MakeEmpty(); message.MakeEmpty();
message.what = MSG_SEARCH_FINISHED; message.what = MSG_SEARCH_FINISHED;
fTarget.SendMessage(&message); fTarget.SendMessage(&message);
@ -251,79 +200,36 @@ Grepper::_GrepperThread()
} }
void int32
Grepper::_SetPattern(const char* src) Grepper::_CountLines(BString& str, int32 startPos, int32 endPos)
{ {
if (src == NULL) int32 ret = 0;
return; for (int32 i = startPos; i < endPos; i++) {
if (str[i] == '\n')
if (!fEscapeText) { ret++;
fPattern = strdup(src);
return;
} }
return ret;
// We will simply guess the size of the memory buffer
// that we need. This should always be large enough.
fPattern = (char*)malloc((strlen(src) + 1) * 3 * sizeof(char));
if (fPattern == NULL)
return;
const char* srcPtr = src;
char* dstPtr = fPattern;
// Put double quotes around the pattern, so separate
// words are considered to be part of a single string.
*dstPtr++ = '"';
while (*srcPtr != '\0') {
char c = *srcPtr++;
// Put a backslash in front of characters
// that should be escaped.
if ((c == '.') || (c == ',')
|| (c == '[') || (c == ']')
|| (c == '?') || (c == '*')
|| (c == '+') || (c == '-')
|| (c == ':') || (c == '^')
|| (c == '"') || (c == '`')) {
*dstPtr++ = '\\';
} else if ((c == '\\') || (c == '$')) {
// Some characters need to be escaped
// with *three* backslashes in a row.
*dstPtr++ = '\\';
*dstPtr++ = '\\';
*dstPtr++ = '\\';
}
// Note: we do not have to escape the
// { } ( ) < > and | characters.
*dstPtr++ = c;
}
*dstPtr++ = '"';
*dstPtr = '\0';
} }
bool BString
Grepper::_EscapeSpecialChars(char* buffer, ssize_t bufferSize) Grepper::_GetLine(BString& str, int32 pos)
{ {
char* copy = strdup(buffer); int32 startPos = 0;
char* start = buffer; int32 endPos = str.Length();
uint32 len = strlen(copy); for (int32 i = pos; i > 0; i--) {
bool result = true; if (str[i] == '\n') {
for (uint32 count = 0; count < len; ++count) { startPos = i + 1;
if (copy[count] == '"' || copy[count] == '$') break;
*buffer++ = '\\'; }
if (buffer - start == bufferSize - 1) { }
result = false; for (int32 i = pos; i < endPos; i++) {
if (str[i] == '\n') {
endPos = i;
break; break;
} }
*buffer++ = copy[count];
} }
*buffer = '\0';
free(copy);
return result;
}
BString ret;
return str.CopyInto(ret, startPos, endPos - startPos);
}

View File

@ -6,6 +6,7 @@
#define GREPPER_H #define GREPPER_H
#include <Messenger.h> #include <Messenger.h>
#include <String.h>
class FileIterator; class FileIterator;
class Model; class Model;
@ -13,7 +14,7 @@ class Model;
// Executes "grep" in a background thread. // Executes "grep" in a background thread.
class Grepper { class Grepper {
public: public:
Grepper(const char* pattern, const Model* model, Grepper(BString pattern, const Model* model,
const BHandler* target, const BHandler* target,
FileIterator* iterator); FileIterator* iterator);
virtual ~Grepper(); virtual ~Grepper();
@ -30,16 +31,14 @@ private:
// The thread function that does the actual grepping. // The thread function that does the actual grepping.
int32 _GrepperThread(); int32 _GrepperThread();
// Remembers, and possibly escapes, the search pattern. // Counts the number of linebreaks from startPos to endPos.
void _SetPattern(const char* source); int32 _CountLines(BString& str, int32 startPos,
int32 endPos);
// Prepends all quotes, dollars and backslashes with at backslash // Gets the complete line that "pos" is on.
// to prevent the shell from misinterpreting them. BString _GetLine(BString& str, int32 pos);
bool _EscapeSpecialChars(char* buffer,
ssize_t bufferSize);
private: private:
// The (escaped) search pattern. // The search pattern.
char* fPattern; BString fPattern;
// The settings from the model. // The settings from the model.
BMessenger fTarget; BMessenger fTarget;

View File

@ -35,7 +35,6 @@ Model::Model()
fRecurseLinks(false), fRecurseLinks(false),
fSkipDotDirs(true), fSkipDotDirs(true),
fCaseSensitive(false), fCaseSensitive(false),
fEscapeText(true),
fTextOnly(true), fTextOnly(true),
fInvokePe(false), fInvokePe(false),
@ -91,9 +90,6 @@ Model::LoadPrefs()
sizeof(int32)) > 0) sizeof(int32)) > 0)
fCaseSensitive = (value != 0); fCaseSensitive = (value != 0);
if (file.ReadAttr("EscapeText", B_INT32_TYPE, 0, &value, sizeof(int32)) > 0)
fEscapeText = (value != 0);
if (file.ReadAttr("TextOnly", B_INT32_TYPE, 0, &value, sizeof(int32)) > 0) if (file.ReadAttr("TextOnly", B_INT32_TYPE, 0, &value, sizeof(int32)) > 0)
fTextOnly = (value != 0); fTextOnly = (value != 0);
@ -147,9 +143,6 @@ Model::SavePrefs()
value = fCaseSensitive ? 1 : 0; value = fCaseSensitive ? 1 : 0;
file.WriteAttr("CaseSensitive", B_INT32_TYPE, 0, &value, sizeof(int32)); file.WriteAttr("CaseSensitive", B_INT32_TYPE, 0, &value, sizeof(int32));
value = fEscapeText ? 1 : 0;
file.WriteAttr("EscapeText", B_INT32_TYPE, 0, &value, sizeof(int32));
value = fTextOnly ? 1 : 0; value = fTextOnly ? 1 : 0;
file.WriteAttr("TextOnly", B_INT32_TYPE, 0, &value, sizeof(int32)); file.WriteAttr("TextOnly", B_INT32_TYPE, 0, &value, sizeof(int32));

View File

@ -23,7 +23,6 @@ enum {
MSG_RECURSE_DIRS, MSG_RECURSE_DIRS,
MSG_SKIP_DOT_DIRS, MSG_SKIP_DOT_DIRS,
MSG_CASE_SENSITIVE, MSG_CASE_SENSITIVE,
MSG_ESCAPE_TEXT,
MSG_TEXT_ONLY, MSG_TEXT_ONLY,
MSG_INVOKE_PE, MSG_INVOKE_PE,
MSG_CHECKBOX_SHOW_LINES, MSG_CHECKBOX_SHOW_LINES,
@ -35,7 +34,6 @@ enum {
MSG_REPORT_FILE_NAME, MSG_REPORT_FILE_NAME,
MSG_REPORT_RESULT, MSG_REPORT_RESULT,
MSG_REPORT_ERROR,
MSG_SEARCH_FINISHED, MSG_SEARCH_FINISHED,
MSG_NEW_WINDOW, MSG_NEW_WINDOW,
@ -87,9 +85,6 @@ public:
// Whether the search is case sensitive. // Whether the search is case sensitive.
bool fCaseSensitive; bool fCaseSensitive;
// Whether the search pattern will be escaped.
bool fEscapeText;
// Whether we look at text files only. // Whether we look at text files only.
bool fTextOnly; bool fTextOnly;