/*************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(), 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 = TQt::black; dim = TQt::darkGreen; result = TQt::blue; if (isSelected()) bkground = TQt::lightGray; else bkground = TQt::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(m_part->partController()->activePart()); if (ro_part) { SelectionInterface *selectIface = dynamic_cast(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 TQt 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 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::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(item); if (!i || !i->isCustomItem()) return; GrepListBoxItem *gi = static_cast(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() ) 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(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(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"