From 4f9e1c4a060334dd17f6e9dc894b7659e4a475f5 Mon Sep 17 00:00:00 2001 From: Christian Surlykke Date: Sun, 13 Jan 2013 20:36:29 +0100 Subject: [PATCH] Added search functionality --- lib/CMakeLists.txt | 12 +++- lib/HistorySearch.cpp | 156 ++++++++++++++++++++++++++++++++++++++++++ lib/HistorySearch.h | 70 +++++++++++++++++++ lib/SearchBar.cpp | 92 +++++++++++++++++++++++++ lib/SearchBar.h | 55 +++++++++++++++ lib/SearchBar.ui | 85 +++++++++++++++++++++++ lib/qtermwidget.cpp | 67 +++++++++++++++++- lib/qtermwidget.h | 10 +++ src/CMakeLists.txt | 1 + src/main.cpp | 12 +++- 10 files changed, 554 insertions(+), 6 deletions(-) create mode 100644 lib/HistorySearch.cpp create mode 100644 lib/HistorySearch.h create mode 100644 lib/SearchBar.cpp create mode 100644 lib/SearchBar.h create mode 100644 lib/SearchBar.ui diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 5664b4e..9235671 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -43,6 +43,7 @@ set ( SRCS Emulation.cpp Filter.cpp History.cpp + HistorySearch.cpp KeyboardTranslator.cpp konsole_wcwidth.cpp kprocess.cpp @@ -53,6 +54,7 @@ set ( SRCS qtermwidget.cpp Screen.cpp ScreenWindow.cpp + SearchBar.cpp Session.cpp ShellCommand.cpp TerminalCharacterDecoder.cpp @@ -66,13 +68,15 @@ set ( SRCS set ( HDRS Emulation.h Filter.h + HistorySearch.h kprocess.h kptydevice.h kptyprocess.h Pty.h qtermwidget.h ScreenWindow.h - Session.h + SearchBar.h + Session.h TerminalDisplay.h Vt102Emulation.h ) @@ -88,8 +92,10 @@ include ( ${QT_USE_FILE} ) # Includes Qt4 headers and libraries (the above comma QT4_WRAP_CPP ( MOC_SRCS ${HDRS} ) # Moc's the headers #include ( ${CMAKE_BINARY_DIR} ) # For including the heades generated by ui files +QT4_WRAP_UI(UI_SRCS SearchBar.ui) + #| qtermwidget specific -include_directories ( ${PROJECT_SOURCE_DIR} ) # You mark some of the headers as global, so I just add the source directory to the includes +include_directories ( ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) # You mark some of the headers as global, so I just add the source directory to the includes # dirs @@ -119,7 +125,7 @@ endif (HAVE_UPDWTMPX) #| Create the Library #add_library( qtermwidget STATIC ${SRCS} ${MOC_SRCS} ) -add_library ( qtermwidget SHARED ${SRCS} ${MOC_SRCS} ) +add_library ( qtermwidget SHARED ${SRCS} ${MOC_SRCS} ${UI_SRCS}) if (APPLE) # this is a must to load the lib correctly diff --git a/lib/HistorySearch.cpp b/lib/HistorySearch.cpp new file mode 100644 index 0000000..1f5a3cf --- /dev/null +++ b/lib/HistorySearch.cpp @@ -0,0 +1,156 @@ +/* + Copyright 2013 Christian Surlykke + + 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ +#include +#include +#include + +#include "TerminalCharacterDecoder.h" +#include "Emulation.h" +#include "HistorySearch.h" + +HistorySearch::HistorySearch(EmulationPtr emulation, QRegExp regExp, + bool forwards, int startColumn, int startLine, + QObject* parent) : +QObject(parent), +m_emulation(emulation), +m_regExp(regExp), +m_forwards(forwards), +m_startColumn(startColumn), +m_startLine(startLine) { +} + +HistorySearch::~HistorySearch() { +} + +void HistorySearch::search() { + bool found = false; + + if (! m_regExp.isEmpty()) + { + if (m_forwards) { + found = search(m_startColumn, m_startLine, -1, m_emulation->lineCount()) || search(0, 0, m_startColumn, m_startLine); + } else { + found = search(0, 0, m_startColumn, m_startLine) || search(m_startColumn, m_startLine, -1, m_emulation->lineCount()); + } + } + + if (found) { + emit matchFound(m_foundStartColumn, m_foundStartLine, m_foundEndColumn, m_foundEndLine); + } else { + emit noMatchFound(); + } + + deleteLater(); +} + +bool HistorySearch::search(int startColumn, int startLine, int endColumn, int endLine) { + qDebug() << "search from" << startColumn << "," << startLine + << "to" << endColumn << "," << endLine; + + int linesRead = 0; + int linesToRead = endLine - startLine + 1; + + qDebug() << "linesToRead:" << linesToRead; + + // We read process history from (and including) startLine to (and including) endLine in + // blocks of at most 10K lines so that we do not use unhealthy amounts of memory + int blockSize; + while ((blockSize = qMin(10000, linesToRead - linesRead)) > 0) { + + QString string; + QTextStream searchStream(&string); + PlainTextDecoder decoder; + decoder.begin(&searchStream); + decoder.setRecordLinePositions(true); + + // Calculate lines to read and read them + int blockStartLine = m_forwards ? startLine + linesRead : endLine - linesRead - blockSize + 1; + int chunkEndLine = blockStartLine + blockSize - 1; + m_emulation->writeToStream(&decoder, blockStartLine, chunkEndLine); + + // We search between startColumn in the first line of the string and endColumn in the last + // line of the string. First we calculate the position (in the string) of endColumn in the + // last line of the string + int endPosition; + + // The String that Emulator.writeToStream produces has a newline at the end, and so ends with an + // empty line - we ignore that. + int numberOfLinesInString = decoder.linePositions().size() - 1; + if (numberOfLinesInString > 0 && endColumn > -1 ) + { + endPosition = decoder.linePositions().at(numberOfLinesInString - 1) + endColumn; + } + else + { + endPosition = string.size(); + } + + // So now we can log for m_regExp in the string between startColumn and endPosition + int matchStart; + if (m_forwards) + { + matchStart = string.indexOf(m_regExp, startColumn); + if (matchStart >= endPosition) + matchStart = -1; + } + else + { + matchStart = string.lastIndexOf(m_regExp, endPosition - 1); + if (matchStart < startColumn) + matchStart = -1; + } + + if (matchStart > -1) + { + int matchEnd = matchStart + m_regExp.matchedLength() - 1; + qDebug() << "Found in string from" << matchStart << "to" << matchEnd; + + // Translate startPos and endPos to startColum, startLine, endColumn and endLine in history. + int startLineNumberInString = findLineNumberInString(decoder.linePositions(), matchStart); + m_foundStartColumn = matchStart - decoder.linePositions().at(startLineNumberInString); + m_foundStartLine = startLineNumberInString + startLine + linesRead; + + int endLineNumberInString = findLineNumberInString(decoder.linePositions(), matchEnd); + m_foundEndColumn = matchEnd - decoder.linePositions().at(endLineNumberInString); + m_foundEndLine = endLineNumberInString + startLine + linesRead; + + qDebug() << "m_foundStartColumn" << m_foundStartColumn + << "m_foundStartLine" << m_foundEndLine + << "m_foundEndColumn" << m_foundEndColumn + << "m_foundEndLine" << m_foundEndLine; + + return true; + } + + + linesRead += blockSize; + } + + qDebug() << "Not found"; + return false; +} + + +int HistorySearch::findLineNumberInString(QList linePositions, int position) { + int lineNum = 0; + while (lineNum + 1 < linePositions.size() && linePositions[lineNum + 1] <= position) + lineNum++; + + return lineNum; +} \ No newline at end of file diff --git a/lib/HistorySearch.h b/lib/HistorySearch.h new file mode 100644 index 0000000..c315299 --- /dev/null +++ b/lib/HistorySearch.h @@ -0,0 +1,70 @@ +/* + Copyright 2013 Christian Surlykke + + 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ +#ifndef TASK_H +#define TASK_H + +#include +#include +#include + +#include +#include + +#include "Emulation.h" +#include "TerminalCharacterDecoder.h" + +using namespace Konsole; + +typedef QPointer EmulationPtr; + +class HistorySearch : public QObject +{ + Q_OBJECT + +public: + explicit HistorySearch(EmulationPtr emulation, QRegExp regExp, bool forwards, + int startColumn, int startLine, QObject* parent); + + ~HistorySearch(); + + void search(); + +signals: + void matchFound(int startColumn, int startLine, int endColumn, int endLine); + void noMatchFound(); + +private: + bool search(int startColumn, int startLine, int endColumn, int endLine); + int findLineNumberInString(QList linePositions, int position); + + + EmulationPtr m_emulation; + QRegExp m_regExp; + bool m_forwards; + int m_startColumn; + int m_startLine; + + int m_foundStartColumn; + int m_foundStartLine; + int m_foundEndColumn; + int m_foundEndLine; +}; + +#endif /* TASK_H */ + diff --git a/lib/SearchBar.cpp b/lib/SearchBar.cpp new file mode 100644 index 0000000..590af18 --- /dev/null +++ b/lib/SearchBar.cpp @@ -0,0 +1,92 @@ +/* + Copyright 2013 Christian Surlykke + + 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ +#include +#include +#include +#include + +#include "SearchBar.h" + +SearchBar::SearchBar(QWidget *parent) : + QWidget(parent), + m_regExp("") +{ + widget.setupUi(this); + connect(widget.closeButton, SIGNAL(clicked()), this, SLOT(hide())); + connect(widget.searchTextEdit, SIGNAL(textChanged(QString)), this, SLOT(searchTextChanged())); + connect(widget.findPreviousButton, SIGNAL(clicked()), this, SLOT(findPrevious())); + connect(widget.findNextButton, SIGNAL(clicked()), this, SLOT(findNext())); + + QMenu *optionsMenu = new QMenu(widget.optionsButton); + widget.optionsButton->setMenu(optionsMenu); + + m_matchCaseMenuEntry = optionsMenu->addAction(tr("Match case")); + m_matchCaseMenuEntry->setCheckable(true); + m_matchCaseMenuEntry->setChecked(true); + + m_useRegularExpressionMenuEntry = optionsMenu->addAction(tr("Regular expression")); + m_useRegularExpressionMenuEntry->setCheckable(true); + + m_highlightMatchesMenuEntry = optionsMenu->addAction(tr("Higlight all matches")); + m_highlightMatchesMenuEntry->setCheckable(true); + m_highlightMatchesMenuEntry->setChecked(true); +} + +SearchBar::~SearchBar() { +} + +void SearchBar::show() +{ + QWidget::show(); + widget.searchTextEdit->setFocus(); +} + +void SearchBar::hide() +{ + QWidget::hide(); +} + +void SearchBar::toggleShown() +{ + isHidden() ? show() : hide(); +} + +void SearchBar::searchTextChanged() +{ + m_regExp = QRegExp(widget.searchTextEdit->text()); + + if (! m_useRegularExpressionMenuEntry->isChecked()) { + m_regExp.setPatternSyntax(QRegExp::FixedString); + } + if (! m_matchCaseMenuEntry->isChecked()) { + m_regExp.setCaseSensitivity(Qt::CaseInsensitive); + } + + emit search(m_regExp, true, false); +} + +void SearchBar::findNext() +{ + emit search(m_regExp, true, true); +} + +void SearchBar::findPrevious() +{ + emit search(m_regExp, false, false); +} diff --git a/lib/SearchBar.h b/lib/SearchBar.h new file mode 100644 index 0000000..80df1a5 --- /dev/null +++ b/lib/SearchBar.h @@ -0,0 +1,55 @@ +/* + Copyright 2013 Christian Surlykke + + 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ +#ifndef _SEARCHBAR_H +#define _SEARCHBAR_H + +#include + +#include "ui_SearchBar.h" +#include "HistorySearch.h" + +class SearchBar : public QWidget { + Q_OBJECT +public: + SearchBar(QWidget* parent = 0); + virtual ~SearchBar(); + virtual void show(); + virtual void hide(); + +public slots: + void toggleShown(); + +signals: + void search(QRegExp regexp, bool forwards, bool skip); + +private slots: + void searchTextChanged(); + void findNext(); + void findPrevious(); + +private: + QRegExp m_regExp; + + Ui::SearchBar widget; + QAction *m_matchCaseMenuEntry; + QAction *m_useRegularExpressionMenuEntry; + QAction *m_highlightMatchesMenuEntry; +}; + +#endif /* _SEARCHBAR_H */ diff --git a/lib/SearchBar.ui b/lib/SearchBar.ui new file mode 100644 index 0000000..6e0c4e8 --- /dev/null +++ b/lib/SearchBar.ui @@ -0,0 +1,85 @@ + + + SearchBar + + + + 0 + 0 + 399 + 40 + + + + SearchBar + + + + + + X + + + + + + + + + + + + Find: + + + + + + + + + + < + + + + + + + + + + + + > + + + + + + + + + + + + ... + + + + + + + + QToolButton::InstantPopup + + + Qt::DownArrow + + + + + + + + diff --git a/lib/qtermwidget.cpp b/lib/qtermwidget.cpp index a21bd75..d7cd280 100644 --- a/lib/qtermwidget.cpp +++ b/lib/qtermwidget.cpp @@ -16,7 +16,11 @@ Boston, MA 02110-1301, USA. */ - +#include +#include +#include +#include +#include "SearchBar.h" #include "qtermwidget.h" #include "ColorTables.h" @@ -27,6 +31,7 @@ #include "TerminalDisplay.h" #include "KeyboardTranslator.h" #include "ColorScheme.h" +#include "SearchBar.h" using namespace Konsole; @@ -119,6 +124,47 @@ void QTermWidget::selectionChanged(bool textSelected) emit copyAvailable(textSelected); } +void QTermWidget::search(QRegExp regexp, bool forwards, bool next) +{ + int startColumn, startLine; + + if (next) // search from end of current selection + { + m_impl->m_terminalDisplay->screenWindow()->screen()->getSelectionEnd(startColumn, startLine); + } + else // search from start of current selection + { + m_impl->m_terminalDisplay->screenWindow()->screen()->getSelectionStart(startColumn, startLine); + } + + qDebug() << "current selection starts at: " << startColumn << startLine; + qDebug() << "current cursor position: " << m_impl->m_terminalDisplay->screenWindow()->cursorPosition(); + + + HistorySearch *historySearch = + new HistorySearch(m_impl->m_session->emulation(), regexp, forwards, startColumn, startLine, this); + connect(historySearch, SIGNAL(matchFound(int, int, int, int)), this, SLOT(matchFound(int, int, int, int))); + connect(historySearch, SIGNAL(noMatchFound()), this, SLOT(noMatchFound())); + historySearch->search(); +} + + +void QTermWidget::matchFound(int startColumn, int startLine, int endColumn, int endLine) +{ + ScreenWindow* sw = m_impl->m_terminalDisplay->screenWindow(); + qDebug() << "Scroll to" << startLine; + sw->scrollTo(startLine); + sw->setTrackOutput(false); + sw->notifyOutputChanged(); + sw->setSelectionStart(startColumn, startLine - sw->currentLine(), false); + sw->setSelectionEnd(endColumn, endLine - sw->currentLine()); +} + +void QTermWidget::noMatchFound() +{ + m_impl->m_terminalDisplay->screenWindow()->clearSelection(); +} + int QTermWidget::getShellPID() { return m_impl->m_session->processId(); @@ -161,7 +207,19 @@ void QTermWidget::startShellProgram() void QTermWidget::init(int startnow) { + m_layout = new QVBoxLayout(); + m_layout->setMargin(0); + setLayout(m_layout); + m_impl = new TermWidgetImpl(this); + m_impl->m_terminalDisplay->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + m_layout->addWidget(m_impl->m_terminalDisplay); + + m_searchBar = new SearchBar(this); + m_searchBar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); + connect(m_searchBar, SIGNAL(search(QRegExp, bool, bool)), this, SLOT(search(QRegExp, bool, bool))); + m_layout->addWidget(m_searchBar); + m_searchBar->hide(); if (startnow && m_impl->m_session) { m_impl->m_session->run(); @@ -186,6 +244,8 @@ void QTermWidget::init(int startnow) font.setPointSize(10); font.setStyleHint(QFont::TypeWriter); setTerminalFont(font); + m_searchBar->setFont(font); + setScrollBarPosition(NoScrollBar); m_impl->m_session->addView(m_impl->m_terminalDisplay); @@ -356,6 +416,11 @@ QString QTermWidget::keyBindings() return m_impl->m_session->keyBindings(); } +void QTermWidget::toggleShowSearchBar() +{ + m_searchBar->toggleShown(); +} + bool QTermWidget::flowControlEnabled(void) { return m_impl->m_session->flowControlEnabled(); diff --git a/lib/qtermwidget.h b/lib/qtermwidget.h index db3d699..ff7a882 100644 --- a/lib/qtermwidget.h +++ b/lib/qtermwidget.h @@ -23,6 +23,7 @@ #include struct TermWidgetImpl; +class SearchBar; class QTermWidget : public QWidget { @@ -144,6 +145,8 @@ public slots: */ void clear(); + void toggleShowSearchBar(); + protected: virtual void resizeEvent(QResizeEvent *); @@ -151,9 +154,16 @@ protected slots: void sessionFinished(); void selectionChanged(bool textSelected); +private slots: + void search(QRegExp, bool forwards, bool skip); + void matchFound(int startColumn, int startLine, int endColumn, int endLine); + void noMatchFound(); + private: void init(int startnow); TermWidgetImpl * m_impl; + SearchBar* m_searchBar; + QVBoxLayout *m_layout; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c921acc..10a9c4b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,7 @@ set ( SRCS #| qtermwidget Library include_directories ( ${PROJECT_SOURCE_DIR}/../lib + ${CMAKE_CURRENT_BINARY_DIR}/../lib ) set(QTERMWIDGET_LIB qtermwidget) diff --git a/src/main.cpp b/src/main.cpp index 25bccb0..2cd12b9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,15 +21,23 @@ #include #include #include - +#include +#include #include "qtermwidget.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); + QIcon::setThemeName("oxygen"); QMainWindow *mainWindow = new QMainWindow(); - + QTermWidget *console = new QTermWidget(); + + QMenuBar *menuBar = new QMenuBar(mainWindow); + QMenu *actionsMenu = new QMenu("Actions", menuBar); + menuBar->addMenu(actionsMenu); + actionsMenu->addAction("Find..", console, SLOT(toggleShowSearchBar()), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_F)); + mainWindow->setMenuBar(menuBar); QFont font = QApplication::font(); #ifdef Q_WS_MAC