diff --git a/src/EditProfileDialog.h b/src/EditProfileDialog.h --- a/src/EditProfileDialog.h +++ b/src/EditProfileDialog.h @@ -162,6 +162,7 @@ void toggleCtrlRequiredForDrag(bool); void toggleDropUrlsAsText(bool); void toggleCopyTextToClipboard(bool); + void toggleTrimLeadingSpacesInSelectedText(bool); void toggleTrimTrailingSpacesInSelectedText(bool); void pasteFromX11Selection(); void pasteFromClipboard(); diff --git a/src/EditProfileDialog.cpp b/src/EditProfileDialog.cpp --- a/src/EditProfileDialog.cpp +++ b/src/EditProfileDialog.cpp @@ -1228,6 +1228,10 @@ _ui->copyTextToClipboardButton, Profile::AutoCopySelectedText, SLOT(toggleCopyTextToClipboard(bool)) }, + { + _ui->trimLeadingSpacesButton, Profile::TrimLeadingSpacesInSelectedText, + SLOT(toggleTrimLeadingSpacesInSelectedText(bool)) + }, { _ui->trimTrailingSpacesButton, Profile::TrimTrailingSpacesInSelectedText, SLOT(toggleTrimTrailingSpacesInSelectedText(bool)) @@ -1423,6 +1427,11 @@ updateTempProfileProperty(Profile::AutoCopySelectedText, enable); } +void EditProfileDialog::toggleTrimLeadingSpacesInSelectedText(bool enable) +{ + updateTempProfileProperty(Profile::TrimLeadingSpacesInSelectedText, enable); +} + void EditProfileDialog::toggleTrimTrailingSpacesInSelectedText(bool enable) { updateTempProfileProperty(Profile::TrimTrailingSpacesInSelectedText, enable); diff --git a/src/EditProfileDialog.ui b/src/EditProfileDialog.ui --- a/src/EditProfileDialog.ui +++ b/src/EditProfileDialog.ui @@ -925,6 +925,16 @@ + + + + Trim leading spaces in selected text, useful in some instances + + + Trim leading spaces + + + diff --git a/src/Emulation.cpp b/src/Emulation.cpp --- a/src/Emulation.cpp +++ b/src/Emulation.cpp @@ -100,7 +100,7 @@ void Emulation::checkSelectedText() { - QString text = _currentScreen->selectedText(true); + QString text = _currentScreen->selectedText(Screen::PreserveLineBreaks); emit selectionChanged(text); } diff --git a/src/Filter.cpp b/src/Filter.cpp --- a/src/Filter.cpp +++ b/src/Filter.cpp @@ -142,6 +142,7 @@ reset(); PlainTextDecoder decoder; + decoder.setLeadingWhitespace(false); decoder.setTrailingWhitespace(false); // setup new shared buffers for the filters to process on diff --git a/src/Profile.h b/src/Profile.h --- a/src/Profile.h +++ b/src/Profile.h @@ -205,6 +205,8 @@ CtrlRequiredForDrag, /** (bool) If true, automatically copy selected text into the clipboard */ AutoCopySelectedText, + /** (bool) If true, leading spaces are trimmed in selected text */ + TrimLeadingSpacesInSelectedText, /** (bool) If true, trailing spaces are trimmed in selected text */ TrimTrailingSpacesInSelectedText, /** (bool) If true, then dropped URLs will be pasted as text without asking */ diff --git a/src/Profile.cpp b/src/Profile.cpp --- a/src/Profile.cpp +++ b/src/Profile.cpp @@ -114,6 +114,7 @@ , { CtrlRequiredForDrag, "CtrlRequiredForDrag" , INTERACTION_GROUP , QVariant::Bool } , { DropUrlsAsText , "DropUrlsAsText" , INTERACTION_GROUP , QVariant::Bool } , { AutoCopySelectedText , "AutoCopySelectedText" , INTERACTION_GROUP , QVariant::Bool } + , { TrimLeadingSpacesInSelectedText , "TrimLeadingSpacesInSelectedText" , INTERACTION_GROUP , QVariant::Bool } , { TrimTrailingSpacesInSelectedText , "TrimTrailingSpacesInSelectedText" , INTERACTION_GROUP , QVariant::Bool } , { PasteFromSelectionEnabled , "PasteFromSelectionEnabled" , INTERACTION_GROUP , QVariant::Bool } , { PasteFromClipboardEnabled , "PasteFromClipboardEnabled" , INTERACTION_GROUP , QVariant::Bool } @@ -187,6 +188,7 @@ setProperty(OpenLinksByDirectClickEnabled, false); setProperty(CtrlRequiredForDrag, true); setProperty(AutoCopySelectedText, false); + setProperty(TrimLeadingSpacesInSelectedText, false); setProperty(TrimTrailingSpacesInSelectedText, false); setProperty(DropUrlsAsText, false); setProperty(PasteFromSelectionEnabled, true); diff --git a/src/Screen.h b/src/Screen.h --- a/src/Screen.h +++ b/src/Screen.h @@ -73,6 +73,15 @@ class Screen { public: + enum DecodingOption { + PlainText = 0x0, + ConvertToHtml = 0x1, + PreserveLineBreaks = 0x2, + TrimLeadingWhitespace = 0x4, + TrimTrailingWhitespace = 0x8 + }; + Q_DECLARE_FLAGS(DecodingOptions, DecodingOption) + /** Construct a new screen image of size @p lines by @p columns. */ Screen(int lines, int columns); ~Screen(); @@ -443,10 +452,11 @@ * be inserted into the returned text at the end of each terminal line. * @param trimTrailingSpaces Specifies whether trailing spaces should be * trimmed in the returned text. + * @param trimLeadingSpaces Specifies whether leading spaces should be + * trimmed in the returned text. * @param html Specifies if returned text should have HTML tags. */ - QString selectedText(bool preserveLineBreaks, bool trimTrailingSpaces = false, - bool html = false) const; + QString selectedText(const DecodingOptions options) const; /** * Convenience method. Returns the text between two indices. @@ -456,10 +466,11 @@ * be inserted into the returned text at the end of each terminal line. * @param trimTrailingSpaces Specifies whether trailing spaces should be * trimmed in the returned text. + * @param trimLeadingSpaces Specifies whether leading spaces should be + * trimmed in the returned text. * @param html Specifies if returned text should have HTML tags. */ - QString text(int startIndex, int endIndex, bool preserveLineBreaks, - bool trimTrailingSpaces = false, bool html = false) const; + QString text(int startIndex, int endIndex, const DecodingOptions options) const; /** * Copies part of the output to a stream. @@ -481,9 +492,10 @@ * be inserted into the returned text at the end of each terminal line. * @param trimTrailingSpaces Specifies whether trailing spaces should be * trimmed in the returned text. + * @param trimLeadingSpaces Specifies whether leading spaces should be + * trimmed in the returned text. */ - void writeSelectionToStream(TerminalCharacterDecoder *decoder, bool - preserveLineBreaks = true, bool trimTrailingSpaces = false) const; + void writeSelectionToStream(TerminalCharacterDecoder *decoder, const Konsole::Screen::DecodingOptions options) const; /** * Checks if the text between from and to is inside the current @@ -601,8 +613,7 @@ //decoder - a decoder which converts terminal characters (an Character array) into text //appendNewLine - if true a new line character (\n) is appended to the end of the line int copyLineToStream(int line, int start, int count, TerminalCharacterDecoder *decoder, - bool appendNewLine, bool preserveLineBreaks, - bool trimTrailingSpaces) const; + bool appendNewLine, const Konsole::Screen::DecodingOptions options) const; //fills a section of the screen image with the character 'c' //the parameters are specified as offsets from the start of the screen image. @@ -634,7 +645,7 @@ // copies text from 'startIndex' to 'endIndex' to a stream // startIndex and endIndex are positions generated using the loc(x,y) macro void writeToStream(TerminalCharacterDecoder *decoder, int startIndex, int endIndex, - bool preserveLineBreaks = true, bool trimTrailingSpaces = false) const; + const Konsole::Screen::DecodingOptions options) const; // copies 'count' lines from the screen buffer into 'dest', // starting from 'startLine', where 0 is the first line in the screen buffer void copyFromScreen(Character *dest, int startLine, int count) const; @@ -715,6 +726,10 @@ // last position where we added a character int _lastPos; }; + } +Q_DECLARE_OPERATORS_FOR_FLAGS(Konsole::Screen::DecodingOptions) + + #endif // SCREEN_H diff --git a/src/Screen.cpp b/src/Screen.cpp --- a/src/Screen.cpp +++ b/src/Screen.cpp @@ -1087,24 +1087,24 @@ return pos >= _selTopLeft && pos <= _selBottomRight && columnInSelection; } -QString Screen::selectedText(bool preserveLineBreaks, bool trimTrailingSpaces, bool html) const +QString Screen::selectedText(const DecodingOptions options) const { if (!isSelectionValid()) return QString(); - return text(_selTopLeft, _selBottomRight, preserveLineBreaks, trimTrailingSpaces, html); + return text(_selTopLeft, _selBottomRight, options); } -QString Screen::text(int startIndex, int endIndex, bool preserveLineBreaks, bool trimTrailingSpaces, bool html) const +QString Screen::text(int startIndex, int endIndex, const DecodingOptions options) const { QString result; QTextStream stream(&result, QIODevice::ReadWrite); HTMLDecoder htmlDecoder; PlainTextDecoder plainTextDecoder; TerminalCharacterDecoder *decoder; - if(html) + if(options & ConvertToHtml) { decoder = &htmlDecoder; } @@ -1114,7 +1114,7 @@ } decoder->begin(&stream); - writeToStream(decoder, startIndex, endIndex, preserveLineBreaks, trimTrailingSpaces); + writeToStream(decoder, startIndex, endIndex, options); decoder->end(); return result; @@ -1126,18 +1126,16 @@ } void Screen::writeSelectionToStream(TerminalCharacterDecoder* decoder , - bool preserveLineBreaks, - bool trimTrailingSpaces) const + const DecodingOptions options) const { if (!isSelectionValid()) return; - writeToStream(decoder, _selTopLeft, _selBottomRight, preserveLineBreaks, trimTrailingSpaces); + writeToStream(decoder, _selTopLeft, _selBottomRight, options); } void Screen::writeToStream(TerminalCharacterDecoder* decoder, int startIndex, int endIndex, - bool preserveLineBreaks, - bool trimTrailingSpaces) const + const DecodingOptions options) const { const int top = startIndex / _columns; const int left = startIndex % _columns; @@ -1160,17 +1158,16 @@ count, decoder, appendNewLine, - preserveLineBreaks, - trimTrailingSpaces); + options); // if the selection goes beyond the end of the last line then // append a new line character. // // this makes it possible to 'select' a trailing new line character after // the text on a line. if (y == bottom && copied < count && - !trimTrailingSpaces) { + !options.testFlag(TrimTrailingWhitespace)) { Character newLineChar('\n'); decoder->decodeLine(&newLineChar, 1, 0); } @@ -1182,8 +1179,7 @@ int count, TerminalCharacterDecoder* decoder, bool appendNewLine, - bool preserveLineBreaks, - bool trimTrailingSpaces) const + const DecodingOptions options) const { //buffer to hold characters for decoding //the buffer is static to avoid initializing every @@ -1239,7 +1235,7 @@ int length = _screenLines[screenLine].count(); // Don't remove end spaces in lines that wrap - if (trimTrailingSpaces && ((_lineProperties[screenLine] & LINE_WRAPPED) == 0)) + if (options.testFlag(TrimTrailingWhitespace) && ((_lineProperties[screenLine] & LINE_WRAPPED) == 0)) { // ignore trailing white space at the end of the line for (int i = length-1; i >= 0; i--) @@ -1270,11 +1266,30 @@ // When users ask not to preserve the linebreaks, they usually mean: // `treat LINEBREAK as SPACE, thus joining multiple _lines into // single line in the same way as 'J' does in VIM.` - characterBuffer[count] = preserveLineBreaks ? Character('\n') : Character(' '); + characterBuffer[count] = options.testFlag(PreserveLineBreaks) ? Character('\n') : Character(' '); count++; } } + if (options & TrimLeadingWhitespace) { + int spacesCount = 0; + for (spacesCount = 0; spacesCount < count; spacesCount++) { + if (!QChar(characterBuffer[spacesCount].character).isSpace()) { + break; + } + } + + if (spacesCount >= count) { + return 0; + } + + for (int i=0; i < count - spacesCount; i++) { + characterBuffer[i] = characterBuffer[i + spacesCount]; + } + + count -= spacesCount; + } + //decode line and write to text stream decoder->decodeLine((Character*) characterBuffer , count, currentLineProperties); @@ -1284,7 +1299,7 @@ void Screen::writeLinesToStream(TerminalCharacterDecoder* decoder, int fromLine, int toLine) const { - writeToStream(decoder, loc(0, fromLine), loc(_columns - 1, toLine)); + writeToStream(decoder, loc(0, fromLine), loc(_columns - 1, toLine), PreserveLineBreaks); } void Screen::addHistLine() diff --git a/src/ScreenWindow.h b/src/ScreenWindow.h --- a/src/ScreenWindow.h +++ b/src/ScreenWindow.h @@ -27,9 +27,9 @@ // Konsole #include "Character.h" +#include "Screen.h" namespace Konsole { -class Screen; /** * Provides a window onto a section of a terminal screen. A terminal widget can then render @@ -229,10 +229,10 @@ * * @param preserveLineBreaks See Screen::selectedText() * @param trimTrailingSpaces See Screen::selectedText() + * @param trimLeadingSpaces See Screen::selectedText() * @param html Specifies if returned text should have HTML tags. */ - QString selectedText(bool preserveLineBreaks, bool trimTrailingSpaces = false, - bool html = false) const; + QString selectedText(const Konsole::Screen::DecodingOptions options) const; public Q_SLOTS: /** diff --git a/src/ScreenWindow.cpp b/src/ScreenWindow.cpp --- a/src/ScreenWindow.cpp +++ b/src/ScreenWindow.cpp @@ -125,10 +125,9 @@ return result; } -QString ScreenWindow::selectedText(bool preserveLineBreaks, bool trimTrailingSpaces, - bool html) const +QString ScreenWindow::selectedText(const Screen::DecodingOptions options) const { - return _screen->selectedText(preserveLineBreaks, trimTrailingSpaces, html); + return _screen->selectedText(options); } void ScreenWindow::getSelectionStart(int &column, int &line) diff --git a/src/SessionController.cpp b/src/SessionController.cpp --- a/src/SessionController.cpp +++ b/src/SessionController.cpp @@ -1148,7 +1148,7 @@ void SessionController::searchBarEvent() { - QString selectedText = _view->screenWindow()->selectedText(true, true); + QString selectedText = _view->screenWindow()->selectedText(Screen::PreserveLineBreaks | Screen::TrimLeadingWhitespace | Screen::TrimTrailingWhitespace); if (!selectedText.isEmpty()) _searchBar->setSearchText(selectedText); diff --git a/src/TerminalCharacterDecoder.h b/src/TerminalCharacterDecoder.h --- a/src/TerminalCharacterDecoder.h +++ b/src/TerminalCharacterDecoder.h @@ -74,6 +74,17 @@ public: PlainTextDecoder(); + /** + * Set whether leading whitespace at the end of lines should be included + * in the output. + * Defaults to true. + */ + void setLeadingWhitespace(bool enable); + /** + * Returns whether leading whitespace at the end of lines is included + * in the output. + */ + bool leadingWhitespace() const; /** * Set whether trailing whitespace at the end of lines should be included * in the output. @@ -102,6 +113,7 @@ private: QTextStream *_output; + bool _includeLeadingWhitespace; bool _includeTrailingWhitespace; bool _recordLinePositions; diff --git a/src/TerminalCharacterDecoder.cpp b/src/TerminalCharacterDecoder.cpp --- a/src/TerminalCharacterDecoder.cpp +++ b/src/TerminalCharacterDecoder.cpp @@ -33,10 +33,19 @@ using namespace Konsole; PlainTextDecoder::PlainTextDecoder() : _output(nullptr) + , _includeLeadingWhitespace(true) , _includeTrailingWhitespace(true) , _recordLinePositions(false) { } +void PlainTextDecoder::setLeadingWhitespace(bool enable) +{ + _includeLeadingWhitespace = enable; +} +bool PlainTextDecoder::leadingWhitespace() const +{ + return _includeLeadingWhitespace; +} void PlainTextDecoder::setTrailingWhitespace(bool enable) { _includeTrailingWhitespace = enable; @@ -82,12 +91,26 @@ QString plainText; plainText.reserve(count); - int outputCount = count; + // If we should remove leading whitespace find the first non-space character + int start = 0; + if (!_includeLeadingWhitespace) { + for (start = 0; start < count; start++) { + if (!characters[start].isSpace()) { + break; + } + } + } + + int outputCount = count - start; + + if (outputCount <= 0) { + return; + } // if inclusion of trailing whitespace is disabled then find the end of the // line if (!_includeTrailingWhitespace) { - for (int i = count - 1 ; i >= 0 ; i--) { + for (int i = count - 1 ; i >= start ; i--) { if (!characters[i].isSpace()) break; else @@ -97,7 +120,7 @@ // find out the last technically real character in the line int realCharacterGuard = -1; - for (int i = count - 1 ; i >= 0 ; i--) { + for (int i = count - 1 ; i >= start ; i--) { // FIXME: the special case of '\n' here is really ugly // Maybe the '\n' should be added after calling this method in // Screen::copyLineToStream() @@ -107,7 +130,7 @@ } } - for (int i = 0; i < outputCount;) { + for (int i = start; i < outputCount;) { if ((characters[i].rendition & RE_EXTENDED_CHAR) != 0) { ushort extendedCharLength = 0; const ushort* chars = ExtendedCharTable::instance.lookupExtendedChar(characters[i].character, extendedCharLength); diff --git a/src/TerminalDisplay.h b/src/TerminalDisplay.h --- a/src/TerminalDisplay.h +++ b/src/TerminalDisplay.h @@ -207,6 +207,22 @@ return _openLinksByDirectClick; } + /** + * Sets whether leading spaces should be trimmed in selected text. + */ + void setTrimLeadingSpaces(bool enabled) + { + _trimLeadingSpaces = enabled; + } + + /** + * Returns true if leading spaces should be trimmed in selected text. + */ + bool trimLeadingSpaces() const + { + return _trimLeadingSpaces; + } + /** * Sets whether trailing spaces should be trimmed in selected text. */ @@ -841,6 +857,9 @@ QPoint findWordStart(const QPoint &pnt); QPoint findWordEnd(const QPoint &pnt); + // Uses the current settings for trimming whitespace and preserving linebreaks to create a proper flag value for Screen + Screen::DecodingOptions currentDecodingOptions(); + // the window onto the terminal screen which this display // is currently showing. QPointer _screenWindow; @@ -970,6 +989,7 @@ SessionController *_sessionController; + bool _trimLeadingSpaces; // trim leading spaces in selected text bool _trimTrailingSpaces; // trim trailing spaces in selected text bool _mouseWheelZoom; // enable mouse wheel zooming or not diff --git a/src/TerminalDisplay.cpp b/src/TerminalDisplay.cpp --- a/src/TerminalDisplay.cpp +++ b/src/TerminalDisplay.cpp @@ -377,6 +377,7 @@ , _useFontLineCharacters(false) , _printerFriendly(false) , _sessionController(nullptr) + , _trimLeadingSpaces(false) , _trimTrailingSpaces(false) , _margin(1) , _centerContents(false) @@ -2730,6 +2731,22 @@ return QPoint(x, y); } +Screen::DecodingOptions TerminalDisplay::currentDecodingOptions() +{ + Screen::DecodingOptions decodingOptions; + if (_preserveLineBreaks) { + decodingOptions |= Screen::PreserveLineBreaks; + } + if (_trimLeadingSpaces) { + decodingOptions |= Screen::TrimLeadingWhitespace; + } + if (_trimTrailingSpaces) { + decodingOptions |= Screen::TrimTrailingWhitespace; + } + + return decodingOptions; +} + void TerminalDisplay::mouseTripleClickEvent(QMouseEvent* ev) { if (_screenWindow == nullptr) return; @@ -2893,10 +2910,11 @@ if (_screenWindow == nullptr) return; - QString text = _screenWindow->selectedText(_preserveLineBreaks, _trimTrailingSpaces); + + QString text = _screenWindow->selectedText(currentDecodingOptions()); if (text.isEmpty()) return; - QString html = _screenWindow->selectedText(_preserveLineBreaks, _trimTrailingSpaces, true); + QString html = _screenWindow->selectedText(currentDecodingOptions() | Screen::ConvertToHtml); auto mimeData = new QMimeData; mimeData->setText(text); @@ -2915,10 +2933,10 @@ if (_screenWindow == nullptr) return; - QString text = _screenWindow->selectedText(_preserveLineBreaks, _trimTrailingSpaces); + QString text = _screenWindow->selectedText(currentDecodingOptions()); if (text.isEmpty()) return; - QString html = _screenWindow->selectedText(_preserveLineBreaks, _trimTrailingSpaces, true); + QString html = _screenWindow->selectedText(currentDecodingOptions() | Screen::ConvertToHtml); auto mimeData = new QMimeData; mimeData->setText(text); diff --git a/src/TerminalDisplayAccessible.cpp b/src/TerminalDisplayAccessible.cpp --- a/src/TerminalDisplayAccessible.cpp +++ b/src/TerminalDisplayAccessible.cpp @@ -98,8 +98,7 @@ return QString(); } - return display->screenWindow()->screen()->text(0, display->_usedColumns * display->_usedLines, - true); + return display->screenWindow()->screen()->text(0, display->_usedColumns * display->_usedLines, Screen::PreserveLineBreaks); } void TerminalDisplayAccessible::addSelection(int startOffset, int endOffset) @@ -186,7 +185,7 @@ return QString(); } - return display()->screenWindow()->screen()->text(startOffset, endOffset, true); + return display()->screenWindow()->screen()->text(startOffset, endOffset, Screen::PreserveLineBreaks); } TerminalDisplay *TerminalDisplayAccessible::display() const diff --git a/src/ViewManager.cpp b/src/ViewManager.cpp --- a/src/ViewManager.cpp +++ b/src/ViewManager.cpp @@ -913,6 +913,7 @@ view->setDropUrlsAsText(profile->property(Profile::DropUrlsAsText)); view->setBidiEnabled(profile->bidiRenderingEnabled()); view->setLineSpacing(profile->lineSpacing()); + view->setTrimLeadingSpaces(profile->property(Profile::TrimLeadingSpacesInSelectedText)); view->setTrimTrailingSpaces(profile->property(Profile::TrimTrailingSpacesInSelectedText)); view->setOpenLinksByDirectClick(profile->property(Profile::OpenLinksByDirectClickEnabled));