You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdevelop/parts/grepview/grepviewwidget.cpp

536 lines
14 KiB

/***************************************************************************
* Copyright (C) 1999-2001 by Bernd Gehrmann *
* bernd@kdevelop.org *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <tqdir.h>
#include <tqlayout.h>
#include <tqregexp.h>
#include <tqpainter.h>
#include <tqtoolbutton.h>
#include <kdialogbase.h>
#include <klocale.h>
#include <kprocess.h>
#include <kparts/part.h>
#include <ktexteditor/selectioninterface.h>
#include <kaction.h>
#include <kpopupmenu.h>
#include <ktabwidget.h>
#include <kpushbutton.h>
#include <kiconloader.h>
#include <kmessagebox.h>
using namespace KTextEditor;
#include "kdevcore.h"
#include "kdevproject.h"
#include "kdevmainwindow.h"
#include "kdevpartcontroller.h"
#include "grepdlg.h"
#include "grepviewpart.h"
#include "grepviewwidget.h"
class GrepListBoxItem : public ProcessListBoxItem
{
public:
GrepListBoxItem(const TQString &fileName, const TQString &lineNumber, const TQString &text, bool showFilename);
TQString filename()
{ return fileName; }
int linenumber()
{ return lineNumber.toInt(); }
virtual bool isCustomItem();
private:
virtual void paint(TQPainter *p);
TQString fileName, lineNumber, text;
bool show;
};
GrepListBoxItem::GrepListBoxItem(const TQString &fileName, const TQString &lineNumber, const TQString &text, bool showFilename)
: ProcessListBoxItem( TQString::null, Normal),
fileName(fileName), lineNumber(lineNumber), text(text.stripWhiteSpace()),
show(showFilename)
{
this->text.replace( TQChar('\t'), TQString(" ") );
}
bool GrepListBoxItem::isCustomItem()
{
return true;
}
void GrepListBoxItem::paint(TQPainter *p)
{
TQColor base, dim, result, bkground;
if (listBox()) {
const TQColorGroup& group = listBox()->palette().active();
if (isSelected()) {
bkground = group.button();
base = group.buttonText();
}
else
{
bkground = group.base();
base = group.text();
}
dim = blend(base, bkground);
result = group.link();
}
else
{
base = Qt::black;
dim = Qt::darkGreen;
result = Qt::blue;
if (isSelected())
bkground = Qt::lightGray;
else
bkground = Qt::white;
}
TQFontMetrics fm = p->fontMetrics();
TQString stx = lineNumber + ": ";
int y = fm.ascent()+fm.leading()/2;
int x = 3;
p->fillRect(p->window(), TQBrush(bkground));
if (show)
{
p->setPen(dim);
p->drawText(x, y, fileName);
x += fm.width(fileName);
}
else
{
p->setPen(base);
TQFont font1(p->font());
TQFont font2(font1);
font2.setBold(true);
p->setFont(font2);
p->drawText(x, y, stx);
p->setFont(font1);
x += fm.width(stx);
p->setPen(result);
p->drawText(x, y, text);
}
}
GrepViewWidget::GrepViewWidget(GrepViewPart *part) : TQWidget(0, "grepview widget")
{
m_layout = new TQHBoxLayout(this, 0, -1, "greplayout");
m_tabWidget = new KTabWidget(this);
m_layout->add(m_tabWidget);
m_curOutput = new GrepViewProcessWidget(m_tabWidget);
m_tabWidget->addTab(m_curOutput, i18n("Search Results"));
grepdlg = new GrepDialog( part, this, "grep widget");
connect( grepdlg, TQT_SIGNAL(searchClicked()), this, TQT_SLOT(searchActivated()) );
connect( m_curOutput, TQT_SIGNAL(processExited(KProcess* )), this, TQT_SLOT(slotSearchProcessExited()) );
connect( m_tabWidget, TQT_SIGNAL(currentChanged(TQWidget*)), this, TQT_SLOT(slotOutputTabChanged()) );
connect( m_curOutput, TQT_SIGNAL(clicked(TQListBoxItem*)),
this, TQT_SLOT(slotExecuted(TQListBoxItem*)) );
connect( m_curOutput, TQT_SIGNAL(returnPressed(TQListBoxItem*)),
this, TQT_SLOT(slotExecuted(TQListBoxItem*)) );
connect( m_curOutput, TQT_SIGNAL(contextMenuRequested( TQListBoxItem*, const TQPoint&)), this, TQT_SLOT(popupMenu(TQListBoxItem*, const TQPoint&)));
m_part = part;
m_closeButton = new TQToolButton(m_tabWidget);//@todo change text/icon
m_closeButton->setIconSet(SmallIconSet("tab_remove"));
m_closeButton->setEnabled(false);
connect (m_closeButton, TQT_SIGNAL(clicked()), this, TQT_SLOT(slotCloseCurrentOutput()));
m_tabWidget->setCornerWidget(m_closeButton);
}
GrepViewWidget::~GrepViewWidget()
{}
void GrepViewWidget::showDialog()
{
// Get the selected text if there is any
KParts::ReadOnlyPart *ro_part = dynamic_cast<KParts::ReadOnlyPart*>(m_part->partController()->activePart());
if (ro_part)
{
SelectionInterface *selectIface = dynamic_cast<SelectionInterface*>(ro_part);
if(selectIface && selectIface->hasSelection())
{
TQString selText = selectIface->selection();
if(!selText.contains('\n'))
{
grepdlg->setPattern(selText);
}
}
}
// Determine if we have a list of project files
KDevProject *openProject = m_part->project();
if (openProject)
{
grepdlg->setEnableProjectBox(!openProject->allFiles().isEmpty());
}
else
{
grepdlg->setEnableProjectBox(false);
}
grepdlg->show();
}
// @todo - put this somewhere common - or just use Qt if possible
static TQString escape(const TQString &str)
{
TQString escaped("[]{}()\\^$?.+-*|");
TQString res;
for (uint i=0; i < str.length(); ++i)
{
if (escaped.find(str[i]) != -1)
res += "\\";
res += str[i];
}
return res;
}
void GrepViewWidget::showDialogWithPattern(TQString pattern)
{
// Before anything, this removes line feeds from the
// beginning and the end.
int len = pattern.length();
if (len > 0 && pattern[0] == '\n')
{
pattern.remove(0, 1);
len--;
}
if (len > 0 && pattern[len-1] == '\n')
pattern.truncate(len-1);
grepdlg->setPattern( pattern );
// Determine if we have a list of project files
KDevProject *openProject = m_part->project();
if (openProject)
{
grepdlg->setEnableProjectBox(!openProject->allFiles().isEmpty());
}
else
{
grepdlg->setEnableProjectBox(false);
}
grepdlg->show();
}
void GrepViewWidget::searchActivated()
{
if ( grepdlg->keepOutputFlag() )
slotKeepOutput();
m_tabWidget->showPage( m_curOutput );
m_curOutput->setLastFileName("");
m_curOutput->setMatchCount( 0 );
TQString command, files;
// waba: code below breaks on filenames containing a ',' !!!
TQStringList filelist = TQStringList::split(",", grepdlg->filesString());
if (grepdlg->useProjectFilesFlag())
{
KDevProject *openProject = m_part->project();
if (openProject)
{
TQString tmpFilePath;
TQStringList projectFiles = openProject->allFiles();
if (!projectFiles.isEmpty())
{
tmpFilePath = openProject->projectDirectory() + TQChar(TQDir::separator()) + ".grep.tmp";
TQString dir = grepdlg->directoryString(), file;
TQValueList<TQRegExp> regExpList;
if (dir.endsWith(TQChar(TQDir::separator())))
dir.truncate(dir.length() - 1);
if (!filelist.isEmpty())
{
for (TQStringList::Iterator it = filelist.begin(); it != filelist.end(); ++it)
regExpList.append(TQRegExp(*it, true, true));
}
m_tempFile.setName(tmpFilePath);
if (m_tempFile.open(IO_WriteOnly))
{
TQTextStream out(&m_tempFile);
for (TQStringList::Iterator it = projectFiles.begin(); it != projectFiles.end(); ++it)
{
file = TQDir::cleanDirPath(openProject->projectDirectory() + TQChar(TQDir::separator()) + *it);
TQFileInfo info(file);
if (grepdlg->recursiveFlag() && !info.dirPath(true).startsWith(dir)) continue;
if (!grepdlg->recursiveFlag() && info.dirPath(true) != dir) continue;
bool matchOne = regExpList.count() == 0;
for (TQValueList<TQRegExp>::Iterator it2 = regExpList.begin(); it2 != regExpList.end() && !matchOne; ++it2)
matchOne = (*it2).exactMatch(file);
if (matchOne)
out << KShellProcess::quote(file) + "\n";
}
m_tempFile.close();
}
else
{
KMessageBox::error(this, i18n("Unable to create a temporary file for search."));
return;
}
}
command = "cat ";
command += tmpFilePath.replace(' ', "\\ ");
}
}
else
{
if (!filelist.isEmpty())
{
TQStringList::Iterator it(filelist.begin());
files = KShellProcess::quote(*it);
++it;
for (; it != filelist.end(); ++it)
files += " -o -name " + KShellProcess::quote(*it);
}
TQString filepattern = "find ";
filepattern += KShellProcess::quote(grepdlg->directoryString());
if (!grepdlg->recursiveFlag())
filepattern += " -maxdepth 1";
filepattern += " \\( -name ";
filepattern += files;
filepattern += " \\) -print -follow";
if (grepdlg->noFindErrorsFlag())
filepattern += " 2>/dev/null";
command = filepattern + " " ;
}
TQStringList excludelist = TQStringList::split(",", grepdlg->excludeString());
if (!excludelist.isEmpty())
{
TQStringList::Iterator it(excludelist.begin());
command += "| grep -v ";
for (; it != excludelist.end(); ++it)
command += "-e " + KShellProcess::quote(*it) + " ";
}
if (!grepdlg->useProjectFilesFlag())
{
// quote spaces in filenames going to xargs
command += "| sed \"s/ /\\\\\\ /g\" ";
}
command += "| xargs " ;
#ifndef USE_SOLARIS
command += "egrep -H -n -s ";
#else
// -H reported as not being available on Solaris,
// but we're buggy without it on Linux.
command += "egrep -n ";
#endif
if (!grepdlg->caseSensitiveFlag())
{
command += "-i ";
}
command += "-e ";
m_lastPattern = grepdlg->patternString();
TQString pattern = grepdlg->templateString();
if (grepdlg->regexpFlag())
pattern.replace(TQRegExp("%s"), grepdlg->patternString());
else
pattern.replace(TQRegExp("%s"), escape( grepdlg->patternString() ) );
command += KShellProcess::quote(pattern);
m_curOutput->startJob("", command);
m_part->mainWindow()->raiseView(this);
m_part->core()->running(m_part, true);
}
void GrepViewWidget::slotExecuted(TQListBoxItem* item)
{
ProcessListBoxItem *i = static_cast<ProcessListBoxItem*>(item);
if (!i || !i->isCustomItem())
return;
GrepListBoxItem *gi = static_cast<GrepListBoxItem*>(i);
m_part->partController()->editDocument( KURL( gi->filename() ), gi->linenumber()-1 );
}
void GrepViewProcessWidget::insertStdoutLine(const TQCString &line)
{
int pos;
TQString filename, linenumber, rest;
TQString str;
if( !grepbuf.isEmpty() )
{
str = TQString::fromLocal8Bit( grepbuf+line );
grepbuf.truncate( 0 );
}else
{
str = TQString::fromLocal8Bit( line );
}
if ( (pos = str.find(':')) != -1)
{
filename = str.left(pos);
str.remove( 0, pos+1 );
if ( ( pos = str.find(':') ) != -1)
{
linenumber = str.left(pos);
str.remove( 0, pos+1 );
// filename will be displayed only once
// selecting filename will display line 1 of file,
// otherwise, line of requested search
if ( _lastfilename != filename )
{
_lastfilename = filename;
insertItem(new GrepListBoxItem(filename, "0", str, true));
insertItem(new GrepListBoxItem(filename, linenumber, str, false));
}
else
{
insertItem(new GrepListBoxItem(filename, linenumber, str, false));
}
maybeScrollToBottom();
}
m_matchCount++;
}
}
void GrepViewWidget::projectChanged(KDevProject *project)
{
TQString dir = project? project->projectDirectory() : TQDir::homeDirPath();
grepdlg->setDirectory(dir);
}
void GrepViewWidget::popupMenu(TQListBoxItem*, const TQPoint& p)
{
if(m_curOutput->isRunning()) return;
KPopupMenu rmbMenu;
if(KAction *findAction = m_part->actionCollection()->action("edit_grep"))
{
rmbMenu.insertTitle(i18n("Find in Files"));
findAction->plug(&rmbMenu);
rmbMenu.exec(p);
}
}
void GrepViewWidget::slotKeepOutput( )
{
if ( m_lastPattern == TQString::null ) return;
m_tabWidget->changeTab(m_curOutput, m_lastPattern);
m_curOutput = new GrepViewProcessWidget(m_tabWidget);
m_tabWidget->insertTab(m_curOutput, i18n("Search Results"), 0);
connect( m_curOutput, TQT_SIGNAL(clicked(TQListBoxItem*)), this, TQT_SLOT(slotExecuted(TQListBoxItem*)) );
connect( m_curOutput, TQT_SIGNAL(returnPressed(TQListBoxItem*)), this, TQT_SLOT(slotExecuted(TQListBoxItem*)) );
connect( m_curOutput, TQT_SIGNAL(processExited(KProcess* )), this, TQT_SLOT(slotSearchProcessExited()) );
connect( m_curOutput, TQT_SIGNAL(contextMenuRequested( TQListBoxItem*, const TQPoint&)), this, TQT_SLOT(popupMenu(TQListBoxItem*, const TQPoint&)));
}
void GrepViewWidget::slotCloseCurrentOutput( )
{
ProcessWidget* pw = static_cast<ProcessWidget*>(m_tabWidget->currentPage());
if (pw == m_curOutput)
return;
m_tabWidget->removePage(pw);
delete pw;
if (m_tabWidget->count() == 1)
m_closeButton->setEnabled( false );
}
void GrepViewWidget::killJob( int signo )
{
m_curOutput->killJob( signo );
if (!m_tempFile.name().isEmpty() && m_tempFile.exists())
m_tempFile.remove();
}
bool GrepViewWidget::isRunning( ) const
{
return m_curOutput->isRunning();
}
void GrepViewWidget::slotOutputTabChanged( )
{
ProcessWidget* pw = static_cast<ProcessWidget*>(m_tabWidget->currentPage());
if (pw == m_curOutput)
m_closeButton->setEnabled( false );
else
m_closeButton->setEnabled( true );
}
void GrepViewWidget::slotSearchProcessExited( )
{
m_part->core()->running(m_part, false);
if (!m_tempFile.name().isEmpty() && m_tempFile.exists())
m_tempFile.remove();
}
void GrepViewProcessWidget::childFinished( bool normal, int status )
{
// When xargs executes grep several times (because the command line
// generated would be too large for a single grep) and one of those
// sets of files passed to grep does not contain a match, then an
// error status of 123 is set by xargs, even if there is a match in
// another set of files.
// Reset this false status here.
if (status == 123 && numRows() > 1)
status = 0;
insertItem(new ProcessListBoxItem(i18n("*** %n match found. ***", "*** %n matches found. ***", m_matchCount), ProcessListBoxItem::Diagnostic));
maybeScrollToBottom();
ProcessWidget::childFinished(normal, status);
}
void GrepViewProcessWidget::addPartialStdoutLine(const TQCString & line)
{
grepbuf += line;
}
#include "grepviewwidget.moc"