RedPanda-CPP/RedPandaIDE/qsynedit/SynEdit.cpp

5094 lines
165 KiB
C++
Raw Normal View History

2021-05-14 23:56:43 +08:00
#include "SynEdit.h"
#include <QApplication>
#include <QFontMetrics>
#include <algorithm>
#include <cmath>
#include <QScrollBar>
#include <QPaintEvent>
2021-05-24 00:41:00 +08:00
#include <QPainter>
#include <QTimerEvent>
2021-05-14 23:56:43 +08:00
#include "highlighter/base.h"
#include "Constants.h"
2021-05-24 00:41:00 +08:00
#include "TextPainter.h"
2021-06-05 23:43:45 +08:00
#include <QClipboard>
2021-05-24 00:41:00 +08:00
#include <QDebug>
2021-05-29 21:35:46 +08:00
#include <QGuiApplication>
2021-06-05 23:43:45 +08:00
#include <QInputMethodEvent>
2021-05-24 18:11:07 +08:00
#include <QPaintEvent>
2021-05-29 21:35:46 +08:00
#include <QStyleHints>
2021-05-14 23:56:43 +08:00
SynEdit::SynEdit(QWidget *parent) : QAbstractScrollArea(parent)
{
2021-05-24 00:41:00 +08:00
qDebug()<<"init SynEdit:";
2021-05-14 23:56:43 +08:00
mPaintLock = 0;
mPainterLock = 0;
mPainting = false;
2021-05-24 00:41:00 +08:00
mLines = std::make_shared<SynEditStringList>(this);
qDebug()<<"init SynEdit: 1";
2021-05-14 23:56:43 +08:00
mOrigLines = mLines;
//fPlugins := TList.Create;
mMouseMoved = false;
mUndoing = false;
mLines->connect(mLines.get(), &SynEditStringList::changed, this, &SynEdit::linesChanged);
mLines->connect(mLines.get(), &SynEditStringList::changing, this, &SynEdit::linesChanging);
mLines->connect(mLines.get(), &SynEditStringList::cleared, this, &SynEdit::linesCleared);
mLines->connect(mLines.get(), &SynEditStringList::deleted, this, &SynEdit::linesDeleted);
mLines->connect(mLines.get(), &SynEditStringList::inserted, this, &SynEdit::linesInserted);
mLines->connect(mLines.get(), &SynEditStringList::putted, this, &SynEdit::linesPutted);
2021-05-24 00:41:00 +08:00
qDebug()<<"init SynEdit: 2";
2021-05-14 23:56:43 +08:00
#ifdef Q_OS_WIN
2021-05-24 18:11:07 +08:00
mFontDummy = QFont("Consolas",12);
2021-05-14 23:56:43 +08:00
#elif Q_OS_LINUX
mFontDummy = QFont("terminal",14);
#else
#error "Not supported!"
#endif
2021-05-24 18:11:07 +08:00
mFontDummy.setStyleStrategy(QFont::PreferAntialias);
2021-05-24 00:41:00 +08:00
setFont(mFontDummy);
qDebug()<<"init SynEdit:3";
2021-05-14 23:56:43 +08:00
mUndoList = std::make_shared<SynEditUndoList>();
mUndoList->connect(mUndoList.get(), &SynEditUndoList::addedUndo, this, &SynEdit::undoAdded);
mOrigUndoList = mUndoList;
mRedoList = std::make_shared<SynEditUndoList>();
mRedoList->connect(mRedoList.get(), &SynEditUndoList::addedUndo, this, &SynEdit::redoAdded);
mOrigRedoList = mRedoList;
2021-05-24 00:41:00 +08:00
qDebug()<<"init SynEdit: 4";
2021-05-24 18:11:07 +08:00
mCaretColor = QColorConstants::Red;
mActiveLineColor = QColorConstants::Svg::lightblue;
mSelectedBackground = QColorConstants::Svg::lightgray;
mSelectedForeground = palette().color(QPalette::Text);
2021-05-14 23:56:43 +08:00
mBookMarkOpt.connect(&mBookMarkOpt, &SynBookMarkOpt::changed, this, &SynEdit::bookMarkOptionsChanged);
// fRightEdge has to be set before FontChanged is called for the first time
mRightEdge = 80;
2021-05-24 00:41:00 +08:00
qDebug()<<"init SynEdit: 5";
2021-05-14 23:56:43 +08:00
mGutter.setRightOffset(21);
mGutter.connect(&mGutter, &SynGutter::changed, this, &SynEdit::gutterChanged);
mGutterWidth = mGutter.width();
//ControlStyle := ControlStyle + [csOpaque, csSetCaption, csNeedsBorderPaint];
//Height := 150;
//Width := 200;
this->setCursor(Qt::CursorShape::IBeamCursor);
//TabStop := True;
mInserting = true;
mMaxScrollWidth = 1024;
mScrollBars = SynScrollStyle::ssBoth;
2021-05-24 00:41:00 +08:00
mExtraLineSpacing = 0;
qDebug()<<"init SynEdit: 6";
2021-05-14 23:56:43 +08:00
this->setFrameShape(QFrame::Panel);
this->setFrameShadow(QFrame::Sunken);
this->setLineWidth(1);
mInsertCaret = SynEditCaretType::ctVerticalLine;
mOverwriteCaret = SynEditCaretType::ctBlock;
mSelectionMode = SynSelectionMode::smNormal;
mActiveSelectionMode = SynSelectionMode::smNormal;
2021-06-03 23:18:51 +08:00
mReadOnly = false;
2021-05-24 00:41:00 +08:00
qDebug()<<"init SynEdit: 7";
2021-05-14 23:56:43 +08:00
//stop qt to auto fill background
setAutoFillBackground(false);
//fFocusList := TList.Create;
//fKbdHandler := TSynEditKbdHandler.Create;
//fMarkList.OnChange := MarkListChange;
2021-05-24 00:41:00 +08:00
qDebug()<<"init SynEdit: 7-1";
2021-05-14 23:56:43 +08:00
setDefaultKeystrokes();
2021-05-24 00:41:00 +08:00
qDebug()<<"init SynEdit: 7-2";
2021-05-14 23:56:43 +08:00
mRightEdgeColor = QColorConstants::Svg::silver;
2021-05-24 00:41:00 +08:00
qDebug()<<"init SynEdit: 8";
2021-05-14 23:56:43 +08:00
/* IME input */
mImeCount = 0;
mMBCSStepAside = false;
/* end of IME input */
mWantReturns = true;
mWantTabs = false;
mTabWidth = 4;
mLeftChar = 1;
mTopLine = 1;
mCaretX = 1;
2021-06-03 23:18:51 +08:00
mLastCaretColumn = 1;
2021-05-14 23:56:43 +08:00
mCaretY = 1;
mBlockBegin.Char = 1;
mBlockBegin.Line = 1;
mBlockEnd = mBlockBegin;
2021-06-04 21:27:51 +08:00
mOptions = eoAutoIndent | eoAddIndent
2021-06-05 23:43:45 +08:00
| eoDragDropEditing | eoEnhanceEndKey | eoTabIndent |
2021-06-03 20:26:36 +08:00
eoShowScrollHint | eoGroupUndo | eoKeepCaretX | eoSelectWordByDblClick;
2021-05-24 00:41:00 +08:00
qDebug()<<"init SynEdit: 9";
2021-05-14 23:56:43 +08:00
mScrollTimer = new QTimer(this);
mScrollTimer->setInterval(100);
connect(mScrollTimer, &QTimer::timeout,this, &SynEdit::scrollTimerHandler);
mScrollHintColor = QColorConstants::Yellow;
mScrollHintFormat = SynScrollHintFormat::shfTopLineOnly;
2021-05-24 00:41:00 +08:00
mContentImage = std::make_shared<QImage>(clientWidth(),clientHeight(),QImage::Format_ARGB32);
mUseCodeFolding = true;
2021-05-24 00:41:00 +08:00
m_blinkTimerId = 0;
2021-05-24 18:11:07 +08:00
m_blinkStatus = 0;
2021-05-24 00:41:00 +08:00
qDebug()<<"init SynEdit: 10";
2021-05-14 23:56:43 +08:00
synFontChanged();
2021-05-24 00:41:00 +08:00
qDebug()<<"init SynEdit: done";
2021-05-24 18:11:07 +08:00
showCaret();
2021-05-24 21:48:03 +08:00
connect(horizontalScrollBar(),&QScrollBar::valueChanged,
this, &SynEdit::doScrolled);
connect(verticalScrollBar(),&QScrollBar::valueChanged,
this, &SynEdit::doScrolled);
2021-06-05 23:43:45 +08:00
//enable input method
setAttribute(Qt::WA_InputMethodEnabled);
2021-05-14 23:56:43 +08:00
}
int SynEdit::displayLineCount()
{
2021-05-24 00:41:00 +08:00
if (mLines->empty()) {
return 0;
}
2021-05-14 23:56:43 +08:00
return lineToRow(mLines->count());
}
DisplayCoord SynEdit::displayXY()
{
return bufferToDisplayPos(caretXY());
}
int SynEdit::displayX()
{
return displayXY().Column;
}
int SynEdit::displayY()
{
return displayXY().Row;
}
BufferCoord SynEdit::caretXY()
{
BufferCoord result;
result.Char = caretX();
result.Line = caretY();
return result;
}
int SynEdit::caretX()
{
return mCaretX;
}
int SynEdit::caretY()
{
return mCaretY;
}
void SynEdit::setCaretX(int value)
{
setCaretXY({value,mCaretY});
}
void SynEdit::setCaretY(int value)
{
setCaretXY({mCaretX,value});
}
void SynEdit::setCaretXY(const BufferCoord &value)
{
setCaretXYCentered(false,value);
}
void SynEdit::setCaretXYEx(bool CallEnsureCursorPos, BufferCoord value)
{
2021-05-27 21:39:18 +08:00
qDebug()<<"new Value"<<value.Line<<value.Char;
2021-05-14 23:56:43 +08:00
bool vTriggerPaint=true; //how to test it?
if (vTriggerPaint)
doOnPaintTransient(SynTransientType::ttBefore);
int nMaxX = mMaxScrollWidth + 1;
if (value.Line > mLines->count())
value.Line = mLines->count();
if (value.Line < 1) {
// this is just to make sure if Lines stringlist should be empty
value.Line = 1;
if (!mOptions.testFlag(SynEditorOption::eoScrollPastEol)) {
nMaxX = 1;
}
} else {
if (!mOptions.testFlag(SynEditorOption::eoScrollPastEol))
2021-05-27 20:33:25 +08:00
nMaxX = mLines->getString(value.Line-1).length()+1;
2021-05-14 23:56:43 +08:00
}
2021-05-24 21:48:03 +08:00
if ((value.Char > nMaxX) && (! (mOptions.testFlag(SynEditorOption::eoScrollPastEol)) ) )
2021-05-14 23:56:43 +08:00
value.Char = nMaxX;
if (value.Char < 1)
value.Char = 1;
if ((value.Char != mCaretX) || (value.Line != mCaretY)) {
incPaintLock();
auto action = finally([this]{
decPaintLock();
});
// simply include the flags, fPaintLock is > 0
if (mCaretX != value.Char) {
mCaretX = value.Char;
mStatusChanges.setFlag(SynStatusChange::scCaretX);
2021-05-27 21:39:18 +08:00
invalidateLine(mCaretY);
2021-05-14 23:56:43 +08:00
}
if (mCaretY != value.Line) {
2021-05-27 20:33:25 +08:00
int oldCaretY = mCaretY;
mCaretY = value.Line;
if (mActiveLineColor.isValid()) {
2021-05-14 23:56:43 +08:00
invalidateLine(mCaretY);
2021-05-27 20:33:25 +08:00
invalidateLine(oldCaretY);
2021-05-14 23:56:43 +08:00
}
mStatusChanges.setFlag(SynStatusChange::scCaretY);
}
// Call UpdateLastCaretX before DecPaintLock because the event handler it
// calls could raise an exception, and we don't want fLastCaretX to be
// left in an undefined state if that happens.
updateLastCaretX();
if (CallEnsureCursorPos)
ensureCursorPosVisible();
mStateFlags.setFlag(SynStateFlag::sfCaretChanged);
mStateFlags.setFlag(SynStateFlag::sfScrollbarChanged);
} else {
// Also call UpdateLastCaretX if the caret didn't move. Apps don't know
// anything about fLastCaretX and they shouldn't need to. So, to avoid any
// unwanted surprises, always update fLastCaretX whenever CaretXY is
// assigned to.
// Note to SynEdit developers: If this is undesirable in some obscure
// case, just save the value of fLastCaretX before assigning to CaretXY and
// restore it afterward as appropriate.
updateLastCaretX();
}
if (vTriggerPaint)
doOnPaintTransient(SynTransientType::ttAfter);
}
void SynEdit::setCaretXYCentered(bool ForceToMiddle, const BufferCoord &value)
{
incPaintLock();
auto action = finally([this] {
decPaintLock();
});
mStatusChanges.setFlag(SynStatusChange::scSelection);
setCaretXYEx(ForceToMiddle,value);
if (selAvail())
invalidateSelection();
mBlockBegin.Char = mCaretX;
mBlockBegin.Line = mCaretY;
mBlockEnd = mBlockBegin;
if (ForceToMiddle)
ensureCursorPosVisibleEx(true); // but here after block has been set
}
2021-06-03 20:26:36 +08:00
bool SynEdit::GetHighlighterAttriAtRowCol(const BufferCoord &XY, QString &Token, PSynHighlighterAttribute &Attri)
{
SynHighlighterTokenType TmpType;
int TmpKind, TmpStart;
return GetHighlighterAttriAtRowColEx(XY, Token, TmpType, TmpKind,TmpStart, Attri);
}
bool SynEdit::GetHighlighterAttriAtRowCol(const BufferCoord &XY, QString &Token, bool &tokenFinished, SynHighlighterTokenType &TokenType, PSynHighlighterAttribute &Attri)
{
int PosX, PosY, endPos, Start;
QString Line;
PosY = XY.Line - 1;
if (mHighlighter && (PosY >= 0) && (PosY < mLines->count())) {
Line = mLines->getString(PosY);
if (PosY == 0) {
mHighlighter->resetState();
} else {
mHighlighter->setState(mLines->ranges(PosY-1),
mLines->braceLevels(PosY-1),
mLines->bracketLevels(PosY-1),
mLines->parenthesisLevels(PosY-1));
}
mHighlighter->setLine(Line, PosY);
PosX = XY.Char;
if ((PosX > 0) && (PosX <= Line.length())) {
while (!mHighlighter->eol()) {
Start = mHighlighter->getTokenPos() + 1;
Token = mHighlighter->getToken();
endPos = Start + Token.length()-1;
if ((PosX >= Start) && (PosX <= endPos)) {
Attri = mHighlighter->getTokenAttribute();
if (PosX == endPos)
tokenFinished = mHighlighter->getTokenFinished();
else
tokenFinished = false;
TokenType = mHighlighter->getTokenType();
return true;
}
mHighlighter->next();
}
}
}
Token = "";
Attri = PSynHighlighterAttribute();
tokenFinished = false;
return false;
}
bool SynEdit::GetHighlighterAttriAtRowColEx(const BufferCoord &XY, QString &Token, SynHighlighterTokenType &TokenType, SynTokenKind &TokenKind, int &Start, PSynHighlighterAttribute &Attri)
{
int PosX, PosY, endPos;
QString Line;
PosY = XY.Line - 1;
if (mHighlighter && (PosY >= 0) && (PosY < mLines->count())) {
Line = mLines->getString(PosY);
if (PosY == 0) {
mHighlighter->resetState();
} else {
mHighlighter->setState(mLines->ranges(PosY-1),
mLines->braceLevels(PosY-1),
mLines->bracketLevels(PosY-1),
mLines->parenthesisLevels(PosY-1));
}
mHighlighter->setLine(Line, PosY);
PosX = XY.Char;
if ((PosX > 0) && (PosX <= Line.length())) {
while (!mHighlighter->eol()) {
Start = mHighlighter->getTokenPos() + 1;
Token = mHighlighter->getToken();
endPos = Start + Token.length()-1;
if ((PosX >= Start) && (PosX <= endPos)) {
Attri = mHighlighter->getTokenAttribute();
TokenKind = mHighlighter->getTokenKind();
TokenType = mHighlighter->getTokenType();
return true;
}
mHighlighter->next();
}
}
}
Token = "";
Attri = PSynHighlighterAttribute();
TokenKind = 0;
TokenType = SynHighlighterTokenType::Default;
return false;
}
2021-05-14 23:56:43 +08:00
void SynEdit::invalidateGutter()
{
invalidateGutterLines(-1, -1);
}
void SynEdit::invalidateGutterLine(int aLine)
{
if ((aLine < 1) || (aLine > mLines->count()))
return;
invalidateGutterLines(aLine, aLine);
}
void SynEdit::invalidateGutterLines(int FirstLine, int LastLine)
{
QRect rcInval;
if (!isVisible())
return;
if (FirstLine == -1 && LastLine == -1) {
2021-05-21 23:33:53 +08:00
rcInval = QRect(0, 0, mGutterWidth, clientHeight());
2021-05-14 23:56:43 +08:00
if (mStateFlags.testFlag(SynStateFlag::sfLinesChanging))
mInvalidateRect = mInvalidateRect.united(rcInval);
else
invalidateRect(rcInval);
} else {
// find the visible lines first
if (LastLine < FirstLine)
std::swap(LastLine, FirstLine);
if (mUseCodeFolding) {
FirstLine = lineToRow(FirstLine);
if (LastLine <= mLines->count())
LastLine = lineToRow(LastLine);
else
LastLine = INT_MAX;
}
FirstLine = std::max(FirstLine, mTopLine);
LastLine = std::min(LastLine, mTopLine + mLinesInWindow);
// any line visible?
if (LastLine >= FirstLine) {
2021-05-21 23:33:53 +08:00
rcInval = {0, mTextHeight * (FirstLine - mTopLine),
2021-05-14 23:56:43 +08:00
mGutterWidth, mTextHeight * (LastLine - mTopLine + 1)};
if (mStateFlags.testFlag(SynStateFlag::sfLinesChanging)) {
mInvalidateRect = mInvalidateRect.united(rcInval);
} else {
invalidateRect(rcInval);
}
}
}
}
/**
* @brief Convert point on the edit (x,y) to (row,column)
* @param aX
* @param aY
* @return
*/
DisplayCoord SynEdit::pixelsToNearestRowColumn(int aX, int aY)
{
// Result is in display coordinates
float f;
f = (aX - mGutterWidth - 2.0) / mCharWidth;
// don't return a partially visible last line
if (aY >= mLinesInWindow * mTextHeight) {
aY = mLinesInWindow * mTextHeight - 1;
if (aY < 0)
aY = 0;
}
return {
.Column = std::max(1, (int)(leftChar() + round(f))),
.Row = std::max(1, mTopLine + (aY / mTextHeight))
};
}
DisplayCoord SynEdit::pixelsToRowColumn(int aX, int aY)
{
return {
.Column = std::max(1, mLeftChar + ((aX - mGutterWidth - 2) / mCharWidth)),
.Row = std::max(1, mTopLine + (aY / mTextHeight))
};
}
2021-05-24 00:41:00 +08:00
QPoint SynEdit::RowColumnToPixels(const DisplayCoord &coord)
{
QPoint result;
2021-05-24 21:48:03 +08:00
result.setX((coord.Column - 1) * mCharWidth + textOffset());
2021-05-24 00:41:00 +08:00
result.setY((coord.Row - mTopLine) * mTextHeight);
return result;
}
2021-05-14 23:56:43 +08:00
/**
* @brief takes a position in the text and transforms it into
* the row and column it appears to be on the screen
* @param p
* @return
*/
DisplayCoord SynEdit::bufferToDisplayPos(const BufferCoord &p)
{
DisplayCoord result {p.Char,p.Line};
2021-05-18 15:49:58 +08:00
// Account for tabs and charColumns
2021-06-05 23:43:45 +08:00
if (p.Line-1 <mLines->count())
result.Column = charToColumn(p.Line,p.Char);
2021-05-14 23:56:43 +08:00
// Account for code folding
if (mUseCodeFolding)
result.Row = foldLineToRow(result.Row);
return result;
}
/**
* @brief takes a position on screen and transfrom it into position of text
* @param p
* @return
*/
BufferCoord SynEdit::displayToBufferPos(const DisplayCoord &p)
{
BufferCoord Result{p.Column,p.Row};
// Account for code folding
if (mUseCodeFolding)
2021-06-03 23:18:51 +08:00
Result.Line = foldRowToLine(p.Row);
2021-05-14 23:56:43 +08:00
// Account for tabs
if (Result.Line <= mLines->count() ) {
2021-05-29 21:35:46 +08:00
Result.Char = columnToChar(Result.Line,p.Column);
}
return Result;
}
2021-05-14 23:56:43 +08:00
2021-05-29 21:35:46 +08:00
int SynEdit::leftSpaces(const QString &line)
{
int result = 0;
if (mOptions.testFlag(eoAutoIndent)) {
for (QChar ch:line) {
if (ch == '\t') {
result += mTabWidth - (result % mTabWidth);
2021-06-04 21:27:51 +08:00
} else if (ch == ' ') {
2021-05-29 21:35:46 +08:00
result ++;
2021-06-04 21:27:51 +08:00
} else {
break;
2021-05-29 21:35:46 +08:00
}
2021-05-24 18:11:07 +08:00
}
2021-05-14 23:56:43 +08:00
}
2021-05-29 21:35:46 +08:00
return result;
2021-05-14 23:56:43 +08:00
}
2021-06-03 20:26:36 +08:00
QString SynEdit::GetLeftSpacing(int charCount, bool wantTabs)
{
if (wantTabs && !mOptions.testFlag(eoTabsToSpaces)) {
2021-06-04 21:27:51 +08:00
return QString(charCount / mTabWidth,'\t') + QString(charCount % mTabWidth,' ');
2021-06-03 20:26:36 +08:00
} else {
2021-06-04 21:27:51 +08:00
return QString(charCount,' ');
2021-06-03 20:26:36 +08:00
}
}
2021-05-18 15:49:58 +08:00
int SynEdit::charToColumn(int aLine, int aChar)
{
2021-05-29 21:35:46 +08:00
Q_ASSERT( (aLine <= mLines->count()) && (aLine >= 1));
2021-05-24 00:41:00 +08:00
if (aLine <= mLines->count()) {
2021-05-18 15:49:58 +08:00
QString s = mLines->getString(aLine - 1);
2021-06-03 20:26:36 +08:00
return charToColumn(s,aChar);
}
}
int SynEdit::charToColumn(const QString &s, int aChar)
{
int x = 0;
int len = std::min(aChar-1,s.length());
for (int i=0;i<len;i++) {
if (s[i] == '\t')
x+=mTabWidth - (x % mTabWidth);
else
x+=charColumns(s[i]);
2021-05-18 15:49:58 +08:00
}
2021-06-03 20:26:36 +08:00
return x+1;
2021-05-29 21:35:46 +08:00
}
int SynEdit::columnToChar(int aLine, int aColumn)
{
Q_ASSERT( (aLine <= mLines->count()) && (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<len;i++) {
if (s[i] == '\t')
x+=mTabWidth - (x % mTabWidth);
else
x+=charColumns(s[i]);
if (x>=aColumn) {
break;
}
}
2021-06-04 21:27:51 +08:00
return i+1;
2021-05-29 21:35:46 +08:00
}
2021-05-18 15:49:58 +08:00
}
2021-05-21 23:33:53 +08:00
int SynEdit::stringColumns(const QString &line, int colsBefore)
2021-05-18 15:49:58 +08:00
{
2021-05-21 23:33:53 +08:00
int columns = colsBefore;
int charCols;
for (int i=0;i<line.length();i++) {
QChar ch = line[i];
if (ch == '\t') {
charCols = mTabWidth - columns % mTabWidth;
} else {
charCols = charColumns(ch);
2021-05-18 15:49:58 +08:00
}
2021-05-21 23:33:53 +08:00
columns+=charCols;
2021-05-18 15:49:58 +08:00
}
2021-05-21 23:33:53 +08:00
return columns-colsBefore;
}
int SynEdit::getLineIndent(const QString &line)
{
int indents = 0;
for (QChar ch:line) {
switch(ch.digitValue()) {
case '\t':
indents+=mTabWidth;
break;
case ' ':
indents+=1;
break;
default:
return indents;
}
}
return indents;
2021-05-18 15:49:58 +08:00
}
2021-05-14 23:56:43 +08:00
int SynEdit::rowToLine(int aRow)
{
2021-05-24 18:11:07 +08:00
if (mUseCodeFolding)
return foldRowToLine(aRow);
else
return aRow;
//return displayToBufferPos({1, aRow}).Line;
2021-05-14 23:56:43 +08:00
}
int SynEdit::lineToRow(int aLine)
{
return bufferToDisplayPos({1, aLine}).Row;
}
int SynEdit::foldRowToLine(int Row)
{
int result = Row;
for (int i=0;i<mAllFoldRanges.count();i++) {
PSynEditFoldRange range = mAllFoldRanges[i];
if (range->collapsed && !range->parentCollapsed() && range->fromLine < result) {
result += range->linesCollapsed;
}
}
return result;
}
int SynEdit::foldLineToRow(int Line)
{
int result = Line;
for (int i=mAllFoldRanges.count()-1;i>=0;i--) {
PSynEditFoldRange range =mAllFoldRanges[i];
if (range->collapsed && !range->parentCollapsed()) {
// Line is found after fold
if (range->toLine < Line)
result -= range->linesCollapsed;
// Inside fold
else if (range->fromLine < Line && Line <= range->toLine)
result -= Line - range->fromLine;
}
}
return result;
}
void SynEdit::setDefaultKeystrokes()
{
mKeyStrokes.resetDefaults();
}
void SynEdit::invalidateLine(int Line)
{
QRect rcInval;
if (mPainterLock >0)
return;
if (Line<1 || Line>mLines->count() || !isVisible())
return;
// invalidate text area of this line
if (mUseCodeFolding)
Line = foldLineToRow(Line);
if (Line >= mTopLine && Line <= mTopLine + mLinesInWindow) {
2021-05-27 20:33:25 +08:00
rcInval = { mGutterWidth,
mTextHeight * (Line - mTopLine),
2021-05-14 23:56:43 +08:00
clientWidth(),
mTextHeight};
if (mStateFlags.testFlag(SynStateFlag::sfLinesChanging))
mInvalidateRect = mInvalidateRect.united(rcInval);
else
invalidateRect(rcInval);
}
}
void SynEdit::invalidateLines(int FirstLine, int LastLine)
{
if (mPainterLock>0)
return;
if (!isVisible())
return;
if (FirstLine == -1 && LastLine == -1) {
QRect rcInval = clientRect();
rcInval.setLeft(rcInval.left()+mGutterWidth);
if (mStateFlags.testFlag(SynStateFlag::sfLinesChanging)) {
mInvalidateRect = mInvalidateRect.united(rcInval);
} else {
invalidateRect(rcInval);
}
} else {
FirstLine = std::max(FirstLine, 1);
LastLine = std::max(LastLine, 1);
// find the visible lines first
if (LastLine < FirstLine)
std::swap(LastLine, FirstLine);
if (LastLine >= mLines->count())
LastLine = INT_MAX; // paint empty space beyond last line
if (mUseCodeFolding) {
FirstLine = lineToRow(FirstLine);
// Could avoid this conversion if (First = Last) and
// (Length < CharsInWindow) but the dependency isn't worth IMO.
if (LastLine < mLines->count())
LastLine = lineToRow(LastLine + 1) - 1;
}
// mTopLine is in display coordinates, so FirstLine and LastLine must be
// converted previously.
FirstLine = std::max(FirstLine, mTopLine);
LastLine = std::min(LastLine, mTopLine + mLinesInWindow);
// any line visible?
if (LastLine >= FirstLine) {
QRect rcInval = {
clientLeft()+mGutterWidth,
mTextHeight * (FirstLine - mTopLine),
clientWidth(), mTextHeight * (LastLine - mTopLine + 1)
};
if (mStateFlags.testFlag(SynStateFlag::sfLinesChanging))
mInvalidateRect = mInvalidateRect.united(rcInval);
else
invalidateRect(rcInval);
}
}
}
void SynEdit::invalidateSelection()
{
if (mPainterLock>0)
return;
2021-06-03 20:26:36 +08:00
invalidateLines(blockBegin().Line, blockEnd().Line);
2021-05-14 23:56:43 +08:00
}
void SynEdit::invalidateRect(const QRect &rect)
{
if (mPainterLock>0)
return;
viewport()->update(rect);
}
void SynEdit::invalidate()
{
if (mPainterLock>0)
return;
viewport()->update();
}
void SynEdit::lockPainter()
{
mPainterLock++;
}
void SynEdit::unlockPainter()
{
Q_ASSERT(mPainterLock>0);
mPainterLock--;
}
bool SynEdit::selAvail()
{
return (mBlockBegin.Char != mBlockEnd.Char) ||
((mBlockBegin.Line != mBlockEnd.Line) && (mActiveSelectionMode != SynSelectionMode::smColumn));
}
void SynEdit::setCaretAndSelection(const BufferCoord &ptCaret, const BufferCoord &ptBefore, const BufferCoord &ptAfter)
{
SynSelectionMode vOldMode = mActiveSelectionMode;
incPaintLock();
auto action = finally([this,vOldMode]{
mActiveSelectionMode = vOldMode;
decPaintLock();
});
internalSetCaretXY(ptCaret);
setBlockBegin(ptBefore);
setBlockEnd(ptAfter);
}
void SynEdit::clearUndo()
{
mUndoList->Clear();
mRedoList->Clear();
}
2021-06-03 20:26:36 +08:00
BufferCoord SynEdit::GetPreviousLeftBracket(int x, int y)
{
QChar Test;
QString vDummy;
PSynHighlighterAttribute attr;
BufferCoord p;
bool isCommentOrStringOrChar;
BufferCoord Result{0,0};
// get char at caret
int PosX = x-1;
int PosY = y;
if (PosX<1)
PosY--;
if (PosY<1 )
return Result;
QString Line = mLines->getString(PosY - 1);
2021-06-03 23:18:51 +08:00
if ((PosX > Line.length()) || (PosX<1))
PosX = Line.length();
int numBrackets = 1;
while (true) {
if (Line.isEmpty()){
PosY--;
if (PosY<1)
return Result;
Line = mLines->getString(PosY - 1);
PosX = Line.length();
continue;
}
Test = Line[PosX-1];
p.Char = PosX;
p.Line = PosY;
if (Test=='{' || Test == '}') {
if (GetHighlighterAttriAtRowCol(p, vDummy, attr)) {
isCommentOrStringOrChar =
(attr == mHighlighter->stringAttribute()) ||
(attr == mHighlighter->commentAttribute()) ||
(attr->name() == SYNS_AttrCharacter);
} else
isCommentOrStringOrChar = false;
if ((Test == '{') && (! isCommentOrStringOrChar))
numBrackets--;
else if ((Test == '}') && (!isCommentOrStringOrChar))
numBrackets++;
if (numBrackets == 0) {
return p;
}
}
PosX--;
if (PosX<1) {
PosY--;
if (PosY<1)
return Result;
Line = mLines->getString(PosY - 1);
PosX = Line.length();
}
}
2021-06-03 20:26:36 +08:00
}
2021-05-16 20:36:00 +08:00
int SynEdit::charColumns(QChar ch)
{
2021-05-21 23:33:53 +08:00
if (ch == ' ')
return 1;
2021-05-24 18:11:07 +08:00
//return std::ceil((int)(fontMetrics().horizontalAdvance(ch) * dpiFactor()) / (double)mCharWidth);
return std::ceil((int)(fontMetrics().horizontalAdvance(ch)) / (double)mCharWidth);
2021-05-16 20:36:00 +08:00
}
double SynEdit::dpiFactor()
{
return fontMetrics().fontDpi() / 96.0;
}
2021-05-24 00:41:00 +08:00
void SynEdit::showCaret()
{
2021-05-24 18:11:07 +08:00
if (m_blinkTimerId==0)
m_blinkTimerId = startTimer(500);
2021-05-24 00:41:00 +08:00
}
void SynEdit::hideCaret()
{
if (m_blinkTimerId!=0) {
killTimer(m_blinkTimerId);
m_blinkTimerId = 0;
2021-05-27 23:45:22 +08:00
m_blinkStatus = 0;
updateCaret();
2021-05-24 00:41:00 +08:00
}
}
2021-05-27 23:45:22 +08:00
bool SynEdit::IsPointInSelection(const BufferCoord &Value)
{
2021-06-03 20:26:36 +08:00
BufferCoord ptBegin = blockBegin();
BufferCoord ptEnd = blockEnd();
2021-05-27 23:45:22 +08:00
if ((Value.Line >= ptBegin.Line) && (Value.Line <= ptEnd.Line) &&
((ptBegin.Line != ptEnd.Line) || (ptBegin.Char != ptEnd.Char))) {
if (mActiveSelectionMode == SynSelectionMode::smLine)
return true;
else if (mActiveSelectionMode == SynSelectionMode::smColumn) {
if (ptBegin.Char > ptEnd.Char)
return (Value.Char >= ptEnd.Char) && (Value.Char < ptBegin.Char);
else if (ptBegin.Char < ptEnd.Char)
return (Value.Char >= ptBegin.Char) && (Value.Char < ptEnd.Char);
else
return false;
} else
return ((Value.Line > ptBegin.Line) || (Value.Char >= ptBegin.Char)) &&
((Value.Line < ptEnd.Line) || (Value.Char < ptEnd.Char));
} else
return false;
}
2021-05-29 21:35:46 +08:00
BufferCoord SynEdit::NextWordPos()
{
return NextWordPosEx(caretXY());
}
BufferCoord SynEdit::NextWordPosEx(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);
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();
2021-06-03 23:18:51 +08:00
// int vTabTrim = 0;
2021-05-29 21:35:46 +08:00
QString helper = "";
if (mCaretX > Len + 1) {
2021-06-03 20:26:36 +08:00
// 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 {
2021-05-29 21:35:46 +08:00
// only move caret one column
internalSetCaretX(mCaretX - 1);
2021-06-03 20:26:36 +08:00
// }
2021-05-29 21:35:46 +08:00
} 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);
2021-06-03 20:26:36 +08:00
helper = lineBreak(); //"/r/n"
2021-05-29 21:35:46 +08:00
}
} else {
// delete text before the caret
int caretColumn = charToColumn(mCaretY,mCaretX);
int SpaceCount1 = leftSpaces(Temp);
int SpaceCount2 = 0;
int newCaretX;
if (SpaceCount1 == caretColumn - 1) {
2021-06-03 20:26:36 +08:00
// 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 {
2021-05-29 21:35:46 +08:00
//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);
2021-06-03 20:26:36 +08:00
// }
2021-05-29 21:35:46 +08:00
ProperSetLine(mCaretY - 1, Temp);
setCaretX(newCaretX);
updateLastCaretX();
mStateFlags.setFlag(SynStateFlag::sfCaretChanged);
statusChanged(SynStatusChange::scCaretX);
} else {
// delete char
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);
}
}
2021-06-03 20:26:36 +08:00
void SynEdit::DeleteCurrentChar()
{
QString helper;
BufferCoord Caret;
if (!mReadOnly) {
doOnPaintTransient(SynTransientType::ttBefore);
if (selAvail())
SetSelectedTextEmpty();
else {
// Call UpdateLastCaretX. Even though the caret doesn't move, the
// current caret position should "stick" whenever text is modified.
updateLastCaretX();
QString Temp = lineText();
int Len = Temp.length();
if (mCaretX <= Len) {
// delete char
helper = Temp.mid(mCaretX-1, 1);
Caret.Char = mCaretX + 1;
Caret.Line = mCaretY;
Temp.remove(mCaretX-1, 1);
ProperSetLine(mCaretY - 1, Temp);
} else {
// join line with the line after
if (mCaretY < mLines->count()) {
2021-06-04 21:27:51 +08:00
ProperSetLine(mCaretY - 1, Temp + mLines->getString(mCaretY));
2021-06-03 20:26:36 +08:00
Caret.Char = 1;
Caret.Line = mCaretY + 1;
helper = lineBreak();
mLines->deleteAt(mCaretY);
if (mCaretX==1)
DoLinesDeleted(mCaretY, 1);
else
DoLinesDeleted(mCaretY + 1, 1);
}
}
if ((Caret.Char != mCaretX) || (Caret.Line != mCaretY)) {
mUndoList->AddChange(SynChangeReason::crSilentDeleteAfterCursor, caretXY(), Caret,
helper, SynSelectionMode::smNormal);
}
}
doOnPaintTransient(SynTransientType::ttAfter);
}
}
void SynEdit::DeleteWord()
{
if (mReadOnly)
return;
BufferCoord start = WordStart();
BufferCoord end = WordEnd();
DeleteFromTo(start,end);
}
void SynEdit::DeleteToEOL()
{
if (mReadOnly)
return;
DeleteFromTo(caretXY(),BufferCoord{lineText().length()+1,mCaretY});
}
void SynEdit::DeleteLastWord()
{
if (mReadOnly)
return;
BufferCoord start = PrevWordPos();
BufferCoord end = WordEndEx(start);
DeleteFromTo(start,end);
}
void SynEdit::DeleteFromBOL()
{
if (mReadOnly)
return;
DeleteFromTo(BufferCoord{1,mCaretY},caretXY());
}
void SynEdit::DeleteLine()
{
if (!mReadOnly && (mLines->count() > 0)
&& ! ((mCaretY == mLines->count()) && (lineText().isEmpty()))) {
doOnPaintTransient(SynTransientType::ttBefore);
if (selAvail())
setBlockBegin(caretXY());
QString helper = lineText();
if (mCaretY == mLines->count()) {
mLines->putString(mCaretY - 1,"");
mUndoList->AddChange(SynChangeReason::crSilentDeleteAfterCursor,
BufferCoord{1, mCaretY},
BufferCoord{helper.length() + 1, mCaretY},
helper, SynSelectionMode::smNormal);
} else {
mLines->deleteAt(mCaretY - 1);
helper = helper + lineBreak();
mUndoList->AddChange(SynChangeReason::crSilentDeleteAfterCursor,
BufferCoord{1, mCaretY},
BufferCoord{helper.length() + 1, mCaretY},
helper, SynSelectionMode::smNormal);
DoLinesDeleted(mCaretY, 1);
}
internalSetCaretXY(BufferCoord{1, mCaretY}); // like seen in the Delphi editor
doOnPaintTransient(SynTransientType::ttAfter);
}
}
void SynEdit::DuplicateLine()
{
if (!mReadOnly && (mLines->count() > 0)) {
doOnPaintTransient(SynTransientType::ttBefore);
mLines->Insert(mCaretY, lineText());
DoLinesInserted(mCaretY + 1, 1);
mUndoList->AddChange(SynChangeReason::crLineBreak,
caretXY(), caretXY(), "", SynSelectionMode::smNormal);
internalSetCaretXY(BufferCoord{1, mCaretY}); // like seen in the Delphi editor
doOnPaintTransient(SynTransientType::ttAfter);
}
}
void SynEdit::MoveSelUp()
{
if (!mReadOnly && (mLines->count() > 0) && (blockBegin().Line > 1)) {
doOnPaintTransient(SynTransientType::ttBefore);
// Backup caret and selection
BufferCoord OrigBlockBegin = blockBegin();
BufferCoord OrigBlockEnd = blockEnd();
// Delete line above selection
QString s = mLines->getString(OrigBlockBegin.Line - 2); // before start, 0 based
mLines->deleteAt(OrigBlockBegin.Line - 2); // before start, 0 based
DoLinesDeleted(OrigBlockBegin.Line - 1, 1); // before start, 1 based
// Insert line below selection
mLines->Insert(OrigBlockEnd.Line - 1, s);
DoLinesInserted(OrigBlockEnd.Line, 1);
// Restore caret and selection
setCaretAndSelection(
BufferCoord{mCaretX, mCaretY - 1},
BufferCoord{1, OrigBlockBegin.Line - 1},
BufferCoord{mLines->getString(OrigBlockEnd.Line - 2).length() + 1, OrigBlockEnd.Line - 1}
);
// Retrieve end of line we moved up
BufferCoord MoveDelim = BufferCoord{mLines->getString(OrigBlockEnd.Line - 1).length() + 1, OrigBlockEnd.Line};
// Support undo, implement as drag and drop
{
mUndoList->BeginBlock();
auto action = finally([this]{
mUndoList->EndBlock();
});
mUndoList->AddChange(SynChangeReason::crSelection, // backup original selection
OrigBlockBegin,
OrigBlockEnd,
"",
SynSelectionMode::smNormal);
mUndoList->AddChange(SynChangeReason::crDragDropInsert,
mBlockBegin, // modified
MoveDelim, // put at end of line me moved up
s + lineBreak() + selText(),
SynSelectionMode::smNormal);
}
doOnPaintTransient(SynTransientType::ttAfter);
}
}
void SynEdit::MoveSelDown()
{
if (!mReadOnly && (mLines->count() > 0) && (blockEnd().Line < mLines->count())) {
doOnPaintTransient(SynTransientType::ttBefore);
// Backup caret and selection
BufferCoord OrigBlockBegin = blockBegin();
BufferCoord OrigBlockEnd = blockEnd();
// Delete line below selection
QString s = mLines->getString(OrigBlockEnd.Line); // after end, 0 based
mLines->deleteAt(OrigBlockEnd.Line); // after end, 0 based
DoLinesDeleted(OrigBlockEnd.Line, 1); // before start, 1 based
// Insert line above selection
mLines->Insert(OrigBlockBegin.Line - 1, s);
DoLinesInserted(OrigBlockBegin.Line, 1);
// Restore caret and selection
setCaretAndSelection(
BufferCoord{mCaretX, mCaretY + 1},
BufferCoord{1, OrigBlockBegin.Line + 1},
BufferCoord{mLines->getString(OrigBlockEnd.Line).length() + 1, OrigBlockEnd.Line + 1}
);
// Retrieve start of line we moved down
BufferCoord MoveDelim = BufferCoord{1, OrigBlockBegin.Line};
// Support undo, implement as drag and drop
{
mUndoList->BeginBlock();
auto action = finally([this] {
mUndoList->EndBlock();
});
mUndoList->AddChange(SynChangeReason::crSelection,
OrigBlockBegin,
OrigBlockEnd,
"",
SynSelectionMode::smNormal);
mUndoList->AddChange(SynChangeReason::crDragDropInsert,
MoveDelim, // put at start of line me moved down
mBlockEnd, // modified
selText() + lineBreak() + s,
SynSelectionMode::smNormal);
}
doOnPaintTransient(SynTransientType::ttAfter);
}
}
void SynEdit::ClearAll()
{
mLines->clear();
mMarkList.clear();
mUndoList->Clear();
mRedoList->Clear();
setModified(false);
}
void SynEdit::InsertLine(bool moveCaret)
{
if (mReadOnly)
return;
int nLinesInserted=0;
mUndoList->BeginBlock();
auto action = finally([this] {
mUndoList->EndBlock();
});
QString helper;
if (selAvail()) {
helper = selText();
BufferCoord iUndoBegin = mBlockBegin;
BufferCoord iUndoEnd = mBlockEnd;
SetSelTextPrimitive("");
mUndoList->AddChange(SynChangeReason::crDelete, iUndoBegin, iUndoEnd, helper,
mActiveSelectionMode);
}
QString Temp = lineText();
QString Temp2 = Temp;
QString Temp3;
int SpaceCount1,SpaceCount2;
PSynHighlighterAttribute Attr;
// This is sloppy, but the Right Thing would be to track the column of markers
// too, so they could be moved depending on whether they are after the caret...
int InsDelta = (mCaretX == 1)?1:0;
int Len = Temp.length();
if (Len > 0) {
if (Len >= mCaretX) {
if (mCaretX > 1) {
Temp = lineText().mid(0, mCaretX - 1);
Temp3 = Temp;
SpaceCount1 = leftSpaces(Temp);
Temp2.remove(0, mCaretX - 1);
ProperSetLine(mCaretY-1,Temp);
2021-06-04 21:27:51 +08:00
QString Temp4;
if (mOptions.testFlag(eoAutoIndent)) {
Temp4=GetLeftSpacing(SpaceCount1, true);
Temp2=TrimLeft(Temp2);
}
2021-06-03 23:18:51 +08:00
if (mOptions.testFlag(eoAddIndent) &&
GetHighlighterAttriAtRowCol(BufferCoord{Temp3.length(), mCaretY},
Temp3, Attr)) { // only add indent to source files
2021-06-03 20:26:36 +08:00
if (Attr != mHighlighter->commentAttribute()) { // and outside of comments
if ((!Temp.isEmpty() && Temp[Temp.length()-1] ==':')
|| (
(Temp[Temp.length()-1] =='{')
&& ((Temp2.isEmpty()) || (Temp2[0]!='}'))
)) { // add more indent for these too
if (!mOptions.testFlag(eoTabsToSpaces)) {
Temp4 = GetLeftSpacing(SpaceCount1+mTabWidth,true);
}
}
}
}
mLines->Insert(mCaretY, Temp4+Temp2);
nLinesInserted++;
SpaceCount1 = mLines->getString(mCaretY).length(); //???
mUndoList->AddChange(SynChangeReason::crLineBreak, caretXY(), caretXY(), Temp2,
SynSelectionMode::smNormal);
if ((Temp.length()>0) && (Temp[Temp.length()-1] == '{') &&
(Temp2.length()>0) && (Temp2[0]=='}')) {
if (mOptions.testFlag(eoAddIndent)) {
Temp4 = GetLeftSpacing(leftSpaces(Temp)+mTabWidth,true)+Temp4;
} else {
Temp4=GetLeftSpacing(leftSpaces(Temp), true);
}
mLines->Insert(mCaretY, Temp4);
nLinesInserted++;
mUndoList->AddChange(SynChangeReason::crLineBreak, caretXY(), caretXY(), "",
SynSelectionMode::smNormal);
if (moveCaret)
internalSetCaretXY(BufferCoord{Temp4.length()+1, mCaretY + 1});
} else {
if (moveCaret)
internalSetCaretXY(BufferCoord{SpaceCount1+1,mCaretY + 1});
}
} else {
mLines->Insert(mCaretY - 1, "");
nLinesInserted++;
mUndoList->AddChange(SynChangeReason::crLineBreak, caretXY(), caretXY(), Temp2,
SynSelectionMode::smNormal);
if (moveCaret)
internalSetCaretY(mCaretY + 1);
}
} else {
SpaceCount2 = 0;
int BackCounter = mCaretY;
if (mOptions.testFlag(eoAutoIndent)) {
do {
BackCounter--;
Temp = mLines->getString(BackCounter);
SpaceCount2 = leftSpaces(Temp);
} while ((BackCounter != 0) && (Temp == ""));
}
mLines->Insert(mCaretY, "");
nLinesInserted++;
BufferCoord Caret = caretXY();
if (moveCaret) {
QString Temp4=GetLeftSpacing(SpaceCount2,true);
if (SpaceCount2 > 0) {
}
if (mOptions.testFlag(eoAddIndent) && GetHighlighterAttriAtRowCol(BufferCoord{Temp.length(), mCaretY},
Temp, Attr)) { // only add indent to source files
if (Attr != mHighlighter->commentAttribute()) { // and outside of comments
if (Temp.length() && (Temp[Temp.length()-1] == '{' || Temp[Temp.length()-1] == ':')) { // add more indent for these too
Temp4=GetLeftSpacing(mTabWidth,true)+Temp4;
}
}
}
mLines->putString(mCaretY,Temp4); // copy previous indent
internalSetCaretXY(BufferCoord{Temp4.length()+1, mCaretY + 1});
}
mUndoList->AddChange(SynChangeReason::crLineBreak, Caret, Caret, "", SynSelectionMode::smNormal);
}
} else {
if (mLines->count() == 0)
mLines->add("");
SpaceCount2 = 0;
if (mOptions.testFlag(eoAutoIndent)) {
int BackCounter = mCaretY - 1;
while (BackCounter >= 0) {
SpaceCount2 = leftSpaces(mLines->getString(BackCounter));
if (mLines->getString(BackCounter).length() > 0)
break;
BackCounter--;
}
}
mLines->Insert(mCaretY - 1, "");
nLinesInserted++;
mUndoList->AddChange(SynChangeReason::crLineBreak, caretXY(), caretXY(), "",
SynSelectionMode::smNormal);
if (moveCaret) {
internalSetCaretXY(BufferCoord{1, mCaretY + 1});
}
}
DoLinesInserted(mCaretY - InsDelta, nLinesInserted);
setBlockBegin(caretXY());
setBlockEnd(caretXY());
ensureCursorPosVisible();
updateLastCaretX();
}
void SynEdit::DoTabKey()
{
// Provide Visual Studio like block indenting
if (mOptions.testFlag(eoTabIndent) && CanDoBlockIndent()) {
doBlockIndent();
return;
}
int i = 0;
{
mUndoList->BeginBlock();
auto action = finally([this]{
mUndoList->EndBlock();
});
if (selAvail()) {
mUndoList->AddChange(SynChangeReason::crDelete,
mBlockBegin,
mBlockEnd,
selText(),
mActiveSelectionMode);
SetSelTextPrimitive("");
}
BufferCoord StartOfBlock = caretXY();
QString Spaces;
int NewCaretX = 0;
if (mOptions.testFlag(eoTabsToSpaces)) {
int cols = charToColumn(mCaretY,mCaretX);
i = tabWidth() - (cols) % mTabWidth;
2021-06-04 21:27:51 +08:00
Spaces = QString(i,' ');
2021-06-03 20:26:36 +08:00
NewCaretX = mCaretX + i;
} else {
Spaces = '\t';
2021-06-05 23:43:45 +08:00
NewCaretX = mCaretX + 1;
2021-06-03 20:26:36 +08:00
}
SetSelTextPrimitive(Spaces);
// Undo is already handled in SetSelText when SelectionMode is Column
if (mActiveSelectionMode != SynSelectionMode::smColumn) {
mUndoList->AddChange(SynChangeReason::crInsert, StartOfBlock,
caretXY(),
selText(),
mActiveSelectionMode);
}
internalSetCaretX(NewCaretX);
}
ensureCursorPosVisible();
}
void SynEdit::DoShiftTabKey()
{
// Provide Visual Studio like block indenting
2021-06-03 23:18:51 +08:00
if (mOptions.testFlag(eoTabIndent) && CanDoBlockIndent()) {
doBlockUnindent();
2021-06-03 20:26:36 +08:00
return;
}
//Don't un-tab if caret is not on line or is beyond line end
if (mCaretY > mLines->count() || mCaretX > lineText().length()+1)
return;
//Don't un-tab if no chars before the Caret
if (mCaretX==1)
return;
QString s = lineText().mid(0,mCaretX-1);
//Only un-tab if caret is at the begin of the line
if (!s.trimmed().isEmpty())
return;
int NewX = 0;
if (s[s.length()-1] == '\t') {
NewX= mCaretX-1;
} else {
int colsBefore = charToColumn(mCaretY,mCaretX)-1;
int spacesToRemove = colsBefore % mTabWidth;
if (spacesToRemove == 0)
spacesToRemove = mTabWidth;
if (spacesToRemove > colsBefore )
spacesToRemove = colsBefore;
NewX = mCaretX;
while (spacesToRemove > 0 && s[NewX-2] == ' ' ) {
NewX--;
spacesToRemove--;
}
}
// perform un-tab
if (NewX != mCaretX) {
BufferCoord OldCaretXY = caretXY();
setBlockBegin(BufferCoord{NewX, mCaretY});
setBlockEnd(caretXY());
QString OldSelText = selText();
SetSelTextPrimitive("");
mUndoList->AddChange(
SynChangeReason::crSilentDelete, BufferCoord{NewX, mCaretY},
OldCaretXY, OldSelText, SynSelectionMode::smNormal);
internalSetCaretX(NewX);
}
}
bool SynEdit::CanDoBlockIndent()
{
BufferCoord BB;
BufferCoord BE;
if (selAvail()) {
BB = blockBegin();
BE = blockEnd();
} else {
BB = caretXY();
BE = caretXY();
}
if (BB.Line > mLines->count() || BE.Line > mLines->count()) {
return false;
}
if (mActiveSelectionMode == SynSelectionMode::smNormal) {
QString s = mLines->getString(BB.Line-1).mid(0,BB.Char-1);
if (!s.trimmed().isEmpty())
return false;
if (BE.Char>1) {
QString s1=mLines->getString(BE.Line-1).mid(BE.Char-1);
QString s2=mLines->getString(BE.Line-1).mid(0,BE.Char-1);
if (!s1.trimmed().isEmpty() && !s2.trimmed().isEmpty())
return false;
}
}
if (mActiveSelectionMode == SynSelectionMode::smColumn) {
int startCol = charToColumn(BB.Line,BB.Char);
int endCol = charToColumn(BE.Line,BE.Char);
for (int i = BB.Line; i<=BE.Line;i++) {
QString line = mLines->getString(i-1);
int startChar = columnToChar(i,startCol);
QString s = line.mid(0,startChar-1);
if (!s.trimmed().isEmpty())
return false;
int endChar = columnToChar(i,endCol);
s=line.mid(endChar-1);
if (!s.trimmed().isEmpty())
return false;
}
}
return true;
}
2021-05-14 23:56:43 +08:00
void SynEdit::clearAreaList(SynEditingAreaList areaList)
{
areaList.clear();
}
void SynEdit::computeCaret(int X, int Y)
{
DisplayCoord vCaretNearestPos = pixelsToNearestRowColumn(X, Y);
vCaretNearestPos.Row = MinMax(vCaretNearestPos.Row, 1, displayLineCount());
setInternalDisplayXY(vCaretNearestPos);
}
void SynEdit::computeScroll(int X, int Y)
{
QRect iScrollBounds; // relative to the client area
// don't scroll if dragging text from other control
// if (not MouseCapture) and (not Dragging) then begin
// fScrollTimer.Enabled := False;
// Exit;
// end;
iScrollBounds = QRect(mGutterWidth+this->frameWidth(), this->frameWidth(), mCharsInWindow * mCharWidth,
mLinesInWindow * mTextHeight);
if (X < iScrollBounds.left())
2021-05-24 00:41:00 +08:00
mScrollDeltaX = (X - iScrollBounds.left()) / mCharWidth - 1;
2021-05-14 23:56:43 +08:00
else if (X >= iScrollBounds.right())
2021-05-24 00:41:00 +08:00
mScrollDeltaX = (X - iScrollBounds.right()) / mCharWidth + 1;
2021-05-14 23:56:43 +08:00
else
mScrollDeltaX = 0;
if (Y < iScrollBounds.top())
2021-05-24 00:41:00 +08:00
mScrollDeltaY = (Y - iScrollBounds.top()) / mTextHeight - 1;
2021-05-14 23:56:43 +08:00
else if (Y >= iScrollBounds.bottom())
2021-05-24 00:41:00 +08:00
mScrollDeltaY = (Y - iScrollBounds.bottom()) / mTextHeight + 1;
2021-05-14 23:56:43 +08:00
else
mScrollDeltaY = 0;
if (mScrollDeltaX!=0 || mScrollDeltaY!=0)
mScrollTimer->start();
}
void SynEdit::doBlockIndent()
{
BufferCoord OrgCaretPos;
BufferCoord BB, BE;
QString StrToInsert;
2021-06-03 23:18:51 +08:00
int e,x,i;
2021-05-14 23:56:43 +08:00
QString Spaces;
SynSelectionMode OrgSelectionMode;
BufferCoord InsertionPos;
OrgSelectionMode = mActiveSelectionMode;
OrgCaretPos = caretXY();
StrToInsert = nullptr;
2021-06-03 20:26:36 +08:00
auto action = finally([&,this]{
2021-06-05 23:43:45 +08:00
if (BB.Char > 1)
BB.Char += Spaces.length();
2021-06-03 20:26:36 +08:00
if (BE.Char > 1)
BE.Char+=Spaces.length();
setCaretAndSelection(OrgCaretPos,
2021-06-05 23:43:45 +08:00
BB, BE);
2021-06-03 20:26:36 +08:00
setActiveSelectionMode(OrgSelectionMode);
});
// keep current selection detail
2021-05-14 23:56:43 +08:00
if (selAvail()) {
2021-06-03 20:26:36 +08:00
BB = blockBegin();
BE = blockEnd();
} else {
BB = caretXY();
BE = caretXY();
}
// build text to insert
2021-06-05 23:43:45 +08:00
if (BE.Char == 1 && BE.Line != BB.Line) {
2021-06-03 20:26:36 +08:00
e = BE.Line - 1;
x = 1;
} else {
e = BE.Line;
if (mOptions.testFlag(SynEditorOption::eoTabsToSpaces))
x = caretX() + mTabWidth;
else
x = caretX() + 1;
}
if (mOptions.testFlag(eoTabsToSpaces)) {
Spaces = QString(mTabWidth,' ') ;
} else {
Spaces = "\t";
}
for (i = BB.Line; i<e;i++) {
StrToInsert+=Spaces+lineBreak();
}
StrToInsert+=Spaces;
{
mUndoList->BeginBlock();
auto action2=finally([this]{
mUndoList->EndBlock();
2021-05-14 23:56:43 +08:00
});
2021-06-03 20:26:36 +08:00
InsertionPos.Line = BB.Line;
if (mActiveSelectionMode == SynSelectionMode::smColumn)
InsertionPos.Char = std::min(BB.Char, BE.Char);
else
InsertionPos.Char = 1;
insertBlock(InsertionPos, InsertionPos, StrToInsert, true);
mUndoList->AddChange(SynChangeReason::crIndent, BB, BE, "", SynSelectionMode::smColumn);
//We need to save the position of the end block for redo
mUndoList->AddChange(SynChangeReason::crIndent,
{BB.Char + Spaces.length(), BB.Line},
{BE.Char + Spaces.length(), BE.Line},
"", SynSelectionMode::smColumn);
//adjust the x position of orgcaretpos appropriately
OrgCaretPos.Char = x;
}
}
2021-05-14 23:56:43 +08:00
2021-06-03 20:26:36 +08:00
void SynEdit::doBlockUnindent()
{
int LastIndent = 0;
int FirstIndent = 0;
BufferCoord BB,BE;
// keep current selection detail
if (selAvail()) {
BB = blockBegin();
BE = blockEnd();
} else {
BB = caretXY();
BE = caretXY();
}
BufferCoord OrgCaretPos = caretXY();
int x = 0;
int e = BE.Line;
// convert selection to complete lines
if (BE.Char == 1)
e = BE.Line - 1;
// build string to delete
QString FullStrToDelete;
2021-06-05 23:43:45 +08:00
for (int i = BB.Line; i<= e;i++) {
2021-06-03 20:26:36 +08:00
QString Line = mLines->getString(i - 1);
FullStrToDelete += Line;
if (i!=e-1)
FullStrToDelete += lineBreak();
if (Line.isEmpty())
continue;
if (Line[0]!=' ' && Line[0]!='\t')
continue;
int charsToDelete = 0;
while (charsToDelete < mTabWidth &&
charsToDelete < Line.length() &&
Line[charsToDelete] == ' ')
charsToDelete++;
if (charsToDelete == 0)
charsToDelete = 1;
if (i==BB.Line)
FirstIndent = charsToDelete;
2021-06-05 23:43:45 +08:00
if (i==e)
2021-06-03 20:26:36 +08:00
LastIndent = charsToDelete;
if (i==OrgCaretPos.Line)
x = charsToDelete;
QString TempString = Line.mid(charsToDelete);
mLines->putString(i-1,TempString);
}
mUndoList->AddChange(
SynChangeReason::crUnindent, BB, BE, FullStrToDelete, mActiveSelectionMode);
// restore selection
//adjust the x position of orgcaretpos appropriately
OrgCaretPos.Char -= x;
BB.Char -= FirstIndent;
BE.Char -= LastIndent;
setCaretAndSelection(OrgCaretPos, BB, BE);
}
void SynEdit::DoAddChar(QChar AChar)
{
if (mReadOnly)
return;
if (!AChar.isPrint())
return;
//DoOnPaintTransient(ttBefore);
if ((mInserting == false) && (!selAvail())) {
setSelLength(1);
}
mUndoList->BeginBlock();
if (mOptions.testFlag(eoAddIndent)) {
// Remove TabWidth of indent of the current line when typing a }
if (AChar == '}' && (mCaretY<=mLines->count())) {
QString temp = mLines->getString(mCaretY-1).mid(0,mCaretX-1);
// and the first nonblank char is this new }
if (temp.trimmed().isEmpty()) {
BufferCoord MatchBracketPos = GetPreviousLeftBracket(mCaretX, mCaretY);
if (MatchBracketPos.Line > 0) {
int i = 0;
QString matchline = mLines->getString(MatchBracketPos.Line-1);
QString line = lineText();
while (i<matchline.length() && (matchline[i]==' ' || matchline[i]=='\t')) {
i++;
}
QString temp = matchline.mid(0,i-1) + line.mid(mCaretX-1);
mLines->putString(mCaretY-1,temp);
internalSetCaretXY(BufferCoord{i,mCaretY});
mUndoList->AddChange(
SynChangeReason::crDelete,
BufferCoord{1, mCaretY},
BufferCoord{line.length()+1, mCaretY},
line,
SynSelectionMode::smNormal
);
mUndoList->AddChange(
SynChangeReason::crInsert,
BufferCoord{1, mCaretY},
BufferCoord{temp.length()+1, mCaretY},
"",
SynSelectionMode::smNormal
);
}
}
2021-05-14 23:56:43 +08:00
}
}
2021-06-03 20:26:36 +08:00
setSelText(AChar);
mUndoList->EndBlock();
2021-05-14 23:56:43 +08:00
2021-06-03 20:26:36 +08:00
//DoOnPaintTransient(ttAfter);
2021-05-14 23:56:43 +08:00
}
2021-06-05 23:43:45 +08:00
void SynEdit::cutToClipboard()
{
if (mReadOnly || !selAvail())
return;
mUndoList->BeginBlock();
auto action = finally([this] {
mUndoList->EndBlock();
});
DoCopyToClipboard(selText());
setSelText("");
}
void SynEdit::copyToClipboard()
{
if (!selAvail())
return;
bool ChangeTrim = (mActiveSelectionMode == SynSelectionMode::smColumn) &&
mOptions.testFlag(eoTrimTrailingSpaces);
QString sText;
{
auto action = finally([&,this] {
if (ChangeTrim)
mOptions.setFlag(eoTrimTrailingSpaces);
});
if (ChangeTrim)
mOptions.setFlag(eoTrimTrailingSpaces,false);
sText = selText();
}
DoCopyToClipboard(sText);
}
void SynEdit::DoCopyToClipboard(const QString &s)
{
QClipboard* clipboard=QGuiApplication::clipboard();
clipboard->clear();
clipboard->setText(s);
}
void SynEdit::pasteFromClipboard()
{
if (mReadOnly)
return;
QClipboard* clipboard = QGuiApplication::clipboard();
if (clipboard->text().isEmpty())
return;
doOnPaintTransient(SynTransientType::ttBefore);
mUndoList->BeginBlock();
bool AddPasteEndMarker = false;
auto action = finally([&,this] {
if (AddPasteEndMarker)
mUndoList->AddChange(
SynChangeReason::crPasteEnd,
blockBegin(),
blockEnd(),
"",
SynSelectionMode::smNormal);
mUndoList->EndBlock();
});
if (!clipboard->text().isEmpty()) {
mUndoList->AddChange(
SynChangeReason::crPasteBegin,
blockBegin(),
blockEnd(),
"",
SynSelectionMode::smNormal);
AddPasteEndMarker = true;
if (selAvail()) {
mUndoList->AddChange(
SynChangeReason::crDelete,
blockBegin(),
blockEnd(),
selText(),
mActiveSelectionMode);
} else
setActiveSelectionMode(selectionMode());
BufferCoord vStartOfBlock = blockBegin();
BufferCoord vEndOfBlock = blockEnd();
mBlockBegin = vStartOfBlock;
mBlockEnd = vEndOfBlock;
SetSelTextPrimitive(clipboard->text());
if (mActiveSelectionMode != SynSelectionMode::smColumn) {
mUndoList->AddChange(
SynChangeReason::crPaste,
vStartOfBlock,
blockEnd(),
selText(),
mActiveSelectionMode);
}
}
}
2021-05-14 23:56:43 +08:00
void SynEdit::incPaintLock()
{
mPaintLock ++ ;
}
void SynEdit::decPaintLock()
{
Q_ASSERT(mPaintLock > 0);
mPaintLock--;
if (mPaintLock == 0 ) {
if (mStateFlags.testFlag(SynStateFlag::sfScrollbarChanged))
updateScrollbars();
if (mStateFlags.testFlag(SynStateFlag::sfCaretChanged))
updateCaret();
if (mStatusChanges!=0)
doOnStatusChange(mStatusChanges);
}
}
bool SynEdit::mouseCapture()
{
return hasMouseTracking();
}
int SynEdit::clientWidth()
{
return viewport()->size().width();
}
int SynEdit::clientHeight()
{
return viewport()->size().height();
}
int SynEdit::clientTop()
{
return 0;
}
int SynEdit::clientLeft()
{
return 0;
}
QRect SynEdit::clientRect()
{
2021-05-21 23:33:53 +08:00
return QRect(0,0, clientWidth(), clientHeight());
2021-05-14 23:56:43 +08:00
}
void SynEdit::synFontChanged()
{
recalcCharExtent();
sizeOrFontChanged(true);
}
void SynEdit::doOnPaintTransient(SynTransientType TransientType)
{
doOnPaintTransientEx(TransientType, false);
}
void SynEdit::updateLastCaretX()
{
mMBCSStepAside = false;
2021-06-03 23:18:51 +08:00
mLastCaretColumn = displayX();
2021-05-14 23:56:43 +08:00
}
void SynEdit::ensureCursorPosVisible()
{
ensureCursorPosVisibleEx(false);
}
void SynEdit::ensureCursorPosVisibleEx(bool ForceToMiddle)
{
incPaintLock();
auto action = finally([this]{
decPaintLock();
});
// Make sure X is visible
int VisibleX = displayX();
if (VisibleX < leftChar())
setLeftChar(VisibleX);
else if (VisibleX >= mCharsInWindow + leftChar() && mCharsInWindow > 0)
setLeftChar(VisibleX - mCharsInWindow + 1);
else
setLeftChar(leftChar());
// Make sure Y is visible
int vCaretRow = displayY();
if (ForceToMiddle) {
if (vCaretRow < mTopLine || vCaretRow>(mTopLine + (mLinesInWindow - 1)))
setTopLine( vCaretRow - (mLinesInWindow - 1) / 2);
} else {
if (vCaretRow < mTopLine)
setTopLine(vCaretRow);
else if (vCaretRow > mTopLine + (mLinesInWindow - 1) && mLinesInWindow > 0)
setTopLine(vCaretRow - (mLinesInWindow - 1));
else
setTopLine(mTopLine);
}
}
void SynEdit::scrollWindow(int dx, int dy)
{
2021-05-24 21:48:03 +08:00
// int nx = horizontalScrollBar()->value()+dx;
// int ny = verticalScrollBar()->value()+dy;
// nx = std::min(std::max(horizontalScrollBar()->minimum(),nx),horizontalScrollBar()->maximum());
// ny = std::min(std::max(verticalScrollBar()->minimum(),ny),verticalScrollBar()->maximum());
// horizontalScrollBar()->setValue(nx);
// verticalScrollBar()->setValue(ny);
2021-05-14 23:56:43 +08:00
}
void SynEdit::setInternalDisplayXY(const DisplayCoord &aPos)
{
incPaintLock();
internalSetCaretXY(displayToBufferPos(aPos));
decPaintLock();
updateLastCaretX();
}
void SynEdit::internalSetCaretXY(const BufferCoord &Value)
{
setCaretXYEx(true, Value);
}
2021-05-29 21:35:46 +08:00
void SynEdit::internalSetCaretX(int Value)
{
internalSetCaretXY(BufferCoord{Value, mCaretY});
}
void SynEdit::internalSetCaretY(int Value)
{
internalSetCaretXY(BufferCoord{mCaretX,Value});
}
2021-05-14 23:56:43 +08:00
void SynEdit::setStatusChanged(SynStatusChanges changes)
{
mStatusChanges = mStatusChanges | changes;
if (mPaintLock == 0)
doOnStatusChange(mStatusChanges);
}
void SynEdit::doOnStatusChange(SynStatusChanges)
{
emit statusChanged(mStatusChanges);
mStatusChanges = SynStatusChange::scNone;
}
void SynEdit::insertBlock(const BufferCoord &BB, const BufferCoord &BE, const QString &ChangeStr, bool AddToUndoList)
{
setCaretAndSelection(BB, BB, BE);
setActiveSelectionMode(SynSelectionMode::smColumn);
2021-06-03 20:26:36 +08:00
SetSelTextPrimitiveEx(SynSelectionMode::smColumn, ChangeStr, AddToUndoList);
2021-05-14 23:56:43 +08:00
setStatusChanged(SynStatusChange::scSelection);
}
void SynEdit::updateScrollbars()
{
int nMaxScroll;
int nMin,nMax,nPage,nPos;
if (mPaintLock!=0) {
mStateFlags.setFlag(SynStateFlag::sfScrollbarChanged);
} else {
mStateFlags.setFlag(SynStateFlag::sfScrollbarChanged,false);
if (mScrollBars != SynScrollStyle::ssNone) {
if (mOptions.testFlag(eoHideShowScrollbars)) {
setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
} else {
setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn);
setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn);
}
if (mScrollBars == SynScrollStyle::ssBoth || mScrollBars == SynScrollStyle::ssHorizontal) {
2021-05-24 21:48:03 +08:00
nMaxScroll = std::max(mLines->lengthOfLongestLine(), 1);
2021-05-14 23:56:43 +08:00
if (mOptions.testFlag(eoScrollPastEol))
nMaxScroll = mMaxScrollWidth;
else
nMaxScroll = std::max(mLines->lengthOfLongestLine(), 1);
if (nMaxScroll <= MAX_SCROLL) {
nMin = 1;
nMax = nMaxScroll;
nPage = mCharsInWindow;
nPos = mLeftChar;
} else {
nMin = 0;
nMax = MAX_SCROLL;
nPage = MulDiv(MAX_SCROLL, mCharsInWindow, nMaxScroll);
nPos = MulDiv(MAX_SCROLL, mLeftChar, nMaxScroll);
}
horizontalScrollBar()->setMinimum(nMin);
horizontalScrollBar()->setMaximum(nMax);
horizontalScrollBar()->setPageStep(nPage);
horizontalScrollBar()->setValue(nPos);
horizontalScrollBar()->setSingleStep(1);
} else
setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn);
if (mScrollBars == SynScrollStyle::ssBoth || mScrollBars == SynScrollStyle::ssVertical) {
nMaxScroll = displayLineCount();
if (mOptions.testFlag(eoScrollPastEof))
nMaxScroll+=mLinesInWindow - 1;
if (nMaxScroll <= MAX_SCROLL) {
nMin = 1;
nMax = std::max(1, nMaxScroll);
nPage = mLinesInWindow;
nPos = mTopLine;
} else {
nMin = 0;
nMax = MAX_SCROLL;
nPage = MulDiv(MAX_SCROLL, mLinesInWindow, nMaxScroll);
nPos = MulDiv(MAX_SCROLL, mTopLine, nMaxScroll);
}
verticalScrollBar()->setMinimum(nMin);
verticalScrollBar()->setMaximum(nMax);
verticalScrollBar()->setPageStep(nPage);
verticalScrollBar()->setValue(nPos);
verticalScrollBar()->setSingleStep(1);
} else
setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
} else {
setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
}
}
}
void SynEdit::updateCaret()
{
mStateFlags.setFlag(SynStateFlag::sfCaretChanged,false);
2021-05-27 23:45:22 +08:00
DisplayCoord coord = displayXY();
QPoint caretPos = RowColumnToPixels(coord);
int caretWidth=mCharWidth;
if (mCaretY <= mLines->count() && mCaretX <= mLines->getString(mCaretY-1).length()) {
caretWidth = charColumns(mLines->getString(mCaretY-1)[mCaretX-1])*mCharWidth;
}
QRect rcCaret(caretPos.x(),caretPos.y(),caretWidth,
mTextHeight);
invalidateRect(rcCaret);
2021-05-14 23:56:43 +08:00
}
void SynEdit::recalcCharExtent()
{
SynFontStyle styles[] = {SynFontStyle::fsBold, SynFontStyle::fsItalic, SynFontStyle::fsStrikeOut, SynFontStyle::fsUnderline};
bool hasStyles[] = {false,false,false,false};
int size = 4;
if (mHighlighter && mHighlighter->attributes().count()>0) {
for (PSynHighlighterAttribute attribute: mHighlighter->attributes().values()) {
for (int i=0;i<size;i++) {
if (attribute->styles().testFlag(styles[i]))
hasStyles[i] = true;
}
}
} else {
hasStyles[0] = font().bold();
hasStyles[1] = font().italic();
hasStyles[2] = font().strikeOut();
hasStyles[3] = font().underline();
}
mTextHeight = 0;
mCharWidth = 0;
mTextHeight = fontMetrics().lineSpacing();
mCharWidth = fontMetrics().horizontalAdvance("M");
if (hasStyles[0]) { // has bold font
QFont f = font();
f.setBold(true);
QFontMetrics fm(f);
if (fm.lineSpacing()>mTextHeight)
mTextHeight=fm.lineSpacing();
if (fm.horizontalAdvance("M")>mCharWidth)
mCharWidth = fm.horizontalAdvance("M");
}
if (hasStyles[1]) { // has strike out font
QFont f = font();
f.setItalic(true);
QFontMetrics fm(f);
if (fm.lineSpacing()>mTextHeight)
mTextHeight=fm.lineSpacing();
if (fm.horizontalAdvance("M")>mCharWidth)
mCharWidth = fm.horizontalAdvance("M");
}
if (hasStyles[2]) { // has strikeout
QFont f = font();
f.setStrikeOut(true);
QFontMetrics fm(f);
if (fm.lineSpacing()>mTextHeight)
mTextHeight=fm.lineSpacing();
if (fm.horizontalAdvance("M")>mCharWidth)
mCharWidth = fm.horizontalAdvance("M");
}
if (hasStyles[3]) { // has underline
QFont f = font();
f.setUnderline(true);
QFontMetrics fm(f);
if (fm.lineSpacing()>mTextHeight)
mTextHeight=fm.lineSpacing();
if (fm.horizontalAdvance("M")>mCharWidth)
mCharWidth = fm.horizontalAdvance("M");
}
mTextHeight += mExtraLineSpacing;
2021-05-24 18:11:07 +08:00
//mCharWidth = mCharWidth * dpiFactor();
//mTextHeight = mTextHeight * dpiFactor();
2021-05-14 23:56:43 +08:00
}
QString SynEdit::expandAtWideGlyphs(const QString &S)
{
QString Result(S.length()*2); // speed improvement
int j = 0;
for (int i=0;i<S.length();i++) {
int CountOfAvgGlyphs = ceil(fontMetrics().horizontalAdvance(S[i])/(double)mCharWidth);
if (j+CountOfAvgGlyphs>=Result.length())
Result.resize(Result.length()+128);
// insert CountOfAvgGlyphs filling chars
while (CountOfAvgGlyphs>1) {
Result[j]=QChar(0xE000);
j++;
CountOfAvgGlyphs--;
}
Result[j]=S[i];
j++;
}
Result.resize(j);
}
void SynEdit::updateModifiedStatus()
{
setModified(!mUndoList->initialState());
}
int SynEdit::scanFrom(int Index)
{
SynRangeState iRange;
int Result = Index;
if (Result >= mLines->count())
return Result;
if (Result == 0) {
mHighlighter->resetState();
} else {
mHighlighter->setState(mLines->ranges(Result-1),
mLines->braceLevels(Result-1),
mLines->bracketLevels(Result-1),
mLines->parenthesisLevels(Result-1));
}
do {
mHighlighter->setLine(mLines->getString(Result), Result);
mHighlighter->nextToEol();
iRange = mHighlighter->getRangeState();
{
if (mLines->ranges(Result).state == iRange.state)
return Result;// avoid the final Decrement
}
mLines->setRange(Result,iRange);
mLines->setParenthesisLevel(Result,mHighlighter->getParenthesisLevel());
mLines->setBraceLevel(Result,mHighlighter->getBraceLevel());
mLines->setBracketLevel(Result,mHighlighter->getBracketLevel());
Result ++ ;
} while (Result < mLines->count());
Result--;
return Result;
}
2021-05-26 00:04:20 +08:00
int SynEdit::scanRanges()
{
if (mHighlighter && !mLines->empty()) {
mHighlighter->resetState();
for (int i =0;i<mLines->count();i++) {
mHighlighter->setLine(mLines->getString(i), i);
qDebug()<<i<<mLines->getString(i);
mHighlighter->nextToEol();
mLines->setRange(i, mHighlighter->getRangeState());
mLines->setParenthesisLevel(i, mHighlighter->getParenthesisLevel());
mLines->setBracketLevel(i, mHighlighter->getBracketLevel());
mLines->setBraceLevel(i, mHighlighter->getBraceLevel());
}
qDebug()<<"finished.";
}
}
2021-05-14 23:56:43 +08:00
void SynEdit::uncollapse(PSynEditFoldRange FoldRange)
{
FoldRange->linesCollapsed = 0;
FoldRange->collapsed = false;
// Redraw the collapsed line
invalidateLines(FoldRange->fromLine, INT_MAX);
// Redraw fold mark
invalidateGutterLines(FoldRange->fromLine, INT_MAX);
}
void SynEdit::foldOnListInserted(int Line, int Count)
{
// Delete collapsed inside selection
for (int i = mAllFoldRanges.count()-1;i>=0;i--) {
PSynEditFoldRange range = mAllFoldRanges[i];
if (range->collapsed || range->parentCollapsed()){
if (range->fromLine == Line - 1) // insertion starts at fold line
uncollapse(range);
else if (range->fromLine >= Line) // insertion of count lines above FromLine
range->move(Count);
}
}
}
void SynEdit::foldOnListDeleted(int Line, int Count)
{
// Delete collapsed inside selection
for (int i = mAllFoldRanges.count()-1;i>=0;i--) {
PSynEditFoldRange range = mAllFoldRanges[i];
if (range->collapsed || range->parentCollapsed()){
if (range->fromLine == Line && Count == 1) // open up because we are messing with the starting line
uncollapse(range);
else if (range->fromLine >= Line - 1 && range->fromLine < Line + Count) // delete inside affectec area
mAllFoldRanges.remove(i);
else if (range->fromLine >= Line + Count) // Move after affected area
range->move(-Count);
}
}
}
void SynEdit::foldOnListCleared()
{
mAllFoldRanges.clear();
}
void SynEdit::rescan()
{
if (!mUseCodeFolding)
return;
rescanForFoldRanges();
invalidateGutter();
}
static void null_deleter(SynEditFoldRanges *) {}
void SynEdit::rescanForFoldRanges()
{
// Delete all uncollapsed folds
for (int i=mAllFoldRanges.count()-1;i>=0;i--) {
PSynEditFoldRange range =mAllFoldRanges[i];
if (!range->collapsed && !range->parentCollapsed())
mAllFoldRanges.remove(i);
}
// Did we leave any collapsed folds and are we viewing a code file?
if (mAllFoldRanges.count() > 0) {
// Add folds to a separate list
PSynEditFoldRanges TemporaryAllFoldRanges = std::make_shared<SynEditFoldRanges>();
scanForFoldRanges(TemporaryAllFoldRanges);
// Combine new with old folds, preserve parent order
for (int i = 0; i< TemporaryAllFoldRanges->count();i++) {
for (int j = 0; j< mAllFoldRanges.count() - 1;j++) {
if (TemporaryAllFoldRanges->range(i)->fromLine < mAllFoldRanges[j]->fromLine) {
mAllFoldRanges.insert(j, TemporaryAllFoldRanges->range(i));
break;
}
// If we can't prepend #i anywhere, just dump it at the end
if (j == mAllFoldRanges.count() - 1)
mAllFoldRanges.add(TemporaryAllFoldRanges->range(i));
}
}
} else {
// We ended up with no folds after deleting, just pass standard data...
PSynEditFoldRanges temp(&mAllFoldRanges, null_deleter);
scanForFoldRanges(temp);
}
}
void SynEdit::scanForFoldRanges(PSynEditFoldRanges TopFoldRanges)
{
PSynEditFoldRanges parentFoldRanges = TopFoldRanges;
2021-05-14 23:56:43 +08:00
// Recursively scan for folds (all types)
for (int i= 0 ; i< mCodeFolding.foldRegions.count() ; i++ ) {
findSubFoldRange(TopFoldRanges, i,parentFoldRanges,PSynEditFoldRange());
2021-05-14 23:56:43 +08:00
}
}
//this func should only be used in findSubFoldRange
int SynEdit::lineHasChar(int Line, int startChar, QChar character, const QString& highlighterAttrName) {
QString CurLine = mLines->getString(Line);
if (!mHighlighter){
for (int i=startChar; i<CurLine.length();i++) {
if (CurLine[i]==character) {
return i;
}
}
} else {
/*
mHighlighter->setState(mLines->ranges(Line),
mLines->braceLevels(Line),
mLines->bracketLevels(Line),
mLines->parenthesisLevels(Line));
mHighlighter->setLine(CurLine,Line);
*/
QString token;
while (!mHighlighter->eol()) {
token = mHighlighter->getToken();
PSynHighlighterAttribute attr = mHighlighter->getTokenAttribute();
if (token == character && attr->name()==highlighterAttrName)
return mHighlighter->getTokenPos();
mHighlighter->next();
}
}
return -1;
}
void SynEdit::findSubFoldRange(PSynEditFoldRanges TopFoldRanges, int FoldIndex,PSynEditFoldRanges& parentFoldRanges, PSynEditFoldRange Parent)
2021-05-14 23:56:43 +08:00
{
PSynEditFoldRange CollapsedFold;
int Line = 0;
QString CurLine;
if (!mHighlighter)
return;
while (Line < mLines->count()) { // index is valid for LinesToScan and fLines
// If there is a collapsed fold over here, skip it
CollapsedFold = collapsedFoldStartAtLine(Line + 1); // only collapsed folds remain
if (CollapsedFold) {
Line = CollapsedFold->toLine;
continue;
}
// Find an opening character on this line
CurLine = mLines->getString(Line);
mHighlighter->setState(mLines->ranges(Line),
mLines->braceLevels(Line),
mLines->bracketLevels(Line),
mLines->parenthesisLevels(Line));
mHighlighter->setLine(CurLine,Line);
QString token;
int pos;
while (!mHighlighter->eol()) {
token = mHighlighter->getToken();
pos = mHighlighter->getTokenPos()+token.length();
PSynHighlighterAttribute attr = mHighlighter->getTokenAttribute();
// We've found a starting character and it have proper highlighting (ignore stuff inside comments...)
if (token == mCodeFolding.foldRegions.get(FoldIndex)->openSymbol && attr->name()==mCodeFolding.foldRegions.get(FoldIndex)->highlight) {
// And ignore lines with both opening and closing chars in them
if (lineHasChar(Line,pos,mCodeFolding.foldRegions.get(FoldIndex)->closeSymbol,
mCodeFolding.foldRegions.get(FoldIndex)->highlight)<0) {
// Add it to the top list of folds
Parent = parentFoldRanges->addByParts(
2021-05-14 23:56:43 +08:00
Parent,
TopFoldRanges,
Line + 1,
mCodeFolding.foldRegions.get(FoldIndex),
Line + 1);
parentFoldRanges = Parent->subFoldRanges;
2021-05-14 23:56:43 +08:00
// Skip until a newline
break;
}
} else if (token == mCodeFolding.foldRegions.get(FoldIndex)->closeSymbol && attr->name()==mCodeFolding.foldRegions.get(FoldIndex)->highlight) {
// And ignore lines with both opening and closing chars in them
if (lineHasChar(Line,pos,mCodeFolding.foldRegions.get(FoldIndex)->openSymbol,
mCodeFolding.foldRegions.get(FoldIndex)->highlight)<0) {
// Stop the recursion if we find a closing char, and return to our parent
if (Parent) {
Parent->toLine = Line + 1;
Parent = Parent->parent;
if (!Parent) {
parentFoldRanges = TopFoldRanges;
} else {
parentFoldRanges = Parent->subFoldRanges;
}
2021-05-14 23:56:43 +08:00
}
// Skip until a newline
break;
}
}
mHighlighter->next();
}
Line++;
}
}
PSynEditFoldRange SynEdit::collapsedFoldStartAtLine(int Line)
{
for (int i = 0; i< mAllFoldRanges.count() - 1; i++ ) {
if (mAllFoldRanges[i]->fromLine == Line && mAllFoldRanges[i]->collapsed) {
return mAllFoldRanges[i];
} else if (mAllFoldRanges[i]->fromLine > Line) {
break; // sorted by line. don't bother scanning further
}
}
return PSynEditFoldRange();
}
void SynEdit::doOnPaintTransientEx(SynTransientType TransientType, bool Lock)
{
//todo: we can't draw to canvas outside paintEvent
}
void SynEdit::initializeCaret()
{
2021-05-24 18:11:07 +08:00
//showCaret();
2021-05-14 23:56:43 +08:00
}
2021-05-21 23:33:53 +08:00
PSynEditFoldRange SynEdit::foldStartAtLine(int Line)
{
for (int i = 0; i<mAllFoldRanges.count();i++) {
PSynEditFoldRange range = mAllFoldRanges[i];
if (range->fromLine == Line ){
return range;
} else if (range->fromLine>Line)
break; // sorted by line. don't bother scanning further
}
2021-05-24 18:11:07 +08:00
return PSynEditFoldRange();
2021-05-21 23:33:53 +08:00
}
QString SynEdit::substringByColumns(const QString &s, int startColumn, int &colLen)
{
int len = s.length();
int columns = 0;
int i = 0;
int oldColumns;
while (columns < startColumn) {
oldColumns = columns;
2021-06-04 21:27:51 +08:00
if (i>=len)
break;
if (s[i] == '\t')
2021-05-21 23:33:53 +08:00
columns += mTabWidth - (columns % mTabWidth);
else
columns += charColumns(s[i]);
i++;
}
QString result;
if (i>=len) {
colLen = 0;
return result;
}
if (colLen>result.capacity()) {
result.resize(colLen);
}
int j=0;
if (i>0) {
result[0]=s[i-1];
j++;
}
while (i<len && columns<startColumn+colLen) {
result[j]=s[i];
if (i < len && s[i] == '\t')
columns += mTabWidth - (columns % mTabWidth);
else
columns += charColumns(s[i]);
i++;
j++;
}
result.resize(j);
colLen = columns-oldColumns;
return result;
}
PSynEditFoldRange SynEdit::foldAroundLine(int Line)
{
return foldAroundLineEx(Line,false,false,false);
}
PSynEditFoldRange SynEdit::foldAroundLineEx(int Line, bool WantCollapsed, bool AcceptFromLine, bool AcceptToLine)
{
2021-05-24 00:41:00 +08:00
// Check global list
PSynEditFoldRange Result = checkFoldRange(&mAllFoldRanges, Line, WantCollapsed, AcceptFromLine, AcceptToLine);
2021-05-21 23:33:53 +08:00
2021-05-24 00:41:00 +08:00
// Found an item in the top level list?
if (Result) {
while (true) {
PSynEditFoldRange ResultChild = checkFoldRange(Result->subFoldRanges.get(), Line, WantCollapsed, AcceptFromLine, AcceptToLine);
if (!ResultChild)
break;
Result = ResultChild; // repeat for this one
}
}
return Result;
2021-05-21 23:33:53 +08:00
}
2021-05-24 00:41:00 +08:00
PSynEditFoldRange SynEdit::checkFoldRange(SynEditFoldRanges *FoldRangeToCheck, int Line, bool WantCollapsed, bool AcceptFromLine, bool AcceptToLine)
2021-05-21 23:33:53 +08:00
{
for (int i = 0; i< FoldRangeToCheck->count(); i++) {
PSynEditFoldRange range = (*FoldRangeToCheck)[i];
if (((range->fromLine < Line) || ((range->fromLine <= Line) && AcceptFromLine)) &&
((range->toLine > Line) || ((range->toLine >= Line) && AcceptToLine))) {
if (range->collapsed == WantCollapsed) {
return range;
}
}
}
return PSynEditFoldRange();
}
2021-05-24 00:41:00 +08:00
PSynEditFoldRange SynEdit::foldEndAtLine(int Line)
{
for (int i = 0; i<mAllFoldRanges.count();i++) {
PSynEditFoldRange range = mAllFoldRanges[i];
if (range->toLine == Line ){
return range;
} else if (range->fromLine>Line)
break; // sorted by line. don't bother scanning further
}
return PSynEditFoldRange();
}
void SynEdit::paintCaret(QPainter &painter, const QRect rcClip)
{
2021-05-24 18:11:07 +08:00
if (m_blinkStatus!=1)
2021-05-24 00:41:00 +08:00
return;
2021-05-24 18:11:07 +08:00
painter.setClipRect(rcClip);
2021-05-24 00:41:00 +08:00
SynEditCaretType ct;
if (this->mInserting) {
ct = mInsertCaret;
} else {
ct =mOverwriteCaret;
}
painter.setPen(QColorConstants::Svg::red);
switch(ct) {
case SynEditCaretType::ctVerticalLine:
painter.drawLine(rcClip.left()+1,rcClip.top(),rcClip.left()+1,rcClip.bottom());
break;
case SynEditCaretType::ctHorizontalLine:
painter.drawLine(rcClip.left(),rcClip.bottom()-1,rcClip.right(),rcClip.bottom()-1);
break;
case SynEditCaretType::ctBlock:
2021-05-27 20:33:25 +08:00
painter.fillRect(rcClip, QColorConstants::Svg::red);
2021-05-24 00:41:00 +08:00
break;
case SynEditCaretType::ctHalfBlock:
QRect rc=rcClip;
rc.setTop(rcClip.top()+rcClip.height() / 2);
2021-05-27 20:33:25 +08:00
painter.fillRect(rcClip, QColorConstants::Svg::red);
2021-05-24 18:11:07 +08:00
break;
2021-05-24 00:41:00 +08:00
}
}
2021-05-24 21:48:03 +08:00
int SynEdit::textOffset()
{
return mGutterWidth + 2 - (mLeftChar-1)*mCharWidth;
}
2021-05-27 20:33:25 +08:00
SynEditorCommand SynEdit::TranslateKeyCode(int key, Qt::KeyboardModifiers modifiers)
{
PSynEditKeyStroke keyStroke = mKeyStrokes.findKeycode2(mLastKey,mLastKeyModifiers,
key, modifiers);
SynEditorCommand cmd=SynEditorCommand::ecNone;
if (keyStroke)
cmd = keyStroke->command();
else {
keyStroke = mKeyStrokes.findKeycode(key,modifiers);
if (keyStroke)
cmd = keyStroke->command();
}
if (cmd == SynEditorCommand::ecNone) {
mLastKey = key;
mLastKeyModifiers = modifiers;
} else {
mLastKey = 0;
mLastKeyModifiers = Qt::NoModifier;
}
return cmd;
}
2021-05-14 23:56:43 +08:00
void SynEdit::sizeOrFontChanged(bool bFont)
{
2021-05-24 00:41:00 +08:00
2021-05-14 23:56:43 +08:00
if (mCharWidth != 0) {
mCharsInWindow = std::max(clientWidth() - mGutterWidth - 2, 0) / mCharWidth;
mLinesInWindow = clientHeight() / mTextHeight;
if (bFont) {
if (mGutter.showLineNumbers())
gutterChanged();
else
updateScrollbars();
mStateFlags.setFlag(SynStateFlag::sfCaretChanged,false);
invalidate();
} else
updateScrollbars();
mStateFlags.setFlag(SynStateFlag::sfScrollbarChanged,false);
if (!mOptions.testFlag(SynEditorOption::eoScrollPastEol))
setLeftChar(mLeftChar);
if (!mOptions.testFlag(SynEditorOption::eoScrollPastEof))
setTopLine(mTopLine);
}
}
void SynEdit::doChange()
{
emit Changed();
}
2021-05-24 21:48:03 +08:00
void SynEdit::doScrolled(int)
{
mLeftChar = horizontalScrollBar()->value();
mTopLine = verticalScrollBar()->value();
invalidate();
}
2021-06-05 23:43:45 +08:00
void SynEdit::doAddStr(const QString &s)
{
if (!selAvail() && !mInserting) {
BufferCoord BB = caretXY();
BufferCoord BE = BB;
BE.Char = BB.Char + s.length();
setCaretAndSelection(caretXY(),BB,BE);
}
setSelText(s);
}
void SynEdit::doUndo()
{
if (mReadOnly)
return;
//Remove Group Break;
if (mUndoList->LastChangeReason() == SynChangeReason::crGroupBreak) {
int OldBlockNumber = mRedoList->blockChangeNumber();
auto action = finally([&,this]{
mRedoList->setBlockChangeNumber(OldBlockNumber);
});
PSynEditUndoItem Item = mUndoList->PopItem();
mRedoList->setBlockChangeNumber(Item->changeNumber());
mRedoList->AddGroupBreak();
}
SynChangeReason FLastChange = mUndoList->LastChangeReason();
bool FAutoComplete = (FLastChange == SynChangeReason::crAutoCompleteEnd);
bool FPasteAction = (FLastChange == SynChangeReason::crPasteEnd);
bool FSpecial1 = (FLastChange == SynChangeReason::crSpecial1End);
bool FSpecial2 = (FLastChange == SynChangeReason::crSpecial2End);
bool FKeepGoing;
PSynEditUndoItem Item = mUndoList->PeekItem();
if (Item) {
int OldChangeNumber = Item->changeNumber();
int SaveChangeNumber = mRedoList->blockChangeNumber();
mRedoList->setBlockChangeNumber(Item->changeNumber());
{
auto action = finally([&,this] {
mRedoList->setBlockChangeNumber(SaveChangeNumber);
});
do {
doUndoItem();
Item = mUndoList->PeekItem();
if (!Item)
FKeepGoing = false;
else {
if (FAutoComplete)
FKeepGoing = (mUndoList->LastChangeReason() != SynChangeReason::crAutoCompleteBegin);
else if (FPasteAction)
FKeepGoing = (mUndoList->LastChangeReason() != SynChangeReason::crPasteBegin);
else if (FSpecial1)
FKeepGoing = (mUndoList->LastChangeReason() != SynChangeReason::crSpecial1Begin);
else if (FSpecial2)
FKeepGoing = (mUndoList->LastChangeReason() != SynChangeReason::crSpecial2Begin);
else if (Item->changeNumber() == OldChangeNumber)
FKeepGoing = true;
else {
FKeepGoing = (mOptions.testFlag(eoGroupUndo) &&
(FLastChange == Item->changeReason()) &&
! (FLastChange == SynChangeReason::crIndent
|| FLastChange == SynChangeReason::crUnindent));
}
FLastChange = Item->changeReason();
}
} while (FKeepGoing);
//we need to eat the last command since it does nothing and also update modified status...
if ((FAutoComplete && (mUndoList->LastChangeReason() == SynChangeReason::crAutoCompleteBegin)) ||
(FPasteAction && (mUndoList->LastChangeReason() == SynChangeReason::crPasteBegin)) ||
(FSpecial1 && (mUndoList->LastChangeReason() == SynChangeReason::crSpecial1Begin)) ||
(FSpecial2 && (mUndoList->LastChangeReason() == SynChangeReason::crSpecial2Begin))) {
doUndoItem();
updateModifiedStatus();
}
}
}
}
void SynEdit::doUndoItem()
{
mUndoing = true;
bool ChangeScrollPastEol = ! mOptions.testFlag(eoScrollPastEol);
PSynEditUndoItem Item = mUndoList->PopItem();
if (Item) {
setActiveSelectionMode(Item->changeSelMode());
incPaintLock();
auto action = finally([&,this]{
mUndoing = false;
if (ChangeScrollPastEol)
mOptions.setFlag(eoScrollPastEol,false);
decPaintLock();
});
mOptions.setFlag(eoScrollPastEol);
switch(Item->changeReason()) {
case SynChangeReason::crCaret:
mRedoList->AddChange(
Item->changeReason(),
caretXY(),
caretXY(), "",
mActiveSelectionMode);
internalSetCaretXY(Item->changeStartPos());
break;
case SynChangeReason::crSelection:
mRedoList->AddChange(
Item->changeReason(),
blockBegin(),
blockEnd(),
"",
mActiveSelectionMode);
setCaretAndSelection(caretXY(), Item->changeStartPos(), Item->changeEndPos());
break;
case SynChangeReason::crInsert:
case SynChangeReason::crPaste:
case SynChangeReason::crDragDropInsert: {
setCaretAndSelection(
Item->changeStartPos(),
Item->changeStartPos(),
Item->changeEndPos());
QString TmpStr = selText();
SetSelTextPrimitiveEx(
Item->changeSelMode(),
Item->changeStr(),
false);
mRedoList->AddChange(
Item->changeReason(),
Item->changeStartPos(),
Item->changeEndPos(),
TmpStr,
Item->changeSelMode());
internalSetCaretXY(Item->changeStartPos());
break;
}
case SynChangeReason::crDeleteAfterCursor:
case SynChangeReason::crDelete:
case SynChangeReason::crSilentDelete:
case SynChangeReason::crSilentDeleteAfterCursor:
case SynChangeReason::crDeleteAll: {
// If there's no selection, we have to set
// the Caret's position manualy.
BufferCoord TmpPos;
if (Item->changeSelMode() == SynSelectionMode::smColumn) {
TmpPos = BufferCoord{
std::min(Item->changeStartPos().Char, Item->changeEndPos().Char),
std::min(Item->changeStartPos().Line, Item->changeEndPos().Line)};
} else {
TmpPos = BufferCoord{
MinBufferCoord(
Item->changeStartPos(),
Item->changeEndPos())};
}
if ( (Item->changeReason() == SynChangeReason::crDeleteAfterCursor
|| Item->changeReason() == SynChangeReason::crSilentDeleteAfterCursor)
&& (TmpPos.Line > mLines->count())) {
internalSetCaretXY(BufferCoord{1, mLines->count()});
mLines->add("");
}
setCaretXY(TmpPos);
SetSelTextPrimitiveEx(
Item->changeSelMode(),
Item->changeStr(),
false);
if ( (Item->changeReason() == SynChangeReason::crDeleteAfterCursor
|| Item->changeReason() == SynChangeReason::crSilentDeleteAfterCursor)) {
TmpPos = Item->changeStartPos();
internalSetCaretXY(TmpPos);
} else {
TmpPos = Item->changeEndPos();
setCaretAndSelection(TmpPos,
Item->changeStartPos(),
Item->changeEndPos());
}
mRedoList->AddChange(
Item->changeReason(),
Item->changeStartPos(),
Item->changeEndPos(),
"",
Item->changeSelMode());
if (Item->changeReason() == SynChangeReason::crDeleteAll) {
internalSetCaretXY(BufferCoord{1, 1});
mBlockEnd = BufferCoord{1, 1};
}
ensureCursorPosVisible();
break;
}
case SynChangeReason::crLineBreak:
// If there's no selection, we have to set
// the Caret's position manualy.
internalSetCaretXY(Item->changeStartPos());
if (mCaretY > 0) {
QString TmpStr = mLines->getString(mCaretY - 1);
if ( (mCaretX > TmpStr.length() + 1) && (leftSpaces(Item->changeStr()) == 0))
TmpStr = TmpStr + QString(mCaretX - 1 - TmpStr.length(), ' ');
ProperSetLine(mCaretY - 1, TmpStr + Item->changeStr());
mLines->deleteAt(mCaretY);
DoLinesDeleted(mCaretY, 1);
}
mRedoList->AddChange(
Item->changeReason(),
Item->changeStartPos(),
Item->changeEndPos(),
"",
Item->changeSelMode());
break;
case SynChangeReason::crIndent:
// restore the selection
setCaretAndSelection(
Item->changeEndPos(),
Item->changeStartPos(),
Item->changeEndPos());
mRedoList->AddChange(
Item->changeReason(),
Item->changeStartPos(),
Item->changeEndPos(),
Item->changeStr(),
Item->changeSelMode());
break;
case SynChangeReason::crUnindent:
// reinsert the string
if (Item->changeSelMode()!= SynSelectionMode::smColumn)
insertBlock(BufferCoord{1, Item->changeStartPos().Line},
BufferCoord{1, Item->changeEndPos().Line},
Item->changeStr(),
false);
else {
int BeginX = std::min(Item->changeStartPos().Char,
Item->changeEndPos().Char);
insertBlock(BufferCoord{BeginX, Item->changeStartPos().Line},
BufferCoord{BeginX, Item->changeEndPos().Line},
Item->changeStr(), false);
}
setCaretAndSelection(
Item->changeStartPos(),
Item->changeStartPos(),
Item->changeEndPos());
mRedoList->AddChange(
Item->changeReason(),
Item->changeStartPos(),
Item->changeEndPos(),
Item->changeStr(),
Item->changeSelMode());
break;
}
}
}
void SynEdit::doRedo()
{
if (mReadOnly)
return;
SynChangeReason FLastChange = mRedoList->LastChangeReason();
bool FAutoComplete = (FLastChange == SynChangeReason::crAutoCompleteBegin);
bool FPasteAction = (FLastChange == SynChangeReason::crPasteBegin);
bool FSpecial1 = (FLastChange == SynChangeReason::crSpecial1Begin);
bool FSpecial2 = (FLastChange == SynChangeReason::crSpecial2Begin);
PSynEditUndoItem Item = mRedoList->PeekItem();
if (!Item)
return;
int OldChangeNumber = Item->changeNumber();
int SaveChangeNumber = mUndoList->blockChangeNumber();
mUndoList->setBlockChangeNumber(Item->changeNumber());
{
auto action = finally([&,this]{
mUndoList->setBlockChangeNumber(SaveChangeNumber);
});
bool FKeepGoing;
do {
doRedoItem();
Item = mRedoList->PeekItem();
if (!Item)
FKeepGoing = false;
else {
if (FAutoComplete)
FKeepGoing = (mRedoList->LastChangeReason() != SynChangeReason::crAutoCompleteEnd);
if (FPasteAction)
FKeepGoing = (mRedoList->LastChangeReason() != SynChangeReason::crPasteEnd);
if (FSpecial1)
FKeepGoing = (mRedoList->LastChangeReason() != SynChangeReason::crSpecial1End);
if (FSpecial2)
FKeepGoing = (mRedoList->LastChangeReason() != SynChangeReason::crSpecial2End);
else if (Item->changeNumber() == OldChangeNumber)
FKeepGoing = true;
else {
FKeepGoing = (mOptions.testFlag(eoGroupUndo) &&
(FLastChange == Item->changeReason()) &&
!(FLastChange == SynChangeReason::crIndent
|| FLastChange == SynChangeReason::crUnindent));
}
FLastChange = Item->changeReason();
}
} while (FKeepGoing);
//we need to eat the last command since it does nothing and also update modified status...
if ((FAutoComplete && (mRedoList->LastChangeReason() == SynChangeReason::crAutoCompleteEnd)) ||
(FPasteAction && (mRedoList->LastChangeReason() == SynChangeReason::crPasteEnd)) ||
(FSpecial1 && (mRedoList->LastChangeReason() == SynChangeReason::crSpecial1End)) ||
(FSpecial2 && (mRedoList->LastChangeReason() == SynChangeReason::crSpecial2End))) {
doRedoItem();
updateModifiedStatus();
}
}
//Remove Group Break
if (mRedoList->LastChangeReason() == SynChangeReason::crGroupBreak) {
int OldBlockNumber = mUndoList->blockChangeNumber();
Item = mRedoList->PopItem();
{
auto action2=finally([&,this]{
mUndoList->setBlockChangeNumber(OldBlockNumber);
});
mUndoList->setBlockChangeNumber(Item->changeNumber());
mUndoList->AddGroupBreak();
}
updateModifiedStatus();
}
}
void SynEdit::doRedoItem()
{
bool ChangeScrollPastEol = ! mOptions.testFlag(eoScrollPastEol);
PSynEditUndoItem Item = mRedoList->PopItem();
if (Item) {
setActiveSelectionMode(Item->changeSelMode());
incPaintLock();
mOptions.setFlag(eoScrollPastEol);
mUndoList->setInsideRedo(true);
auto action = finally([&,this]{
mUndoList->setInsideRedo(false);
if (ChangeScrollPastEol)
mOptions.setFlag(eoScrollPastEol,false);
decPaintLock();
});
switch(Item->changeReason()) {
case SynChangeReason::crCaret:
mUndoList->AddChange(
Item->changeReason(),
caretXY(),
caretXY(),
"",
mActiveSelectionMode);
internalSetCaretXY(Item->changeStartPos());
break;
case SynChangeReason::crSelection:
mUndoList->AddChange(
Item->changeReason(),
blockBegin(),
blockEnd(),
"",
mActiveSelectionMode);
setCaretAndSelection(
caretXY(),
Item->changeStartPos(),
Item->changeEndPos());
break;
case SynChangeReason::crInsert:
case SynChangeReason::crPaste:
case SynChangeReason::crDragDropInsert:
setCaretAndSelection(
Item->changeStartPos(),
Item->changeStartPos(),
Item->changeStartPos());
SetSelTextPrimitiveEx(Item->changeSelMode(), Item->changeStr(), false);
internalSetCaretXY(Item->changeEndPos());
mUndoList->AddChange(Item->changeReason(),
Item->changeStartPos(),
Item->changeEndPos(),
selText(),
Item->changeSelMode());
if (Item->changeReason() == SynChangeReason::crDragDropInsert) {
setCaretAndSelection(Item->changeStartPos(),
Item->changeStartPos(),
Item->changeEndPos());
}
break;
case SynChangeReason::crDeleteAfterCursor:
case SynChangeReason::crSilentDeleteAfterCursor: {
setCaretAndSelection(Item->changeStartPos(), Item->changeStartPos(),
Item->changeEndPos());
QString TempString = selText();
SetSelTextPrimitiveEx(Item->changeSelMode(),
Item->changeStr(),false);
mUndoList->AddChange(Item->changeReason(), Item->changeStartPos(),
Item->changeEndPos(), TempString, Item->changeSelMode());
internalSetCaretXY(Item->changeEndPos());
break;
}
case SynChangeReason::crDelete:
case SynChangeReason::crSilentDelete: {
setCaretAndSelection(Item->changeStartPos(), Item->changeStartPos(),
Item->changeEndPos());
QString TempString = selText();
SetSelTextPrimitiveEx(Item->changeSelMode(), Item->changeStr(),false);
mUndoList->AddChange(Item->changeReason(), Item->changeStartPos(),
Item->changeEndPos(),TempString,
Item->changeSelMode());
internalSetCaretXY(Item->changeStartPos());
break;
};
case SynChangeReason::crLineBreak: {
BufferCoord CaretPt = Item->changeStartPos();
setCaretAndSelection(CaretPt, CaretPt, CaretPt);
CommandProcessor(SynEditorCommand::ecLineBreak);
break;
}
case SynChangeReason::crIndent:
setCaretAndSelection(
Item->changeEndPos(),
Item->changeStartPos(),
Item->changeEndPos());
mUndoList->AddChange(Item->changeReason(), Item->changeStartPos(),
Item->changeEndPos(), Item->changeStr(), Item->changeSelMode());
break;
case SynChangeReason::crUnindent: {
// Delete string
QString StrToDelete = Item->changeStr();
internalSetCaretY(Item->changeStartPos().Line);
int BeginX = 1;
if (Item->changeSelMode() == SynSelectionMode::smColumn)
BeginX = std::min(Item->changeStartPos().Char, Item->changeEndPos().Char);
int Run = 0;
int Len;
QString TempString;
do {
Run = GetEOL(StrToDelete,Run);
if (Run != 0) {
Len = Run;
if (Len > 0) {
TempString = mLines->getString(mCaretY - 1);
TempString.remove(BeginX-1,Len);
mLines->putString(mCaretY-1,TempString);
}
} else
Len = 0;
if (StrToDelete[Run] == '\r') {
Run++;
if (StrToDelete[Run] == '\n')
Run++;
mCaretY++;
}
} while (Run<StrToDelete.length());
if (Item->changeSelMode() == SynSelectionMode::smColumn) {
setCaretAndSelection(Item->changeStartPos(), Item->changeStartPos(),
Item->changeEndPos());
} else {
// restore selection
BufferCoord CaretPt;
if (mOptions.testFlag(eoTabsToSpaces))
CaretPt.Char = Item->changeStartPos().Char - mTabWidth;
else
CaretPt.Char = Item->changeStartPos().Char - 1;
CaretPt.Line = Item->changeStartPos().Line;
setCaretAndSelection(CaretPt, CaretPt,
BufferCoord{Item->changeEndPos().Char - Len,
Item->changeEndPos().Line});
}
mUndoList->AddChange(Item->changeReason(), Item->changeStartPos(),
Item->changeEndPos(), Item->changeStr(), Item->changeSelMode());
break;
}
}
}
}
2021-05-27 23:45:22 +08:00
SynSelectionMode SynEdit::selectionMode() const
{
return mSelectionMode;
}
void SynEdit::setSelectionMode(SynSelectionMode value)
{
if (mSelectionMode!=value) {
mSelectionMode = value;
setActiveSelectionMode(value);
}
}
2021-05-29 21:35:46 +08:00
QString SynEdit::selText()
{
if (!selAvail()) {
return "";
} else {
2021-06-03 20:26:36 +08:00
int ColFrom = blockBegin().Char;
int First = blockBegin().Line - 1;
2021-05-29 21:35:46 +08:00
//
2021-06-03 20:26:36 +08:00
int ColTo = blockEnd().Char;
int Last = blockEnd().Line - 1;
2021-05-29 21:35:46 +08:00
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:
{
2021-06-03 20:26:36 +08:00
First = blockBegin().Line-1;
ColFrom = charToColumn(blockBegin().Line, blockBegin().Char);
Last = blockEnd().Line - 1;
ColTo = charToColumn(blockEnd().Line, blockEnd().Char);
2021-05-29 21:35:46 +08:00
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;
}
void SynEdit::setUseCodeFolding(bool value)
{
if (mUseCodeFolding!=value) {
mUseCodeFolding = value;
rescan();
}
}
2021-05-27 20:33:25 +08:00
QString SynEdit::lineText()
{
if (mCaretY >= 1 && mCaretY <= mLines->count())
return mLines->getString(mCaretY - 1);
else
return QString();
}
void SynEdit::setLineText(const QString s)
{
if (mCaretY >= 1 && mCaretY <= mLines->count())
mLines->putString(mCaretY-1,s);
}
2021-05-26 00:04:20 +08:00
PSynHighlighter SynEdit::highlighter() const
{
return mHighlighter;
}
void SynEdit::setHighlighter(const PSynHighlighter &highlighter)
{
PSynHighlighter oldHighlighter= mHighlighter;
mHighlighter = highlighter;
if (oldHighlighter && mHighlighter &&
oldHighlighter->language() == highlighter->language()) {
} else {
recalcCharExtent();
mLines->beginUpdate();
auto action=finally([this]{
mLines->endUpdate();
});
scanRanges();
}
sizeOrFontChanged(true);
invalidate();
}
2021-05-24 00:41:00 +08:00
PSynEditStringList SynEdit::lines() const
{
return mLines;
}
2021-05-18 15:49:58 +08:00
int SynEdit::tabWidth() const
{
return mTabWidth;
}
2021-05-24 00:41:00 +08:00
bool SynEdit::empty()
{
return mLines->empty();
}
2021-05-27 20:33:25 +08:00
void SynEdit::CommandProcessor(SynEditorCommand Command, QChar AChar, void *pData)
{
// first the program event handler gets a chance to process the command
onProcessCommand(Command, AChar, pData);
if (Command != SynEditorCommand::ecNone)
ExecuteCommand(Command, AChar, pData);
onCommandProcessed(Command, AChar, pData);
}
void SynEdit::MoveCaretHorz(int DX, bool isSelection)
{
BufferCoord ptO = caretXY();
BufferCoord ptDst = ptO;
QString s = lineText();
int nLineLen = s.length();
// only moving or selecting one char can change the line
bool bChangeY = !mOptions.testFlag(SynEditorOption::eoScrollPastEol);
if (bChangeY && (DX == -1) && (ptO.Char == 1) && (ptO.Line > 1)) {
// end of previous line
ptDst.Line--;
ptDst.Char = mLines->getString(ptDst.Line - 1).length() + 1;
} else if (bChangeY && (DX == 1) && (ptO.Char > nLineLen) && (ptO.Line < mLines->count())) {
// start of next line
ptDst.Line++;
ptDst.Char=1;
} else {
ptDst.Char = std::max(1, ptDst.Char + DX);
// don't go past last char when ScrollPastEol option not set
if ((DX > 0) && bChangeY)
ptDst.Char = std::min(ptDst.Char, nLineLen + 1);
}
// set caret and block begin / end
MoveCaretAndSelection(mBlockBegin, ptDst, isSelection);
}
2021-05-27 21:39:18 +08:00
void SynEdit::MoveCaretVert(int DY, bool isSelection)
{
DisplayCoord ptO = displayXY();
DisplayCoord ptDst = ptO;
ptDst.Row+=DY;
if (DY >= 0) {
if (rowToLine(ptDst.Row) > mLines->count())
ptDst.Row = std::max(1, displayLineCount());
} else {
if (ptDst.Row < 1)
ptDst.Row = 1;
}
if (ptO.Row != ptDst.Row) {
if (mOptions.testFlag(eoKeepCaretX))
2021-06-03 23:18:51 +08:00
ptDst.Column = mLastCaretColumn;
2021-05-27 21:39:18 +08:00
}
BufferCoord vDstLineChar = displayToBufferPos(ptDst);
2021-06-03 23:18:51 +08:00
int SaveLastCaretX = mLastCaretColumn;
2021-05-27 21:39:18 +08:00
bool NewStepAside = mMBCSStepAside;
// set caret and block begin / end
incPaintLock();
MoveCaretAndSelection(mBlockBegin, vDstLineChar, isSelection);
decPaintLock();
// Set fMBCSStepAside and restore fLastCaretX after moving caret, since
// UpdateLastCaretX, called by SetCaretXYEx, changes them. This is the one
// case where we don't want that.
mMBCSStepAside = NewStepAside;
2021-06-03 23:18:51 +08:00
mLastCaretColumn = SaveLastCaretX;
2021-05-27 21:39:18 +08:00
}
2021-05-27 20:33:25 +08:00
void SynEdit::MoveCaretAndSelection(const BufferCoord &ptBefore, const BufferCoord &ptAfter, bool isSelection)
{
if (mOptions.testFlag(SynEditorOption::eoGroupUndo) && mUndoList->CanUndo())
mUndoList->AddGroupBreak();
incPaintLock();
if (isSelection) {
if (!selAvail())
setBlockBegin(ptBefore);
setBlockEnd(ptAfter);
} else
setBlockBegin(ptAfter);
internalSetCaretXY(ptAfter);
decPaintLock();
}
void SynEdit::MoveCaretToLineStart(bool isSelection)
{
int newX;
// home key enhancement
if (mOptions.testFlag(SynEditorOption::eoEnhanceHomeKey) && (lineToRow(mCaretY) == displayY())) {
QString s = mLines->getString(mCaretY - 1);
int first_nonblank = 0;
int vMaxX = s.length();
while ((first_nonblank < vMaxX) && (s[first_nonblank] == ' ' || s[first_nonblank] == '\t')) {
first_nonblank++;
}
newX = mCaretX;
if (newX > first_nonblank)
newX = first_nonblank;
else
newX = 1;
} else
newX = 1;
MoveCaretAndSelection(caretXY(), BufferCoord{newX, mCaretY}, isSelection);
}
void SynEdit::MoveCaretToLineEnd(bool isSelection)
{
int vNewX;
if (mOptions.testFlag(SynEditorOption::eoEnhanceEndKey)) {
QString vText = lineText();
int vLastNonBlank = vText.length();
int vMinX = 0;
while ((vLastNonBlank > vMinX) && (vText[vLastNonBlank] == ' ' || vText[vLastNonBlank] =='\t'))
vLastNonBlank--;
vNewX = mCaretX;
if (vNewX > vLastNonBlank)
vNewX = vText.length() + 1;
else
vNewX = vLastNonBlank + 1;
} else
vNewX = lineText().length() + 1;
MoveCaretAndSelection(caretXY(), BufferCoord{vNewX, mCaretY}, isSelection);
}
2021-05-29 21:35:46 +08:00
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()) {
2021-06-03 20:26:36 +08:00
InsertText(Value,PasteMode,AddToUndoList);
2021-05-29 21:35:46 +08:00
}
if (mCaretY < 1)
internalSetCaretY(1);
}
2021-06-03 20:26:36 +08:00
void SynEdit::setSelText(const QString &Value)
{
mUndoList->BeginBlock();
auto action = finally([this]{
mUndoList->EndBlock();
});
if (selAvail()) {
mUndoList->AddChange(
SynChangeReason::crDelete, mBlockBegin, mBlockEnd,
selText(), mActiveSelectionMode);
} else
setActiveSelectionMode(selectionMode());
BufferCoord StartOfBlock = blockBegin();
BufferCoord EndOfBlock = blockEnd();
mBlockBegin = StartOfBlock;
mBlockEnd = EndOfBlock;
SetSelTextPrimitive(Value);
if (!Value.isEmpty() && (mActiveSelectionMode !=SynSelectionMode::smColumn))
mUndoList->AddChange(
SynChangeReason::crInsert,
StartOfBlock,
blockEnd(), "",
mActiveSelectionMode);
}
2021-05-29 21:35:46 +08:00
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;
}
2021-06-03 20:26:36 +08:00
void SynEdit::DoLinesInserted(int FirstLine, int Count)
{
// // gutter marks
// for i := 0 to Marks.Count - 1 do begin
// if Marks[i].Line >= FirstLine then
// Marks[i].Line := Marks[i].Line + Count;
// end;
// // plugins
// if fPlugins <> nil then begin
// for i := 0 to fPlugins.Count - 1 do
// TSynEditPlugin(fPlugins[i]).LinesInserted(FirstLine, Count);
// end;
}
2021-05-29 21:35:46 +08:00
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);
}
2021-06-03 20:26:36 +08:00
void SynEdit::InsertText(const QString &Value, SynSelectionMode PasteMode,bool AddToUndoList)
2021-05-29 21:35:46 +08:00
{
if (Value.isEmpty())
return;
int StartLine = mCaretY;
int StartCol = mCaretX;
int InsertedLines = 0;
switch(PasteMode){
case SynSelectionMode::smNormal:
2021-06-03 20:26:36 +08:00
InsertedLines = InsertTextByNormalMode(Value);
2021-05-29 21:35:46 +08:00
break;
case SynSelectionMode::smColumn:
2021-06-03 20:26:36 +08:00
InsertedLines = InsertTextByColumnMode(Value,AddToUndoList);
2021-05-29 21:35:46 +08:00
break;
case SynSelectionMode::smLine:
2021-06-03 20:26:36 +08:00
InsertedLines = InsertTextByLineMode(Value);
2021-05-29 21:35:46 +08:00
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();
}
2021-06-03 20:26:36 +08:00
int SynEdit::InsertTextByNormalMode(const QString &Value)
{
QString sLeftSide;
QString sRightSide;
QString Str;
int Start;
int P;
bool bChangeScroll;
int SpaceCount;
int Result = 0;
sLeftSide = lineText().mid(0, mCaretX - 1);
if (mCaretX - 1 > sLeftSide.length()) {
if (StringIsBlank(sLeftSide))
sLeftSide = GetLeftSpacing(displayX() - 1, true);
else
2021-06-04 21:27:51 +08:00
sLeftSide += QString(mCaretX - 1 - sLeftSide.length(),' ');
2021-06-03 20:26:36 +08:00
}
sRightSide = lineText().mid(mCaretX-1);
if (mUndoing) {
SpaceCount = 0;
} else {
SpaceCount = leftSpaces(sLeftSide);
}
// step1: insert the first line of Value into current line
Start = 0;
P = GetEOL(Value,Start);
if (P<Value.length()) {
Str = sLeftSide + Value.mid(0, P - Start);
ProperSetLine(mCaretY - 1, Str);
mLines->InsertLines(mCaretY, CountLines(Value,P));
} else {
Str = sLeftSide + Value + sRightSide;
ProperSetLine(mCaretY - 1, Str);
}
// step2: insert remaining lines of Value
while (P < Value.length()) {
if (Value[P] == '\r')
P++;
2021-06-05 23:43:45 +08:00
if (Value[P] == '\n')
2021-06-03 20:26:36 +08:00
P++;
mCaretY++;
mStatusChanges.setFlag(SynStatusChange::scCaretY);
Start = P;
P = GetEOL(Value,Start);
if (P == Start) {
if (P<Value.length())
Str = "";
else
Str = sRightSide;
} else {
Str = Value.mid(Start, P-Start);
if (P>=Value.length())
Str += sRightSide;
}
Str = GetLeftSpacing(SpaceCount, true)+Str;
ProperSetLine(mCaretY - 1, Str);
Result++;
}
bChangeScroll = !mOptions.testFlag(eoScrollPastEol);
mOptions.setFlag(eoScrollPastEol);
auto action = finally([&,this]{
if (bChangeScroll)
mOptions.setFlag(eoScrollPastEol,false);
});
if (mOptions.testFlag(eoTrimTrailingSpaces) && (sRightSide == "")) {
internalSetCaretX(lineText().length()+1);
} else
internalSetCaretX(Str.length() - sRightSide.length()+1);
return Result;
}
int SynEdit::InsertTextByColumnMode(const QString &Value, bool AddToUndoList)
{
QString Str;
QString TempString;
int Start;
int P;
int Len;
2021-06-05 23:43:45 +08:00
int InsertCol;
2021-06-03 20:26:36 +08:00
BufferCoord LineBreakPos;
int Result = 0;
// Insert string at current position
2021-06-05 23:43:45 +08:00
InsertCol = charToColumn(mCaretY,mCaretX);
2021-06-03 20:26:36 +08:00
Start = 0;
do {
P = GetEOL(Value,Start);
if (P != Start) {
Str = Value.mid(0,P-Start);
// Move(Start^, Str[1], P - Start);
if (mCaretY > mLines->count()) {
Result++;
2021-06-05 23:43:45 +08:00
TempString = QString(InsertCol - 1,' ') + Str;
2021-06-03 20:26:36 +08:00
mLines->add("");
if (AddToUndoList) {
LineBreakPos.Line = mCaretY - 1;
LineBreakPos.Char = mLines->getString(mCaretY - 2).length() + 1;
mUndoList->AddChange(SynChangeReason::crLineBreak,
LineBreakPos,
LineBreakPos,
"", SynSelectionMode::smNormal);
}
} else {
TempString = mLines->getString(mCaretY - 1);
Len = stringColumns(TempString,0);
2021-06-05 23:43:45 +08:00
if (Len < InsertCol) {
TempString = TempString + QChar(' ', InsertCol - Len - 1) + Str;
2021-06-03 20:26:36 +08:00
} else {
2021-06-05 23:43:45 +08:00
int insertPos = charToColumn(TempString,InsertCol);
TempString.insert(insertPos-1,Str);
2021-06-03 20:26:36 +08:00
}
}
ProperSetLine(mCaretY - 1, TempString);
// Add undo change here from PasteFromClipboard
if (AddToUndoList) {
mUndoList->AddChange(SynChangeReason::crPaste, BufferCoord{mCaretX, mCaretY},
BufferCoord{mCaretX + (P - Start), mCaretY}, "", mActiveSelectionMode);
}
}
2021-06-04 21:27:51 +08:00
if (P<Value.length() && ((Value[P]=='\r') || (Value[P]=='\n'))) {
2021-06-03 20:26:36 +08:00
P++;
if (P<Value.length() && Value[P]=='\n')
P++;
mCaretY++;
mStatusChanges.setFlag(SynStatusChange::scCaretY);
}
Start = P;
} while (P<Value.length());
mCaretX+=Str.length();
mStatusChanges.setFlag(SynStatusChange::scCaretX);
}
int SynEdit::InsertTextByLineMode(const QString &Value)
{
int Start;
int P;
QString Str;
int Result = 0;
mCaretX = 1;
statusChanged(SynStatusChange::scCaretX);
// Insert string before current line
Start = 0;
do {
P = GetEOL(Value,Start);
if (P != Start)
Str = Value.mid(Start,P - Start);
else
Str = "";
if ((mCaretY == mLines->count()) || mInserting) {
mLines->Insert(mCaretY - 1, "");
Result++;
}
ProperSetLine(mCaretY - 1, Str);
mCaretY++;
mStatusChanges.setFlag(SynStatusChange::scCaretY);
if (P<Value.length() && Value[P]=='\r')
P++;
if (P<Value.length() && Value[P]=='\n')
P++;
Start = P;
} while (P<Value.length());
}
void SynEdit::DeleteFromTo(const BufferCoord &start, const BufferCoord &end)
{
if (mReadOnly)
return;
doOnPaintTransient(SynTransientType::ttBefore);
if ((start.Char != end.Char) || (start.Line != end.Line)) {
setBlockBegin(start);
setBlockEnd(end);
setActiveSelectionMode(SynSelectionMode::smNormal);
QString helper = selText();
SetSelTextPrimitive("");
mUndoList->AddChange(SynChangeReason::crSilentDeleteAfterCursor, start, end,
helper, SynSelectionMode::smNormal);
internalSetCaretXY(start);
}
doOnPaintTransient(SynTransientType::ttAfter);
}
2021-05-24 00:41:00 +08:00
bool SynEdit::onGetSpecialLineColors(int, QColor &, QColor &)
{
2021-05-26 18:32:17 +08:00
return false;
2021-05-24 00:41:00 +08:00
}
void SynEdit::onGetEditingAreas(int, SynEditingAreaList &)
{
}
void SynEdit::onGutterGetText(int aLine, QString &aText)
2021-05-21 23:33:53 +08:00
{
}
2021-05-24 00:41:00 +08:00
void SynEdit::onGutterPaint(QPainter &painter, int aLine, int X, int Y)
{
}
void SynEdit::onPaint(QPainter &)
2021-05-21 23:33:53 +08:00
{
}
2021-06-05 23:43:45 +08:00
void SynEdit::onProcessCommand(SynEditorCommand , QChar , void *)
2021-05-27 20:33:25 +08:00
{
}
2021-06-05 23:43:45 +08:00
void SynEdit::onCommandProcessed(SynEditorCommand , QChar , void *)
2021-05-27 20:33:25 +08:00
{
}
void SynEdit::ExecuteCommand(SynEditorCommand Command, QChar AChar, void *pData)
{
2021-05-29 21:35:46 +08:00
incPaintLock();
auto action=finally([this] {
decPaintLock();
});
2021-05-27 20:33:25 +08:00
switch(Command) {
2021-05-27 21:39:18 +08:00
//horizontal caret movement or selection
2021-05-27 20:33:25 +08:00
case SynEditorCommand::ecLeft:
case SynEditorCommand::ecSelLeft:
MoveCaretHorz(-1, Command == SynEditorCommand::ecSelLeft);
break;
case SynEditorCommand::ecRight:
case SynEditorCommand::ecSelRight:
MoveCaretHorz(1, Command == SynEditorCommand::ecSelRight);
break;
case SynEditorCommand::ecPageLeft:
case SynEditorCommand::ecSelPageLeft:
MoveCaretHorz(-mCharsInWindow, Command == SynEditorCommand::ecSelPageLeft);
break;
case SynEditorCommand::ecPageRight:
case SynEditorCommand::ecSelPageRight:
MoveCaretHorz(mCharsInWindow, Command == SynEditorCommand::ecSelPageRight);
break;
case SynEditorCommand::ecLineStart:
case SynEditorCommand::ecSelLineStart:
MoveCaretToLineStart(Command == SynEditorCommand::ecSelLineStart);
break;
case SynEditorCommand::ecLineEnd:
case SynEditorCommand::ecSelLineEnd:
MoveCaretToLineEnd(Command == SynEditorCommand::ecSelLineEnd);
break;
2021-05-27 21:39:18 +08:00
// vertical caret movement or selection
case SynEditorCommand::ecUp:
case SynEditorCommand::ecSelUp:
MoveCaretVert(-1, Command == SynEditorCommand::ecSelUp);
break;
case SynEditorCommand::ecDown:
case SynEditorCommand::ecSelDown:
MoveCaretVert(1, Command == SynEditorCommand::ecSelDown);
break;
case SynEditorCommand::ecPageUp:
case SynEditorCommand::ecSelPageUp:
case SynEditorCommand::ecPageDown:
case SynEditorCommand::ecSelPageDown:
{
int counter = mLinesInWindow;
if (mOptions.testFlag(eoHalfPageScroll))
counter /= 2;
if (mOptions.testFlag(eoScrollByOneLess)) {
counter -=1;
}
if (counter<0)
break;
if (Command == SynEditorCommand::ecPageUp || Command == SynEditorCommand::ecSelPageUp) {
counter = -counter;
}
MoveCaretVert(counter, Command == SynEditorCommand::ecSelPageUp || Command == SynEditorCommand::ecSelPageDown);
break;
}
case SynEditorCommand::ecPageTop:
case SynEditorCommand::ecSelPageTop:
MoveCaretVert(mTopLine-mCaretY, Command == SynEditorCommand::ecSelPageTop);
break;
case SynEditorCommand::ecPageBottom:
case SynEditorCommand::ecSelPageBottom:
MoveCaretVert(mTopLine+mLinesInWindow-1-mCaretY, Command == SynEditorCommand::ecSelPageBottom);
break;
case SynEditorCommand::ecEditorTop:
case SynEditorCommand::ecSelEditorTop:
MoveCaretVert(1-mCaretY, Command == SynEditorCommand::ecSelEditorTop);
break;
case SynEditorCommand::ecEditorBottom:
case SynEditorCommand::ecSelEditorBottom:
if (!mLines->empty())
MoveCaretVert(mLines->count()-mCaretY, Command == SynEditorCommand::ecSelEditorBottom);
break;
2021-05-27 23:45:22 +08:00
// goto special line / column position
case SynEditorCommand::ecGotoXY:
case SynEditorCommand::ecSelGotoXY:
if (pData)
MoveCaretAndSelection(caretXY(), *((BufferCoord *)(pData)), Command == SynEditorCommand::ecSelGotoXY);
2021-05-29 21:35:46 +08:00
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;
2021-05-27 20:33:25 +08:00
}
2021-05-29 21:35:46 +08:00
case SynEditorCommand::ecSelWord:
SetSelWord();
break;
case SynEditorCommand::ecSelectAll:
SelectAll();
break;
case SynEditorCommand::ecDeleteLastChar:
DeleteLastChar();
break;
2021-06-03 20:26:36 +08:00
case SynEditorCommand::ecDeleteChar:
DeleteCurrentChar();
break;
case SynEditorCommand::ecDeleteWord:
DeleteWord();
break;
case SynEditorCommand::ecDeleteEOL:
DeleteToEOL();
break;
case SynEditorCommand::ecDeleteLastWord:
DeleteLastWord();
break;
case SynEditorCommand::ecDeleteBOL:
DeleteFromBOL();
break;
case SynEditorCommand::ecDeleteLine:
DeleteLine();
break;
case SynEditorCommand::ecDuplicateLine:
DuplicateLine();
break;
case SynEditorCommand::ecMoveSelUp:
MoveSelUp();
break;
case SynEditorCommand::ecMoveSelDown:
MoveSelDown();
break;
case SynEditorCommand::ecClearAll:
ClearAll();
break;
case SynEditorCommand::ecInsertLine:
case SynEditorCommand::ecLineBreak:
InsertLine(Command == SynEditorCommand::ecLineBreak);
break;
case SynEditorCommand::ecTab:
DoTabKey();
break;
case SynEditorCommand::ecShiftTab:
DoShiftTabKey();
break;
case SynEditorCommand::ecChar:
DoAddChar(AChar);
break;
2021-06-03 23:18:51 +08:00
case SynEditorCommand::ecInsertMode:
if (!mReadOnly)
mInserting = true;
break;
case SynEditorCommand::ecOverwriteMode:
if (!mReadOnly)
mInserting = false;
break;
case SynEditorCommand::ecToggleMode:
if (!mReadOnly) {
mInserting = !mInserting;
updateCaret();
}
break;
2021-06-05 23:43:45 +08:00
case SynEditorCommand::ecCut:
if (!mReadOnly && selAvail())
cutToClipboard();
break;
case SynEditorCommand::ecCopy:
if (!mReadOnly && selAvail())
copyToClipboard();
break;
case SynEditorCommand::ecPaste:
if (!mReadOnly)
pasteFromClipboard();
break;
case SynEditorCommand::ecImeStr:
if (!mReadOnly)
doAddStr(*((QString*)pData));
break;
case SynEditorCommand::ecUndo:
if (!mReadOnly)
doUndo();
break;
case SynEditorCommand::ecRedo:
if (!mReadOnly)
doRedo();
break;
2021-06-03 20:26:36 +08:00
}
2021-05-27 20:33:25 +08:00
// procedure ForceCaretX(aCaretX: integer);
// var
// vRestoreScroll: boolean;
// begin
// vRestoreScroll := not (eoScrollPastEol in fOptions);
// Include(fOptions, eoScrollPastEol);
// try
// InternalCaretX := aCaretX;
// finally
// if vRestoreScroll then
// Exclude(fOptions, eoScrollPastEol);
// end;
// end;
// var
// CX: Integer;
// Len: Integer;
// Temp: string;
// Temp2: string;
// Temp3: AnsiString;
// helper: string;
// TabBuffer: string;
// SpaceBuffer: string;
// SpaceCount1: Integer;
// SpaceCount2: Integer;
// BackCounter: Integer;
// OrigBlockBegin: TBufferCoord;
// OrigBlockEnd: TBufferCoord;
// OrigCaret: TBufferCoord;
// MoveDelim: TBufferCoord;
// BeginIndex: integer;
// EndIndex: integer;
// StartOfBlock: TBufferCoord;
// CurrentBracketPos, MatchBracketPos: TBufferCoord;
// bChangeScroll: boolean;
// moveBkm: boolean;
// WP: TBufferCoord;
// Caret: TBufferCoord;
// CaretNew: TBufferCoord;
// i, j: integer;
// {$IFDEF SYN_MBCSSUPPORT}
// s: string;
// {$ENDIF}
// counter: Integer;
// InsDelta: integer;
// iUndoBegin, iUndoEnd: TBufferCoord;
// vCaretRow: integer;
// vTabTrim: integer;
// Attr: TSynHighlighterAttributes;
// StartPos: Integer;
// EndPos: Integer;
// tempStr : AnsiString;
// nLinesInserted: integer;
// begin
// IncPaintLock;
// try
// case Command of
2021-05-29 21:35:46 +08:00
2021-05-27 20:33:25 +08:00
// ecComment:
// DoComment;
// ecUnComment:
// DoUncomment;
// ecToggleComment:
// if not ReadOnly then begin
// OrigBlockBegin := BlockBegin;
// OrigBlockEnd := BlockEnd;
// BeginIndex := OrigBlockBegin.Line - 1;
// // Ignore the last line the cursor is placed on
// if (OrigBlockEnd.Char = 1) and (OrigBlockBegin.Line < OrigBlockEnd.Line) then
// EndIndex := max(0, OrigBlockEnd.Line - 2)
// else
// EndIndex := OrigBlockEnd.Line - 1;
// // if everything is commented, then uncomment
// for I := BeginIndex to EndIndex do begin
// if Pos('//', TrimLeft(fLines[i])) <> 1 then begin // not fully commented
// DoComment; // comment everything
// Exit;
// end;
// end;
// DoUncomment;
// end;
// ecCommentInline: // toggle inline comment
// if not ReadOnly and SelAvail then begin
// Temp := SelText;
// // Check if the selection starts with /* after blanks
// StartPos := -1;
// I := 1;
// while I <= Length(Temp) do begin
// if Temp[I] in [#9, #32] then
// Inc(I)
// else if ((I + 1) <= Length(Temp)) and (Temp[i] = '/') and (Temp[i + 1] = '*') then begin
// StartPos := I;
// break;
// end else
// break;
// end;
// // Check if the selection ends with /* after blanks
// EndPos := -1;
// if StartPos <> -1 then begin
// I := Length(Temp);
// while I > 0 do begin
// if Temp[I] in [#9, #32] then
// Dec(I)
// else if ((I - 1) > 0) and (Temp[i] = '/') and (Temp[i - 1] = '*') then begin
// EndPos := I;
// break;
// end else
// break;
// end;
// end;
// // Keep selection
// OrigBlockBegin := BlockBegin;
// OrigBlockEnd := BlockEnd;
// // Toggle based on current comment status
// if (StartPos <> -1) and (EndPos <> -1) then begin
// SelText := Copy(SelText, StartPos + 2, EndPos - StartPos - 3);
// BlockBegin := OrigBlockBegin;
// BlockEnd := BufferCoord(OrigBlockEnd.Char - 4, OrigBlockEnd.Line);
// end else begin
// SelText := '/*' + SelText + '*/';
// BlockBegin := BufferCoord(OrigBlockBegin.Char, OrigBlockBegin.Line);
// BlockEnd := BufferCoord(OrigBlockEnd.Char + 4, OrigBlockEnd.Line);
// end;
// end;
// ecMatchBracket:
// FindMatchingBracket;
// ecUpperCase,
// ecLowerCase,
// ecToggleCase,
// ecTitleCase,
// ecUpperCaseBlock,
// ecLowerCaseBlock,
// ecToggleCaseBlock:
// if not ReadOnly then
// DoCaseChange(Command);
2021-06-05 23:43:45 +08:00
2021-05-27 20:33:25 +08:00
// ecGotoMarker0..ecGotoMarker9: begin
// if BookMarkOptions.EnableKeys then
// GotoBookMark(Command - ecGotoMarker0);
// end;
// ecSetMarker0..ecSetMarker9: begin
// if BookMarkOptions.EnableKeys then begin
// CX := Command - ecSetMarker0;
// if Assigned(Data) then
// Caret := TBufferCoord(Data^)
// else
// Caret := CaretXY;
// if assigned(fBookMarks[CX]) then begin
// moveBkm := (fBookMarks[CX].Line <> Caret.Line);
// ClearBookMark(CX);
// if moveBkm then
// SetBookMark(CX, Caret.Char, Caret.Line);
// end else
// SetBookMark(CX, Caret.Char, Caret.Line);
// end; // if BookMarkOptions.EnableKeys
// end;
2021-06-05 23:43:45 +08:00
2021-05-27 20:33:25 +08:00
// ecScrollUp, ecScrollDown: begin
// vCaretRow := DisplayY;
// if (vCaretRow < TopLine) or (vCaretRow >= TopLine + LinesInWindow) then
// // If the caret is not in view then, like the Delphi editor, move
// // it in view and do nothing else
// EnsureCursorPosVisible
// else begin
// if Command = ecScrollUp then begin
// TopLine := TopLine - 1;
// if vCaretRow > TopLine + LinesInWindow - 1 then
// MoveCaretVert((TopLine + LinesInWindow - 1) - vCaretRow, False);
// end else begin
// TopLine := TopLine + 1;
// if vCaretRow < TopLine then
// MoveCaretVert(TopLine - vCaretRow, False);
// end;
// EnsureCursorPosVisible;
// Update;
// end;
// end;
// ecScrollLeft: begin
// LeftChar := LeftChar - 1;
// // todo: The following code was commented out because it is not MBCS or hard-tab safe.
// //if CaretX > LeftChar + CharsInWindow then
// // InternalCaretX := LeftChar + CharsInWindow;
// Update;
// end;
// ecScrollRight: begin
// LeftChar := LeftChar + 1;
// // todo: The following code was commented out because it is not MBCS or hard-tab safe.
// //if CaretX < LeftChar then
// // InternalCaretX := LeftChar;
// Update;
// end;
// ecBlockIndent:
// if not ReadOnly then
// DoBlockIndent;
// ecBlockUnindent:
// if not ReadOnly then
// DoBlockUnindent;
// ecNormalSelect:
// SelectionMode := smNormal;
// ecColumnSelect:
// SelectionMode := smColumn;
// ecLineSelect:
// SelectionMode := smLine;
// ecContextHelp: begin
// if Assigned(fOnContextHelp) then
// fOnContextHelp(self, WordAtCursor);
// end;
// {$IFDEF SYN_MBCSSUPPORT}
// ecImeStr: begin;
// {$ENDIF}
// end;
// finally
// DecPaintLock;
// end;
// end;
}
2021-05-14 23:56:43 +08:00
void SynEdit::paintEvent(QPaintEvent *event)
{
if (mPainterLock>0)
return;
if (mPainting)
return;
mPainting = true;
2021-05-24 21:48:03 +08:00
auto action = finally([&,this] {
mPainting = false;
});
2021-05-14 23:56:43 +08:00
// Now paint everything while the caret is hidden.
2021-05-24 00:41:00 +08:00
QPainter painter(viewport());
//Get the invalidated rect.
QRect rcClip = event->rect();
DisplayCoord coord = displayXY();
QPoint caretPos = RowColumnToPixels(coord);
int caretWidth=mCharWidth;
if (mCaretY <= mLines->count() && mCaretX <= mLines->getString(mCaretY-1).length()) {
caretWidth = charColumns(mLines->getString(mCaretY-1)[mCaretX-1])*mCharWidth;
}
QRect rcCaret(caretPos.x(),caretPos.y(),caretWidth,
mTextHeight);
2021-05-24 18:11:07 +08:00
if (rcCaret == rcClip) {
2021-05-24 00:41:00 +08:00
// only update caret
// calculate the needed invalid area for caret
2021-05-27 21:39:18 +08:00
//qDebug()<<"update caret"<<rcCaret;
2021-05-24 18:11:07 +08:00
painter.drawImage(rcCaret,*mContentImage,rcCaret);
2021-05-24 00:41:00 +08:00
} else {
QRect rcDraw;
int nL1, nL2, nC1, nC2;
// Compute the invalid area in lines / columns.
// columns
nC1 = mLeftChar;
if (rcClip.left() > mGutterWidth + 2 )
nC1 += (rcClip.left() - mGutterWidth - 2 ) / mCharWidth;
nC2 = mLeftChar +
(rcClip.right() - mGutterWidth - 2 + mCharWidth - 1) / mCharWidth;
// lines
nL1 = MinMax(mTopLine + rcClip.top() / mTextHeight, mTopLine, displayLineCount());
nL2 = MinMax(mTopLine + (rcClip.bottom() + mTextHeight - 1) / mTextHeight, 1, displayLineCount());
2021-05-24 21:48:03 +08:00
qDebug()<<"Paint:"<<nL1<<nL2<<nC1<<nC2;
2021-05-24 18:11:07 +08:00
QPainter cachePainter(mContentImage.get());
cachePainter.setFont(font());
SynEditTextPainter textPainter(this, &cachePainter,
2021-05-24 00:41:00 +08:00
nL1,nL2,nC1,nC2);
// First paint paint the text area if it was (partly) invalidated.
if (rcClip.right() > mGutterWidth ) {
rcDraw = rcClip;
rcDraw.setLeft( std::max(rcDraw.left(), mGutterWidth));
textPainter.paintTextLines(rcDraw);
}
// Then the gutter area if it was (partly) invalidated.
if (rcClip.left() < mGutterWidth) {
rcDraw = rcClip;
2021-05-27 20:33:25 +08:00
rcDraw.setRight(mGutterWidth-1);
2021-05-24 18:11:07 +08:00
textPainter.paintGutter(rcDraw);
2021-05-24 00:41:00 +08:00
}
//PluginsAfterPaint(Canvas, rcClip, nL1, nL2);
// If there is a custom paint handler call it.
2021-05-24 18:11:07 +08:00
onPaint(painter);
2021-05-24 00:41:00 +08:00
doOnPaintTransient(SynTransientType::ttAfter);
2021-05-24 18:11:07 +08:00
painter.drawImage(rcClip,*mContentImage,rcClip);
2021-05-14 23:56:43 +08:00
}
2021-05-24 18:11:07 +08:00
paintCaret(painter, rcCaret);
2021-05-24 00:41:00 +08:00
}
2021-05-24 21:48:03 +08:00
void SynEdit::resizeEvent(QResizeEvent *)
2021-05-24 00:41:00 +08:00
{
//resize the cache image
std::shared_ptr<QImage> image = std::make_shared<QImage>(clientWidth(),clientHeight(),
QImage::Format_ARGB32);
QRect newRect = image->rect().intersected(mContentImage->rect());
QPainter painter(image.get());
painter.drawImage(newRect,*mContentImage);
mContentImage = image;
sizeOrFontChanged(false);
}
void SynEdit::timerEvent(QTimerEvent *event)
{
if (event->timerId() == m_blinkTimerId) {
2021-05-24 18:11:07 +08:00
m_blinkStatus = 1- m_blinkStatus;
2021-05-27 23:45:22 +08:00
updateCaret();
2021-05-14 23:56:43 +08:00
}
2021-05-24 00:41:00 +08:00
}
2021-05-14 23:56:43 +08:00
2021-05-24 00:41:00 +08:00
bool SynEdit::event(QEvent *event)
{
switch(event->type()) {
case QEvent::FontChange:
synFontChanged();
break;
}
QAbstractScrollArea::event(event);
2021-05-14 23:56:43 +08:00
}
2021-05-24 21:48:03 +08:00
void SynEdit::focusInEvent(QFocusEvent *)
{
showCaret();
}
void SynEdit::focusOutEvent(QFocusEvent *)
{
hideCaret();
}
2021-05-27 20:33:25 +08:00
void SynEdit::keyPressEvent(QKeyEvent *event)
{
SynEditorCommand cmd=TranslateKeyCode(event->key(),event->modifiers());
if (cmd!=SynEditorCommand::ecNone) {
CommandProcessor(cmd,QChar(),nullptr);
event->accept();
} else if (!event->text().isEmpty()) {
QChar c = event->text().at(0);
if (c=='\t' || c.isPrint()) {
CommandProcessor(SynEditorCommand::ecChar,c,nullptr);
event->accept();
}
}
if (!event->isAccepted()) {
QAbstractScrollArea::keyPressEvent(event);
}
}
2021-05-27 23:45:22 +08:00
void SynEdit::mousePressEvent(QMouseEvent *event)
{
bool bWasSel = false;
bool bStartDrag = false;
BufferCoord TmpBegin = mBlockBegin;
BufferCoord TmpEnd = mBlockEnd;
Qt::MouseButton button = event->button();
int X=event->pos().x();
int Y=event->pos().y();
2021-05-29 21:35:46 +08:00
qDebug()<<"Mouse Pressed";
2021-05-27 23:45:22 +08:00
QAbstractScrollArea::mousePressEvent(event);
//fKbdHandler.ExecuteMouseDown(Self, Button, Shift, X, Y);
if (button == Qt::RightButton) {
if (mOptions.testFlag(eoRightMouseMovesCursor) &&
( (selAvail() && ! IsPointInSelection(displayToBufferPos(pixelsToRowColumn(X, Y))))
|| ! selAvail())) {
invalidateSelection();
mBlockEnd=mBlockBegin;
computeCaret(X,Y);
}else {
return;
}
} else if (button == Qt::LeftButton) {
if (selAvail()) {
//remember selection state, as it will be cleared later
bWasSel = true;
2021-05-29 21:35:46 +08:00
mMouseDownPos = event->pos();
2021-05-27 23:45:22 +08:00
}
computeCaret(X,Y);
//I couldn't track down why, but sometimes (and definitely not all the time)
//the block positioning is lost. This makes sure that the block is
//maintained in case they started a drag operation on the block
mBlockBegin = TmpBegin;
mBlockEnd = TmpEnd;
2021-05-29 21:35:46 +08:00
//setMouseTracking(true);
2021-05-27 23:45:22 +08:00
//if mousedown occurred in selected block begin drag operation
mStateFlags.setFlag(SynStateFlag::sfWaitForDragging,false);
if (bWasSel && mOptions.testFlag(eoDragDropEditing) && (X >= mGutterWidth + 2)
&& (mSelectionMode == SynSelectionMode::smNormal) && IsPointInSelection(displayToBufferPos(pixelsToRowColumn(X, Y))) ) {
bStartDrag = true;
}
if (bStartDrag) {
mStateFlags.setFlag(SynStateFlag::sfWaitForDragging);
} else {
2021-05-29 21:35:46 +08:00
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);
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)) {
2021-06-05 23:43:45 +08:00
computeCaret(X, Y);
if (! (event->modifiers() & Qt::ShiftModifier))
setBlockBegin(caretXY());
setBlockEnd(caretXY());
2021-05-29 21:35:46 +08:00
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);
2021-05-27 23:45:22 +08:00
}
2021-06-05 23:43:45 +08:00
// } else if ((buttons == Qt::LeftButton) && (X > mGutterWidth)) {
} else if ((buttons == Qt::LeftButton)) {
2021-05-29 21:35:46 +08:00
// 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;
2021-05-27 23:45:22 +08:00
}
}
2021-06-05 23:43:45 +08:00
void SynEdit::inputMethodEvent(QInputMethodEvent *event)
{
QString s = event->commitString();
if (!s.isEmpty()) {
CommandProcessor(SynEditorCommand::ecImeStr,QChar(),&s);
// for (QChar ch:s) {
// CommandProcessor(SynEditorCommand::ecChar,ch);
// }
}
}
2021-05-14 23:56:43 +08:00
int SynEdit::maxScrollWidth() const
{
return mMaxScrollWidth;
}
void SynEdit::setMaxScrollWidth(int Value)
{
Value = MinMax(Value, 1, INT_MAX - 1);
if (mMaxScrollWidth != Value) {
mMaxScrollWidth = Value;
if (mOptions.testFlag(SynEditorOption::eoScrollPastEol))
updateScrollbars();
}
}
bool SynEdit::modified() const
{
return mModified;
}
void SynEdit::setModified(bool Value)
{
if (Value)
mLastModifyTime = QDateTime::currentDateTime();
if (Value != mModified) {
mModified = Value;
if (mOptions.testFlag(SynEditorOption::eoGroupUndo) && (!Value) && mUndoList->CanUndo())
mUndoList->AddGroupBreak();
mUndoList->setInitialState(!Value);
statusChanged(SynStatusChange::scModified);
}
}
int SynEdit::gutterWidth() const
{
return mGutterWidth;
}
void SynEdit::setGutterWidth(int Value)
{
Value = std::max(Value, 0);
if (mGutterWidth != Value) {
mGutterWidth = Value;
sizeOrFontChanged(false);
}
}
int SynEdit::charWidth() const
{
return mCharWidth;
}
int SynEdit::charsInWindow() const
{
return mCharsInWindow;
}
void SynEdit::bookMarkOptionsChanged()
{
invalidateGutter();
}
void SynEdit::linesChanged()
{
SynSelectionMode vOldMode;
mStateFlags.setFlag(SynStateFlag::sfLinesChanging, false);
if (mUseCodeFolding)
rescan();
updateScrollbars();
vOldMode = mActiveSelectionMode;
setBlockBegin(caretXY());
mActiveSelectionMode = vOldMode;
invalidateRect(mInvalidateRect);
mInvalidateRect = {0,0,0,0};
if (mGutter.showLineNumbers() && (mGutter.autoSize()))
mGutter.autoSizeDigitCount(mLines->count());
if (!mOptions.testFlag(SynEditorOption::eoScrollPastEof))
setTopLine(mTopLine);
}
void SynEdit::linesChanging()
{
mStateFlags.setFlag(SynStateFlag::sfLinesChanging);
}
void SynEdit::linesCleared()
{
if (mUseCodeFolding)
foldOnListCleared();
clearUndo();
// invalidate the *whole* client area
mInvalidateRect={0,0,0,0};
invalidate();
// set caret and selected block to start of text
setCaretXY({1,1});
// scroll to start of text
setTopLine(1);
setLeftChar(1);
mStatusChanges.setFlag(SynStatusChange::scAll);
}
void SynEdit::linesDeleted(int index, int count)
{
if (mUseCodeFolding)
foldOnListDeleted(index + 1, count);
if (mHighlighter && mLines->count() > 0)
scanFrom(index);
invalidateLines(index + 1, INT_MAX);
invalidateGutterLines(index + 1, INT_MAX);
}
void SynEdit::linesInserted(int index, int count)
{
if (mUseCodeFolding)
foldOnListInserted(index + 1, count);
if (mHighlighter && mLines->count() > 0) {
int vLastScan = index;
do {
vLastScan = scanFrom(vLastScan);
vLastScan++;
} while (vLastScan < index + count) ;
}
invalidateLines(index + 1, INT_MAX);
invalidateGutterLines(index + 1, INT_MAX);
}
void SynEdit::linesPutted(int index, int)
{
int vEndLine = index + 1;
if (mHighlighter) {
vEndLine = std::max(vEndLine, scanFrom(index) + 1);
// If this editor is chained then the real owner of text buffer will probably
// have already parsed the changes, so ScanFrom will return immediately.
if (mLines != mOrigLines)
vEndLine = INT_MAX;
}
invalidateLines(index + 1, vEndLine);
}
void SynEdit::undoAdded()
{
updateModifiedStatus();
// we have to clear the redo information, since adding undo info removes
// the necessary context to undo earlier edit actions
if (! mUndoList->insideRedo() &&
mUndoList->PeekItem() && (mUndoList->PeekItem()->changeReason()!=SynChangeReason::crGroupBreak))
mRedoList->Clear();
if (mUndoList->blockCount() == 0 )
doChange();
}
SynSelectionMode SynEdit::activeSelectionMode() const
{
return mActiveSelectionMode;
}
void SynEdit::setActiveSelectionMode(const SynSelectionMode &Value)
{
if (mActiveSelectionMode != Value) {
if (selAvail())
invalidateSelection();
mActiveSelectionMode = Value;
if (selAvail())
invalidateSelection();
setStatusChanged(SynStatusChange::scSelection);
}
}
BufferCoord SynEdit::blockEnd() const
{
2021-06-03 20:26:36 +08:00
if ((mBlockEnd.Line < mBlockBegin.Line)
|| ((mBlockEnd.Line == mBlockBegin.Line) && (mBlockEnd.Char < mBlockBegin.Char)))
return mBlockBegin;
else
return mBlockEnd;
2021-05-14 23:56:43 +08:00
}
void SynEdit::setBlockEnd(BufferCoord Value)
{
setActiveSelectionMode(mSelectionMode);
if (!mOptions.testFlag(eoNoSelection)) {
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());
if (mActiveSelectionMode == SynSelectionMode::smNormal) {
if (Value.Line >= 1 && Value.Line <= mLines->count())
Value.Char = std::min(Value.Char, mLines->getString(Value.Line - 1).length() + 1);
else
Value.Char = 1;
}
if (Value.Char != mBlockEnd.Char || Value.Line != mBlockEnd.Line) {
2021-06-03 20:26:36 +08:00
if (mActiveSelectionMode == SynSelectionMode::smColumn && Value.Char != mBlockEnd.Char) {
invalidateLines(
std::min(mBlockBegin.Line, std::min(mBlockEnd.Line, Value.Line)),
std::max(mBlockBegin.Line, std::max(mBlockEnd.Line, Value.Line)));
mBlockEnd = Value;
} else {
int nLine = mBlockEnd.Line;
mBlockEnd = Value;
if (mActiveSelectionMode != SynSelectionMode::smColumn || mBlockBegin.Char != mBlockEnd.Char)
invalidateLines(nLine, mBlockEnd.Line);
2021-05-14 23:56:43 +08:00
}
2021-06-03 20:26:36 +08:00
setStatusChanged(SynStatusChange::scSelection);
2021-05-14 23:56:43 +08:00
}
}
}
2021-06-03 20:26:36 +08:00
void SynEdit::setSelLength(int Value)
{
if (mBlockBegin.Line>mLines->count() || mBlockBegin.Line<=0)
return;
if (Value >= 0) {
int y = mBlockBegin.Line;
int ch = mBlockBegin.Char;
int x = ch + Value;
QString line;
while (y<=mLines->count()) {
line = mLines->getString(y-1);
if (x <= line.length()+2) {
if (x==line.length()+2)
x = line.length()+1;
break;
}
x -= line.length()+2;
y ++;
}
if (y>mLines->count()) {
y = mLines->count();
x = mLines->getString(y-1).length()+1;
}
BufferCoord iNewEnd{x,y};
setCaretAndSelection(iNewEnd, mBlockBegin, iNewEnd);
} else {
int y = mBlockBegin.Line;
int ch = mBlockBegin.Char;
int x = ch + Value;
QString line;
while (y>=1) {
if (x>=0) {
if (x==0)
x = 1;
break;
}
y--;
line = mLines->getString(y-1);
x += line.length()+2;
}
if (y>mLines->count()) {
y = mLines->count();
x = mLines->getString(y-1).length()+1;
}
BufferCoord iNewStart{x,y};
setCaretAndSelection(iNewStart, iNewStart, mBlockBegin);
}
}
2021-05-14 23:56:43 +08:00
BufferCoord SynEdit::blockBegin() const
{
2021-06-03 20:26:36 +08:00
if ((mBlockEnd.Line < mBlockBegin.Line)
|| ((mBlockEnd.Line == mBlockBegin.Line) && (mBlockEnd.Char < mBlockBegin.Char)))
return mBlockEnd;
else
return mBlockBegin;
2021-05-14 23:56:43 +08:00
}
void SynEdit::setBlockBegin(BufferCoord value)
{
int nInval1, nInval2;
bool SelChanged;
setActiveSelectionMode(mSelectionMode);
if (mOptions.testFlag(SynEditorOption::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());
if (mActiveSelectionMode == SynSelectionMode::smNormal) {
if (value.Line >= 1 && value.Line <= mLines->count())
value.Char = std::min(value.Char, mLines->getString(value.Line - 1).length() + 1);
else
value.Char = 1;
}
if (selAvail()) {
if (mBlockBegin.Line < mBlockEnd.Line) {
nInval1 = std::min(value.Line, mBlockBegin.Line);
nInval2 = std::max(value.Line, mBlockEnd.Line);
} else {
nInval1 = std::min(value.Line, mBlockEnd.Line);
nInval2 = std::max(value.Line, mBlockBegin.Line);
};
mBlockBegin = value;
mBlockEnd = value;
invalidateLines(nInval1, nInval2);
SelChanged = true;
} else {
SelChanged =
(mBlockBegin.Char != value.Char) || (mBlockBegin.Line != value.Line) ||
(mBlockEnd.Char != value.Char) || (mBlockEnd.Line != value.Line);
mBlockBegin = value;
mBlockEnd = value;
}
if (SelChanged)
setStatusChanged(SynStatusChange::scSelection);
}
int SynEdit::leftChar() const
{
return mLeftChar;
}
void SynEdit::setLeftChar(int Value)
{
int MaxVal;
//QRect iTextArea;
2021-05-24 21:48:03 +08:00
MaxVal = mLines->lengthOfLongestLine();
2021-05-14 23:56:43 +08:00
if (mOptions.testFlag(SynEditorOption::eoScrollPastEol)) {
2021-05-24 21:48:03 +08:00
Value = std::min(Value,MaxVal);
2021-05-14 23:56:43 +08:00
} else {
2021-05-24 21:48:03 +08:00
Value = std::min(Value,MaxVal-mCharsInWindow+1);
2021-05-14 23:56:43 +08:00
}
if (Value != mLeftChar) {
2021-05-24 21:48:03 +08:00
horizontalScrollBar()->setValue(Value);
2021-05-14 23:56:43 +08:00
setStatusChanged(SynStatusChange::scLeftChar);
}
}
int SynEdit::linesInWindow() const
{
return mLinesInWindow;
}
int SynEdit::topLine() const
{
return mTopLine;
}
void SynEdit::setTopLine(int Value)
{
if (mOptions.testFlag(SynEditorOption::eoScrollPastEof))
Value = std::min(Value, displayLineCount());
else
Value = std::min(Value, displayLineCount() - mLinesInWindow + 1);
Value = std::max(Value, 1);
if (Value != mTopLine) {
2021-05-24 21:48:03 +08:00
//updateScrollbars();
verticalScrollBar()->setValue(Value);
2021-05-14 23:56:43 +08:00
setStatusChanged(SynStatusChange::scTopLine);
}
}
void SynEdit::redoAdded()
{
updateModifiedStatus();
if (mRedoList->blockCount() == 0 )
doChange();
}
void SynEdit::gutterChanged()
{
if (mGutter.showLineNumbers() && mGutter.autoSize())
mGutter.autoSizeDigitCount(mLines->count());
int nW;
if (mGutter.useFontStyle()) {
QFontMetrics fm=QFontMetrics(mGutter.font());
nW = mGutter.realGutterWidth(fm.averageCharWidth());
} else {
nW = mGutter.realGutterWidth(mCharWidth);
}
if (nW == mGutterWidth)
invalidateGutter();
else
setGutterWidth(nW);
}
void SynEdit::scrollTimerHandler()
{
QPoint iMousePos;
DisplayCoord C;
int X, Y;
iMousePos = QCursor::pos();
iMousePos = mapFromGlobal(iMousePos);
C = pixelsToRowColumn(iMousePos.x(), iMousePos.y());
C.Row = MinMax(C.Row, 1, displayLineCount());
if (mScrollDeltaX != 0) {
setLeftChar(leftChar() + mScrollDeltaX);
X = leftChar();
if (mScrollDeltaX > 0) // scrolling right?
X+=charsInWindow();
C.Column = X;
}
if (mScrollDeltaY != 0) {
if (QApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier))
setTopLine(mTopLine + mScrollDeltaY * mLinesInWindow);
else
setTopLine(mTopLine + mScrollDeltaY);
Y = mTopLine;
if (mScrollDeltaY > 0) // scrolling down?
Y+=mLinesInWindow - 1;
C.Row = MinMax(Y, 1, displayLineCount());
}
BufferCoord vCaret = displayToBufferPos(C);
if ((caretX() != vCaret.Char) || (caretY() != vCaret.Line)) {
// changes to line / column in one go
incPaintLock();
auto action = finally([this]{
decPaintLock();
});
internalSetCaretXY(vCaret);
2021-06-05 23:43:45 +08:00
// if MouseCapture is True we're changing selection. otherwise we're dragging
// if (mouseCapture())
setBlockEnd(caretXY());
2021-05-14 23:56:43 +08:00
}
computeScroll(iMousePos.x(), iMousePos.y());
}