diff --git a/RedPandaIDE/mainwindow.cpp b/RedPandaIDE/mainwindow.cpp index 117fdba5..aba99a7a 100644 --- a/RedPandaIDE/mainwindow.cpp +++ b/RedPandaIDE/mainwindow.cpp @@ -234,7 +234,7 @@ void MainWindow::on_actionSelectAll_triggered() { Editor * editor = mEditorList->getEditor(); if (editor != NULL ) { - //editor->selectAll(); + editor->SelectAll(); } } diff --git a/RedPandaIDE/qsynedit/MiscProcs.cpp b/RedPandaIDE/qsynedit/MiscProcs.cpp index e1b264b3..a6e7a149 100644 --- a/RedPandaIDE/qsynedit/MiscProcs.cpp +++ b/RedPandaIDE/qsynedit/MiscProcs.cpp @@ -3,6 +3,7 @@ #include #include #include +#include int MinMax(int x, int mi, int ma) { @@ -540,3 +541,53 @@ SynFontStyles getFontStyles(const QFont &font) styles.setFlag(SynFontStyle::fsUnderline, font.underline()); styles.setFlag(SynFontStyle::fsStrikeOut, font.strikeOut()); } + +bool isWordChar(const QChar& ch) { + return (ch == '_') || ch.isLetterOrNumber(); +} + +int StrScanForWordChar(const QString &s, int startPos) +{ + for (int i=startPos-1;is.length()) + return 0; + while (i>=0) { + if (isWordChar(s[i])) + return i+1; + i--; + } + return 0; +} + +int StrRScanForNonWordChar(const QString &s, int startPos) +{ + int i = startPos-1; + if (i>s.length()) + return 0; + while (i>=0) { + if (!isWordChar(s[i])) + return i+1; + i--; + } + return 0; +} diff --git a/RedPandaIDE/qsynedit/MiscProcs.h b/RedPandaIDE/qsynedit/MiscProcs.h index 6e33cb5b..edcd79ad 100644 --- a/RedPandaIDE/qsynedit/MiscProcs.h +++ b/RedPandaIDE/qsynedit/MiscProcs.h @@ -89,4 +89,32 @@ void SynDrawGradient(QPaintDevice* ACanvas, const QColor& AStartColor, const QCo SynFontStyles getFontStyles(const QFont& font); +/** + * Find the first occurency of word char in s, starting from startPos + * Note: the index of first char in s in 1 + * @return index of the char founded , 0 if not found + */ +int StrScanForWordChar(const QString& s, int startPos); + +/** + * Find the first occurency of non word char in s, starting from startPos + * Note: the index of first char in s in 1 + * @return index of the char founded , 0 if not found + */ +int StrScanForNonWordChar(const QString& s, int startPos); + +/** + * Find the first occurency of word char in s right to left, starting from startPos + * Note: the index of first char in s in 1 + * @return index of the char founded , 0 if not found + */ +int StrRScanForWordChar(const QString& s, int startPos); + +/** + * Find the first occurency of non word char in s right to left, starting from startPos + * Note: the index of first char in s in 1 + * @return index of the char founded , 0 if not found + */ +int StrRScanForNonWordChar(const QString& s, int startPos); + #endif // MISCPROCS_H diff --git a/RedPandaIDE/qsynedit/SynEdit.cpp b/RedPandaIDE/qsynedit/SynEdit.cpp index f5d2b11b..b8d6a9a2 100644 --- a/RedPandaIDE/qsynedit/SynEdit.cpp +++ b/RedPandaIDE/qsynedit/SynEdit.cpp @@ -11,7 +11,9 @@ #include "Constants.h" #include "TextPainter.h" #include +#include #include +#include SynEdit::SynEdit(QWidget *parent) : QAbstractScrollArea(parent) { @@ -113,7 +115,7 @@ SynEdit::SynEdit(QWidget *parent) : QAbstractScrollArea(parent) mBlockEnd = mBlockBegin; mOptions = eoAutoIndent | eoDragDropEditing | eoEnhanceEndKey | eoShowScrollHint | eoSmartTabs | eoTabsToSpaces | - eoSmartTabDelete| eoGroupUndo | eoKeepCaretX; + eoSmartTabDelete| eoGroupUndo | eoKeepCaretX | eoSelectWordByDblClick; qDebug()<<"init SynEdit: 9"; mScrollTimer = new QTimer(this); @@ -398,46 +400,68 @@ BufferCoord SynEdit::displayToBufferPos(const DisplayCoord &p) BufferCoord Result{p.Column,p.Row}; // Account for code folding if (mUseCodeFolding) - Result.Line = foldRowToLine(Result.Line); + Result.Line = foldRowToLine(p.Column); // Account for tabs if (Result.Line <= mLines->count() ) { - QString s = mLines->getString(Result.Line - 1); - int l = s.length(); - int x = 0; - int i = 0; - - while (x < p.Column && icount()) && (aLine >= 1)); if (aLine <= mLines->count()) { QString s = mLines->getString(aLine - 1); - int l = s.length(); int x = 0; int len = std::min(aChar-1,s.length()); for (int i=0;icount()) && (aLine >= 1)); + if (aLine <= mLines->count()) { + QString s = mLines->getString(aLine - 1); + int x = 0; + int len = s.length(); + int i; + for (i=0;i=aColumn) { + break; + } + } + if (i= 1) && (CY <= mLines->count())) { + QString Line = mLines->getString(CY - 1); + int LineLen = Line.length(); + if (CX >= LineLen) { + // find first IdentChar or multibyte char in the next line + if (CY < mLines->count()) { + Line = mLines->getString(CY); + CY++; + CX=StrScanForWordChar(Line,1); + if (CX==0) + CX=1; + } + } else { + // find next "whitespace" if current char is an IdentChar + if (!Line[CX-1].isSpace()) + CX = StrScanForNonWordChar(Line,CX); + // if "whitespace" found, find the next IdentChar + if (CX > 0) + CX = StrScanForWordChar(Line, CX); + // if one of those failed position at the begin of next line + if (CX == 0) { + if (CY < mLines->count()) { + Line = mLines->getString(CY); + CY++; + CX=StrScanForWordChar(Line,1); + if (CX==0) + CX=1; + } else { + CX=Line.length()+1; + } + } + } + } + return BufferCoord{CX,CY}; +} + +BufferCoord SynEdit::WordStart() +{ + return WordStartEx(caretXY()); +} + +BufferCoord SynEdit::WordStartEx(const BufferCoord &XY) +{ + int CX = XY.Char; + int CY = XY.Line; + // valid line? + if ((CY >= 1) && (CY <= mLines->count())) { + QString Line = mLines->getString(CY - 1); + CX = std::min(CX, Line.length()); + if (CX > 1) { + if (!(Line[CX - 1].isSpace())) + CX = StrRScanForNonWordChar(Line, CX - 1) + 1; + else + CX = StrRScanForWordChar(Line, CX - 1) + 1; + } + } + return BufferCoord{CX,CY}; +} + +BufferCoord SynEdit::WordEnd() +{ + return WordEndEx(caretXY()); +} + +BufferCoord SynEdit::WordEndEx(const BufferCoord &XY) +{ + int CX = XY.Char; + int CY = XY.Line; + // valid line? + if ((CY >= 1) && (CY <= mLines->count())) { + QString Line = mLines->getString(CY - 1); + if (CX <= Line.length()) { + if (!(Line[CX - 1].isSpace())) + CX = StrScanForNonWordChar(Line, CX); + else + CX = StrScanForWordChar(Line, CX); + if (CX == 0) + CX = Line.length() + 1; + } + } + return BufferCoord{CX,CY}; +} + +BufferCoord SynEdit::PrevWordPos() +{ + return PrevWordPosEx(caretXY()); +} + +BufferCoord SynEdit::PrevWordPosEx(const BufferCoord &XY) +{ + int CX = XY.Char; + int CY = XY.Line; + // valid line? + if ((CY >= 1) and (CY <= mLines->count())) { + QString Line = mLines->getString(CY - 1); + CX = std::min(CX, Line.length()); + if (CX <= 1) { + // find last IdentChar in the previous line + if (CY > 1) { + CY -- ; + Line = mLines->getString(CY - 1); + CX = StrRScanForWordChar(Line, Line.length())+1; + } + } else { + // if previous char is a "whitespace" search for the last IdentChar + if (Line[CX - 2].isSpace()) + CX = StrRScanForWordChar(Line, CX - 1); + if (CX > 0) // search for the first IdentChar of this "word" + CX = StrRScanForNonWordChar(Line, CX - 1)+1; + if (CX == 0) { + // find last IdentChar in the previous line + if (CY > 1) { + CY -- ; + Line = mLines->getString(CY - 1); + CX = StrRScanForWordChar(Line, Line.length())+1; + } else { + CX = 1; + } + } + } + } + return BufferCoord{CX,CY}; +} + +void SynEdit::SetSelWord() +{ + SetWordBlock(caretXY()); +} + +void SynEdit::SetWordBlock(BufferCoord Value) +{ + if (mOptions.testFlag(eoScrollPastEol)) + Value.Char = MinMax(Value.Char, 1, mMaxScrollWidth + 1); + else + Value.Char = std::max(Value.Char, 1); + Value.Line = MinMax(Value.Line, 1, mLines->count()); + QString TempString = mLines->getString(Value.Line - 1); //needed for CaretX = LineLength +1 + if (Value.Char > TempString.length()) { + internalSetCaretXY(BufferCoord{TempString.length()+1, Value.Line}); + return; + } + + BufferCoord v_WordStart = WordStartEx(Value); + BufferCoord v_WordEnd = WordEndEx(Value); + if ((v_WordStart.Line == v_WordEnd.Line) && (v_WordStart.Char < v_WordEnd.Char)) + setCaretAndSelection(v_WordEnd, v_WordStart, v_WordEnd); +} + +void SynEdit::SelectAll() +{ + BufferCoord LastPt; + LastPt.Char = 1; + if (mLines->empty()) { + LastPt.Line = 1; + } else { + LastPt.Line = mLines->count(); + LastPt.Char = mLines->getString(LastPt.Line-1).length()+1; + } + setCaretAndSelection(caretXY(), BufferCoord{1, 1}, LastPt); + // Selection should have changed... + statusChanged(SynStatusChange::scSelection); +} + +void SynEdit::DeleteLastChar() +{ + // if not ReadOnly then begin + doOnPaintTransientEx(SynTransientType::ttBefore, true); + auto action = finally([this]{ + ensureCursorPosVisible(); + doOnPaintTransientEx(SynTransientType::ttAfter, true); + }); + // try + if (selAvail()) { + SetSelectedTextEmpty(); + return; + } + QString Temp = lineText(); + //TabBuffer := Lines.ExpandedStrings[CaretY - 1]; + int Len = Temp.length(); + BufferCoord Caret = caretXY(); + int vTabTrim = 0; + QString helper = ""; + if (mCaretX > Len + 1) { + if (mOptions.setFlag(eoSmartTabDelete)) { + //It's at the end of the line, move it to the length + if (Len > 0) + internalSetCaretX(Len + 1); + else { + //move it as if there were normal spaces there + int SpaceCount1 = mCaretX - 1; + int SpaceCount2 = SpaceCount1; + // unindent + if (SpaceCount1 > 0) { + int BackCounter = mCaretY - 2; + while (BackCounter >= 0) { + SpaceCount2 = leftSpaces(mLines->getString(BackCounter)); + if (SpaceCount2 < SpaceCount1) + break; + BackCounter--; + } + } + if (SpaceCount2 >= SpaceCount1) + SpaceCount2 = 0; + setCaretX(SpaceCount2+1); + updateLastCaretX(); + mStateFlags.setFlag(SynStateFlag::sfCaretChanged); + statusChanged(SynStatusChange::scCaretX); + } + } else { + // only move caret one column + internalSetCaretX(mCaretX - 1); + } + } else if (mCaretX == 1) { + // join this line with the last line if possible + if (mCaretY > 1) { + internalSetCaretY(mCaretY - 1); + internalSetCaretX(mLines->getString(mCaretY - 1).length() + 1); + mLines->deleteAt(mCaretY); + DoLinesDeleted(mCaretY+1, 1); + if (mOptions.testFlag(eoTrimTrailingSpaces)) + Temp = TrimRight(Temp); + setLineText(lineText() + Temp); + //helper = "\r\n"; + } + } else { + // delete text before the caret + int caretColumn = charToColumn(mCaretY,mCaretX); + int SpaceCount1 = leftSpaces(Temp); + int SpaceCount2 = 0; + int newCaretX; + + if (SpaceCount1 == caretColumn - 1) { + if (mOptions.testFlag(eoSmartTabDelete)) { + // unindent + if (SpaceCount1 > 0) { + int BackCounter = mCaretY - 2; + while (BackCounter >= 0) { + SpaceCount2 = leftSpaces(mLines->getString(BackCounter)); + if (SpaceCount2 < SpaceCount1) + break; + BackCounter--; + } + } + if (SpaceCount2 >= SpaceCount1) + SpaceCount2 = 0; + + newCaretX = columnToChar(mCaretY,SpaceCount2+1); + helper = Temp.mid(newCaretX - 1, mCaretX - newCaretX); + Temp.remove(newCaretX - 1, mCaretX - newCaretX); + } else { + + //how much till the next tab column + int BackCounter = (caretColumn - 1) % mTabWidth; + if (BackCounter == 0) + BackCounter = mTabWidth; + SpaceCount2 = std::max(0,SpaceCount1 - mTabWidth); + newCaretX = columnToChar(mCaretY,SpaceCount2+1); + helper = Temp.mid(newCaretX - 1, mCaretX - newCaretX); + Temp.remove(newCaretX-1,mCaretX - newCaretX); + } + ProperSetLine(mCaretY - 1, Temp); + setCaretX(newCaretX); + updateLastCaretX(); + mStateFlags.setFlag(SynStateFlag::sfCaretChanged); + statusChanged(SynStatusChange::scCaretX); + } else { + // delete char + counter := 1; + internalSetCaretX(mCaretX - 1); + // Stores the previous "expanded" CaretX if the line contains tabs. +// if (mOptions.testFlag(eoTrimTrailingSpaces) && (Len <> Length(TabBuffer)) then +// vTabTrim := CharIndex2CaretPos(CaretX, TabWidth, Temp); + helper = Temp[mCaretX-1]; + Temp.remove(mCaretX-1,1); + ProperSetLine(mCaretY - 1, Temp); + } + } + if ((Caret.Char != mCaretX) || (Caret.Line != mCaretY)) { + mUndoList->AddChange(SynChangeReason::crSilentDelete, caretXY(), Caret, helper, + SynSelectionMode::smNormal); + } +} + void SynEdit::clearAreaList(SynEditingAreaList areaList) { areaList.clear(); @@ -951,6 +1268,16 @@ void SynEdit::internalSetCaretXY(const BufferCoord &Value) setCaretXYEx(true, Value); } +void SynEdit::internalSetCaretX(int Value) +{ + internalSetCaretXY(BufferCoord{Value, mCaretY}); +} + +void SynEdit::internalSetCaretY(int Value) +{ + internalSetCaretXY(BufferCoord{mCaretX,Value}); +} + void SynEdit::setStatusChanged(SynStatusChanges changes) { mStatusChanges = mStatusChanges | changes; @@ -1635,6 +1962,71 @@ void SynEdit::setSelectionMode(SynSelectionMode value) } } +QString SynEdit::selText() +{ + if (!selAvail()) { + return ""; + } else { + int ColFrom = mBlockBegin.Char; + int First = mBlockBegin.Line - 1; + // + int ColTo = mBlockEnd.Char; + int Last = mBlockEnd.Line - 1; + switch(mActiveSelectionMode) { + case SynSelectionMode::smNormal: + if (First == Last) + return mLines->getString(First).mid(ColFrom-1, ColTo - ColFrom); + else { + QString result = mLines->getString(First).mid(ColFrom-1); + result+= lineBreak(); + for (int i = First + 1; i<=Last - 1; i++) { + result += mLines->getString(i); + result+=lineBreak(); + } + result += mLines->getString(Last).left(ColTo-1); + return result; + } + case SynSelectionMode::smColumn: + { + First = mBlockBegin.Line-1; + ColFrom = charToColumn(mBlockBegin.Line, mBlockBegin.Char); + Last = mBlockEnd.Line - 1; + ColTo = charToColumn(mBlockEnd.Line, mBlockEnd.Char); + if (ColFrom > ColTo) + std::swap(ColFrom, ColTo); + QString result; + for (int i = First; i <= Last; i++) { + int l = columnToChar(i,ColFrom); + int r = columnToChar(i,ColTo-1); + QString s = mLines->getString(i); + result += s.mid(l-1,r-l+1); + result+=lineBreak(); + } + return result; + } + case SynSelectionMode::smLine: + { + QString result; + // If block selection includes LastLine, + // line break code(s) of the last line will not be added. + for (int i= First; i<=Last - 1;i++) { + result += mLines->getString(i); + result+=lineBreak(); + } + result += mLines->getString(Last); + if (Last < mLines->count() - 1) + result+=lineBreak(); + return result; + } + } + } +} + +QString SynEdit::lineBreak() +{ + return mLines->lineBreak(); +} + bool SynEdit::useCodeFolding() const { return mUseCodeFolding; @@ -1829,6 +2221,161 @@ void SynEdit::MoveCaretToLineEnd(bool isSelection) MoveCaretAndSelection(caretXY(), BufferCoord{vNewX, mCaretY}, isSelection); } +void SynEdit::SetSelectedTextEmpty() +{ + BufferCoord vUndoBegin = mBlockBegin; + BufferCoord vUndoEnd = mBlockEnd; + QString vSelText = selText(); + SetSelTextPrimitive(""); + if ((vUndoBegin.Line < vUndoEnd.Line) || ( + (vUndoBegin.Line == vUndoEnd.Line) && (vUndoBegin.Char < vUndoEnd.Char))) { + mUndoList->AddChange(SynChangeReason::crDelete, vUndoBegin, vUndoEnd, vSelText, + mActiveSelectionMode); + } else { + mUndoList->AddChange(SynChangeReason::crDeleteAfterCursor, vUndoBegin, vUndoEnd, vSelText, + mActiveSelectionMode); + } +} + +void SynEdit::SetSelTextPrimitive(const QString &aValue) +{ + SetSelTextPrimitiveEx(mActiveSelectionMode, aValue, true); +} + +void SynEdit::SetSelTextPrimitiveEx(SynSelectionMode PasteMode, const QString &Value, bool AddToUndoList) +{ + incPaintLock(); + mLines->beginUpdate(); + auto action = finally([this] { + mLines->endUpdate(); + decPaintLock(); + }); + BufferCoord BB = mBlockBegin; + BufferCoord BE = mBlockEnd; + if (selAvail()) { + DeleteSelection(BB,BE); + internalSetCaretXY(BB); + } + if (!Value.isEmpty()) { + InsertText(Value,PasteMode); + } + if (mCaretY < 1) + internalSetCaretY(1); +} + +void SynEdit::DoLinesDeleted(int FirstLine, int Count) +{ +// // gutter marks +// for i := 0 to Marks.Count - 1 do begin +// if Marks[i].Line >= FirstLine + Count then +// Marks[i].Line := Marks[i].Line - Count +// else if Marks[i].Line > FirstLine then +// Marks[i].Line := FirstLine; +// end; +// // plugins +// if fPlugins <> nil then begin +// for i := 0 to fPlugins.Count - 1 do +// TSynEditPlugin(fPlugins[i]).LinesDeleted(FirstLine, Count); + // end; +} + +void SynEdit::ProperSetLine(int ALine, const QString &ALineText) +{ + if (mOptions.testFlag(eoTrimTrailingSpaces)) + mLines->putString(ALine,TrimRight(ALineText)); + else + mLines->putString(ALine,ALineText); +} + +void SynEdit::DeleteSelection(const BufferCoord &BB, const BufferCoord &BE) +{ + bool UpdateMarks = false; + int MarkOffset = 0; + switch(mActiveSelectionMode) { + case SynSelectionMode::smNormal: + if (mLines->count() > 0) { + // Create a string that contains everything on the first line up + // to the selection mark, and everything on the last line after + // the selection mark. + QString TempString = mLines->getString(BB.Line - 1).mid(0, BB.Char - 1) + + mLines->getString(BE.Line - 1).mid(BE.Char-1); + // Delete all lines in the selection range. + mLines->deleteLines(BB.Line, BE.Line - BB.Line); + ProperSetLine(BB.Line-1,TempString); + UpdateMarks = true; + internalSetCaretXY(BB); + } + break; + case SynSelectionMode::smColumn: + { + int First = BB.Line-1; + int ColFrom = charToColumn(BB.Line, BB.Char); + int Last = BE.Line - 1; + int ColTo = charToColumn(BE.Line, BE.Char); + if (ColFrom > ColTo) + std::swap(ColFrom, ColTo); + QString result; + for (int i = First; i <= Last; i++) { + int l = columnToChar(i,ColFrom); + int r = columnToChar(i,ColTo-1); + QString s = mLines->getString(i); + s.remove(l-1,r-l+1); + ProperSetLine(i,s); + } + // Lines never get deleted completely, so keep caret at end. + internalSetCaretXY(BB); + // Column deletion never removes a line entirely, so no mark + // updating is needed here. + break; + } + case SynSelectionMode::smLine: + if (BE.Line == mLines->count()) { + mLines->putString(BE.Line - 1,""); + mLines->deleteLines(BB.Line-1,BE.Line-BB.Line); + } else { + mLines->deleteLines(BB.Line-1,BE.Line-BB.Line+1); + } + // smLine deletion always resets to first column. + internalSetCaretXY(BufferCoord{1, BB.Line}); + UpdateMarks = true; + MarkOffset = 1; + break; + } + // Update marks + if (UpdateMarks) + DoLinesDeleted(BB.Line, BE.Line - BB.Line + MarkOffset); +} + +void SynEdit::InsertText(const QString &Value, SynSelectionMode PasteMode) +{ + if (Value.isEmpty()) + return; + + int StartLine = mCaretY; + int StartCol = mCaretX; + int InsertedLines = 0; + switch(PasteMode){ + case SynSelectionMode::smNormal: + InsertedLines = InsertNormal(StartLine,StartCol,Value); + break; + case SynSelectionMode::smColumn: + InsertedLines = InsertColumn(StartLine,StartCol,Value); + break; + case SynSelectionMode::smLine: + InsertedLines = InsertLine(StartLine,StartCol,Value); + break; + } + // We delete selected based on the current selection mode, but paste + // what's on the clipboard according to what it was when copied. + // Update marks + if (InsertedLines > 0) { + if ((PasteMode == SynSelectionMode::smNormal) && (StartCol > 1)) + StartLine++; + DoLinesInserted(StartLine, InsertedLines); + } + ensureCursorPosVisible(); +} + bool SynEdit::onGetSpecialLineColors(int, QColor &, QColor &) { return false; @@ -1866,6 +2413,10 @@ void SynEdit::onCommandProcessed(SynEditorCommand Command, QChar AChar, void *pD void SynEdit::ExecuteCommand(SynEditorCommand Command, QChar AChar, void *pData) { + incPaintLock(); + auto action=finally([this] { + decPaintLock(); + }); switch(Command) { //horizontal caret movement or selection case SynEditorCommand::ecLeft: @@ -1942,8 +2493,33 @@ void SynEdit::ExecuteCommand(SynEditorCommand Command, QChar AChar, void *pData) case SynEditorCommand::ecSelGotoXY: if (pData) MoveCaretAndSelection(caretXY(), *((BufferCoord *)(pData)), Command == SynEditorCommand::ecSelGotoXY); + break; + // word selection + case SynEditorCommand::ecWordLeft: + case SynEditorCommand::ecSelWordLeft: + { + BufferCoord CaretNew = PrevWordPos(); + MoveCaretAndSelection(caretXY(), CaretNew, Command == SynEditorCommand::ecSelWordLeft); + break; } + case SynEditorCommand::ecWordRight: + case SynEditorCommand::ecSelWordRight: + { + BufferCoord CaretNew = NextWordPos(); + MoveCaretAndSelection(caretXY(), CaretNew, Command == SynEditorCommand::ecSelWordRight); + break; + } + case SynEditorCommand::ecSelWord: + SetSelWord(); + break; + case SynEditorCommand::ecSelectAll: + SelectAll(); + break; + case SynEditorCommand::ecDeleteLastChar: + DeleteLastChar(); + break; +} // procedure SetSelectedTextEmpty; // var // vSelText: string; @@ -2020,187 +2596,8 @@ void SynEdit::ExecuteCommand(SynEditorCommand Command, QChar AChar, void *pData) // IncPaintLock; // try // case Command of -// // goto special line / column position -// ecGotoXY, ecSelGotoXY: -// if Assigned(Data) then begin -// MoveCaretAndSelection(CaretXY, TBufferCoord(Data^), Command = ecSelGotoXY); -// Update; -// end; -// // word selection -// ecWordLeft, ecSelWordLeft: begin -// CaretNew := PrevWordPos; -// MoveCaretAndSelection(CaretXY, CaretNew, Command = ecSelWordLeft); -// end; -// ecWordRight, ecSelWordRight: begin -// CaretNew := NextWordPos; -// MoveCaretAndSelection(CaretXY, CaretNew, Command = ecSelWordRight); -// end; -// ecSelWord: begin -// SetSelWord; -// end; -// ecSelectAll: begin -// SelectAll; -// end; -// ecDeleteLastChar: -// if not ReadOnly then begin -// DoOnPaintTransientEx(ttBefore, true); -// try -// if SelAvail then -// SetSelectedTextEmpty -// else begin -// Temp := LineText; -// TabBuffer := Lines.ExpandedStrings[CaretY - 1]; -// Len := Length(Temp); -// Caret := CaretXY; -// vTabTrim := 0; -// if CaretX > Len + 1 then begin -// helper := ''; -// if eoSmartTabDelete in fOptions then begin -// //It's at the end of the line, move it to the length -// if Len > 0 then -// InternalCaretX := Len + 1 -// else begin -// //move it as if there were normal spaces there -// SpaceCount1 := CaretX - 1; -// SpaceCount2 := 0; -// // unindent -// if SpaceCount1 > 0 then begin -// BackCounter := CaretY - 2; -// //It's better not to have if statement inside loop -// if (eoTrimTrailingSpaces in Options) and (Len = 0) then -// while BackCounter >= 0 do begin -// SpaceCount2 := LeftSpacesEx(Lines[BackCounter], True); -// if SpaceCount2 < SpaceCount1 then -// break; -// Dec(BackCounter); -// end else -// while BackCounter >= 0 do begin -// SpaceCount2 := LeftSpaces(Lines[BackCounter]); -// if SpaceCount2 < SpaceCount1 then -// break; -// Dec(BackCounter); -// end; -// if (BackCounter = -1) and (SpaceCount2 > SpaceCount1) then -// SpaceCount2 := 0; -// end; -// if SpaceCount2 = SpaceCount1 then -// SpaceCount2 := 0; -// fCaretX := fCaretX - (SpaceCount1 - SpaceCount2); -// UpdateLastCaretX; -// fStateFlags := fStateFlags + [sfCaretChanged]; -// StatusChanged([scCaretX]); -// end; -// end else begin -// // only move caret one column -// InternalCaretX := CaretX - 1; -// end; -// end else if CaretX = 1 then begin -// // join this line with the last line if possible -// if CaretY > 1 then begin -// InternalCaretY := CaretY - 1; -// InternalCaretX := Length(Lines[CaretY - 1]) + 1; -// Lines.Delete(CaretY); -// DoLinesDeleted(CaretY+1, 1); -// if eoTrimTrailingSpaces in Options then -// Temp := TrimTrailingSpaces(Temp); -// LineText := LineText + Temp; -// helper := #13#10; -// end; -// end else begin -// // delete text before the caret -// SpaceCount1 := LeftSpaces(Temp); -// SpaceCount2 := 0; -// if (Temp[CaretX - 1] <= #32) and (SpaceCount1 = CaretX - 1) then begin -// if eoSmartTabDelete in fOptions then begin -// // unindent -// if SpaceCount1 > 0 then begin -// BackCounter := CaretY - 2; -// while BackCounter >= 0 do begin -// SpaceCount2 := LeftSpaces(Lines[BackCounter]); -// if SpaceCount2 < SpaceCount1 then -// break; -// Dec(BackCounter); -// end; -// if (BackCounter = -1) and (SpaceCount2 > SpaceCount1) then -// SpaceCount2 := 0; -// end; -// if SpaceCount2 = SpaceCount1 then -// SpaceCount2 := 0; -// helper := Copy(Temp, 1, SpaceCount1 - SpaceCount2); -// Delete(Temp, 1, SpaceCount1 - SpaceCount2); -// end else begin -// SpaceCount2 := SpaceCount1; -// //how much till the next tab column -// BackCounter := (DisplayX - 1) mod FTabWidth; -// if BackCounter = 0 then -// BackCounter := FTabWidth; -// SpaceCount1 := 0; -// CX := DisplayX - BackCounter; -// while (SpaceCount1 < FTabWidth) and -// (SpaceCount1 < BackCounter) and -// (TabBuffer[CX] <> #9) do begin -// Inc(SpaceCount1); -// Inc(CX); -// end; -// {$IFOPT R+} -// // Avoids an exception when compiled with $R+. -// // 'CX' can be 'Length(TabBuffer)+1', which isn't an AV and evaluates -// //to #0. But when compiled with $R+, Delphi raises an Exception. -// if CX <= Length(TabBuffer) then -// {$ENDIF} -// if TabBuffer[CX] = #9 then -// SpaceCount1 := SpaceCount1 + 1; - -// if SpaceCount2 = SpaceCount1 then begin -// helper := Copy(Temp, 1, SpaceCount1); -// Delete(Temp, 1, SpaceCount1); -// end else begin -// helper := Copy(Temp, SpaceCount2 - SpaceCount1 + 1, SpaceCount1); -// Delete(Temp, SpaceCount2 - SpaceCount1 + 1, SpaceCount1); -// end; -// SpaceCount2 := 0; -// end; -// ProperSetLine(CaretY - 1, Temp); -// fCaretX := fCaretX - (SpaceCount1 - SpaceCount2); -// UpdateLastCaretX; -// fStateFlags := fStateFlags + [sfCaretChanged]; -// StatusChanged([scCaretX]); -// end else begin -// // delete char -// counter := 1; -// {$IFDEF SYN_MBCSSUPPORT} -// if (CaretX >= 3) and (ByteType(Temp, CaretX - 2) = mbLeadByte) then -// Inc(counter); -// {$ENDIF} -// InternalCaretX := CaretX - counter; -// // Stores the previous "expanded" CaretX if the line contains tabs. -// if (eoTrimTrailingSpaces in Options) and (Len <> Length(TabBuffer)) then -// vTabTrim := CharIndex2CaretPos(CaretX, TabWidth, Temp); -// helper := Copy(Temp, CaretX, counter); -// Delete(Temp, CaretX, counter); -// ProperSetLine(CaretY - 1, Temp); -// // Calculates a delta to CaretX to compensate for trimmed tabs. -// if vTabTrim <> 0 then -// if Length(Temp) <> Length(LineText) then -// Dec(vTabTrim, CharIndex2CaretPos(CaretX, TabWidth, LineText)) -// else -// vTabTrim := 0; -// end; -// end; -// if (Caret.Char <> CaretX) or (Caret.Line <> CaretY) then begin -// fUndoList.AddChange(crSilentDelete, CaretXY, Caret, helper, -// smNormal); -// if vTabTrim <> 0 then -// ForceCaretX(CaretX + vTabTrim); -// end; -// end; -// EnsureCursorPosVisible; -// finally -// DoOnPaintTransientEx(ttAfter, true); -// end; -// end; // ecDeleteChar: // if not ReadOnly then begin // DoOnPaintTransient(ttBefore); @@ -3013,15 +3410,8 @@ void SynEdit::mousePressEvent(QMouseEvent *event) Qt::MouseButton button = event->button(); int X=event->pos().x(); int Y=event->pos().y(); + qDebug()<<"Mouse Pressed"; - if (button == Qt::LeftButton) { - if (selAvail()) { - //remember selection state, as it will be cleared later - bWasSel = true; - mMouseDownX = X; - mMouseDownY = Y; - } - } QAbstractScrollArea::mousePressEvent(event); //fKbdHandler.ExecuteMouseDown(Self, Button, Shift, X, Y); @@ -3040,8 +3430,7 @@ void SynEdit::mousePressEvent(QMouseEvent *event) if (selAvail()) { //remember selection state, as it will be cleared later bWasSel = true; - mMouseDownX = X; - mMouseDownY = Y; + mMouseDownPos = event->pos(); } computeCaret(X,Y); //I couldn't track down why, but sometimes (and definitely not all the time) @@ -3050,7 +3439,7 @@ void SynEdit::mousePressEvent(QMouseEvent *event) mBlockBegin = TmpBegin; mBlockEnd = TmpEnd; - setMouseTracking(true); + //setMouseTracking(true); //if mousedown occurred in selected block begin drag operation mStateFlags.setFlag(SynStateFlag::sfWaitForDragging,false); if (bWasSel && mOptions.testFlag(eoDragDropEditing) && (X >= mGutterWidth + 2) @@ -3060,25 +3449,91 @@ void SynEdit::mousePressEvent(QMouseEvent *event) if (bStartDrag) { mStateFlags.setFlag(SynStateFlag::sfWaitForDragging); } else { - if (!mStateFlags.testFlag(SynStateFlag::sfDblClicked)) { - if (event->modifiers() == Qt::ShiftModifier) - //BlockBegin and BlockEnd are restored to their original position in the - //code from above and SetBlockEnd will take care of proper invalidation - setBlockEnd(caretXY()); - else if (mOptions.testFlag(eoAltSetsColumnMode) && - (mActiveSelectionMode != SynSelectionMode::smLine)) { - if (event->modifiers() == Qt::AltModifier) - setSelectionMode(SynSelectionMode::smColumn); - else - setSelectionMode(SynSelectionMode::smNormal); - } - //Selection mode must be set before calling SetBlockBegin - setBlockBegin(caretXY()); - } + if (event->modifiers() == Qt::ShiftModifier) + //BlockBegin and BlockEnd are restored to their original position in the + //code from above and SetBlockEnd will take care of proper invalidation + setBlockEnd(caretXY()); + else if (mOptions.testFlag(eoAltSetsColumnMode) && + (mActiveSelectionMode != SynSelectionMode::smLine)) { + if (event->modifiers() == Qt::AltModifier) + setSelectionMode(SynSelectionMode::smColumn); + else + setSelectionMode(SynSelectionMode::smNormal); + } + //Selection mode must be set before calling SetBlockBegin + setBlockBegin(caretXY()); } } } +void SynEdit::mouseReleaseEvent(QMouseEvent *event) +{ + QAbstractScrollArea::mouseReleaseEvent(event); + Qt::MouseButton button = event->button(); + int X=event->pos().x(); + int Y=event->pos().y(); + + if (!mMouseMoved && (X < mGutterWidth + 2)) { + //doOnGutterClick(event); + } + + mScrollTimer->stop(); +// if ((button = ) and (Shift = [ssRight]) and Assigned(PopupMenu) then +// exit; + //setMouseTracking(false); + + if (mStateFlags.testFlag(SynStateFlag::sfWaitForDragging) && + !mStateFlags.testFlag(SynStateFlag::sfDblClicked)) { +// computeCaret(X, Y); +// if not (ssShift in Shift) then +// SetBlockBegin(CaretXY); +// SetBlockEnd(CaretXY); + mStateFlags.setFlag(SynStateFlag::sfWaitForDragging, false); + } + mStateFlags.setFlag(SynStateFlag::sfDblClicked,false); +} + +void SynEdit::mouseMoveEvent(QMouseEvent *event) +{ + QAbstractScrollArea::mouseMoveEvent(event); + mMouseMoved = true; + Qt::MouseButtons buttons = event->buttons(); + int X=event->pos().x(); + int Y=event->pos().y(); +// if (!hasMouseTracking()) +// return; + + if ((mStateFlags.testFlag(SynStateFlag::sfWaitForDragging))) { + if ( ( event->pos() - mMouseDownPos).manhattanLength()>=QApplication::startDragDistance()) { + mStateFlags.setFlag(SynStateFlag::sfWaitForDragging); + //BeginDrag(false); + } + } else if ((buttons == Qt::LeftButton) && (X > mGutterWidth)) { + // should we begin scrolling? + computeScroll(X, Y); + DisplayCoord P = pixelsToNearestRowColumn(X, Y); + P.Row = MinMax(P.Row, 1, displayLineCount()); + if (mScrollDeltaX != 0) + P.Column = displayX(); + if (mScrollDeltaY != 0) + P.Row = displayY(); + internalSetCaretXY(displayToBufferPos(P)); + setBlockEnd(caretXY()); + } +} + +void SynEdit::mouseDoubleClickEvent(QMouseEvent *event) +{ + QAbstractScrollArea::mouseDoubleClickEvent(event); + QPoint ptMouse = event->pos(); + if (ptMouse.x() >= mGutterWidth + 2) { + if (!mOptions.testFlag(eoNoSelection)) + SetWordBlock(caretXY()); + mStateFlags.setFlag(SynStateFlag::sfDblClicked); + //MouseCapture := FALSE; + } +} + int SynEdit::maxScrollWidth() const { return mMaxScrollWidth; diff --git a/RedPandaIDE/qsynedit/SynEdit.h b/RedPandaIDE/qsynedit/SynEdit.h index 96db4898..e257b82e 100644 --- a/RedPandaIDE/qsynedit/SynEdit.h +++ b/RedPandaIDE/qsynedit/SynEdit.h @@ -91,7 +91,8 @@ enum SynEditorOption { eoTabIndent = 0x02000000, //When active and act as block indent, unindent when text is selected eoTabsToSpaces = 0x04000000, //Converts a tab character to a specified number of space characters eoShowRainbowColor = 0x08000000, - eoTrimTrailingSpaces = 0x10000000 //Spaces at the end of lines will be trimmed and not saved + eoTrimTrailingSpaces = 0x10000000, //Spaces at the end of lines will be trimmed and not saved + eoSelectWordByDblClick = 0x20000000 }; Q_DECLARE_FLAGS(SynEditorOptions, SynEditorOption) @@ -172,7 +173,9 @@ public: QPoint RowColumnToPixels(const DisplayCoord& coord); DisplayCoord bufferToDisplayPos(const BufferCoord& p); BufferCoord displayToBufferPos(const DisplayCoord& p); + int leftSpaces(const QString& line); int charToColumn(int aLine, int aChar); + int columnToChar(int aLine, int aColumn); int stringColumns(const QString& line, int colsBefore); int getLineIndent(const QString& line); int rowToLine(int aRow); @@ -197,6 +200,20 @@ public: void showCaret(); void hideCaret(); bool IsPointInSelection(const BufferCoord& Value); + BufferCoord NextWordPos(); + BufferCoord NextWordPosEx(const BufferCoord& XY); + BufferCoord WordStart(); + BufferCoord WordStartEx(const BufferCoord& XY); + BufferCoord WordEnd(); + BufferCoord WordEndEx(const BufferCoord& XY); + BufferCoord PrevWordPos(); + BufferCoord PrevWordPosEx(const BufferCoord& XY); + void SetSelWord(); + void SetWordBlock(BufferCoord Value); + +//Commands + void SelectAll(); + void DeleteLastChar(); // setter && getters int topLine() const; @@ -246,6 +263,10 @@ public: SynSelectionMode selectionMode() const; void setSelectionMode(SynSelectionMode value); + QString selText(); + + QString lineBreak(); + signals: void Changed(); @@ -306,6 +327,8 @@ private: void scrollWindow(int dx,int dy); void setInternalDisplayXY(const DisplayCoord& aPos); void internalSetCaretXY(const BufferCoord& Value); + void internalSetCaretX(int Value); + void internalSetCaretY(int Value); void setStatusChanged(SynStatusChanges changes); void doOnStatusChange(SynStatusChanges changes); void insertBlock(const BufferCoord& BB, const BufferCoord& BE, const QString& ChangeStr, @@ -352,6 +375,14 @@ private: bool isSelection); void MoveCaretToLineStart(bool isSelection); void MoveCaretToLineEnd(bool isSelection); + void SetSelectedTextEmpty(); + void SetSelTextPrimitive(const QString& aValue); + void SetSelTextPrimitiveEx(SynSelectionMode PasteMode, + const QString& Value, bool AddToUndoList); + void DoLinesDeleted(int FirstLine, int Count); + void ProperSetLine(int ALine, const QString& ALineText); + void DeleteSelection(const BufferCoord& BB, const BufferCoord& BE); + void InsertText(const QString& Value, SynSelectionMode PasteMode); private slots: void bookMarkOptionsChanged(); @@ -415,8 +446,7 @@ private: PSynEditUndoList mUndoList; PSynEditUndoList mRedoList; SynEditMarkList mBookMarks; - int mMouseDownX; - int mMouseDownY; + QPoint mMouseDownPos; SynBookMarkOpt mBookMarkOpt; bool mHideSelection; int mMouseWheelAccumulator; @@ -476,9 +506,6 @@ private: int m_blinkTimerId; int m_blinkStatus; - - - friend class SynEditTextPainter; // QWidget interface @@ -491,6 +518,9 @@ void focusInEvent(QFocusEvent *event) override; void focusOutEvent(QFocusEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void mousePressEvent(QMouseEvent *event) override; +void mouseReleaseEvent(QMouseEvent *event) override; +void mouseMoveEvent(QMouseEvent *event) override; +void mouseDoubleClickEvent(QMouseEvent *event) override; }; #endif // SYNEDIT_H diff --git a/RedPandaIDE/qsynedit/TextBuffer.cpp b/RedPandaIDE/qsynedit/TextBuffer.cpp index 44bbf66d..c4f2eb6a 100644 --- a/RedPandaIDE/qsynedit/TextBuffer.cpp +++ b/RedPandaIDE/qsynedit/TextBuffer.cpp @@ -90,6 +90,18 @@ int SynEditStringList::lengthOfLongestLine() return 0; } +QString SynEditStringList::lineBreak() +{ + switch(mFileEndingType) { + case FileEndingType::Linux: + return "\n"; + case FileEndingType::Windows: + return "\r\n"; + case FileEndingType::Mac: + return "\r"; + } +} + SynRangeState SynEditStringList::ranges(int Index) { if (Index>=0 && Index < mList.size()) { @@ -340,14 +352,7 @@ QString SynEditStringList::GetTextStr() QString Result; for (PSynEditStringRec& line:mList) { Result.append(line->fString); - switch(mFileEndingType) { - case FileEndingType::Linux: - Result.append('\n'); - case FileEndingType::Windows: - Result.append("\r\n"); - case FileEndingType::Mac: - Result.append("\r"); - } + Result.append(lineBreak()); } return Result; } @@ -489,14 +494,14 @@ void SynEditStringList::LoadFromFile(QFile &file, const QByteArray& encoding, QB allAscii = isTextAllAscii(line); } if (allAscii) { - addItem(removeLineEnds(QString::fromLatin1(line))); + addItem(TrimRight(QString::fromLatin1(line))); } else { QString newLine = codec->toUnicode(line.constData(),line.length(),&state); if (state.invalidChars>0) { needReread = true; break; } - addItem(removeLineEnds(newLine)); + addItem(TrimRight(newLine)); } if (file.atEnd()){ break; @@ -528,7 +533,7 @@ void SynEditStringList::LoadFromFile(QFile &file, const QByteArray& encoding, QB QString line; clear(); while (textStream.readLineInto(&line)) { - addItem(removeLineEnds(line)); + addItem(TrimRight(line)); } } diff --git a/RedPandaIDE/qsynedit/TextBuffer.h b/RedPandaIDE/qsynedit/TextBuffer.h index 14f57634..2e3d06c0 100644 --- a/RedPandaIDE/qsynedit/TextBuffer.h +++ b/RedPandaIDE/qsynedit/TextBuffer.h @@ -56,6 +56,7 @@ public: int braceLevels(int Index); int lineColumns(int Index); int lengthOfLongestLine(); + QString lineBreak(); SynRangeState ranges(int Index); void setRange(int Index, SynRangeState ARange); void setParenthesisLevel(int Index, int level); diff --git a/RedPandaIDE/utils.cpp b/RedPandaIDE/utils.cpp index e60e0ce8..fdc833fc 100644 --- a/RedPandaIDE/utils.cpp +++ b/RedPandaIDE/utils.cpp @@ -364,7 +364,7 @@ void inflateRect(QRect &rect, int dx, int dy) rect.setBottom(rect.bottom()+dy); } -QString removeLineEnds(const QString &s) +QString TrimRight(const QString &s) { if (s.isEmpty()) return s; diff --git a/RedPandaIDE/utils.h b/RedPandaIDE/utils.h index 52d794c9..e9acb656 100644 --- a/RedPandaIDE/utils.h +++ b/RedPandaIDE/utils.h @@ -75,27 +75,21 @@ QString getCompiledExecutableName(const QString filename); void splitStringArguments(const QString& arguments, QStringList& argumentList); bool programHasConsole(const QString& filename); QString toLocalPath(const QString& filename); - using LineProcessFunc = std::function; QStringList ReadStreamToLines(QTextStream* stream); - void ReadStreamToLines(QTextStream* stream, LineProcessFunc lineFunc); - QStringList TextToLines(const QString& text); - void TextToLines(const QString& text, LineProcessFunc lineFunc); QStringList ReadFileToLines(const QString& fileName, QTextCodec* codec); - void ReadFileToLines(const QString& fileName, QTextCodec* codec, LineProcessFunc lineFunc); void decodeKey(int combinedKey, int& key, Qt::KeyboardModifiers& modifiers); - void inflateRect(QRect& rect, int delta); void inflateRect(QRect& rect, int dx, int dy); -QString removeLineEnds(const QString& s); +QString TrimRight(const QString& s); template class final_action