diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -82,14 +82,17 @@ Pty.cpp RenameTabDialog.cpp RenameTabWidget.cpp - Screen.cpp + SaveHistoryTask.cpp + SearchHistoryTask.cpp + Screen.cpp ScreenWindow.cpp ScrollState.cpp Session.cpp SessionController.cpp SessionManager.cpp SessionListModel.cpp - ShellCommand.cpp + SessionTask.cpp + ShellCommand.cpp TabTitleFormatButton.cpp TerminalCharacterDecoder.cpp ExtendedCharTable.cpp diff --git a/src/SaveHistoryTask.h b/src/SaveHistoryTask.h new file mode 100644 --- /dev/null +++ b/src/SaveHistoryTask.h @@ -0,0 +1,75 @@ +/* + Copyright 2006-2008 by Robert Knight + Copyright 2009 by Thomas Dreibholz + + 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 SAVEHISTORYTASK_H +#define SAVEHISTORYTASK_H + +#include "SessionTask.h" +#include "TerminalCharacterDecoder.h" + +#include + +namespace Konsole +{ + +/** + * A task which prompts for a URL for each session and saves that session's output + * to the given URL + */ +class SaveHistoryTask : public SessionTask +{ + Q_OBJECT + +public: + /** Constructs a new task to save session output to URLs */ + explicit SaveHistoryTask(QObject *parent = nullptr); + ~SaveHistoryTask() Q_DECL_OVERRIDE; + + /** + * Opens a save file dialog for each session in the group and begins saving + * each session's history to the given URL. + * + * The data transfer is performed asynchronously and will continue after execute() returns. + */ + void execute() Q_DECL_OVERRIDE; + +private Q_SLOTS: + void jobDataRequested(KIO::Job *job, QByteArray &data); + void jobResult(KJob *job); + +private: + class SaveJob // structure to keep information needed to process + // incoming data requests from jobs + { + public: + QPointer session; // the session associated with a history save job + int lastLineFetched; // the last line processed in the previous data request + // set this to -1 at the start of the save job + + TerminalCharacterDecoder *decoder; // decoder used to convert terminal characters + // into output + }; + + QHash _jobSession; +}; + +} + +#endif diff --git a/src/SaveHistoryTask.cpp b/src/SaveHistoryTask.cpp new file mode 100644 --- /dev/null +++ b/src/SaveHistoryTask.cpp @@ -0,0 +1,170 @@ +/* + Copyright 2006-2008 by Robert Knight + Copyright 2009 by Thomas Dreibholz + + 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 "SaveHistoryTask.h" + +#include +#include +#include + +#include +#include + +#include "SessionManager.h" +#include "Emulation.h" + +namespace Konsole { + +SaveHistoryTask::SaveHistoryTask(QObject* parent) + : SessionTask(parent) +{ +} + +SaveHistoryTask::~SaveHistoryTask() = default; + +void SaveHistoryTask::execute() +{ + // TODO - think about the UI when saving multiple history sessions, if there are more than two or + // three then providing a URL for each one will be tedious + + // TODO - show a warning ( preferably passive ) if saving the history output fails + QFileDialog* dialog = new QFileDialog(QApplication::activeWindow(), + QString(), + QDir::homePath()); + dialog->setAcceptMode(QFileDialog::AcceptSave); + + QStringList mimeTypes { + QStringLiteral("text/plain"), + QStringLiteral("text/html") + }; + dialog->setMimeTypeFilters(mimeTypes); + + // iterate over each session in the task and display a dialog to allow the user to choose where + // to save that session's history. + // then start a KIO job to transfer the data from the history to the chosen URL + foreach(const auto& session, sessions()) { + dialog->setWindowTitle(i18n("Save Output From %1", session->title(Session::NameRole))); + + int result = dialog->exec(); + + if (result != QDialog::Accepted) { + continue; + } + + QUrl url = (dialog->selectedUrls()).at(0); + + if (!url.isValid()) { + // UI: Can we make this friendlier? + KMessageBox::sorry(nullptr , i18n("%1 is an invalid URL, the output could not be saved.", url.url())); + continue; + } + + KIO::TransferJob* job = KIO::put(url, + -1, // no special permissions + // overwrite existing files + // do not resume an existing transfer + // show progress information only for remote + // URLs + KIO::Overwrite | (url.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags) + // a better solution would be to show progress + // information after a certain period of time + // instead, since the overall speed of transfer + // depends on factors other than just the protocol + // used + ); + + SaveJob jobInfo; + jobInfo.session = session; + jobInfo.lastLineFetched = -1; // when each request for data comes in from the KIO subsystem + // lastLineFetched is used to keep track of how much of the history + // has already been sent, and where the next request should continue + // from. + // this is set to -1 to indicate the job has just been started + + if (((dialog->selectedNameFilter()).contains(QLatin1String("html"), Qt::CaseInsensitive)) || + ((dialog->selectedFiles()).at(0).endsWith(QLatin1String("html"), Qt::CaseInsensitive))) { + Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); + jobInfo.decoder = new HTMLDecoder(profile); + } else { + jobInfo.decoder = new PlainTextDecoder(); + } + + _jobSession.insert(job, jobInfo); + + connect(job, &KIO::TransferJob::dataReq, this, &Konsole::SaveHistoryTask::jobDataRequested); + connect(job, &KIO::TransferJob::result, this, &Konsole::SaveHistoryTask::jobResult); + } + + dialog->deleteLater(); +} + +void SaveHistoryTask::jobDataRequested(KIO::Job* job , QByteArray& data) +{ + // TODO - Report progress information for the job + + // PERFORMANCE: Do some tests and tweak this value to get faster saving + const int LINES_PER_REQUEST = 500; + + SaveJob& info = _jobSession[job]; + + // transfer LINES_PER_REQUEST lines from the session's history + // to the save location + if (!info.session.isNull()) { + // note: when retrieving lines from the emulation, + // the first line is at index 0. + + int sessionLines = info.session->emulation()->lineCount(); + + if (sessionLines - 1 == info.lastLineFetched) { + return; // if there is no more data to transfer then stop the job + } + + int copyUpToLine = qMin(info.lastLineFetched + LINES_PER_REQUEST , + sessionLines - 1); + + QTextStream stream(&data, QIODevice::ReadWrite); + info.decoder->begin(&stream); + info.session->emulation()->writeToStream(info.decoder , info.lastLineFetched + 1 , copyUpToLine); + info.decoder->end(); + + info.lastLineFetched = copyUpToLine; + } +} +void SaveHistoryTask::jobResult(KJob* job) +{ + if (job->error() != 0) { + KMessageBox::sorry(nullptr , i18n("A problem occurred when saving the output.\n%1", job->errorString())); + } + + TerminalCharacterDecoder * decoder = _jobSession[job].decoder; + + _jobSession.remove(job); + + delete decoder; + + // notify the world that the task is done + emit completed(true); + + if (autoDelete()) { + deleteLater(); + } +} + +} diff --git a/src/SearchHistoryTask.h b/src/SearchHistoryTask.h new file mode 100644 --- /dev/null +++ b/src/SearchHistoryTask.h @@ -0,0 +1,102 @@ +/* + Copyright 2006-2008 by Robert Knight + Copyright 2009 by Thomas Dreibholz + + 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 SEARCHHISTORYTASK_H +#define SEARCHHISTORYTASK_H + +#include +#include +#include + +#include "SessionTask.h" +#include "Enumeration.h" +#include "ScreenWindow.h" +#include "Session.h" + +namespace Konsole +{ + +//class SearchHistoryThread; +/** + * A task which searches through the output of sessions for matches for a given regular expression. + * SearchHistoryTask operates on ScreenWindow instances rather than sessions added by addSession(). + * A screen window can be added to the list to search using addScreenWindow() + * + * When execute() is called, the search begins in the direction specified by searchDirection(), + * starting at the position of the current selection. + * + * FIXME - This is not a proper implementation of SessionTask, in that it ignores sessions specified + * with addSession() + * + * TODO - Implementation requirements: + * May provide progress feedback to the user when searching very large output logs. + */ +class SearchHistoryTask : public SessionTask +{ + Q_OBJECT + +public: + /** + * Constructs a new search task. + */ + explicit SearchHistoryTask(QObject *parent = nullptr); + + /** Adds a screen window to the list to search when execute() is called. */ + void addScreenWindow(Session *session, ScreenWindow *searchWindow); + + /** Sets the regular expression which is searched for when execute() is called */ + void setRegExp(const QRegularExpression &expression); + /** Returns the regular expression which is searched for when execute() is called */ + QRegularExpression regExp() const; + + /** Specifies the direction to search in when execute() is called. */ + void setSearchDirection(Enum::SearchDirection direction); + /** Returns the current search direction. See setSearchDirection(). */ + Enum::SearchDirection searchDirection() const; + + /** The line from which the search will be done **/ + void setStartLine(int line); + + /** + * Performs a search through the session's history, starting at the position + * of the current selection, in the direction specified by setSearchDirection(). + * + * If it finds a match, the ScreenWindow specified in the constructor is + * scrolled to the position where the match occurred and the selection + * is set to the matching text. execute() then returns immediately. + * + * To continue the search looking for further matches, call execute() again. + */ + void execute() Q_DECL_OVERRIDE; + +private: + using ScreenWindowPtr = QPointer; + + void executeOnScreenWindow(QPointer session, ScreenWindowPtr window); + void highlightResult(ScreenWindowPtr window, int findPos); + + QMap< QPointer, ScreenWindowPtr > _windows; + QRegularExpression _regExp; + Enum::SearchDirection _direction; + int _startLine; +}; + +} +#endif diff --git a/src/SearchHistoryTask.cpp b/src/SearchHistoryTask.cpp new file mode 100644 --- /dev/null +++ b/src/SearchHistoryTask.cpp @@ -0,0 +1,222 @@ +/* + Copyright 2006-2008 by Robert Knight + Copyright 2009 by Thomas Dreibholz + + 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 "SearchHistoryTask.h" +#include "TerminalCharacterDecoder.h" +#include "Emulation.h" + +namespace Konsole { + +void SearchHistoryTask::addScreenWindow(Session* session , ScreenWindow* searchWindow) +{ + _windows.insert(session, searchWindow); +} + +void SearchHistoryTask::execute() +{ + auto iter = QMapIterator,ScreenWindowPtr>(_windows); + + while (iter.hasNext()) { + iter.next(); + executeOnScreenWindow(iter.key() , iter.value()); + } +} + +void SearchHistoryTask::executeOnScreenWindow(QPointer session , ScreenWindowPtr window) +{ + Q_ASSERT(session); + Q_ASSERT(window); + + Emulation* emulation = session->emulation(); + + if (!_regExp.pattern().isEmpty()) { + int pos = -1; + const bool forwards = (_direction == Enum::ForwardsSearch); + const int lastLine = window->lineCount() - 1; + + int startLine; + if (forwards && (_startLine == lastLine)) { + startLine = 0; + } else if (!forwards && (_startLine == 0)) { + startLine = lastLine; + } else { + startLine = _startLine + (forwards ? 1 : -1); + } + + QString string; + + //text stream to read history into string for pattern or regular expression searching + QTextStream searchStream(&string); + + PlainTextDecoder decoder; + decoder.setRecordLinePositions(true); + + //setup first and last lines depending on search direction + int line = startLine; + + //read through and search history in blocks of 10K lines. + //this balances the need to retrieve lots of data from the history each time + //(for efficient searching) + //without using silly amounts of memory if the history is very large. + const int maxDelta = qMin(window->lineCount(), 10000); + int delta = forwards ? maxDelta : -maxDelta; + + int endLine = line; + bool hasWrapped = false; // set to true when we reach the top/bottom + // of the output and continue from the other + // end + + //loop through history in blocks of lines. + do { + // ensure that application does not appear to hang + // if searching through a lengthy output + QApplication::processEvents(); + + // calculate lines to search in this iteration + if (hasWrapped) { + if (endLine == lastLine) { + line = 0; + } else if (endLine == 0) { + line = lastLine; + } + + endLine += delta; + + if (forwards) { + endLine = qMin(startLine , endLine); + } else { + endLine = qMax(startLine , endLine); + } + } else { + endLine += delta; + + if (endLine > lastLine) { + hasWrapped = true; + endLine = lastLine; + } else if (endLine < 0) { + hasWrapped = true; + endLine = 0; + } + } + + decoder.begin(&searchStream); + emulation->writeToStream(&decoder, qMin(endLine, line) , qMax(endLine, line)); + decoder.end(); + + // line number search below assumes that the buffer ends with a new-line + string.append(QLatin1Char('\n')); + + if (forwards) { + pos = string.indexOf(_regExp); + } else { + pos = string.lastIndexOf(_regExp); + } + + //if a match is found, position the cursor on that line and update the screen + if (pos != -1) { + int newLines = 0; + QList linePositions = decoder.linePositions(); + while (newLines < linePositions.count() && linePositions[newLines] <= pos) { + newLines++; + } + + // ignore the new line at the start of the buffer + newLines--; + + int findPos = qMin(line, endLine) + newLines; + + highlightResult(window, findPos); + + emit completed(true); + + return; + } + + //clear the current block of text and move to the next one + string.clear(); + line = endLine; + } while (startLine != endLine); + + // if no match was found, clear selection to indicate this + window->clearSelection(); + window->notifyOutputChanged(); + } + + emit completed(false); +} +void SearchHistoryTask::highlightResult(ScreenWindowPtr window , int findPos) +{ + //work out how many lines into the current block of text the search result was found + //- looks a little painful, but it only has to be done once per search. + + ////qDebug() << "Found result at line " << findPos; + + //update display to show area of history containing selection + if ((findPos < window->currentLine()) || + (findPos >= (window->currentLine() + window->windowLines()))) { + int centeredScrollPos = findPos - window->windowLines() / 2; + if (centeredScrollPos < 0) { + centeredScrollPos = 0; + } + + window->scrollTo(centeredScrollPos); + } + + window->setTrackOutput(false); + window->notifyOutputChanged(); + window->setCurrentResultLine(findPos); +} + +SearchHistoryTask::SearchHistoryTask(QObject* parent) + : SessionTask(parent) + , _direction(Enum::BackwardsSearch) + , _startLine(0) +{ +} + +void SearchHistoryTask::setSearchDirection(Enum::SearchDirection direction) +{ + _direction = direction; +} + +void SearchHistoryTask::setStartLine(int line) +{ + _startLine = line; +} + +Enum::SearchDirection SearchHistoryTask::searchDirection() const +{ + return _direction; +} + +void SearchHistoryTask::setRegExp(const QRegularExpression &expression) +{ + _regExp = expression; +} + +QRegularExpression SearchHistoryTask::regExp() const +{ + return _regExp; +} + +} diff --git a/src/SessionController.h b/src/SessionController.h --- a/src/SessionController.h +++ b/src/SessionController.h @@ -64,9 +64,6 @@ class FileFilter; class EditProfileDialog; -// SaveHistoryTask -class TerminalCharacterDecoder; - using SessionPtr = QPointer; /** @@ -371,165 +368,6 @@ return !_session.isNull() && !_view.isNull(); } -/** - * Abstract class representing a task which can be performed on a group of sessions. - * - * Create a new instance of the appropriate sub-class for the task you want to perform and - * call the addSession() method to add each session which needs to be processed. - * - * Finally, call the execute() method to perform the sub-class specific action on each - * of the sessions. - */ -class SessionTask : public QObject -{ - Q_OBJECT - -public: - explicit SessionTask(QObject *parent = nullptr); - - /** - * Sets whether the task automatically deletes itself when the task has been finished. - * Depending on whether the task operates synchronously or asynchronously, the deletion - * may be scheduled immediately after execute() returns or it may happen some time later. - */ - void setAutoDelete(bool enable); - /** Returns true if the task automatically deletes itself. See setAutoDelete() */ - bool autoDelete() const; - - /** Adds a new session to the group */ - void addSession(Session *session); - - /** - * Executes the task on each of the sessions in the group. - * The completed() signal is emitted when the task is finished, depending on the specific sub-class - * execute() may be synchronous or asynchronous - */ - virtual void execute() = 0; - -Q_SIGNALS: - /** - * Emitted when the task has completed. - * Depending on the task this may occur just before execute() returns, or it - * may occur later - * - * @param success Indicates whether the task completed successfully or not - */ - void completed(bool success); - -protected: - /** Returns a list of sessions in the group */ - QList< SessionPtr > sessions() const; - -private: - bool _autoDelete; - QList< SessionPtr > _sessions; -}; - -/** - * A task which prompts for a URL for each session and saves that session's output - * to the given URL - */ -class SaveHistoryTask : public SessionTask -{ - Q_OBJECT - -public: - /** Constructs a new task to save session output to URLs */ - explicit SaveHistoryTask(QObject *parent = nullptr); - ~SaveHistoryTask() Q_DECL_OVERRIDE; - - /** - * Opens a save file dialog for each session in the group and begins saving - * each session's history to the given URL. - * - * The data transfer is performed asynchronously and will continue after execute() returns. - */ - void execute() Q_DECL_OVERRIDE; - -private Q_SLOTS: - void jobDataRequested(KIO::Job *job, QByteArray &data); - void jobResult(KJob *job); - -private: - class SaveJob // structure to keep information needed to process - // incoming data requests from jobs - { - public: - SessionPtr session; // the session associated with a history save job - int lastLineFetched; // the last line processed in the previous data request - // set this to -1 at the start of the save job - - TerminalCharacterDecoder *decoder; // decoder used to convert terminal characters - // into output - }; - - QHash _jobSession; -}; - -//class SearchHistoryThread; -/** - * A task which searches through the output of sessions for matches for a given regular expression. - * SearchHistoryTask operates on ScreenWindow instances rather than sessions added by addSession(). - * A screen window can be added to the list to search using addScreenWindow() - * - * When execute() is called, the search begins in the direction specified by searchDirection(), - * starting at the position of the current selection. - * - * FIXME - This is not a proper implementation of SessionTask, in that it ignores sessions specified - * with addSession() - * - * TODO - Implementation requirements: - * May provide progress feedback to the user when searching very large output logs. - */ -class SearchHistoryTask : public SessionTask -{ - Q_OBJECT - -public: - /** - * Constructs a new search task. - */ - explicit SearchHistoryTask(QObject *parent = nullptr); - - /** Adds a screen window to the list to search when execute() is called. */ - void addScreenWindow(Session *session, ScreenWindow *searchWindow); - - /** Sets the regular expression which is searched for when execute() is called */ - void setRegExp(const QRegularExpression &expression); - /** Returns the regular expression which is searched for when execute() is called */ - QRegularExpression regExp() const; - - /** Specifies the direction to search in when execute() is called. */ - void setSearchDirection(Enum::SearchDirection direction); - /** Returns the current search direction. See setSearchDirection(). */ - Enum::SearchDirection searchDirection() const; - - /** The line from which the search will be done **/ - void setStartLine(int line); - - /** - * Performs a search through the session's history, starting at the position - * of the current selection, in the direction specified by setSearchDirection(). - * - * If it finds a match, the ScreenWindow specified in the constructor is - * scrolled to the position where the match occurred and the selection - * is set to the matching text. execute() then returns immediately. - * - * To continue the search looking for further matches, call execute() again. - */ - void execute() Q_DECL_OVERRIDE; - -private: - using ScreenWindowPtr = QPointer; - - void executeOnScreenWindow(SessionPtr session, ScreenWindowPtr window); - void highlightResult(ScreenWindowPtr window, int findPos); - - QMap< SessionPtr, ScreenWindowPtr > _windows; - QRegularExpression _regExp; - Enum::SearchDirection _direction; - int _startLine; -}; } #endif //SESSIONCONTROLLER_H diff --git a/src/SessionController.cpp b/src/SessionController.cpp --- a/src/SessionController.cpp +++ b/src/SessionController.cpp @@ -72,11 +72,8 @@ #include "SessionManager.h" #include "Enumeration.h" #include "PrintOptions.h" - -// for SaveHistoryTask -#include -#include -#include "TerminalCharacterDecoder.h" +#include "SaveHistoryTask.h" +#include "SearchHistoryTask.h" // For Unix signal names #include @@ -1781,345 +1778,6 @@ return !(qApp->applicationName() == QLatin1String("konsole")); } -SessionTask::SessionTask(QObject* parent) - : QObject(parent) - , _autoDelete(false) -{ -} -void SessionTask::setAutoDelete(bool enable) -{ - _autoDelete = enable; -} -bool SessionTask::autoDelete() const -{ - return _autoDelete; -} -void SessionTask::addSession(Session* session) -{ - _sessions << session; -} -QList SessionTask::sessions() const -{ - return _sessions; -} - -SaveHistoryTask::SaveHistoryTask(QObject* parent) - : SessionTask(parent) -{ -} -SaveHistoryTask::~SaveHistoryTask() = default; - -void SaveHistoryTask::execute() -{ - // TODO - think about the UI when saving multiple history sessions, if there are more than two or - // three then providing a URL for each one will be tedious - - // TODO - show a warning ( preferably passive ) if saving the history output fails - QFileDialog* dialog = new QFileDialog(QApplication::activeWindow(), - QString(), - QDir::homePath()); - dialog->setAcceptMode(QFileDialog::AcceptSave); - - QStringList mimeTypes; - mimeTypes << QStringLiteral("text/plain"); - mimeTypes << QStringLiteral("text/html"); - dialog->setMimeTypeFilters(mimeTypes); - - // iterate over each session in the task and display a dialog to allow the user to choose where - // to save that session's history. - // then start a KIO job to transfer the data from the history to the chosen URL - foreach(const SessionPtr& session, sessions()) { - dialog->setWindowTitle(i18n("Save Output From %1", session->title(Session::NameRole))); - - int result = dialog->exec(); - - if (result != QDialog::Accepted) { - continue; - } - - QUrl url = (dialog->selectedUrls()).at(0); - - if (!url.isValid()) { - // UI: Can we make this friendlier? - KMessageBox::sorry(nullptr , i18n("%1 is an invalid URL, the output could not be saved.", url.url())); - continue; - } - - KIO::TransferJob* job = KIO::put(url, - -1, // no special permissions - // overwrite existing files - // do not resume an existing transfer - // show progress information only for remote - // URLs - KIO::Overwrite | (url.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags) - // a better solution would be to show progress - // information after a certain period of time - // instead, since the overall speed of transfer - // depends on factors other than just the protocol - // used - ); - - SaveJob jobInfo; - jobInfo.session = session; - jobInfo.lastLineFetched = -1; // when each request for data comes in from the KIO subsystem - // lastLineFetched is used to keep track of how much of the history - // has already been sent, and where the next request should continue - // from. - // this is set to -1 to indicate the job has just been started - - if (((dialog->selectedNameFilter()).contains(QLatin1String("html"), Qt::CaseInsensitive)) || - ((dialog->selectedFiles()).at(0).endsWith(QLatin1String("html"), Qt::CaseInsensitive))) { - Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); - jobInfo.decoder = new HTMLDecoder(profile); - } else { - jobInfo.decoder = new PlainTextDecoder(); - } - - _jobSession.insert(job, jobInfo); - - connect(job, &KIO::TransferJob::dataReq, this, &Konsole::SaveHistoryTask::jobDataRequested); - connect(job, &KIO::TransferJob::result, this, &Konsole::SaveHistoryTask::jobResult); - } - - dialog->deleteLater(); -} -void SaveHistoryTask::jobDataRequested(KIO::Job* job , QByteArray& data) -{ - // TODO - Report progress information for the job - - // PERFORMANCE: Do some tests and tweak this value to get faster saving - const int LINES_PER_REQUEST = 500; - - SaveJob& info = _jobSession[job]; - - // transfer LINES_PER_REQUEST lines from the session's history - // to the save location - if (!info.session.isNull()) { - // note: when retrieving lines from the emulation, - // the first line is at index 0. - - int sessionLines = info.session->emulation()->lineCount(); - - if (sessionLines - 1 == info.lastLineFetched) { - return; // if there is no more data to transfer then stop the job - } - - int copyUpToLine = qMin(info.lastLineFetched + LINES_PER_REQUEST , - sessionLines - 1); - - QTextStream stream(&data, QIODevice::ReadWrite); - info.decoder->begin(&stream); - info.session->emulation()->writeToStream(info.decoder , info.lastLineFetched + 1 , copyUpToLine); - info.decoder->end(); - - info.lastLineFetched = copyUpToLine; - } -} -void SaveHistoryTask::jobResult(KJob* job) -{ - if (job->error() != 0) { - KMessageBox::sorry(nullptr , i18n("A problem occurred when saving the output.\n%1", job->errorString())); - } - - TerminalCharacterDecoder * decoder = _jobSession[job].decoder; - - _jobSession.remove(job); - - delete decoder; - - // notify the world that the task is done - emit completed(true); - - if (autoDelete()) { - deleteLater(); - } -} -void SearchHistoryTask::addScreenWindow(Session* session , ScreenWindow* searchWindow) -{ - _windows.insert(session, searchWindow); -} -void SearchHistoryTask::execute() -{ - QMapIterator< SessionPtr , ScreenWindowPtr > iter(_windows); - - while (iter.hasNext()) { - iter.next(); - executeOnScreenWindow(iter.key() , iter.value()); - } -} - -void SearchHistoryTask::executeOnScreenWindow(SessionPtr session , ScreenWindowPtr window) -{ - Q_ASSERT(session); - Q_ASSERT(window); - - Emulation* emulation = session->emulation(); - - if (!_regExp.pattern().isEmpty()) { - int pos = -1; - const bool forwards = (_direction == Enum::ForwardsSearch); - const int lastLine = window->lineCount() - 1; - - int startLine; - if (forwards && (_startLine == lastLine)) { - startLine = 0; - } else if (!forwards && (_startLine == 0)) { - startLine = lastLine; - } else { - startLine = _startLine + (forwards ? 1 : -1); - } - - QString string; - - //text stream to read history into string for pattern or regular expression searching - QTextStream searchStream(&string); - - PlainTextDecoder decoder; - decoder.setRecordLinePositions(true); - - //setup first and last lines depending on search direction - int line = startLine; - - //read through and search history in blocks of 10K lines. - //this balances the need to retrieve lots of data from the history each time - //(for efficient searching) - //without using silly amounts of memory if the history is very large. - const int maxDelta = qMin(window->lineCount(), 10000); - int delta = forwards ? maxDelta : -maxDelta; - - int endLine = line; - bool hasWrapped = false; // set to true when we reach the top/bottom - // of the output and continue from the other - // end - - //loop through history in blocks of lines. - do { - // ensure that application does not appear to hang - // if searching through a lengthy output - QApplication::processEvents(); - - // calculate lines to search in this iteration - if (hasWrapped) { - if (endLine == lastLine) { - line = 0; - } else if (endLine == 0) { - line = lastLine; - } - - endLine += delta; - - if (forwards) { - endLine = qMin(startLine , endLine); - } else { - endLine = qMax(startLine , endLine); - } - } else { - endLine += delta; - - if (endLine > lastLine) { - hasWrapped = true; - endLine = lastLine; - } else if (endLine < 0) { - hasWrapped = true; - endLine = 0; - } - } - - decoder.begin(&searchStream); - emulation->writeToStream(&decoder, qMin(endLine, line) , qMax(endLine, line)); - decoder.end(); - - // line number search below assumes that the buffer ends with a new-line - string.append(QLatin1Char('\n')); - - if (forwards) { - pos = string.indexOf(_regExp); - } else { - pos = string.lastIndexOf(_regExp); - } - - //if a match is found, position the cursor on that line and update the screen - if (pos != -1) { - int newLines = 0; - QList linePositions = decoder.linePositions(); - while (newLines < linePositions.count() && linePositions[newLines] <= pos) { - newLines++; - } - - // ignore the new line at the start of the buffer - newLines--; - - int findPos = qMin(line, endLine) + newLines; - - highlightResult(window, findPos); - - emit completed(true); - - return; - } - - //clear the current block of text and move to the next one - string.clear(); - line = endLine; - } while (startLine != endLine); - - // if no match was found, clear selection to indicate this - window->clearSelection(); - window->notifyOutputChanged(); - } - - emit completed(false); -} -void SearchHistoryTask::highlightResult(ScreenWindowPtr window , int findPos) -{ - //work out how many lines into the current block of text the search result was found - //- looks a little painful, but it only has to be done once per search. - - ////qDebug() << "Found result at line " << findPos; - - //update display to show area of history containing selection - if ((findPos < window->currentLine()) || - (findPos >= (window->currentLine() + window->windowLines()))) { - int centeredScrollPos = findPos - window->windowLines() / 2; - if (centeredScrollPos < 0) { - centeredScrollPos = 0; - } - - window->scrollTo(centeredScrollPos); - } - - window->setTrackOutput(false); - window->notifyOutputChanged(); - window->setCurrentResultLine(findPos); -} - -SearchHistoryTask::SearchHistoryTask(QObject* parent) - : SessionTask(parent) - , _direction(Enum::BackwardsSearch) - , _startLine(0) -{ -} -void SearchHistoryTask::setSearchDirection(Enum::SearchDirection direction) -{ - _direction = direction; -} -void SearchHistoryTask::setStartLine(int line) -{ - _startLine = line; -} -Enum::SearchDirection SearchHistoryTask::searchDirection() const -{ - return _direction; -} -void SearchHistoryTask::setRegExp(const QRegularExpression &expression) -{ - _regExp = expression; -} -QRegularExpression SearchHistoryTask::regExp() const -{ - return _regExp; -} - QString SessionController::userTitle() const { if (!_session.isNull()) { diff --git a/src/SessionTask.h b/src/SessionTask.h new file mode 100644 --- /dev/null +++ b/src/SessionTask.h @@ -0,0 +1,87 @@ +/* + Copyright 2006-2008 by Robert Knight + Copyright 2009 by Thomas Dreibholz + + 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 SESSIONTASK_H +#define SESSIONTASK_H + +#include +#include +#include + +#include "Session.h" + +namespace Konsole { + +/** + * Abstract class representing a task which can be performed on a group of sessions. + * + * Create a new instance of the appropriate sub-class for the task you want to perform and + * call the addSession() method to add each session which needs to be processed. + * + * Finally, call the execute() method to perform the sub-class specific action on each + * of the sessions. + */ +class SessionTask : public QObject +{ + Q_OBJECT + +public: + explicit SessionTask(QObject *parent = nullptr); + + /** + * Sets whether the task automatically deletes itself when the task has been finished. + * Depending on whether the task operates synchronously or asynchronously, the deletion + * may be scheduled immediately after execute() returns or it may happen some time later. + */ + void setAutoDelete(bool enable); + /** Returns true if the task automatically deletes itself. See setAutoDelete() */ + bool autoDelete() const; + + /** Adds a new session to the group */ + void addSession(Session *session); + + /** + * Executes the task on each of the sessions in the group. + * The completed() signal is emitted when the task is finished, depending on the specific sub-class + * execute() may be synchronous or asynchronous + */ + virtual void execute() = 0; + +Q_SIGNALS: + /** + * Emitted when the task has completed. + * Depending on the task this may occur just before execute() returns, or it + * may occur later + * + * @param success Indicates whether the task completed successfully or not + */ + void completed(bool success); + +protected: + /** Returns a list of sessions in the group */ + QList< QPointer > sessions() const; + +private: + bool _autoDelete; + QList< QPointer > _sessions; +}; + +} +#endif diff --git a/src/SessionTask.cpp b/src/SessionTask.cpp new file mode 100644 --- /dev/null +++ b/src/SessionTask.cpp @@ -0,0 +1,52 @@ +/* + Copyright 2006-2008 by Robert Knight + Copyright 2009 by Thomas Dreibholz + + 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 "SessionTask.h" + +namespace Konsole +{ + +SessionTask::SessionTask(QObject* parent) + : QObject(parent) + , _autoDelete(false) +{ +} + +void SessionTask::setAutoDelete(bool enable) +{ + _autoDelete = enable; +} + +bool SessionTask::autoDelete() const +{ + return _autoDelete; +} + +void SessionTask::addSession(Session* session) +{ + _sessions.append(session); +} + +QList> SessionTask::sessions() const +{ + return _sessions; +} + +}