2021-04-06 23:10:57 +08:00
|
|
|
#include "editor.h"
|
|
|
|
|
|
|
|
#include <QtCore/QFileInfo>
|
2021-04-09 17:48:25 +08:00
|
|
|
#include <QFont>
|
2021-04-08 10:29:21 +08:00
|
|
|
#include <QTextCodec>
|
2021-04-07 22:44:08 +08:00
|
|
|
#include <QVariant>
|
2021-04-09 17:48:25 +08:00
|
|
|
#include <QWheelEvent>
|
2021-04-07 21:13:15 +08:00
|
|
|
#include <memory>
|
2021-04-08 10:29:21 +08:00
|
|
|
#include "settings.h"
|
|
|
|
#include "mainwindow.h"
|
2021-04-11 21:33:08 +08:00
|
|
|
#include "systemconsts.h"
|
2021-04-11 12:39:22 +08:00
|
|
|
#include <QFileDialog>
|
|
|
|
#include <QMessageBox>
|
|
|
|
#include <QDebug>
|
2021-06-12 22:36:23 +08:00
|
|
|
#include <QMimeData>
|
2021-05-24 00:41:00 +08:00
|
|
|
#include "qsynedit/highlighter/cpp.h"
|
2021-05-26 00:04:20 +08:00
|
|
|
#include "HighlighterManager.h"
|
2021-06-12 22:36:23 +08:00
|
|
|
#include "qsynedit/exporter/synrtfexporter.h"
|
|
|
|
#include "qsynedit/exporter/synhtmlexporter.h"
|
2021-06-19 22:58:35 +08:00
|
|
|
#include "qsynedit/Constants.h"
|
2021-06-12 22:36:23 +08:00
|
|
|
#include <QGuiApplication>
|
|
|
|
#include <QClipboard>
|
2021-06-24 16:05:19 +08:00
|
|
|
#include <QPainter>
|
2021-08-29 17:23:40 +08:00
|
|
|
#include <QToolTip>
|
2021-08-29 22:08:43 +08:00
|
|
|
#include <QApplication>
|
2021-06-24 16:05:19 +08:00
|
|
|
#include "iconsmanager.h"
|
2021-08-01 09:13:38 +08:00
|
|
|
#include "debugger.h"
|
2021-08-23 10:16:06 +08:00
|
|
|
#include "editorlist.h"
|
2021-08-29 17:23:40 +08:00
|
|
|
#include <QDebug>
|
2021-04-07 21:13:15 +08:00
|
|
|
|
|
|
|
using namespace std;
|
2021-04-06 23:10:57 +08:00
|
|
|
|
2021-04-11 21:33:08 +08:00
|
|
|
SaveException::SaveException(const QString& reason) {
|
|
|
|
mReason = reason;
|
2021-08-29 10:14:07 +08:00
|
|
|
mReasonBuffer = mReason.toLocal8Bit();
|
2021-04-11 21:33:08 +08:00
|
|
|
}
|
|
|
|
SaveException::SaveException(const QString&& reason) {
|
|
|
|
mReason = reason;
|
2021-08-29 10:14:07 +08:00
|
|
|
mReasonBuffer = mReason.toLocal8Bit();
|
2021-04-11 21:33:08 +08:00
|
|
|
}
|
|
|
|
const QString& SaveException::reason() const noexcept{
|
|
|
|
return mReason;
|
|
|
|
}
|
2021-08-29 10:14:07 +08:00
|
|
|
const char* SaveException::what() const noexcept {
|
|
|
|
return mReasonBuffer;
|
2021-04-11 21:33:08 +08:00
|
|
|
}
|
|
|
|
|
2021-05-26 00:04:20 +08:00
|
|
|
int Editor::newfileCount=0;
|
2021-04-06 23:10:57 +08:00
|
|
|
|
2021-06-19 22:58:35 +08:00
|
|
|
Editor::Editor(QWidget *parent):
|
|
|
|
Editor(parent,QObject::tr("untitled"),ENCODING_SYSTEM_DEFAULT,false,true,nullptr)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2021-04-09 17:48:25 +08:00
|
|
|
Editor::Editor(QWidget *parent, const QString& filename,
|
2021-04-08 10:29:21 +08:00
|
|
|
const QByteArray& encoding,
|
2021-04-06 23:10:57 +08:00
|
|
|
bool inProject, bool isNew,
|
|
|
|
QTabWidget* parentPageControl):
|
2021-05-24 00:41:00 +08:00
|
|
|
SynEdit(parent),
|
2021-04-08 10:29:21 +08:00
|
|
|
mEncodingOption(encoding),
|
2021-06-20 14:30:47 +08:00
|
|
|
mFilename(filename),
|
|
|
|
mParentPageControl(parentPageControl),
|
2021-04-06 23:10:57 +08:00
|
|
|
mInProject(inProject),
|
2021-06-24 16:05:19 +08:00
|
|
|
mIsNew(isNew),
|
|
|
|
mSyntaxErrorColor(QColorConstants::Red),
|
2021-07-26 00:22:08 +08:00
|
|
|
mSyntaxWarningColor("orange"),
|
|
|
|
mLineCount(0),
|
2021-08-25 08:48:33 +08:00
|
|
|
mActiveBreakpointLine(-1),
|
2021-08-29 17:23:40 +08:00
|
|
|
mLastIdCharPressed(0),
|
|
|
|
mCurrentWord(),
|
|
|
|
mCurrentTipType(TipType::None)
|
2021-04-06 23:10:57 +08:00
|
|
|
{
|
2021-04-07 21:13:15 +08:00
|
|
|
if (mFilename.isEmpty()) {
|
2021-05-26 00:04:20 +08:00
|
|
|
newfileCount++;
|
2021-06-07 21:34:48 +08:00
|
|
|
mFilename = tr("untitled%1").arg(newfileCount);
|
2021-04-07 21:13:15 +08:00
|
|
|
}
|
2021-04-06 23:10:57 +08:00
|
|
|
QFileInfo fileInfo(mFilename);
|
2021-06-19 22:58:35 +08:00
|
|
|
if (mParentPageControl!=nullptr) {
|
2021-04-11 12:39:22 +08:00
|
|
|
mParentPageControl->addTab(this,QString());
|
|
|
|
updateCaption();
|
|
|
|
}
|
2021-08-27 16:38:55 +08:00
|
|
|
|
2021-05-27 01:05:49 +08:00
|
|
|
PSynHighlighter highlighter;
|
2021-04-06 23:10:57 +08:00
|
|
|
if (!isNew) {
|
|
|
|
loadFile();
|
2021-06-07 11:02:03 +08:00
|
|
|
highlighter = highlighterManager.getHighlighter(mFilename);
|
2021-04-06 23:10:57 +08:00
|
|
|
} else {
|
2021-04-08 10:29:21 +08:00
|
|
|
if (mEncodingOption == ENCODING_AUTO_DETECT)
|
|
|
|
mFileEncoding = ENCODING_ASCII;
|
2021-04-07 21:13:15 +08:00
|
|
|
else
|
2021-04-08 10:29:21 +08:00
|
|
|
mFileEncoding = mEncodingOption;
|
2021-06-07 11:02:03 +08:00
|
|
|
highlighter=highlighterManager.getCppHighlighter();
|
2021-05-27 01:05:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (highlighter) {
|
|
|
|
setHighlighter(highlighter);
|
|
|
|
setUseCodeFolding(true);
|
|
|
|
} else {
|
|
|
|
setUseCodeFolding(false);
|
2021-04-06 23:10:57 +08:00
|
|
|
}
|
2021-04-09 17:48:25 +08:00
|
|
|
|
2021-08-23 17:27:17 +08:00
|
|
|
if (inProject) {
|
|
|
|
//todo:
|
|
|
|
} else {
|
|
|
|
initParser();
|
|
|
|
}
|
2021-08-29 10:29:56 +08:00
|
|
|
// mCompletionPopup = std::make_shared<CodeCompletionPopup>();
|
|
|
|
// mHeaderCompletionPopup = std::make_shared<HeaderCompletionPopup>();
|
|
|
|
mCompletionPopup = pMainWindow->completionPopup();
|
|
|
|
mHeaderCompletionPopup = pMainWindow->headerCompletionPopup();
|
2021-08-25 08:48:33 +08:00
|
|
|
|
2021-06-07 11:02:03 +08:00
|
|
|
applySettings();
|
2021-06-20 14:30:47 +08:00
|
|
|
applyColorScheme(pSettings->editor().colorScheme());
|
2021-06-10 09:34:59 +08:00
|
|
|
|
|
|
|
connect(this,&SynEdit::statusChanged,this,&Editor::onStatusChanged);
|
2021-07-03 13:57:22 +08:00
|
|
|
connect(this,&SynEdit::gutterClicked,this,&Editor::onGutterClicked);
|
2021-08-23 17:27:17 +08:00
|
|
|
|
|
|
|
onStatusChanged(SynStatusChange::scOpenFile);
|
2021-04-07 22:44:08 +08:00
|
|
|
}
|
2021-04-06 23:10:57 +08:00
|
|
|
|
2021-04-07 22:44:08 +08:00
|
|
|
Editor::~Editor() {
|
2021-06-19 22:58:35 +08:00
|
|
|
if (mParentPageControl!=nullptr) {
|
2021-04-09 17:48:25 +08:00
|
|
|
int index = mParentPageControl->indexOf(this);
|
|
|
|
mParentPageControl->removeTab(index);
|
|
|
|
}
|
|
|
|
this->setParent(0);
|
2021-04-06 23:10:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::loadFile() {
|
|
|
|
QFile file(mFilename);
|
2021-05-24 00:41:00 +08:00
|
|
|
this->lines()->LoadFromFile(file,mEncodingOption,mFileEncoding);
|
2021-05-24 18:11:07 +08:00
|
|
|
this->setModified(false);
|
|
|
|
updateCaption();
|
2021-06-12 22:36:23 +08:00
|
|
|
pMainWindow->updateForEncodingInfo();
|
2021-06-25 12:40:11 +08:00
|
|
|
if (pSettings->editor().syntaxCheck() && pSettings->editor().syntaxCheckWhenSave())
|
|
|
|
pMainWindow->checkSyntaxInBack(this);
|
2021-04-07 21:13:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::saveFile(const QString &filename) {
|
|
|
|
QFile file(filename);
|
2021-05-24 00:41:00 +08:00
|
|
|
this->lines()->SaveToFile(file,mEncodingOption,mFileEncoding);
|
2021-06-12 22:36:23 +08:00
|
|
|
pMainWindow->updateForEncodingInfo();
|
2021-06-25 12:40:11 +08:00
|
|
|
if (pSettings->editor().syntaxCheck() && pSettings->editor().syntaxCheckWhenSave())
|
|
|
|
pMainWindow->checkSyntaxInBack(this);
|
2021-06-12 22:36:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::convertToEncoding(const QByteArray &encoding)
|
|
|
|
{
|
|
|
|
mEncodingOption = encoding;
|
|
|
|
setModified(true);
|
|
|
|
save();
|
2021-04-06 23:10:57 +08:00
|
|
|
}
|
|
|
|
|
2021-08-23 10:16:06 +08:00
|
|
|
bool Editor::save(bool force, bool doReparse) {
|
2021-04-11 12:39:22 +08:00
|
|
|
if (this->mIsNew) {
|
|
|
|
return saveAs();
|
|
|
|
}
|
|
|
|
QFileInfo info(mFilename);
|
2021-04-11 21:33:08 +08:00
|
|
|
//is this file writable;
|
2021-04-11 12:39:22 +08:00
|
|
|
if (!force && !info.isWritable()) {
|
2021-06-21 11:21:26 +08:00
|
|
|
QMessageBox::critical(pMainWindow,tr("Error"),
|
2021-06-20 22:54:16 +08:00
|
|
|
tr("File %1 is not writable!").arg(mFilename));
|
2021-04-11 12:39:22 +08:00
|
|
|
return false;
|
|
|
|
}
|
2021-05-24 00:41:00 +08:00
|
|
|
if (this->modified()|| force) {
|
2021-04-11 21:33:08 +08:00
|
|
|
try {
|
|
|
|
saveFile(mFilename);
|
|
|
|
setModified(false);
|
2021-06-12 22:36:23 +08:00
|
|
|
mIsNew = false;
|
|
|
|
this->updateCaption();
|
2021-04-11 21:33:08 +08:00
|
|
|
} catch (SaveException& exception) {
|
2021-08-23 10:16:06 +08:00
|
|
|
if (!force) {
|
|
|
|
QMessageBox::critical(pMainWindow,tr("Error"),
|
2021-04-11 21:33:08 +08:00
|
|
|
exception.reason());
|
2021-08-23 10:16:06 +08:00
|
|
|
}
|
2021-04-11 21:33:08 +08:00
|
|
|
return false;
|
|
|
|
}
|
2021-04-11 12:39:22 +08:00
|
|
|
}
|
2021-04-08 10:29:21 +08:00
|
|
|
|
2021-08-23 10:16:06 +08:00
|
|
|
if (doReparse && mParser) {
|
|
|
|
reparse();
|
2021-04-11 12:39:22 +08:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::saveAs(){
|
2021-04-11 21:33:08 +08:00
|
|
|
QString selectedFileFilter = pSystemConsts->defaultFileFilter();
|
2021-04-11 12:39:22 +08:00
|
|
|
QString newName = QFileDialog::getSaveFileName(pMainWindow,
|
2021-04-11 21:33:08 +08:00
|
|
|
tr("Save As"), QString(), pSystemConsts->defaultFileFilters().join(";;"),
|
|
|
|
&selectedFileFilter);
|
2021-04-11 12:39:22 +08:00
|
|
|
if (newName.isEmpty()) {
|
|
|
|
return false;
|
|
|
|
}
|
2021-04-11 21:33:08 +08:00
|
|
|
try {
|
|
|
|
mFilename = newName;
|
2021-06-12 22:36:23 +08:00
|
|
|
saveFile(mFilename);
|
2021-04-11 21:33:08 +08:00
|
|
|
mIsNew = false;
|
|
|
|
setModified(false);
|
2021-06-12 22:36:23 +08:00
|
|
|
this->updateCaption();
|
2021-04-11 21:33:08 +08:00
|
|
|
} catch (SaveException& exception) {
|
2021-06-21 11:21:26 +08:00
|
|
|
QMessageBox::critical(pMainWindow,tr("Error"),
|
2021-04-11 21:33:08 +08:00
|
|
|
exception.reason());
|
|
|
|
return false;
|
|
|
|
}
|
2021-04-11 12:39:22 +08:00
|
|
|
|
|
|
|
//todo: update (reassign highlighter)
|
|
|
|
//todo: remove old file from parser and reparse file
|
|
|
|
//todo: unmoniter/ monitor file
|
|
|
|
//todo: update windows caption
|
|
|
|
//todo: update class browser;
|
2021-04-08 10:29:21 +08:00
|
|
|
return true;
|
2021-04-06 23:10:57 +08:00
|
|
|
}
|
2021-04-08 10:29:21 +08:00
|
|
|
|
2021-04-12 00:00:29 +08:00
|
|
|
void Editor::activate()
|
|
|
|
{
|
2021-06-19 22:58:35 +08:00
|
|
|
if (mParentPageControl!=nullptr)
|
|
|
|
mParentPageControl->setCurrentWidget(this);
|
|
|
|
setFocus();
|
2021-04-12 00:00:29 +08:00
|
|
|
}
|
|
|
|
|
2021-04-11 21:33:08 +08:00
|
|
|
const QByteArray& Editor::encodingOption() const noexcept{
|
2021-04-08 10:29:21 +08:00
|
|
|
return mEncodingOption;
|
2021-04-06 23:10:57 +08:00
|
|
|
}
|
2021-04-11 21:33:08 +08:00
|
|
|
void Editor::setEncodingOption(const QByteArray& encoding) noexcept{
|
2021-04-08 10:29:21 +08:00
|
|
|
mEncodingOption = encoding;
|
2021-06-20 09:27:37 +08:00
|
|
|
if (!isNew())
|
|
|
|
loadFile();
|
2021-06-12 22:36:23 +08:00
|
|
|
else
|
|
|
|
pMainWindow->updateForEncodingInfo();
|
2021-04-08 10:29:21 +08:00
|
|
|
}
|
2021-04-11 21:33:08 +08:00
|
|
|
const QByteArray& Editor::fileEncoding() const noexcept{
|
2021-04-06 23:10:57 +08:00
|
|
|
return mFileEncoding;
|
|
|
|
}
|
2021-04-11 21:33:08 +08:00
|
|
|
const QString& Editor::filename() const noexcept{
|
2021-04-06 23:10:57 +08:00
|
|
|
return mFilename;
|
|
|
|
}
|
2021-04-11 21:33:08 +08:00
|
|
|
bool Editor::inProject() const noexcept{
|
2021-04-06 23:10:57 +08:00
|
|
|
return mInProject;
|
|
|
|
}
|
2021-04-11 21:33:08 +08:00
|
|
|
bool Editor::isNew() const noexcept {
|
2021-04-06 23:10:57 +08:00
|
|
|
return mIsNew;
|
|
|
|
}
|
2021-04-09 17:48:25 +08:00
|
|
|
|
2021-04-11 21:33:08 +08:00
|
|
|
QTabWidget* Editor::pageControl() noexcept{
|
2021-04-09 17:48:25 +08:00
|
|
|
return mParentPageControl;
|
|
|
|
}
|
|
|
|
|
2021-06-23 08:55:56 +08:00
|
|
|
void Editor::undoSymbolCompletion(int pos)
|
|
|
|
{
|
|
|
|
PSynHighlighterAttribute Attr;
|
|
|
|
QString Token;
|
|
|
|
bool tokenFinished;
|
|
|
|
SynHighlighterTokenType tokenType;
|
|
|
|
|
|
|
|
if (!highlighter())
|
|
|
|
return;
|
|
|
|
if (!pSettings->editor().removeSymbolPairs())
|
|
|
|
return;
|
|
|
|
if (!GetHighlighterAttriAtRowCol(caretXY(), Token, tokenFinished, tokenType, Attr))
|
|
|
|
return;
|
|
|
|
if ((tokenType == SynHighlighterTokenType::Comment) && (!tokenFinished))
|
|
|
|
return ;
|
|
|
|
//convert caret x to string index;
|
|
|
|
pos--;
|
|
|
|
|
|
|
|
if (pos<0 || pos+1>=lineText().length())
|
|
|
|
return;
|
2021-08-29 10:14:07 +08:00
|
|
|
QChar DeletedChar = lineText().at(pos);
|
|
|
|
QChar NextChar = lineText().at(pos+1);
|
2021-06-23 08:55:56 +08:00
|
|
|
if ((tokenType == SynHighlighterTokenType::Character) && (DeletedChar != '\''))
|
|
|
|
return;
|
|
|
|
if (tokenType == SynHighlighterTokenType::StringEscapeSequence)
|
|
|
|
return;
|
|
|
|
if (tokenType == SynHighlighterTokenType::String) {
|
|
|
|
if ((DeletedChar!='"') && (DeletedChar!='('))
|
|
|
|
return;
|
|
|
|
if ((DeletedChar=='"') && (Token!="\"\""))
|
|
|
|
return;
|
|
|
|
if ((DeletedChar=='(') && (!Token.startsWith("R\"")))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ((DeletedChar == '\'') && (tokenType == SynHighlighterTokenType::Number))
|
|
|
|
return;
|
|
|
|
if ((DeletedChar == '<') &&
|
|
|
|
((tokenType != SynHighlighterTokenType::PreprocessDirective)
|
|
|
|
|| !lineText().startsWith("#include")))
|
|
|
|
return;
|
|
|
|
if ( (pSettings->editor().completeBracket() && (DeletedChar == '[') && (NextChar == ']')) ||
|
|
|
|
(pSettings->editor().completeParenthese() && (DeletedChar == '(') && (NextChar == ')')) ||
|
|
|
|
(pSettings->editor().completeGlobalInclude() && (DeletedChar == '<') && (NextChar == '>')) ||
|
|
|
|
(pSettings->editor().completeBrace() && (DeletedChar == '{') && (NextChar == '}')) ||
|
|
|
|
(pSettings->editor().completeSingleQuote() && (DeletedChar == '\'') && (NextChar == '\'')) ||
|
|
|
|
(pSettings->editor().completeDoubleQuote() && (DeletedChar == '\"') && (NextChar == '\"'))) {
|
|
|
|
CommandProcessor(SynEditorCommand::ecDeleteChar);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-09 17:48:25 +08:00
|
|
|
void Editor::wheelEvent(QWheelEvent *event) {
|
|
|
|
if ( (event->modifiers() & Qt::ControlModifier)!=0) {
|
2021-06-12 22:36:23 +08:00
|
|
|
int size = pSettings->editor().fontSize();
|
2021-04-09 17:48:25 +08:00
|
|
|
if (event->angleDelta().y()>0) {
|
2021-06-12 22:36:23 +08:00
|
|
|
size = std::min(99,size+1);
|
|
|
|
pSettings->editor().setFontSize(size);
|
|
|
|
pMainWindow->updateEditorSettings();
|
2021-06-09 17:12:23 +08:00
|
|
|
event->accept();
|
|
|
|
return;
|
2021-07-01 19:44:38 +08:00
|
|
|
} else if (event->angleDelta().y()<0) {
|
2021-06-12 22:36:23 +08:00
|
|
|
size = std::max(2,size-1);
|
|
|
|
pSettings->editor().setFontSize(size);
|
|
|
|
pMainWindow->updateEditorSettings();
|
2021-06-09 17:12:23 +08:00
|
|
|
event->accept();
|
|
|
|
return;
|
2021-04-09 17:48:25 +08:00
|
|
|
}
|
|
|
|
}
|
2021-06-09 17:12:23 +08:00
|
|
|
SynEdit::wheelEvent(event);
|
2021-04-08 10:29:21 +08:00
|
|
|
}
|
2021-04-11 12:39:22 +08:00
|
|
|
|
2021-06-12 22:36:23 +08:00
|
|
|
void Editor::focusInEvent(QFocusEvent *event)
|
|
|
|
{
|
|
|
|
SynEdit::focusInEvent(event);
|
|
|
|
pMainWindow->updateEditorActions();
|
|
|
|
pMainWindow->updateStatusbarForLineCol();
|
|
|
|
pMainWindow->updateForStatusbarModeInfo();
|
2021-08-23 17:27:17 +08:00
|
|
|
pMainWindow->updateClassBrowserForEditor(this);
|
2021-06-12 22:36:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::focusOutEvent(QFocusEvent *event)
|
|
|
|
{
|
|
|
|
SynEdit::focusOutEvent(event);
|
|
|
|
pMainWindow->updateEditorActions();
|
|
|
|
pMainWindow->updateStatusbarForLineCol();
|
|
|
|
pMainWindow->updateForStatusbarModeInfo();
|
|
|
|
}
|
|
|
|
|
2021-06-21 16:25:21 +08:00
|
|
|
void Editor::keyPressEvent(QKeyEvent *event)
|
|
|
|
{
|
|
|
|
bool handled = false;
|
2021-08-25 08:48:33 +08:00
|
|
|
auto action = finally([&,this]{
|
|
|
|
if (!handled) {
|
|
|
|
SynEdit::keyPressEvent(event);
|
|
|
|
} else {
|
|
|
|
event->accept();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (readOnly())
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (event->key()) {
|
|
|
|
case Qt::Key_Delete:
|
|
|
|
// remove completed character
|
|
|
|
mLastIdCharPressed = 0;
|
|
|
|
undoSymbolCompletion(caretX());
|
|
|
|
return;
|
|
|
|
case Qt::Key_Backspace:
|
|
|
|
// remove completed character
|
|
|
|
mLastIdCharPressed = 0;
|
|
|
|
undoSymbolCompletion(caretX()-1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString t = event->text();
|
|
|
|
if (t.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
QChar ch = t[0];
|
|
|
|
if (isIdentChar(ch)) {
|
|
|
|
mLastIdCharPressed++;
|
|
|
|
// if devCodeCompletion.Enabled and devCodeCompletion.ShowCompletionWhileInput then begin
|
|
|
|
if (mLastIdCharPressed==1) {
|
|
|
|
if (mParser->isIncludeLine(lineText())) {
|
|
|
|
// is a #include line
|
|
|
|
setSelText(ch);
|
|
|
|
showHeaderCompletion(false);
|
|
|
|
handled=true;
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
QString lastWord = getPreviousWordAtPositionForSuggestion(caretXY());
|
|
|
|
if (!lastWord.isEmpty()) {
|
|
|
|
if (CppTypeKeywords.contains(lastWord)) {
|
|
|
|
//last word is a type keyword, this is a var or param define, and dont show suggestion
|
|
|
|
// if devEditor.UseTabnine then
|
|
|
|
// ShowTabnineCompletion;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
PStatement statement = mParser->findStatementOf(
|
|
|
|
mFilename,
|
|
|
|
lastWord,
|
|
|
|
caretY());
|
|
|
|
StatementKind kind = mParser->getKindOfStatement(statement);
|
|
|
|
if (kind == StatementKind::skClass
|
|
|
|
|| kind == StatementKind::skEnumClassType
|
|
|
|
|| kind == StatementKind::skEnumType
|
|
|
|
|| kind == StatementKind::skTypedef) {
|
|
|
|
//last word is a typedef/class/struct, this is a var or param define, and dont show suggestion
|
|
|
|
// if devEditor.UseTabnine then
|
|
|
|
// ShowTabnineCompletion;
|
|
|
|
return;
|
|
|
|
}
|
2021-08-18 13:42:32 +08:00
|
|
|
}
|
2021-08-25 08:48:33 +08:00
|
|
|
setSelText(ch);
|
|
|
|
showCompletion(false);
|
|
|
|
handled=true;
|
2021-08-26 20:18:20 +08:00
|
|
|
return;
|
2021-06-23 08:55:56 +08:00
|
|
|
}
|
2021-06-21 16:25:21 +08:00
|
|
|
}
|
2021-08-25 08:48:33 +08:00
|
|
|
|
|
|
|
// }
|
2021-06-21 16:25:21 +08:00
|
|
|
} else {
|
2021-08-25 08:48:33 +08:00
|
|
|
//preprocessor ?
|
|
|
|
if ((mLastIdCharPressed=0) && (ch=='#') && lineText().isEmpty()) {
|
|
|
|
// and devCodeCompletion.Enabled and devCodeCompletion.ShowCompletionWhileInput
|
|
|
|
mLastIdCharPressed++;
|
|
|
|
setSelText(ch);
|
|
|
|
showCompletion(false);
|
|
|
|
handled=true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//javadoc directive?
|
|
|
|
if ((mLastIdCharPressed=0) && (ch=='#') &&
|
|
|
|
lineText().trimmed().startsWith('*')) {
|
|
|
|
// and devCodeCompletion.Enabled and devCodeCompletion.ShowCompletionWhileInput
|
|
|
|
mLastIdCharPressed++;
|
|
|
|
setSelText(ch);
|
|
|
|
showCompletion(false);
|
|
|
|
handled=true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mLastIdCharPressed = 0;
|
|
|
|
switch (ch.unicode()) {
|
|
|
|
case '"':
|
|
|
|
case '\'':
|
|
|
|
case '(':
|
|
|
|
case ')':
|
|
|
|
case '{':
|
|
|
|
case '}':
|
|
|
|
case '[':
|
|
|
|
case ']':
|
|
|
|
case '<':
|
|
|
|
case '>':
|
|
|
|
case '*':
|
|
|
|
handled = handleSymbolCompletion(ch);
|
|
|
|
return;
|
|
|
|
}
|
2021-06-21 16:25:21 +08:00
|
|
|
}
|
2021-08-25 08:48:33 +08:00
|
|
|
|
|
|
|
// Spawn code completion window if we are allowed to
|
|
|
|
// if devCodeCompletion.Enabled then begin
|
2021-08-26 20:18:20 +08:00
|
|
|
handled = handleCodeCompletion(ch);
|
2021-08-25 08:48:33 +08:00
|
|
|
// end;
|
2021-06-21 16:25:21 +08:00
|
|
|
}
|
|
|
|
|
2021-06-24 16:05:19 +08:00
|
|
|
void Editor::onGutterPaint(QPainter &painter, int aLine, int X, int Y)
|
|
|
|
{
|
|
|
|
// Get point where to draw marks
|
|
|
|
//X := (fText.Gutter.RealGutterWidth(fText.CharWidth) - fText.Gutter.RightOffset) div 2 - 3;
|
|
|
|
X = 5;
|
|
|
|
Y += (this->textHeight() - 16) / 2;
|
|
|
|
|
2021-07-26 00:22:08 +08:00
|
|
|
if (mActiveBreakpointLine == aLine) {
|
|
|
|
painter.drawPixmap(X,Y,*(pIconsManager->activeBreakpoint()));
|
|
|
|
} else if (hasBreakpoint(aLine)) {
|
2021-07-03 13:57:22 +08:00
|
|
|
painter.drawPixmap(X,Y,*(pIconsManager->breakpoint()));
|
2021-08-01 01:06:43 +08:00
|
|
|
} else {
|
|
|
|
PSyntaxIssueList lst = getSyntaxIssuesAtLine(aLine);
|
|
|
|
if (lst) {
|
|
|
|
bool hasError=false;
|
2021-08-29 10:14:07 +08:00
|
|
|
for (const PSyntaxIssue& issue : *lst) {
|
2021-08-01 01:06:43 +08:00
|
|
|
if (issue->issueType == CompileIssueType::Error) {
|
|
|
|
hasError = true;
|
|
|
|
break;;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (hasError) {
|
|
|
|
painter.drawPixmap(X,Y,*(pIconsManager->syntaxError()));
|
|
|
|
} else {
|
|
|
|
painter.drawPixmap(X,Y,*(pIconsManager->syntaxWarning()));
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2021-06-24 16:05:19 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::onGetEditingAreas(int Line, SynEditingAreaList &areaList)
|
|
|
|
{
|
|
|
|
areaList.clear();
|
|
|
|
// if (fTabStopBegin >=0) and (fTabStopY=Line) then begin
|
|
|
|
// areaType:=eatEditing;
|
|
|
|
// System.new(p);
|
|
|
|
// spaceCount := fText.LeftSpacesEx(fLineBeforeTabStop,True);
|
|
|
|
// spaceBefore := Length(fLineBeforeTabStop) - Length(TrimLeft(fLineBeforeTabStop));
|
|
|
|
// p.beginX := fTabStopBegin + spaceCount - spaceBefore ;
|
|
|
|
// p.endX := fTabStopEnd + spaceCount - spaceBefore ;
|
|
|
|
// p.color := dmMain.Cpp.StringAttri.Foreground;
|
|
|
|
// areaList.Add(p);
|
|
|
|
// ColBorder := dmMain.Cpp.StringAttri.Foreground;
|
|
|
|
// Exit;
|
|
|
|
// end;
|
|
|
|
// StrToThemeColor(tc,devEditor.Syntax.Values[cWN]);
|
|
|
|
PSyntaxIssueList lst = getSyntaxIssuesAtLine(Line);
|
|
|
|
if (lst) {
|
2021-08-29 10:14:07 +08:00
|
|
|
for (const PSyntaxIssue& issue: *lst) {
|
2021-06-24 16:05:19 +08:00
|
|
|
PSynEditingArea p=std::make_shared<SynEditingArea>();
|
|
|
|
p->beginX = issue->col;
|
|
|
|
p->endX = issue->endCol;
|
|
|
|
if (issue->issueType == CompileIssueType::Error) {
|
|
|
|
p->color = mSyntaxErrorColor;
|
|
|
|
} else {
|
2021-07-26 00:22:08 +08:00
|
|
|
p->color = mSyntaxWarningColor;
|
2021-06-24 16:05:19 +08:00
|
|
|
}
|
|
|
|
p->type = SynEditingAreaType::eatWaveUnderLine;
|
|
|
|
areaList.append(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-03 13:57:22 +08:00
|
|
|
bool Editor::onGetSpecialLineColors(int Line, QColor &foreground, QColor &backgroundColor)
|
|
|
|
{
|
2021-07-27 00:14:24 +08:00
|
|
|
if (Line == mActiveBreakpointLine &&
|
|
|
|
mActiveBreakpointForegroundColor.isValid()
|
|
|
|
&& mActiveBreakpointBackgroundColor.isValid()) {
|
2021-07-26 00:22:08 +08:00
|
|
|
foreground = mActiveBreakpointForegroundColor;
|
|
|
|
backgroundColor = mActiveBreakpointBackgroundColor;
|
2021-07-27 00:14:24 +08:00
|
|
|
return true;
|
|
|
|
} else if (hasBreakpoint(Line) &&
|
|
|
|
mBreakpointForegroundColor.isValid()
|
|
|
|
&& mBreakpointBackgroundColor.isValid()) {
|
2021-07-26 00:22:08 +08:00
|
|
|
foreground = mBreakpointForegroundColor;
|
|
|
|
backgroundColor = mBreakpointBackgroundColor;
|
2021-07-27 00:14:24 +08:00
|
|
|
return true;
|
2021-07-26 00:22:08 +08:00
|
|
|
}
|
|
|
|
// end else if Line = fErrorLine then begin
|
|
|
|
// StrToThemeColor(tc, devEditor.Syntax.Values[cErr]);
|
|
|
|
// BG := tc.Background;
|
|
|
|
// FG := tc.Foreground;
|
|
|
|
// if (BG <> clNone) or (FG<>clNone) then
|
|
|
|
// Special := TRUE;
|
|
|
|
// end;
|
2021-07-27 00:14:24 +08:00
|
|
|
return false;
|
2021-07-03 13:57:22 +08:00
|
|
|
}
|
|
|
|
|
2021-08-29 22:51:23 +08:00
|
|
|
void Editor::onPreparePaintHighlightToken(int line, int aChar, const QString &token, PSynHighlighterAttribute attr, SynFontStyles &style, QColor &foreground, QColor &background)
|
2021-08-28 09:01:40 +08:00
|
|
|
{
|
|
|
|
if (token.isEmpty())
|
|
|
|
return;
|
|
|
|
//selection
|
|
|
|
if (selAvail() && highlighter()) {
|
|
|
|
if ((
|
|
|
|
(attr->name() == SYNS_AttrIdentifier)
|
|
|
|
|| (attr->name() == SYNS_AttrReservedWord)
|
|
|
|
|| (attr->name() == SYNS_AttrPreprocessor)
|
|
|
|
)
|
|
|
|
&& (token == selText())) {
|
|
|
|
foreground = selectedForeground();
|
|
|
|
background = selectedBackground();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-29 22:51:23 +08:00
|
|
|
qDebug()<<token<<"-"<<attr->name()<<" - "<<line<<" : "<<aChar;
|
2021-08-28 09:01:40 +08:00
|
|
|
if (mParser && mCompletionPopup && (attr->name() == SYNS_AttrIdentifier)) {
|
2021-08-29 22:51:23 +08:00
|
|
|
BufferCoord p{aChar,line};
|
2021-08-28 09:01:40 +08:00
|
|
|
BufferCoord pBeginPos,pEndPos;
|
|
|
|
QString s= getWordAtPosition(p, pBeginPos,pEndPos, WordPurpose::wpInformation);
|
2021-08-29 22:51:23 +08:00
|
|
|
qDebug()<<s;
|
2021-08-28 09:01:40 +08:00
|
|
|
PStatement statement = mParser->findStatementOf(mFilename,
|
|
|
|
s , p.Line);
|
|
|
|
StatementKind kind = mParser->getKindOfStatement(statement);
|
|
|
|
if (kind == StatementKind::skUnknown) {
|
|
|
|
if ((pEndPos.Line>=1)
|
2021-08-29 10:14:07 +08:00
|
|
|
&& (pEndPos.Char>=0)
|
2021-08-28 09:01:40 +08:00
|
|
|
&& (pEndPos.Char < lines()->getString(pEndPos.Line-1).length())
|
|
|
|
&& (lines()->getString(pEndPos.Line-1)[pEndPos.Char] == '(')) {
|
|
|
|
kind = StatementKind::skFunction;
|
|
|
|
} else {
|
|
|
|
kind = StatementKind::skVariable;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
foreground = mCompletionPopup->colors().value(kind,
|
|
|
|
highlighter()->identifierAttribute()->foreground());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-29 17:23:40 +08:00
|
|
|
bool Editor::event(QEvent *event)
|
|
|
|
{
|
|
|
|
if (event->type() == QEvent::ToolTip) {
|
|
|
|
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
|
|
|
|
BufferCoord p;
|
|
|
|
TipType reason = getTipType(helpEvent->pos(),p);
|
|
|
|
PSyntaxIssue pError;
|
|
|
|
int line ;
|
|
|
|
if (reason == TipType::Error) {
|
|
|
|
pError = getSyntaxIssueAtPosition(p);
|
2021-08-29 22:08:43 +08:00
|
|
|
} else if ((reason == TipType::None) && PointToLine(helpEvent->pos(),line)) {
|
2021-08-29 17:23:40 +08:00
|
|
|
//it's on gutter
|
|
|
|
//see if its error;
|
|
|
|
PSyntaxIssueList issues = getSyntaxIssuesAtLine(line);
|
|
|
|
if (issues && !issues->isEmpty()) {
|
|
|
|
reason = TipType::Error;
|
|
|
|
pError = issues->front();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get subject
|
|
|
|
bool isIncludeLine = false;
|
|
|
|
BufferCoord pBeginPos,pEndPos;
|
|
|
|
QString s;
|
|
|
|
switch (reason) {
|
|
|
|
case TipType::Preprocessor:
|
|
|
|
// When hovering above a preprocessor line, determine if we want to show an include or a identifier hint
|
|
|
|
s = lines()->getString(p.Line - 1);
|
|
|
|
isIncludeLine = mParser->isIncludeLine(s);
|
|
|
|
if (!isIncludeLine)
|
|
|
|
s = WordAtRowCol(p);
|
|
|
|
break;
|
|
|
|
case TipType::Identifier:
|
|
|
|
if (pMainWindow->debugger()->executing())
|
|
|
|
s = getWordAtPosition(p, pBeginPos,pEndPos, WordPurpose::wpEvaluation); // debugging
|
|
|
|
else if (//devEditor.ParserHints and
|
|
|
|
!mCompletionPopup->isVisible()
|
|
|
|
&& !mHeaderCompletionPopup->isVisible())
|
|
|
|
s = getWordAtPosition(p, pBeginPos,pEndPos, WordPurpose::wpInformation); // information during coding
|
|
|
|
break;
|
|
|
|
case TipType::Selection:
|
|
|
|
s = selText(); // when a selection is available, always only use that
|
|
|
|
break;
|
|
|
|
case TipType::Error:
|
|
|
|
s = pError->token;
|
|
|
|
break;
|
|
|
|
case TipType::None:
|
|
|
|
cancelHint();
|
|
|
|
event->ignore();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't rescan the same stuff over and over again (that's slow)
|
|
|
|
// if (s = fCurrentWord) and (fText.Hint<>'') then
|
|
|
|
s = s.trimmed();
|
|
|
|
if ((s == mCurrentWord) && (mCurrentTipType == reason)) {
|
2021-08-29 22:08:43 +08:00
|
|
|
QApplication* app = dynamic_cast<QApplication *>(QApplication::instance());
|
|
|
|
if (app->keyboardModifiers().testFlag(Qt::ControlModifier)) {
|
|
|
|
setCursor(Qt::PointingHandCursor);
|
|
|
|
} else {
|
|
|
|
setCursor(Qt::ArrowCursor);
|
|
|
|
}
|
2021-08-29 17:23:40 +08:00
|
|
|
event->ignore();
|
|
|
|
return true; // do NOT remove hint when subject stays the same
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove hint
|
|
|
|
cancelHint();
|
|
|
|
mCurrentWord = s;
|
|
|
|
mCurrentTipType = reason;
|
|
|
|
|
|
|
|
|
|
|
|
// Determine what to do with subject
|
|
|
|
QString hint = "";
|
|
|
|
switch (reason) {
|
|
|
|
case TipType::Preprocessor:
|
|
|
|
if (isIncludeLine) {
|
|
|
|
hint = getFileHint(s);
|
|
|
|
} else if (//devEditor.ParserHints and
|
|
|
|
!mCompletionPopup->isVisible()
|
|
|
|
&& !mHeaderCompletionPopup->isVisible()) {
|
|
|
|
hint = getParserHint(s,p.Line);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TipType::Identifier:
|
|
|
|
case TipType::Selection:
|
|
|
|
if (!mCompletionPopup->isVisible()
|
|
|
|
&& !mHeaderCompletionPopup->isVisible()) {
|
|
|
|
if (pMainWindow->debugger()->executing()) {
|
2021-08-29 22:51:23 +08:00
|
|
|
showDebugHint(s,p.Line);
|
2021-08-29 17:23:40 +08:00
|
|
|
} else { //if devEditor.ParserHints {
|
|
|
|
hint = getParserHint(s, p.Line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TipType::Error:
|
2021-08-29 22:08:43 +08:00
|
|
|
hint = getErrorHint(pError);
|
2021-08-29 17:23:40 +08:00
|
|
|
}
|
|
|
|
if (!hint.isEmpty()) {
|
2021-08-29 22:08:43 +08:00
|
|
|
QApplication* app = dynamic_cast<QApplication *>(QApplication::instance());
|
|
|
|
if (app->keyboardModifiers().testFlag(Qt::ControlModifier)) {
|
|
|
|
setCursor(Qt::PointingHandCursor);
|
|
|
|
} else {
|
|
|
|
setCursor(Qt::ArrowCursor);
|
|
|
|
}
|
2021-08-29 17:23:40 +08:00
|
|
|
QToolTip::showText(helpEvent->globalPos(),hint);
|
|
|
|
event->ignore();
|
2021-08-29 22:08:43 +08:00
|
|
|
} else {
|
|
|
|
event->ignore();
|
|
|
|
}
|
2021-08-29 17:23:40 +08:00
|
|
|
}
|
|
|
|
return SynEdit::event(event);
|
|
|
|
}
|
|
|
|
|
2021-06-12 22:36:23 +08:00
|
|
|
void Editor::copyToClipboard()
|
|
|
|
{
|
|
|
|
if (pSettings->editor().copySizeLimit()) {
|
2021-08-27 00:49:50 +08:00
|
|
|
int startLine = blockBegin().Line;
|
|
|
|
int endLine = blockEnd().Line;
|
|
|
|
if ((endLine-startLine+1) > pSettings->editor().copyLineLimits()) {
|
2021-06-21 11:21:26 +08:00
|
|
|
QMessageBox::critical(pMainWindow,tr("Error"),
|
2021-06-12 22:36:23 +08:00
|
|
|
tr("The text to be copied exceeds count limit!"));
|
|
|
|
return;
|
|
|
|
}
|
2021-08-27 00:49:50 +08:00
|
|
|
if ((selText().length()) > pSettings->editor().copyCharLimits() * 1000) {
|
2021-06-21 11:21:26 +08:00
|
|
|
QMessageBox::critical(pMainWindow,tr("Error"),
|
2021-06-12 22:36:23 +08:00
|
|
|
tr("The text to be copied exceeds character limit!"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch(pSettings->editor().copyWithFormatAs()) {
|
|
|
|
case 1: //HTML
|
|
|
|
copyAsHTML();
|
|
|
|
break;;
|
|
|
|
default:
|
|
|
|
SynEdit::copyToClipboard();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::cutToClipboard()
|
|
|
|
{
|
|
|
|
if (pSettings->editor().copySizeLimit()) {
|
|
|
|
if (lines()->count() > pSettings->editor().copyLineLimits()) {
|
2021-06-21 11:21:26 +08:00
|
|
|
QMessageBox::critical(pMainWindow,tr("Error"),
|
2021-06-12 22:36:23 +08:00
|
|
|
tr("The text to be cut exceeds count limit!"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (lines()->getTextLength() > pSettings->editor().copyCharLimits() * 1000) {
|
2021-06-21 11:21:26 +08:00
|
|
|
QMessageBox::critical(pMainWindow,tr("Error"),
|
2021-06-12 22:36:23 +08:00
|
|
|
tr("The text to be cut exceeds character limit!"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SynEdit::cutToClipboard();
|
2021-04-11 12:39:22 +08:00
|
|
|
}
|
|
|
|
|
2021-06-12 22:36:23 +08:00
|
|
|
void Editor::copyAsHTML()
|
|
|
|
{
|
|
|
|
if (!selAvail())
|
|
|
|
return;
|
|
|
|
SynHTMLExporter SynExporterHTML;
|
|
|
|
|
|
|
|
SynExporterHTML.setTitle(QFileInfo(mFilename).fileName());
|
|
|
|
SynExporterHTML.setExportAsText(false);
|
2021-06-20 22:54:16 +08:00
|
|
|
SynExporterHTML.setUseBackground(pSettings->editor().copyHTMLUseBackground());
|
2021-06-12 22:36:23 +08:00
|
|
|
SynExporterHTML.setFont(font());
|
2021-06-20 22:54:16 +08:00
|
|
|
PSynHighlighter hl = highlighter();
|
|
|
|
if (!pSettings->editor().copyHTMLUseEditorColor()) {
|
|
|
|
hl = highlighterManager.copyHighlighter(highlighter());
|
|
|
|
highlighterManager.applyColorScheme(hl,pSettings->editor().copyHTMLColorScheme());
|
|
|
|
}
|
|
|
|
SynExporterHTML.setHighlighter(hl);
|
2021-06-12 22:36:23 +08:00
|
|
|
SynExporterHTML.setCreateHTMLFragment(true);
|
|
|
|
|
|
|
|
SynExporterHTML.ExportRange(lines(),blockBegin(),blockEnd());
|
|
|
|
|
|
|
|
QMimeData * mimeData = new QMimeData;
|
|
|
|
|
|
|
|
//sethtml will convert buffer to QString , which will cause encoding trouble
|
|
|
|
mimeData->setData(SynExporterHTML.clipboardFormat(),SynExporterHTML.buffer());
|
|
|
|
mimeData->setText(selText());
|
|
|
|
|
|
|
|
QGuiApplication::clipboard()->clear();
|
|
|
|
QGuiApplication::clipboard()->setMimeData(mimeData);
|
2021-04-11 13:55:31 +08:00
|
|
|
}
|
|
|
|
|
2021-06-24 16:05:19 +08:00
|
|
|
void Editor::setCaretPosition(int line, int col)
|
|
|
|
{
|
|
|
|
this->uncollapseAroundLine(line);
|
|
|
|
this->setCaretXYCentered(true,BufferCoord{col,line});
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::setCaretPositionAndActivate(int line, int col)
|
|
|
|
{
|
|
|
|
this->uncollapseAroundLine(line);
|
|
|
|
if (!this->hasFocus())
|
|
|
|
this->activate();
|
|
|
|
this->setCaretXYCentered(true,BufferCoord{col,line});
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::addSyntaxIssues(int line, int startChar, int endChar, CompileIssueType errorType, const QString &hint)
|
2021-06-23 22:38:02 +08:00
|
|
|
{
|
|
|
|
PSyntaxIssue pError;
|
|
|
|
BufferCoord p;
|
|
|
|
QString token;
|
|
|
|
SynHighlighterTokenType tokenType;
|
|
|
|
int tokenKind,start;
|
|
|
|
PSynHighlighterAttribute attr;
|
|
|
|
PSyntaxIssueList lst;
|
|
|
|
if ((line<1) || (line>lines()->count()))
|
|
|
|
return;
|
|
|
|
pError = std::make_shared<SyntaxIssue>();
|
|
|
|
p.Char = startChar;
|
|
|
|
p.Line = line;
|
|
|
|
if (startChar >= lines()->getString(line-1).length()) {
|
|
|
|
start = 1;
|
|
|
|
token = lines()->getString(line-1);
|
2021-06-24 16:05:19 +08:00
|
|
|
} else if (endChar < 1) {
|
2021-06-23 22:38:02 +08:00
|
|
|
if (!GetHighlighterAttriAtRowColEx(p,token,tokenType,tokenKind,start,attr))
|
|
|
|
return;
|
2021-06-24 16:05:19 +08:00
|
|
|
} else {
|
|
|
|
start = startChar;
|
|
|
|
token = lines()->getString(line-1).mid(start-1,endChar-startChar);
|
2021-06-23 22:38:02 +08:00
|
|
|
}
|
|
|
|
pError->startChar = start;
|
|
|
|
pError->endChar = start + token.length();
|
|
|
|
pError->col = charToColumn(line,pError->startChar);
|
|
|
|
pError->endCol = charToColumn(line,pError->endChar);
|
|
|
|
pError->hint = hint;
|
|
|
|
pError->token = token;
|
|
|
|
pError->issueType = errorType;
|
|
|
|
if (mSyntaxIssues.contains(line)) {
|
|
|
|
lst = mSyntaxIssues[line];
|
|
|
|
} else {
|
|
|
|
lst = std::make_shared<SyntaxIssueList>();
|
|
|
|
mSyntaxIssues[line] = lst;
|
|
|
|
}
|
|
|
|
lst->append(pError);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::clearSyntaxIssues()
|
|
|
|
{
|
|
|
|
mSyntaxIssues.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::gotoNextSyntaxIssue()
|
|
|
|
{
|
|
|
|
auto iter = mSyntaxIssues.find(caretY());
|
|
|
|
if (iter==mSyntaxIssues.end())
|
|
|
|
return;
|
|
|
|
iter++;
|
|
|
|
if (iter==mSyntaxIssues.end())
|
|
|
|
return;
|
|
|
|
BufferCoord p;
|
|
|
|
p.Char = (*iter)->at(0)->startChar;
|
|
|
|
p.Line = iter.key();
|
|
|
|
setCaretXY(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::gotoPrevSyntaxIssue()
|
|
|
|
{
|
|
|
|
auto iter = mSyntaxIssues.find(caretY());
|
|
|
|
if (iter==mSyntaxIssues.end())
|
|
|
|
return;
|
|
|
|
if (iter==mSyntaxIssues.begin())
|
|
|
|
return;
|
|
|
|
iter--;
|
|
|
|
BufferCoord p;
|
|
|
|
p.Char = (*iter)->at(0)->startChar;
|
|
|
|
p.Line = iter.key();
|
|
|
|
setCaretXY(p);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::hasNextSyntaxIssue() const
|
|
|
|
{
|
|
|
|
auto iter = mSyntaxIssues.find(caretY());
|
|
|
|
if (iter==mSyntaxIssues.end())
|
|
|
|
return false;
|
|
|
|
iter++;
|
|
|
|
if (iter==mSyntaxIssues.end())
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::hasPrevSyntaxIssue() const
|
|
|
|
{
|
|
|
|
auto iter = mSyntaxIssues.find(caretY());
|
|
|
|
if (iter==mSyntaxIssues.end())
|
|
|
|
return true;
|
|
|
|
if (iter==mSyntaxIssues.begin())
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-06-24 16:05:19 +08:00
|
|
|
Editor::PSyntaxIssueList Editor::getSyntaxIssuesAtLine(int line)
|
2021-06-23 22:38:02 +08:00
|
|
|
{
|
|
|
|
if (mSyntaxIssues.contains(line))
|
|
|
|
return mSyntaxIssues[line];
|
|
|
|
return PSyntaxIssueList();
|
|
|
|
}
|
|
|
|
|
2021-06-24 16:05:19 +08:00
|
|
|
Editor::PSyntaxIssue Editor::getSyntaxIssueAtPosition(const BufferCoord &pos)
|
2021-06-23 22:38:02 +08:00
|
|
|
{
|
2021-06-24 16:05:19 +08:00
|
|
|
PSyntaxIssueList lst = getSyntaxIssuesAtLine(pos.Line);
|
2021-08-29 17:23:40 +08:00
|
|
|
if (!lst)
|
|
|
|
return PSyntaxIssue();
|
2021-08-29 10:14:07 +08:00
|
|
|
foreach (const PSyntaxIssue& issue, *lst) {
|
2021-06-23 22:38:02 +08:00
|
|
|
if (issue->startChar<=pos.Char && pos.Char<=issue->endChar)
|
|
|
|
return issue;
|
|
|
|
}
|
|
|
|
return PSyntaxIssue();
|
|
|
|
}
|
|
|
|
|
2021-06-12 22:36:23 +08:00
|
|
|
void Editor::onModificationChanged(bool) {
|
|
|
|
updateCaption();
|
2021-04-11 13:55:31 +08:00
|
|
|
}
|
|
|
|
|
2021-06-10 09:34:59 +08:00
|
|
|
void Editor::onStatusChanged(SynStatusChanges changes)
|
|
|
|
{
|
2021-08-23 17:27:17 +08:00
|
|
|
if (!changes.testFlag(SynStatusChange::scReadOnly)
|
2021-07-02 10:32:29 +08:00
|
|
|
&& !changes.testFlag(SynStatusChange::scInsertMode)
|
|
|
|
&& (lines()->count()!=mLineCount)
|
2021-06-24 20:43:09 +08:00
|
|
|
&& (lines()->count()!=0) && ((mLineCount>0) || (lines()->count()>1))) {
|
2021-08-23 10:16:06 +08:00
|
|
|
reparse();
|
2021-07-02 10:32:29 +08:00
|
|
|
if (!readOnly() && pSettings->editor().syntaxCheck() && pSettings->editor().syntaxCheckWhenLineChanged())
|
2021-06-25 12:40:11 +08:00
|
|
|
pMainWindow->checkSyntaxInBack(this);
|
2021-06-24 20:43:09 +08:00
|
|
|
}
|
|
|
|
mLineCount = lines()->count();
|
2021-08-27 23:51:42 +08:00
|
|
|
// if (not (scOpenFile in Changes)) and (fText.Lines.Count <> fLineCount)
|
2021-06-10 09:34:59 +08:00
|
|
|
// and (fText.Lines.Count <> 0) and ((fLineCount>0) or (fText.Lines.Count>1)) then begin
|
|
|
|
// if devCodeCompletion.Enabled
|
|
|
|
// and SameStr(mainForm.ClassBrowser.CurrentFile,FileName) // Don't reparse twice
|
|
|
|
// then begin
|
|
|
|
// Reparse;
|
|
|
|
// end;
|
|
|
|
// if fText.Focused and devEditor.AutoCheckSyntax and devEditor.CheckSyntaxWhenReturn
|
|
|
|
// and (fText.Highlighter = dmMain.Cpp) then begin
|
|
|
|
// mainForm.CheckSyntaxInBack(self);
|
|
|
|
// end;
|
|
|
|
// end;
|
|
|
|
// fLineCount := fText.Lines.Count;
|
|
|
|
// // scModified is only fired when the modified state changes
|
|
|
|
if (changes.testFlag(scModified)) {
|
|
|
|
updateCaption();
|
|
|
|
}
|
|
|
|
|
|
|
|
// if (fTabStopBegin >=0) and (fTabStopY=fText.CaretY) then begin
|
|
|
|
// if StartsStr(fLineBeforeTabStop,fText.LineText) and EndsStr(fLineAfterTabStop, fText.LineText) then
|
|
|
|
// fTabStopBegin := Length(fLineBeforeTabStop);
|
|
|
|
// if fLineAfterTabStop = '' then
|
|
|
|
// fTabStopEnd := Length(fText.LineText)+1
|
|
|
|
// else
|
|
|
|
// fTabStopEnd := Length(fText.LineText) - Length(fLineAfterTabStop);
|
|
|
|
// fXOffsetSince := fTabStopEnd - fText.CaretX;
|
|
|
|
// if (fText.CaretX < fTabStopBegin) or (fText.CaretX > (fTabStopEnd+1)) then begin
|
|
|
|
// fTabStopBegin :=-1;
|
|
|
|
// end;
|
|
|
|
// end;
|
|
|
|
|
|
|
|
// scSelection includes anything caret related
|
|
|
|
if (changes.testFlag(SynStatusChange::scSelection)) {
|
2021-06-12 22:36:23 +08:00
|
|
|
pMainWindow->updateStatusbarForLineCol();
|
2021-06-10 09:34:59 +08:00
|
|
|
|
|
|
|
// // Update the function tip
|
|
|
|
// fFunctionTip.ForceHide := false;
|
|
|
|
// if Assigned(fFunctionTipTimer) then begin
|
|
|
|
// if fFunctionTip.Activated and FunctionTipAllowed then begin
|
|
|
|
// fFunctionTip.Parser := fParser;
|
|
|
|
// fFunctionTip.FileName := fFileName;
|
|
|
|
// fFunctionTip.Show;
|
|
|
|
// end else begin // Reset the timer
|
|
|
|
// fFunctionTipTimer.Enabled := false;
|
|
|
|
// fFunctionTipTimer.Enabled := true;
|
|
|
|
// end;
|
|
|
|
}
|
|
|
|
|
|
|
|
// // Remove error line colors
|
|
|
|
// if not fIgnoreCaretChange then begin
|
|
|
|
// if (fErrorLine <> -1) and not fText.SelAvail then begin
|
|
|
|
// fText.InvalidateLine(fErrorLine);
|
|
|
|
// fText.InvalidateGutterLine(fErrorLine);
|
|
|
|
// fErrorLine := -1;
|
|
|
|
// end;
|
|
|
|
// end else
|
|
|
|
// fIgnoreCaretChange := false;
|
|
|
|
|
|
|
|
// if fText.SelAvail then begin
|
|
|
|
// if fText.GetWordAtRowCol(fText.CaretXY) = fText.SelText then begin
|
|
|
|
// fSelChanged:=True;
|
|
|
|
// BeginUpdate;
|
|
|
|
// EndUpdate;
|
|
|
|
// end else if fSelChanged then begin
|
|
|
|
// fSelChanged:=False; //invalidate to unhighlight others
|
|
|
|
// BeginUpdate;
|
|
|
|
// EndUpdate;
|
|
|
|
// end;
|
|
|
|
// end else if fSelChanged then begin
|
|
|
|
// fSelChanged:=False; //invalidate to unhighlight others
|
|
|
|
// BeginUpdate;
|
|
|
|
// EndUpdate;
|
|
|
|
// end;
|
|
|
|
// end;
|
|
|
|
|
2021-06-12 22:36:23 +08:00
|
|
|
if (changes.testFlag(scInsertMode) | changes.testFlag(scReadOnly))
|
|
|
|
pMainWindow->updateForStatusbarModeInfo();
|
|
|
|
|
|
|
|
pMainWindow->updateEditorActions();
|
2021-06-10 09:34:59 +08:00
|
|
|
|
2021-06-21 16:25:21 +08:00
|
|
|
// mainForm.CaretList.AddCaret(self,fText.CaretY,fText.CaretX);
|
|
|
|
}
|
|
|
|
|
2021-08-29 10:14:07 +08:00
|
|
|
void Editor::onGutterClicked(Qt::MouseButton button, int , int , int line)
|
2021-07-03 13:57:22 +08:00
|
|
|
{
|
|
|
|
if (button == Qt::LeftButton) {
|
|
|
|
toggleBreakpoint(line);
|
|
|
|
}
|
|
|
|
mGutterClickedLine = line;
|
|
|
|
}
|
|
|
|
|
2021-08-29 22:08:43 +08:00
|
|
|
void Editor::onTipEvalValueReady(const QString &value)
|
|
|
|
{
|
|
|
|
if (mCurrentWord == mCurrentDebugTipWord) {
|
|
|
|
QToolTip::showText(QCursor::pos(), mCurrentDebugTipWord + " = " + value );
|
|
|
|
}
|
|
|
|
disconnect(pMainWindow->debugger(), &Debugger::evalValueReady,
|
|
|
|
this, &Editor::onTipEvalValueReady);
|
|
|
|
}
|
|
|
|
|
2021-06-22 23:00:34 +08:00
|
|
|
QChar Editor::getCurrentChar()
|
|
|
|
{
|
|
|
|
if (lineText().length()<caretX())
|
|
|
|
return QChar();
|
|
|
|
else
|
2021-08-29 10:14:07 +08:00
|
|
|
return lineText().at(caretX()-1);
|
2021-06-22 23:00:34 +08:00
|
|
|
}
|
|
|
|
|
2021-06-22 13:24:26 +08:00
|
|
|
bool Editor::handleSymbolCompletion(QChar key)
|
2021-06-21 16:25:21 +08:00
|
|
|
{
|
2021-06-22 13:24:26 +08:00
|
|
|
if (!pSettings->editor().completeSymbols() || selAvail())
|
|
|
|
return false;
|
2021-06-22 23:00:34 +08:00
|
|
|
if (!insertMode())
|
|
|
|
return false;
|
2021-06-21 16:25:21 +08:00
|
|
|
|
2021-06-22 13:24:26 +08:00
|
|
|
//todo: better methods to detect current caret type
|
|
|
|
if (caretX() <= 1) {
|
|
|
|
if (caretY()>1) {
|
|
|
|
if (highlighter()->isLastLineCommentNotFinished(lines()->ranges(caretY() - 2).state))
|
|
|
|
return false;
|
|
|
|
if (highlighter()->isLastLineStringNotFinished(lines()->ranges(caretY() - 2).state)
|
|
|
|
&& (key!='\"') && (key!='\''))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
BufferCoord HighlightPos = BufferCoord{caretX()-1, caretY()};
|
|
|
|
// Check if that line is highlighted as comment
|
|
|
|
PSynHighlighterAttribute Attr;
|
|
|
|
QString Token;
|
|
|
|
bool tokenFinished;
|
|
|
|
SynHighlighterTokenType tokenType;
|
|
|
|
if (GetHighlighterAttriAtRowCol(HighlightPos, Token, tokenFinished, tokenType,Attr)) {
|
|
|
|
if ((tokenType == SynHighlighterTokenType::Comment) && (!tokenFinished))
|
|
|
|
return false;
|
|
|
|
if ((tokenType == SynHighlighterTokenType::String) && (!tokenFinished)
|
|
|
|
&& (key!='\'') && (key!='\"') && (key!='(') && (key!=')'))
|
|
|
|
return false;
|
|
|
|
if (( key=='<' || key =='>') && (tokenType != SynHighlighterTokenType::PreprocessDirective))
|
|
|
|
return false;
|
|
|
|
if ((key == '\'') && (Attr->name() == "SYNS_AttrNumber"))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if that line is highlighted as string or character or comment
|
|
|
|
// if (Attr = fText.Highlighter.StringAttribute) or (Attr = fText.Highlighter.CommentAttribute) or SameStr(Attr.Name,
|
|
|
|
// 'Character') then
|
|
|
|
// Exit;
|
|
|
|
|
|
|
|
QuoteStatus status;
|
|
|
|
switch(key.unicode()) {
|
|
|
|
case '(':
|
|
|
|
if (pSettings->editor().completeParenthese()) {
|
2021-06-22 23:00:34 +08:00
|
|
|
return handleParentheseCompletion();
|
2021-06-22 13:24:26 +08:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
case ')':
|
|
|
|
if (pSettings->editor().completeParenthese() && pSettings->editor().overwriteSymbols()) {
|
2021-06-22 23:00:34 +08:00
|
|
|
return handleParentheseSkip();
|
2021-06-22 13:24:26 +08:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
case '[':
|
|
|
|
if (pSettings->editor().completeBracket()) {
|
2021-06-22 23:00:34 +08:00
|
|
|
return handleBracketCompletion();
|
2021-06-22 13:24:26 +08:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
case ']':
|
|
|
|
if (pSettings->editor().completeBracket() && pSettings->editor().overwriteSymbols()) {
|
2021-06-22 23:00:34 +08:00
|
|
|
return handleBracketSkip();
|
2021-06-22 13:24:26 +08:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
case '*':
|
2021-06-22 23:00:34 +08:00
|
|
|
status = getQuoteStatus();
|
2021-06-22 13:24:26 +08:00
|
|
|
if (pSettings->editor().completeComment() && (status == QuoteStatus::NotQuote)) {
|
2021-06-22 23:00:34 +08:00
|
|
|
return handleMultilineCommentCompletion();
|
2021-06-22 13:24:26 +08:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
case '{':
|
|
|
|
if (pSettings->editor().completeBrace()) {
|
2021-06-22 23:00:34 +08:00
|
|
|
return handleBraceCompletion();
|
2021-06-22 13:24:26 +08:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
case '}':
|
|
|
|
if (pSettings->editor().completeBrace() && pSettings->editor().overwriteSymbols()) {
|
2021-06-22 23:00:34 +08:00
|
|
|
return handleBraceSkip();
|
2021-06-22 13:24:26 +08:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
case '\'':
|
|
|
|
if (pSettings->editor().completeSingleQuote()) {
|
2021-06-22 23:00:34 +08:00
|
|
|
return handleSingleQuoteCompletion();
|
2021-06-22 13:24:26 +08:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
case '\"':
|
|
|
|
if (pSettings->editor().completeDoubleQuote()) {
|
2021-06-22 23:00:34 +08:00
|
|
|
return handleDoubleQuoteCompletion();
|
2021-06-22 13:24:26 +08:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
case '<':
|
|
|
|
if (pSettings->editor().completeGlobalInclude()) { // #include <>
|
2021-06-22 23:00:34 +08:00
|
|
|
return handleGlobalIncludeCompletion();
|
2021-06-22 13:24:26 +08:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
case '>':
|
|
|
|
if (pSettings->editor().completeGlobalInclude() && pSettings->editor().overwriteSymbols()) { // #include <>
|
2021-06-22 23:00:34 +08:00
|
|
|
return handleGlobalIncludeSkip();
|
2021-06-22 13:24:26 +08:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-06-22 23:00:34 +08:00
|
|
|
bool Editor::handleParentheseCompletion()
|
|
|
|
{
|
|
|
|
QuoteStatus status = getQuoteStatus();
|
|
|
|
if (status == QuoteStatus::RawString || status == QuoteStatus::NotQuote) {
|
|
|
|
beginUpdate();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,'(');
|
|
|
|
BufferCoord oldCaret = caretXY();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,')');
|
|
|
|
setCaretXY(oldCaret);
|
|
|
|
endUpdate();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// if (status == QuoteStatus::NotQuote) && FunctionTipAllowed then
|
|
|
|
// fFunctionTip.Activated := true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::handleParentheseSkip()
|
2021-06-22 13:24:26 +08:00
|
|
|
{
|
2021-06-22 23:00:34 +08:00
|
|
|
if (getCurrentChar() != ')')
|
|
|
|
return false;
|
|
|
|
QuoteStatus status = getQuoteStatus();
|
|
|
|
if (status == QuoteStatus::RawStringNoEscape) {
|
|
|
|
setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (status != QuoteStatus::NotQuote)
|
|
|
|
return false;
|
|
|
|
BufferCoord pos = getMatchingBracket();
|
|
|
|
if (pos.Line != 0) {
|
|
|
|
setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// if FunctionTipAllowed then
|
|
|
|
// fFunctionTip.Activated := false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::handleBracketCompletion()
|
|
|
|
{
|
|
|
|
// QuoteStatus status = getQuoteStatus();
|
|
|
|
// if (status == QuoteStatus::RawString || status == QuoteStatus::NotQuote) {
|
|
|
|
beginUpdate();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,'[');
|
|
|
|
BufferCoord oldCaret = caretXY();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,']');
|
|
|
|
setCaretXY(oldCaret);
|
|
|
|
endUpdate();
|
|
|
|
return true;
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::handleBracketSkip()
|
|
|
|
{
|
|
|
|
if (getCurrentChar() != ']')
|
|
|
|
return false;
|
|
|
|
BufferCoord pos = getMatchingBracket();
|
|
|
|
if (pos.Line != 0) {
|
|
|
|
setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::handleMultilineCommentCompletion()
|
|
|
|
{
|
|
|
|
if (((caretX() > 1) && (caretX()-1 < lineText().length())) && (lineText()[caretX() - 1] == '/')) {
|
|
|
|
beginUpdate();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,'*');
|
|
|
|
BufferCoord oldCaret = caretXY();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,'*');
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,'/');
|
|
|
|
setCaretXY(oldCaret);
|
|
|
|
endUpdate();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::handleBraceCompletion()
|
|
|
|
{
|
|
|
|
QString s = lineText().trimmed();
|
|
|
|
int i= caretY()-2;
|
|
|
|
while ((s.isEmpty()) && (i>=0)) {
|
|
|
|
s=lines()->getString(i);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
beginUpdate();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,'{');
|
|
|
|
BufferCoord oldCaret = caretXY();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,'}');
|
|
|
|
if (
|
|
|
|
( (s.startsWith("struct")
|
|
|
|
|| s.startsWith("class")
|
|
|
|
|| s.startsWith("union")
|
|
|
|
|| s.startsWith("typedef")
|
|
|
|
|| s.startsWith("public")
|
|
|
|
|| s.startsWith("private")
|
|
|
|
|| s.startsWith("enum") )
|
|
|
|
&& !s.contains(';')
|
|
|
|
) || s.endsWith('=')) {
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,';');
|
|
|
|
}
|
|
|
|
setCaretXY(oldCaret);
|
|
|
|
endUpdate();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::handleBraceSkip()
|
|
|
|
{
|
|
|
|
if (getCurrentChar() != '}')
|
|
|
|
return false;
|
|
|
|
BufferCoord pos = getMatchingBracket();
|
|
|
|
if (pos.Line != 0) {
|
|
|
|
setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::handleSingleQuoteCompletion()
|
|
|
|
{
|
|
|
|
QuoteStatus status = getQuoteStatus();
|
|
|
|
QChar ch = getCurrentChar();
|
|
|
|
if (ch == '\'') {
|
|
|
|
if (status == QuoteStatus::SingleQuote) {
|
|
|
|
setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (status == QuoteStatus::NotQuote) {
|
2021-06-23 08:55:56 +08:00
|
|
|
if (ch == 0 || highlighter()->isWordBreakChar(ch) || highlighter()->isSpaceChar(ch)) {
|
2021-06-22 23:00:34 +08:00
|
|
|
// insert ''
|
|
|
|
beginUpdate();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,'\'');
|
|
|
|
BufferCoord oldCaret = caretXY();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,'\'');
|
|
|
|
setCaretXY(oldCaret);
|
|
|
|
endUpdate();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::handleDoubleQuoteCompletion()
|
|
|
|
{
|
|
|
|
QuoteStatus status = getQuoteStatus();
|
|
|
|
QChar ch = getCurrentChar();
|
|
|
|
if (ch == '"') {
|
2021-06-23 08:55:56 +08:00
|
|
|
if (status == QuoteStatus::DoubleQuote || status == QuoteStatus::RawString) {
|
2021-06-22 23:00:34 +08:00
|
|
|
setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (status == QuoteStatus::NotQuote) {
|
2021-06-23 08:55:56 +08:00
|
|
|
if ((ch == 0) || highlighter()->isWordBreakChar(ch) || highlighter()->isSpaceChar(ch)) {
|
2021-06-22 23:00:34 +08:00
|
|
|
// insert ""
|
|
|
|
beginUpdate();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,'"');
|
|
|
|
BufferCoord oldCaret = caretXY();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,'"');
|
|
|
|
setCaretXY(oldCaret);
|
|
|
|
endUpdate();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::handleGlobalIncludeCompletion()
|
|
|
|
{
|
|
|
|
if (!lineText().startsWith('#'))
|
|
|
|
return false;
|
|
|
|
QString s= lineText().mid(1).trimmed();
|
|
|
|
if (!s.startsWith("include")) //it's not #include
|
|
|
|
return false;
|
|
|
|
beginUpdate();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,'<');
|
|
|
|
BufferCoord oldCaret = caretXY();
|
|
|
|
CommandProcessor(SynEditorCommand::ecChar,'>');
|
|
|
|
setCaretXY(oldCaret);
|
|
|
|
endUpdate();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::handleGlobalIncludeSkip()
|
|
|
|
{
|
|
|
|
if (getCurrentChar()!='>')
|
|
|
|
return false;
|
|
|
|
QString s= lineText().mid(1).trimmed();
|
|
|
|
if (!s.startsWith("include")) //it's not #include
|
|
|
|
return false;
|
|
|
|
BufferCoord pos = getMatchingBracket();
|
|
|
|
if (pos.Line != 0) {
|
|
|
|
setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2021-06-22 13:24:26 +08:00
|
|
|
}
|
|
|
|
|
2021-08-26 20:18:20 +08:00
|
|
|
bool Editor::handleCodeCompletion(QChar key)
|
2021-08-25 08:48:33 +08:00
|
|
|
{
|
|
|
|
if (!mCompletionPopup->isEnabled())
|
2021-08-26 20:18:20 +08:00
|
|
|
return false;
|
2021-08-25 08:48:33 +08:00
|
|
|
switch(key.unicode()) {
|
|
|
|
case '.':
|
2021-08-26 20:18:20 +08:00
|
|
|
setSelText(key);
|
2021-08-25 08:48:33 +08:00
|
|
|
showCompletion(false);
|
2021-08-26 20:18:20 +08:00
|
|
|
return true;
|
2021-08-25 08:48:33 +08:00
|
|
|
case '>':
|
2021-08-26 20:18:20 +08:00
|
|
|
setSelText(key);
|
2021-08-25 08:48:33 +08:00
|
|
|
if ((caretX() > 1) && (lineText().length() >= 1) &&
|
|
|
|
(lineText()[caretX() - 2] == '-'))
|
|
|
|
showCompletion(false);
|
2021-08-26 20:18:20 +08:00
|
|
|
return true;
|
2021-08-25 08:48:33 +08:00
|
|
|
case ':':
|
2021-08-26 20:18:20 +08:00
|
|
|
setSelText(key);
|
2021-08-25 08:48:33 +08:00
|
|
|
if ((caretX() > 1) && (lineText().length() >= 1) &&
|
|
|
|
(lineText()[caretX() - 2] == ':'))
|
|
|
|
showCompletion(false);
|
2021-08-26 20:18:20 +08:00
|
|
|
return true;
|
2021-08-25 08:48:33 +08:00
|
|
|
case '/':
|
|
|
|
case '\\':
|
2021-08-26 20:18:20 +08:00
|
|
|
setSelText(key);
|
2021-08-25 08:48:33 +08:00
|
|
|
if (mParser->isIncludeLine(lineText())) {
|
|
|
|
showHeaderCompletion(false);
|
|
|
|
}
|
2021-08-26 20:18:20 +08:00
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
2021-08-25 08:48:33 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-23 10:16:06 +08:00
|
|
|
void Editor::initParser()
|
|
|
|
{
|
|
|
|
mParser = std::make_shared<CppParser>();
|
|
|
|
mParser->setOnGetFileStream(
|
|
|
|
std::bind(
|
|
|
|
&EditorList::getContentFromOpenedEditor,pMainWindow->editorList(),
|
|
|
|
std::placeholders::_1, std::placeholders::_2));
|
|
|
|
resetCppParser(mParser);
|
|
|
|
mParser->setEnabled((highlighter() && highlighter()->getClass() == SynHighlighterClass::CppHighlighter));
|
|
|
|
}
|
|
|
|
|
2021-06-22 13:24:26 +08:00
|
|
|
Editor::QuoteStatus Editor::getQuoteStatus()
|
|
|
|
{
|
|
|
|
QuoteStatus Result = QuoteStatus::NotQuote;
|
|
|
|
if ((caretY()>1) && highlighter()->isLastLineStringNotFinished(lines()->ranges(caretY() - 2).state))
|
|
|
|
Result = QuoteStatus::DoubleQuote;
|
|
|
|
|
|
|
|
QString Line = lines()->getString(caretY()-1);
|
2021-06-22 23:00:34 +08:00
|
|
|
int posX = caretX()-1;
|
|
|
|
if (posX >= Line.length()) {
|
|
|
|
posX = Line.length()-1;
|
|
|
|
}
|
2021-06-23 08:55:56 +08:00
|
|
|
for (int i=0; i<posX;i++) {
|
2021-06-22 23:00:34 +08:00
|
|
|
if (i+1<Line.length() && (Line[i] == 'R') && (Line[i+1] == '"') && (Result == QuoteStatus::NotQuote)) {
|
|
|
|
Result = QuoteStatus::RawString;
|
|
|
|
i++; // skip R
|
|
|
|
} else if (Line[i] == '(') {
|
|
|
|
switch(Result) {
|
|
|
|
case QuoteStatus::RawString:
|
|
|
|
Result=QuoteStatus::RawStringNoEscape;
|
|
|
|
break;
|
|
|
|
//case RawStringNoEscape: do nothing
|
|
|
|
}
|
|
|
|
} else if (Line[i] == ')') {
|
|
|
|
switch(Result) {
|
|
|
|
case QuoteStatus::RawStringNoEscape:
|
|
|
|
Result=QuoteStatus::RawString;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (Line[i] == '"') {
|
|
|
|
switch(Result) {
|
|
|
|
case QuoteStatus::NotQuote:
|
|
|
|
Result = QuoteStatus::DoubleQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::SingleQuote:
|
|
|
|
Result = QuoteStatus::SingleQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::SingleQuoteEscape:
|
|
|
|
Result = QuoteStatus::SingleQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::DoubleQuote:
|
|
|
|
Result = QuoteStatus::NotQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::DoubleQuoteEscape:
|
|
|
|
Result = QuoteStatus::DoubleQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::RawString:
|
|
|
|
Result=QuoteStatus::NotQuote;
|
|
|
|
break;
|
|
|
|
//RawStringNoEscape: do nothing
|
|
|
|
}
|
|
|
|
} else if (Line[i] == '\'') {
|
|
|
|
switch(Result) {
|
|
|
|
case QuoteStatus::NotQuote:
|
|
|
|
Result = QuoteStatus::SingleQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::SingleQuote:
|
|
|
|
Result = QuoteStatus::NotQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::SingleQuoteEscape:
|
|
|
|
Result = QuoteStatus::SingleQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::DoubleQuote:
|
|
|
|
Result = QuoteStatus::DoubleQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::DoubleQuoteEscape:
|
|
|
|
Result = QuoteStatus::DoubleQuote;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (Line[i] == '\\') {
|
|
|
|
switch(Result) {
|
|
|
|
case QuoteStatus::NotQuote:
|
|
|
|
Result = QuoteStatus::NotQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::SingleQuote:
|
|
|
|
Result = QuoteStatus::SingleQuoteEscape;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::SingleQuoteEscape:
|
|
|
|
Result = QuoteStatus::SingleQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::DoubleQuote:
|
|
|
|
Result = QuoteStatus::DoubleQuoteEscape;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::DoubleQuoteEscape:
|
|
|
|
Result = QuoteStatus::DoubleQuote;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch(Result) {
|
|
|
|
case QuoteStatus::NotQuote:
|
|
|
|
Result = QuoteStatus::NotQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::SingleQuote:
|
|
|
|
Result = QuoteStatus::SingleQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::SingleQuoteEscape:
|
|
|
|
Result = QuoteStatus::SingleQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::DoubleQuote:
|
|
|
|
Result = QuoteStatus::DoubleQuote;
|
|
|
|
break;
|
|
|
|
case QuoteStatus::DoubleQuoteEscape:
|
|
|
|
Result = QuoteStatus::DoubleQuote;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-06-22 13:24:26 +08:00
|
|
|
return Result;
|
2021-06-10 09:34:59 +08:00
|
|
|
}
|
|
|
|
|
2021-08-23 10:16:06 +08:00
|
|
|
void Editor::reparse()
|
|
|
|
{
|
|
|
|
parseFile(mParser,mFilename,mInProject);
|
|
|
|
}
|
|
|
|
|
2021-08-25 08:48:33 +08:00
|
|
|
void Editor::showCompletion(bool autoComplete)
|
|
|
|
{
|
|
|
|
// if not devCodeCompletion.Enabled then
|
|
|
|
// Exit;
|
|
|
|
if (!mParser->enabled())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (mCompletionPopup->isVisible()) // already in search, don't do it again
|
|
|
|
return;
|
|
|
|
|
|
|
|
QString word="";
|
|
|
|
|
|
|
|
QString s;
|
|
|
|
PSynHighlighterAttribute attr;
|
|
|
|
bool tokenFinished;
|
|
|
|
SynHighlighterTokenType tokenType;
|
|
|
|
BufferCoord pBeginPos, pEndPos;
|
|
|
|
if (GetHighlighterAttriAtRowCol(
|
|
|
|
BufferCoord{caretX() - 1,
|
|
|
|
caretY()}, s, tokenFinished,tokenType, attr)) {
|
|
|
|
if (tokenType == SynHighlighterTokenType::PreprocessDirective) {//Preprocessor
|
2021-08-25 23:53:35 +08:00
|
|
|
word = getWordAtPosition(caretXY(),pBeginPos,pEndPos, WordPurpose::wpDirective);
|
|
|
|
if (!word.startsWith('#')) {
|
|
|
|
//showTabnineCompletion();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (tokenType == SynHighlighterTokenType::Comment) { //Comment, javadoc tag
|
|
|
|
word = getWordAtPosition(caretXY(),pBeginPos,pEndPos, WordPurpose::wpJavadoc);
|
|
|
|
if (!word.startsWith('@')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
(tokenType != SynHighlighterTokenType::Symbol) &&
|
|
|
|
(tokenType != SynHighlighterTokenType::Space) &&
|
|
|
|
(tokenType != SynHighlighterTokenType::Identifier)
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
2021-08-25 08:48:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Position it at the top of the next line
|
2021-08-25 23:53:35 +08:00
|
|
|
QPoint p = RowColumnToPixels(displayXY());
|
|
|
|
p+=QPoint(0,textHeight()+2);
|
|
|
|
mCompletionPopup->move(mapToGlobal(p));
|
|
|
|
|
|
|
|
// fCompletionBox.RecordUsage := devCodeCompletion.RecordUsage;
|
|
|
|
// fCompletionBox.SortByScope := devCodeCompletion.SortByScope;
|
|
|
|
// fCompletionBox.ShowKeywords := devCodeCompletion.ShowKeywords;
|
|
|
|
// fCompletionBox.ShowCodeIns := devCodeCompletion.ShowCodeIns;
|
|
|
|
// fCompletionBox.IgnoreCase := devCodeCompletion.IgnoreCase;
|
|
|
|
// fCompletionBox.CodeInsList := dmMain.CodeInserts.ItemList;
|
|
|
|
// fCompletionBox.SymbolUsage := dmMain.SymbolUsage;
|
|
|
|
// fCompletionBox.ShowCount := devCodeCompletion.MaxCount;
|
2021-08-25 08:48:33 +08:00
|
|
|
//Set Font size;
|
2021-08-25 23:53:35 +08:00
|
|
|
mCompletionPopup->setFont(font());
|
2021-08-25 08:48:33 +08:00
|
|
|
|
|
|
|
// Redirect key presses to completion box if applicable
|
2021-08-25 23:53:35 +08:00
|
|
|
//todo:
|
|
|
|
mCompletionPopup->setKeypressedCallback([this](QKeyEvent *event)->bool{
|
|
|
|
return onCompletionKeyPressed(event);
|
|
|
|
});
|
|
|
|
mCompletionPopup->setParser(mParser);
|
|
|
|
//todo:
|
|
|
|
//mCompletionPopup->setUseCppKeyword(mUseCppSyntax);
|
|
|
|
mCompletionPopup->show();
|
2021-08-25 08:48:33 +08:00
|
|
|
|
|
|
|
// Scan the current function body
|
2021-08-25 23:53:35 +08:00
|
|
|
mCompletionPopup->setCurrentStatement(
|
|
|
|
mParser->findAndScanBlockAt(mFilename, caretY())
|
|
|
|
);
|
2021-08-25 08:48:33 +08:00
|
|
|
|
2021-08-25 23:53:35 +08:00
|
|
|
if (word.isEmpty())
|
|
|
|
word=getWordAtPosition(caretXY(),pBeginPos,pEndPos, WordPurpose::wpCompletion);
|
2021-08-25 08:48:33 +08:00
|
|
|
//if not fCompletionBox.Visible then
|
2021-08-25 23:53:35 +08:00
|
|
|
mCompletionPopup->prepareSearch(word, mFilename, pBeginPos.Line);
|
2021-08-25 08:48:33 +08:00
|
|
|
|
|
|
|
// Filter the whole statement list
|
2021-08-25 23:53:35 +08:00
|
|
|
if (mCompletionPopup->search(word, autoComplete)) { //only one suggestion and it's not input while typing
|
|
|
|
completionInsert(); // if only have one suggestion, just use it
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::showHeaderCompletion(bool autoComplete)
|
|
|
|
{
|
2021-08-29 10:14:07 +08:00
|
|
|
// if not devCodeCompletion.Enabled then
|
|
|
|
// Exit;
|
|
|
|
|
|
|
|
if (mHeaderCompletionPopup->isVisible()) // already in search, don't do it again
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Position it at the top of the next line
|
|
|
|
QPoint p = RowColumnToPixels(displayXY());
|
|
|
|
p.setY(p.y() + textHeight() + 2);
|
|
|
|
mHeaderCompletionPopup->move(mapToGlobal(p));
|
|
|
|
|
|
|
|
|
|
|
|
// fHeaderCompletionBox.IgnoreCase := devCodeCompletion.IgnoreCase;
|
|
|
|
// fHeaderCompletionBox.ShowCount := devCodeCompletion.MaxCount;
|
|
|
|
//Set Font size;
|
|
|
|
mHeaderCompletionPopup->setFont(font());
|
|
|
|
|
|
|
|
// Redirect key presses to completion box if applicable
|
|
|
|
mHeaderCompletionPopup->setKeypressedCallback([this](QKeyEvent* event)->bool{
|
|
|
|
return onHeaderCompletionKeyPressed(event);
|
|
|
|
});
|
|
|
|
mHeaderCompletionPopup->setParser(mParser);
|
|
|
|
|
|
|
|
BufferCoord pBeginPos,pEndPos;
|
|
|
|
QString word = getWordAtPosition(caretXY(),pBeginPos,pEndPos,
|
|
|
|
WordPurpose::wpHeaderCompletionStart);
|
|
|
|
if (word.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!word.startsWith('"') && !word.startsWith('<'))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (word.lastIndexOf('"')>0 || word.lastIndexOf('>')>0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mHeaderCompletionPopup->show();
|
|
|
|
mHeaderCompletionPopup->setSearchLocal(word.startsWith('"'));
|
|
|
|
word.remove(0,1);
|
|
|
|
|
|
|
|
mHeaderCompletionPopup->prepareSearch(word, mFilename);
|
|
|
|
|
|
|
|
// Filter the whole statement list
|
|
|
|
if (mHeaderCompletionPopup->search(word, autoComplete)) //only one suggestion and it's not input while typing
|
|
|
|
headerCompletionInsert(); // if only have one suggestion, just use it
|
2021-08-25 23:53:35 +08:00
|
|
|
}
|
|
|
|
|
2021-08-26 11:58:29 +08:00
|
|
|
bool Editor::testInFunc(int x, int y)
|
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
QString s = lines()->getString(y);
|
|
|
|
int posY = y;
|
|
|
|
int posX = std::min(x,s.length()-1); // x is started from 1
|
|
|
|
int bracketLevel=0;
|
|
|
|
while (true) {
|
|
|
|
while (posX < 0) {
|
|
|
|
posY--;
|
|
|
|
if (posY < 0)
|
|
|
|
return false;
|
|
|
|
s = lines()->getString(posY);
|
|
|
|
posX = s.length()-1;
|
|
|
|
}
|
|
|
|
if (s[posX] == '>'
|
|
|
|
|| s[posX] == ']') {
|
|
|
|
bracketLevel++;
|
|
|
|
} else if (s[posX] == '<'
|
|
|
|
|| s[posX] == '[') {
|
|
|
|
bracketLevel--;
|
|
|
|
} else if (bracketLevel==0) {
|
|
|
|
switch (s[posX].unicode()) {
|
|
|
|
case '(':
|
|
|
|
return true;
|
|
|
|
case ';':
|
|
|
|
case '{':
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!(isIdentChar(s[posX])
|
|
|
|
|| s[posX] == ' '
|
|
|
|
|| s[posX] == '\t'
|
|
|
|
|| s[posX] == '*'
|
|
|
|
|| s[posX] == '&'))
|
|
|
|
break;;
|
|
|
|
}
|
|
|
|
posX--;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-08-26 17:48:23 +08:00
|
|
|
void Editor::completionInsert(bool appendFunc)
|
|
|
|
{
|
|
|
|
PStatement statement = mCompletionPopup->selectedStatement();
|
|
|
|
if (!statement)
|
|
|
|
return;
|
|
|
|
// if devCodeCompletion.RecordUsage and (Statement^._Kind <> skUserCodeIn) then begin
|
|
|
|
// idx:=Utils.FastIndexOf(dmMain.SymbolUsage,Statement^._FullName);
|
|
|
|
// if idx = -1 then begin
|
|
|
|
// usageCount:=1;
|
|
|
|
// dmMain.SymbolUsage.AddObject(Statement^._FullName, pointer(1))
|
|
|
|
// end else begin
|
|
|
|
// usageCount := 1 + integer(dmMain.SymbolUsage.Objects[idx]);
|
|
|
|
// dmMain.SymbolUsage.Objects[idx] := pointer( usageCount );
|
|
|
|
// end;
|
|
|
|
// Statement^._UsageCount := usageCount;
|
|
|
|
// end;
|
|
|
|
|
|
|
|
QString funcAddOn = "";
|
|
|
|
|
|
|
|
// delete the part of the word that's already been typed ...
|
2021-08-28 09:01:40 +08:00
|
|
|
BufferCoord p = WordEnd();
|
|
|
|
setBlockBegin(WordStart());
|
2021-08-26 17:48:23 +08:00
|
|
|
setBlockEnd(p);
|
|
|
|
|
|
|
|
// if we are inserting a function,
|
|
|
|
if (appendFunc) {
|
|
|
|
if (statement->kind == StatementKind::skFunction
|
|
|
|
|| statement->kind == StatementKind::skConstructor
|
|
|
|
|| statement->kind == StatementKind::skDestructor) {
|
|
|
|
if ((p.Char >= lineText().length()) // it's the last char on line
|
2021-08-29 10:14:07 +08:00
|
|
|
|| (lineText().at(p.Char) != '(')) { // it don't have '(' after it
|
2021-08-26 17:48:23 +08:00
|
|
|
if (statement->fullName!="std::endl")
|
|
|
|
funcAddOn = "()";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ... by replacing the selection
|
|
|
|
if (statement->kind == StatementKind::skUserCodeIn) { // it's a user code template
|
|
|
|
//insertUserCodeIn(Statement->value);
|
|
|
|
} else {
|
|
|
|
if (
|
|
|
|
(statement->kind == StatementKind::skKeyword
|
|
|
|
|| statement->kind == StatementKind::skPreprocessor)
|
|
|
|
&& (statement->command.startsWith('#')
|
|
|
|
|| statement->command.startsWith('@'))
|
|
|
|
) {
|
|
|
|
setSelText(statement->command.mid(1));
|
|
|
|
} else
|
|
|
|
setSelText(statement->command + funcAddOn);
|
|
|
|
|
|
|
|
if (!funcAddOn.isEmpty())
|
|
|
|
mLastIdCharPressed = 0;
|
|
|
|
|
|
|
|
// Move caret inside the ()'s, only when the user has something to do there...
|
|
|
|
if (!funcAddOn.isEmpty()
|
|
|
|
&& (statement->args != "()")
|
|
|
|
&& (statement->args != "(void)")) {
|
|
|
|
setCaretX(caretX() - funcAddOn.length()+1);
|
|
|
|
|
|
|
|
//todo: function hint
|
|
|
|
// immediately activate function hint
|
|
|
|
// if devEditor.ShowFunctionTip and Assigned(fText.Highlighter) then begin
|
|
|
|
// fText.SetFocus;
|
|
|
|
// fFunctionTip.Parser := fParser;
|
|
|
|
// fFunctionTip.FileName := fFileName;
|
|
|
|
// fFunctionTip.Show;
|
|
|
|
// end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mCompletionPopup->hide();
|
|
|
|
}
|
|
|
|
|
2021-08-29 10:14:07 +08:00
|
|
|
void Editor::headerCompletionInsert()
|
|
|
|
{
|
|
|
|
QString headerName = mHeaderCompletionPopup->selectedFilename();
|
|
|
|
if (headerName.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// delete the part of the word that's already been typed ...
|
|
|
|
BufferCoord p = caretXY();
|
|
|
|
int posBegin = p.Char-1;
|
|
|
|
int posEnd = p.Char-1;
|
|
|
|
QString sLine = lineText();
|
|
|
|
while ((posBegin>0) &&
|
|
|
|
(isIdentChar(sLine[posBegin-1]) || (sLine[posBegin-1]=='.')))
|
|
|
|
posBegin--;
|
|
|
|
|
|
|
|
while ((posEnd < sLine.length())
|
|
|
|
&& (isIdentChar(sLine[posEnd]) || (sLine[posEnd]=='.')))
|
|
|
|
posEnd++;
|
|
|
|
p.Char = posBegin+1;
|
|
|
|
setBlockBegin(p);
|
|
|
|
p.Char = posEnd+1;
|
|
|
|
setBlockEnd(p);
|
|
|
|
|
|
|
|
setSelText(headerName);
|
|
|
|
|
|
|
|
mCompletionPopup->hide();
|
|
|
|
}
|
|
|
|
|
2021-08-26 17:48:23 +08:00
|
|
|
bool Editor::onCompletionKeyPressed(QKeyEvent *event)
|
|
|
|
{
|
|
|
|
bool processed = false;
|
|
|
|
if (!mCompletionPopup->isEnabled())
|
|
|
|
return false;
|
2021-08-29 10:14:07 +08:00
|
|
|
QString oldPhrase = mCompletionPopup->phrase();
|
|
|
|
WordPurpose purpose = WordPurpose::wpCompletion;
|
|
|
|
if (oldPhrase.startsWith('#')) {
|
|
|
|
purpose = WordPurpose::wpDirective;
|
|
|
|
} else if (oldPhrase.startsWith('@')) {
|
|
|
|
purpose = WordPurpose::wpJavadoc;
|
|
|
|
}
|
2021-08-26 17:48:23 +08:00
|
|
|
QString phrase;
|
|
|
|
BufferCoord pBeginPos,pEndPos;
|
|
|
|
switch (event->key()) {
|
|
|
|
case Qt::Key_Backspace:
|
|
|
|
ExecuteCommand(
|
|
|
|
SynEditorCommand::ecDeleteLastChar,
|
|
|
|
QChar(), nullptr); // Simulate backspace in editor
|
|
|
|
phrase = getWordAtPosition(caretXY(),
|
|
|
|
pBeginPos,pEndPos,
|
2021-08-29 10:14:07 +08:00
|
|
|
purpose);
|
2021-08-26 17:48:23 +08:00
|
|
|
mLastIdCharPressed = phrase.length();
|
|
|
|
mCompletionPopup->search(phrase, false);
|
|
|
|
return true;
|
|
|
|
case Qt::Key_Escape:
|
|
|
|
mCompletionPopup->hide();
|
|
|
|
return true;
|
|
|
|
case Qt::Key_Return:
|
|
|
|
case Qt::Key_Tab:
|
|
|
|
//CompletionInsert(devCodeCompletion.AppendFunc);
|
|
|
|
completionInsert(false);
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
if (event->text().isEmpty()) {
|
|
|
|
//stop completion
|
|
|
|
mCompletionPopup->hide();
|
|
|
|
keyPressEvent(event);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
QChar ch = event->text().front();
|
|
|
|
if (isIdentChar(ch)) {
|
|
|
|
setSelText(ch);
|
2021-08-29 10:14:07 +08:00
|
|
|
phrase = getWordAtPosition(caretXY(),
|
2021-08-26 17:48:23 +08:00
|
|
|
pBeginPos,pEndPos,
|
2021-08-29 10:14:07 +08:00
|
|
|
purpose);
|
2021-08-26 17:48:23 +08:00
|
|
|
mLastIdCharPressed = phrase.length();
|
|
|
|
mCompletionPopup->search(phrase, false);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
//stop completion
|
|
|
|
mCompletionPopup->hide();
|
|
|
|
keyPressEvent(event);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return processed;
|
|
|
|
}
|
|
|
|
|
2021-08-29 10:14:07 +08:00
|
|
|
bool Editor::onHeaderCompletionKeyPressed(QKeyEvent *event)
|
|
|
|
{
|
|
|
|
bool processed = false;
|
|
|
|
if (!mCompletionPopup->isEnabled())
|
|
|
|
return false;
|
|
|
|
QString phrase;
|
|
|
|
BufferCoord pBeginPos,pEndPos;
|
|
|
|
switch (event->key()) {
|
|
|
|
case Qt::Key_Backspace:
|
|
|
|
ExecuteCommand(
|
|
|
|
SynEditorCommand::ecDeleteLastChar,
|
|
|
|
QChar(), nullptr); // Simulate backspace in editor
|
|
|
|
phrase = getWordAtPosition(caretXY(),
|
|
|
|
pBeginPos,pEndPos,
|
|
|
|
WordPurpose::wpHeaderCompletion);
|
|
|
|
mLastIdCharPressed = phrase.length();
|
|
|
|
mHeaderCompletionPopup->search(phrase, false);
|
|
|
|
return true;
|
|
|
|
case Qt::Key_Escape:
|
|
|
|
mHeaderCompletionPopup->hide();
|
|
|
|
return true;
|
|
|
|
case Qt::Key_Return:
|
|
|
|
case Qt::Key_Tab:
|
|
|
|
//CompletionInsert(devCodeCompletion.AppendFunc);
|
|
|
|
headerCompletionInsert();
|
|
|
|
mHeaderCompletionPopup->hide();
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
if (event->text().isEmpty()) {
|
|
|
|
//stop completion
|
|
|
|
mHeaderCompletionPopup->hide();
|
|
|
|
keyPressEvent(event);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
QChar ch = event->text().front();
|
|
|
|
if (isIdentChar(ch)) {
|
|
|
|
setSelText(ch);
|
|
|
|
phrase = getWordAtPosition(caretXY(),
|
|
|
|
pBeginPos,pEndPos,
|
|
|
|
WordPurpose::wpHeaderCompletion);
|
|
|
|
mLastIdCharPressed = phrase.length();
|
|
|
|
mHeaderCompletionPopup->search(phrase, false);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
//stop completion
|
|
|
|
mHeaderCompletionPopup->hide();
|
|
|
|
keyPressEvent(event);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return processed;
|
|
|
|
}
|
|
|
|
|
2021-08-29 17:23:40 +08:00
|
|
|
Editor::TipType Editor::getTipType(QPoint point, BufferCoord& pos)
|
|
|
|
{
|
|
|
|
// Only allow in the text area...
|
|
|
|
if (PointToCharLine(point, pos)) {
|
|
|
|
if (!pMainWindow->debugger()->executing()
|
|
|
|
&& getSyntaxIssueAtPosition(pos)) {
|
|
|
|
return TipType::Error;
|
|
|
|
}
|
|
|
|
|
|
|
|
PSynHighlighterAttribute attr;
|
|
|
|
QString s;
|
|
|
|
|
|
|
|
// Only allow hand tips in highlighted areas
|
|
|
|
if (GetHighlighterAttriAtRowCol(pos,s,attr)) {
|
|
|
|
// Only allow Identifiers, Preprocessor directives, and selection
|
|
|
|
if (attr) {
|
|
|
|
if (selAvail()) {
|
|
|
|
// do not allow when dragging selection
|
|
|
|
if (IsPointInSelection(pos))
|
|
|
|
return TipType::Selection;
|
2021-08-29 22:08:43 +08:00
|
|
|
} else if (attr->name() == SYNS_AttrIdentifier)
|
2021-08-29 17:23:40 +08:00
|
|
|
return TipType::Identifier;
|
|
|
|
else if (attr->name() == SYNS_AttrPreprocessor)
|
|
|
|
return TipType::Preprocessor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return TipType::None;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::cancelHint()
|
|
|
|
{
|
|
|
|
//MainForm.Debugger.OnEvalReady := nil;
|
|
|
|
|
|
|
|
// disable editor hint
|
|
|
|
QToolTip::hideText();
|
|
|
|
mCurrentWord = "";
|
|
|
|
mCurrentTipType = TipType::None;
|
2021-08-29 22:08:43 +08:00
|
|
|
setCursor(Qt::IBeamCursor);
|
2021-08-29 17:23:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
QString Editor::getFileHint(const QString &s)
|
|
|
|
{
|
|
|
|
QString fileName = mParser->getHeaderFileName(mFilename, s);
|
|
|
|
if (QFileInfo(fileName).exists()) {
|
|
|
|
return fileName + " - " + tr("Ctrl+click for more info");
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Editor::getParserHint(const QString &s, int line)
|
|
|
|
{
|
|
|
|
// This piece of code changes the parser database, possibly making hints and code completion invalid...
|
|
|
|
QString result;
|
|
|
|
PStatement statement = mParser->findStatementOf(mFilename, s, line);
|
|
|
|
if (!statement)
|
|
|
|
return result;
|
|
|
|
if (statement->kind == StatementKind::skFunction
|
|
|
|
|| statement->kind == StatementKind::skConstructor
|
|
|
|
|| statement->kind == StatementKind::skDestructor) {
|
|
|
|
PStatement parentScope = statement->parentScope.lock();
|
|
|
|
if (parentScope && parentScope->kind == StatementKind::skNamespace) {
|
|
|
|
PStatementList namespaceStatementsList =
|
|
|
|
mParser->findNamespace(parentScope->command);
|
|
|
|
if (namespaceStatementsList) {
|
|
|
|
foreach (const PStatement& namespaceStatement, *namespaceStatementsList) {
|
|
|
|
QString hint = getHintForFunction(statement,namespaceStatement,
|
|
|
|
mFilename,line);
|
|
|
|
if (!hint.isEmpty()) {
|
|
|
|
if (!result.isEmpty())
|
|
|
|
result += "<BR />";
|
|
|
|
result += hint;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
result = getHintForFunction(statement, parentScope,
|
|
|
|
mFilename,line);
|
|
|
|
} else if (statement->line>0) {
|
|
|
|
QFileInfo fileInfo(statement->fileName);
|
2021-08-29 22:08:43 +08:00
|
|
|
result = mParser->prettyPrintStatement(statement,mFilename, line) + " - " +
|
2021-08-29 17:23:40 +08:00
|
|
|
QString(" %1 (%2) ")
|
|
|
|
.arg(fileInfo.fileName())
|
|
|
|
.arg(statement->line)
|
|
|
|
+ tr("Ctrl+click for more info");
|
|
|
|
} else { // hard defines
|
2021-08-29 22:08:43 +08:00
|
|
|
result = mParser->prettyPrintStatement(statement, mFilename);
|
2021-08-29 17:23:40 +08:00
|
|
|
}
|
|
|
|
// Result := StringReplace(Result, '|', #5, [rfReplaceAll]);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-08-29 22:08:43 +08:00
|
|
|
void Editor::showDebugHint(const QString &s, int line)
|
|
|
|
{
|
2021-08-29 22:51:23 +08:00
|
|
|
PStatement statement = mParser->findStatementOf(mFilename,s,line);
|
2021-08-29 22:08:43 +08:00
|
|
|
if (statement) {
|
|
|
|
if (statement->kind != StatementKind::skVariable
|
2021-08-29 22:51:23 +08:00
|
|
|
&& statement->kind != StatementKind::skGlobalVariable
|
|
|
|
&& statement->kind != StatementKind::skLocalVariable
|
|
|
|
&& statement->kind != StatementKind::skParameter) {
|
2021-08-29 22:08:43 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pMainWindow->debugger()->commandRunning())
|
|
|
|
return;
|
|
|
|
connect(pMainWindow->debugger(), &Debugger::evalValueReady,
|
|
|
|
this, &Editor::onTipEvalValueReady);
|
|
|
|
mCurrentDebugTipWord = s;
|
|
|
|
pMainWindow->debugger()->sendCommand("print",s,false);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Editor::getErrorHint(const PSyntaxIssue& issue)
|
|
|
|
{
|
|
|
|
if (issue) {
|
|
|
|
return issue->hint;
|
|
|
|
} else {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-29 17:23:40 +08:00
|
|
|
QString Editor::getHintForFunction(const PStatement &statement, const PStatement &scopeStatement, const QString& filename, int line)
|
|
|
|
{
|
|
|
|
QString result;
|
|
|
|
const StatementMap& children = mParser->statementList().childrenStatements(scopeStatement);
|
|
|
|
foreach (const PStatement& childStatement, children){
|
|
|
|
if (statement->command == childStatement->command
|
|
|
|
&& statement->kind == childStatement->kind) {
|
|
|
|
if ((line < childStatement->line) &&
|
|
|
|
childStatement->fileName == filename)
|
|
|
|
continue;
|
|
|
|
if (!result.isEmpty())
|
|
|
|
result += "<BR />";
|
2021-08-29 22:08:43 +08:00
|
|
|
result = mParser->prettyPrintStatement(childStatement,filename,line) + " - " +
|
2021-08-29 17:23:40 +08:00
|
|
|
QString(" %1 (%2) ")
|
|
|
|
.arg(filename)
|
|
|
|
.arg(childStatement->line)
|
|
|
|
+ tr("Ctrl+click for more info");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-08-25 23:53:35 +08:00
|
|
|
QString Editor::getWordAtPosition(const BufferCoord &p, BufferCoord &pWordBegin, BufferCoord &pWordEnd, WordPurpose purpose)
|
|
|
|
{
|
|
|
|
QString result = "";
|
|
|
|
QString s;
|
2021-08-26 11:58:29 +08:00
|
|
|
if ((p.Line<1) || (p.Line>lines()->count())) {
|
|
|
|
pWordBegin = p;
|
|
|
|
pWordEnd = p;
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
s = lines()->getString(p.Line - 1);
|
|
|
|
int len = s.length();
|
|
|
|
|
|
|
|
int wordBegin = p.Char - 1 - 1; //BufferCoord::Char starts with 1
|
|
|
|
int wordEnd = p.Char - 1 - 1;
|
|
|
|
|
|
|
|
// Copy forward until end of word
|
|
|
|
if (purpose == WordPurpose::wpEvaluation
|
|
|
|
|| purpose == WordPurpose::wpInformation) {
|
|
|
|
while (wordEnd + 1 < len) {
|
|
|
|
if ((purpose == WordPurpose::wpEvaluation)
|
|
|
|
&& (s[wordEnd + 1] == '[')) {
|
|
|
|
if (!findComplement(s, '[', ']', wordEnd, 1))
|
2021-08-25 23:53:35 +08:00
|
|
|
break;
|
2021-08-26 11:58:29 +08:00
|
|
|
} else if (isIdentChar(s[wordEnd + 1])) {
|
|
|
|
wordEnd++;
|
|
|
|
} else
|
|
|
|
break;
|
2021-08-25 23:53:35 +08:00
|
|
|
}
|
2021-08-26 11:58:29 +08:00
|
|
|
}
|
2021-08-25 23:53:35 +08:00
|
|
|
|
2021-08-26 11:58:29 +08:00
|
|
|
// Copy backward until #
|
|
|
|
if (purpose == WordPurpose::wpDirective) {
|
|
|
|
while ((wordBegin >= 0) && (wordBegin < len)) {
|
|
|
|
if (isIdentChar(s[wordBegin]))
|
|
|
|
wordBegin--;
|
|
|
|
else if (s[wordBegin] == '#') {
|
|
|
|
wordBegin--;
|
|
|
|
break;
|
|
|
|
} else
|
|
|
|
break;
|
2021-08-25 23:53:35 +08:00
|
|
|
}
|
2021-08-26 11:58:29 +08:00
|
|
|
}
|
2021-08-25 23:53:35 +08:00
|
|
|
|
2021-08-26 11:58:29 +08:00
|
|
|
// Copy backward until @
|
|
|
|
if (purpose == WordPurpose::wpJavadoc) {
|
|
|
|
while ((wordBegin >= 0) && (wordBegin < len)) {
|
|
|
|
if (isIdentChar(s[wordBegin]))
|
|
|
|
wordBegin--;
|
|
|
|
else if (s[wordBegin] == '@') {
|
|
|
|
wordBegin--;
|
|
|
|
break;
|
|
|
|
} else
|
|
|
|
break;
|
2021-08-25 23:53:35 +08:00
|
|
|
}
|
2021-08-26 11:58:29 +08:00
|
|
|
}
|
2021-08-25 23:53:35 +08:00
|
|
|
|
2021-08-26 11:58:29 +08:00
|
|
|
// Copy backward until begin of path
|
|
|
|
if (purpose == WordPurpose::wpHeaderCompletion) {
|
|
|
|
while ((wordBegin >= 0) && (wordBegin < len)) {
|
|
|
|
if (isIdentChar(s[wordBegin]))
|
|
|
|
wordBegin--;
|
|
|
|
else if (s[wordBegin] == '/'
|
|
|
|
|| s[wordBegin] == '\\'
|
|
|
|
|| s[wordBegin] == '.') {
|
|
|
|
wordBegin--;
|
|
|
|
break;
|
|
|
|
} else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (purpose == WordPurpose::wpHeaderCompletionStart) {
|
|
|
|
while ((wordBegin >= 0) && (wordBegin < len)) {
|
|
|
|
if (s[wordBegin] == '"'
|
|
|
|
|| s[wordBegin] == '<') {
|
|
|
|
wordBegin--;
|
|
|
|
break;
|
|
|
|
} else if (s[wordBegin] == '/'
|
2021-08-25 23:53:35 +08:00
|
|
|
|| s[wordBegin] == '\\'
|
|
|
|
|| s[wordBegin] == '.') {
|
|
|
|
wordBegin--;
|
2021-08-26 11:58:29 +08:00
|
|
|
} else if (isIdentChar(s[wordBegin]))
|
|
|
|
wordBegin--;
|
|
|
|
else
|
|
|
|
break;
|
2021-08-25 23:53:35 +08:00
|
|
|
}
|
2021-08-26 11:58:29 +08:00
|
|
|
}
|
2021-08-25 23:53:35 +08:00
|
|
|
|
2021-08-26 11:58:29 +08:00
|
|
|
// && ( wordBegin < len)
|
|
|
|
// Copy backward until begin of word
|
|
|
|
if (purpose == WordPurpose::wpCompletion
|
|
|
|
|| purpose == WordPurpose::wpEvaluation
|
|
|
|
|| purpose == WordPurpose::wpInformation) {
|
|
|
|
while ((wordBegin >= 0) && (wordBegin<len)) {
|
|
|
|
if (s[wordBegin] == ']') {
|
|
|
|
if (!findComplement(s, ']', '[', wordBegin, -1))
|
2021-08-25 23:53:35 +08:00
|
|
|
break;
|
|
|
|
else
|
2021-08-26 11:58:29 +08:00
|
|
|
wordBegin++; // step over mathing [
|
|
|
|
} else if (isIdentChar(s[wordBegin])) {
|
|
|
|
wordBegin--;
|
|
|
|
} else if (s[wordBegin] == '.'
|
|
|
|
|| s[wordBegin] == ':'
|
|
|
|
|| s[wordBegin] == '~') { // allow destructor signs
|
|
|
|
wordBegin--;
|
|
|
|
} else if (
|
|
|
|
(s[wordBegin] == '>')
|
|
|
|
&& (wordBegin+2<len)
|
|
|
|
&& (s[wordBegin+1]==':')
|
|
|
|
&& (s[wordBegin+2]==':')
|
|
|
|
) { // allow template
|
|
|
|
if (!findComplement(s, '>', '<', wordBegin, -1))
|
2021-08-25 23:53:35 +08:00
|
|
|
break;
|
2021-08-26 11:58:29 +08:00
|
|
|
else
|
|
|
|
wordBegin--; // step over >
|
|
|
|
} else if ((wordBegin-1 >= 0)
|
|
|
|
&& (s[wordBegin - 1] == '-')
|
|
|
|
&& (s[wordBegin] == '>')) {
|
|
|
|
wordBegin-=2;
|
|
|
|
} else if ((wordBegin-1 >= 0)
|
|
|
|
&& (s[wordBegin - 1] == ':')
|
|
|
|
&& (s[wordBegin] == ':')) {
|
|
|
|
wordBegin-=2;
|
|
|
|
} else if ((wordBegin > 0)
|
|
|
|
&& (s[wordBegin] == ')')) {
|
|
|
|
if (!findComplement(s, ')', '(', wordBegin, -1))
|
2021-08-25 23:53:35 +08:00
|
|
|
break;
|
2021-08-26 11:58:29 +08:00
|
|
|
else
|
|
|
|
wordBegin--; // step over mathing (
|
|
|
|
} else
|
|
|
|
break;
|
2021-08-25 23:53:35 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get end result
|
2021-08-26 20:18:20 +08:00
|
|
|
result = s.mid(wordBegin+1, wordEnd - wordBegin);
|
2021-08-25 23:53:35 +08:00
|
|
|
pWordBegin.Line = p.Line;
|
|
|
|
pWordBegin.Char = wordBegin+1;
|
|
|
|
pWordEnd.Line = p.Line;
|
|
|
|
pWordEnd.Char = wordEnd;
|
2021-08-26 11:58:29 +08:00
|
|
|
|
|
|
|
// last line still have part of word
|
|
|
|
if (!result.isEmpty()
|
|
|
|
&& (
|
|
|
|
result[0] == '.'
|
|
|
|
|| result[0] == '-')
|
|
|
|
&& (purpose == WordPurpose::wpCompletion
|
|
|
|
|| purpose == WordPurpose::wpEvaluation
|
|
|
|
|| purpose == WordPurpose::wpInformation)) {
|
|
|
|
int i = wordBegin;
|
|
|
|
int line=p.Line;
|
|
|
|
while (line>=1) {
|
|
|
|
while (i>=0) {
|
|
|
|
if (s[i] == ' '
|
|
|
|
|| s[i] == '\t')
|
|
|
|
i--;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (i<0) {
|
|
|
|
line--;
|
|
|
|
if (line>=1) {
|
|
|
|
s=lines()->getString(line-1);
|
|
|
|
i=s.length();
|
|
|
|
continue;
|
|
|
|
} else
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
BufferCoord highlightPos;
|
|
|
|
BufferCoord pDummy;
|
|
|
|
highlightPos.Line = line;
|
|
|
|
highlightPos.Char = i+1;
|
|
|
|
result = getWordAtPosition(highlightPos,pWordBegin,pDummy,purpose)+result;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-25 23:53:35 +08:00
|
|
|
|
|
|
|
// Strip function parameters
|
2021-08-26 11:58:29 +08:00
|
|
|
int paramBegin,paramEnd;
|
|
|
|
while (true) {
|
|
|
|
paramBegin = result.indexOf('(');
|
|
|
|
if (paramBegin > 0) {
|
|
|
|
paramEnd = paramBegin;
|
|
|
|
if ((paramBegin==0)
|
|
|
|
&& findComplement(result, '(', ')', paramEnd, 1)
|
|
|
|
&& (paramEnd = result.length()-1) ) {
|
|
|
|
//remove the enclosing parenthese pair
|
|
|
|
result = result.mid(1,result.length()-2);
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
paramEnd = paramBegin;
|
|
|
|
if (findComplement(result, '(', ')', paramEnd, 1)) {
|
|
|
|
result.remove(paramBegin, paramEnd - paramBegin + 1);
|
|
|
|
} else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else
|
2021-08-25 23:53:35 +08:00
|
|
|
break;
|
2021-08-26 11:58:29 +08:00
|
|
|
}
|
2021-08-25 23:53:35 +08:00
|
|
|
|
2021-08-26 11:58:29 +08:00
|
|
|
paramBegin = 0;
|
|
|
|
while ((paramBegin < result.length()) && (result[paramBegin] == '*')) {
|
|
|
|
paramBegin++;
|
|
|
|
}
|
|
|
|
result.remove(0,paramBegin);
|
|
|
|
return result;
|
|
|
|
}
|
2021-08-25 23:53:35 +08:00
|
|
|
|
2021-08-26 11:58:29 +08:00
|
|
|
QString Editor::getPreviousWordAtPositionForSuggestion(const BufferCoord &p)
|
|
|
|
{
|
|
|
|
QString result;
|
|
|
|
if ((p.Line<1) || (p.Line>lines()->count())) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
bool inFunc = testInFunc(p.Char-1,p.Line-1);
|
|
|
|
|
|
|
|
QString s = lines()->getString(p.Line - 1);
|
|
|
|
int wordBegin;
|
|
|
|
int wordEnd = p.Char-1;
|
|
|
|
if (wordEnd >= s.length())
|
|
|
|
wordEnd = s.length()-1;
|
|
|
|
while (true) {
|
|
|
|
int bracketLevel=0;
|
|
|
|
bool skipNextWord=false;
|
|
|
|
while (wordEnd > 0) {
|
|
|
|
if (s[wordEnd] == '>'
|
|
|
|
|| s[wordEnd] == ']') {
|
|
|
|
bracketLevel++;
|
|
|
|
} else if (s[wordEnd] == '<'
|
|
|
|
|| s[wordEnd] == '[') {
|
|
|
|
bracketLevel--;
|
|
|
|
} else if (bracketLevel==0) {
|
|
|
|
//we can't differentiate multiple definition and function parameter define here , so we don't handle ','
|
|
|
|
if (s[wordEnd] == ',') {
|
|
|
|
if (inFunc) // in func, dont skip ','
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
skipNextWord=true;
|
|
|
|
} else if (s[wordEnd] != ' '
|
|
|
|
&& s[wordEnd] != '\t') {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
wordEnd--;
|
|
|
|
}
|
|
|
|
if (wordEnd<0)
|
|
|
|
return "";
|
|
|
|
if (bracketLevel > 0)
|
|
|
|
return "";
|
|
|
|
if (!isIdentChar(s[wordEnd]))
|
|
|
|
return "";
|
|
|
|
|
|
|
|
wordBegin = wordEnd;
|
|
|
|
while ((wordBegin >= 0) && isIdentChar(s[wordBegin])) {
|
|
|
|
wordBegin--;
|
|
|
|
}
|
|
|
|
wordBegin++;
|
|
|
|
|
|
|
|
if (s[wordBegin]>='0' && s[wordBegin]<='9') // not valid word
|
|
|
|
return "";
|
|
|
|
|
|
|
|
result = s.mid(wordBegin, wordEnd - wordBegin+1);
|
|
|
|
if ((result != "const") && !skipNextWord)
|
|
|
|
break;
|
|
|
|
wordEnd = wordBegin-1;
|
|
|
|
}
|
|
|
|
return result;
|
2021-08-25 08:48:33 +08:00
|
|
|
}
|
|
|
|
|
2021-08-23 17:27:17 +08:00
|
|
|
const PCppParser &Editor::parser() const
|
|
|
|
{
|
|
|
|
return mParser;
|
|
|
|
}
|
|
|
|
|
2021-07-03 13:57:22 +08:00
|
|
|
int Editor::gutterClickedLine() const
|
|
|
|
{
|
|
|
|
return mGutterClickedLine;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::toggleBreakpoint(int line)
|
|
|
|
{
|
|
|
|
if (hasBreakpoint(line)) {
|
|
|
|
mBreakpointLines.remove(line);
|
2021-08-01 09:13:38 +08:00
|
|
|
pMainWindow->debugger()->removeBreakpoint(line,this);
|
2021-07-03 13:57:22 +08:00
|
|
|
} else {
|
|
|
|
mBreakpointLines.insert(line);
|
2021-08-01 09:13:38 +08:00
|
|
|
pMainWindow->debugger()->addBreakpoint(line,this);
|
2021-07-03 13:57:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
invalidateGutterLine(line);
|
|
|
|
invalidateLine(line);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::hasBreakpoint(int line)
|
|
|
|
{
|
|
|
|
return mBreakpointLines.contains(line);
|
|
|
|
}
|
|
|
|
|
2021-07-26 00:22:08 +08:00
|
|
|
void Editor::removeBreakpointFocus()
|
|
|
|
{
|
|
|
|
if (mActiveBreakpointLine!=-1) {
|
|
|
|
int oldLine = mActiveBreakpointLine;
|
|
|
|
mActiveBreakpointLine = -1;
|
|
|
|
invalidateGutterLine(oldLine);
|
|
|
|
invalidateLine(oldLine);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-26 18:22:09 +08:00
|
|
|
void Editor::setActiveBreakpointFocus(int Line, bool setFocus)
|
|
|
|
{
|
|
|
|
if (Line != mActiveBreakpointLine) {
|
|
|
|
removeBreakpointFocus();
|
|
|
|
|
|
|
|
// Put the caret at the active breakpoint
|
|
|
|
mActiveBreakpointLine = Line;
|
|
|
|
|
|
|
|
if (setFocus)
|
|
|
|
setCaretPositionAndActivate(Line,1);
|
|
|
|
else
|
|
|
|
setCaretPosition(Line,1);
|
|
|
|
|
|
|
|
// Invalidate new active line
|
|
|
|
invalidateGutterLine(Line);
|
|
|
|
invalidateLine(Line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-07 11:02:03 +08:00
|
|
|
void Editor::applySettings()
|
|
|
|
{
|
|
|
|
SynEditorOptions options = eoAltSetsColumnMode |
|
|
|
|
eoDragDropEditing | eoDropFiles | eoKeepCaretX | eoTabsToSpaces |
|
2021-06-07 21:34:48 +08:00
|
|
|
eoRightMouseMovesCursor | eoScrollByOneLess | eoTabIndent | eoHideShowScrollbars;
|
2021-06-09 17:12:23 +08:00
|
|
|
|
|
|
|
//options
|
2021-06-07 11:02:03 +08:00
|
|
|
options.setFlag(eoAddIndent,pSettings->editor().addIndent());
|
|
|
|
options.setFlag(eoAutoIndent,pSettings->editor().autoIndent());
|
|
|
|
options.setFlag(eoTabsToSpaces,pSettings->editor().tabToSpaces());
|
|
|
|
|
|
|
|
options.setFlag(eoKeepCaretX,pSettings->editor().keepCaretX());
|
|
|
|
options.setFlag(eoEnhanceHomeKey,pSettings->editor().enhanceHomeKey());
|
|
|
|
options.setFlag(eoEnhanceEndKey,pSettings->editor().enhanceEndKey());
|
|
|
|
|
2021-06-08 21:41:42 +08:00
|
|
|
options.setFlag(eoHideShowScrollbars,pSettings->editor().autoHideScrollbar());
|
|
|
|
options.setFlag(eoScrollPastEol,pSettings->editor().scrollPastEol());
|
|
|
|
options.setFlag(eoScrollPastEof,pSettings->editor().scrollPastEof());
|
|
|
|
options.setFlag(eoScrollByOneLess,pSettings->editor().scrollByOneLess());
|
|
|
|
options.setFlag(eoHalfPageScroll,pSettings->editor().halfPageScroll());
|
2021-06-07 11:02:03 +08:00
|
|
|
setOptions(options);
|
|
|
|
|
|
|
|
setTabWidth(pSettings->editor().tabWidth());
|
|
|
|
setInsertCaret(pSettings->editor().caretForInsert());
|
|
|
|
setOverwriteCaret(pSettings->editor().caretForOverwrite());
|
|
|
|
setCaretColor(pSettings->editor().caretColor());
|
2021-06-09 17:12:23 +08:00
|
|
|
|
|
|
|
QFont f=QFont(pSettings->editor().fontName(),pSettings->editor().fontSize());
|
|
|
|
f.setStyleStrategy(QFont::PreferAntialias);
|
|
|
|
setFont(f);
|
|
|
|
|
|
|
|
// Set gutter properties
|
|
|
|
gutter().setLeftOffset(pSettings->editor().gutterLeftOffset());
|
|
|
|
gutter().setRightOffset(pSettings->editor().gutterRightOffset());
|
|
|
|
gutter().setBorderStyle(SynGutterBorderStyle::None);
|
|
|
|
gutter().setUseFontStyle(pSettings->editor().gutterUseCustomFont());
|
|
|
|
if (pSettings->editor().gutterUseCustomFont()) {
|
|
|
|
f=QFont(pSettings->editor().gutterFontName(),pSettings->editor().gutterFontSize());
|
|
|
|
} else {
|
|
|
|
f=QFont(pSettings->editor().fontName(),pSettings->editor().fontSize());
|
|
|
|
}
|
|
|
|
f.setStyleStrategy(QFont::PreferAntialias);
|
|
|
|
gutter().setFont(f);
|
|
|
|
gutter().setDigitCount(pSettings->editor().gutterDigitsCount());
|
|
|
|
gutter().setVisible(pSettings->editor().gutterVisible());
|
|
|
|
gutter().setAutoSize(pSettings->editor().gutterAutoSize());
|
|
|
|
gutter().setShowLineNumbers(pSettings->editor().gutterShowLineNumbers());
|
|
|
|
gutter().setLeadingZeros(pSettings->editor().gutterAddLeadingZero());
|
|
|
|
if (pSettings->editor().gutterLineNumbersStartZero())
|
|
|
|
gutter().setLineNumberStart(0);
|
|
|
|
else
|
|
|
|
gutter().setLineNumberStart(1);
|
|
|
|
//font color
|
|
|
|
|
2021-06-07 11:02:03 +08:00
|
|
|
}
|
|
|
|
|
2021-06-19 22:58:35 +08:00
|
|
|
void Editor::applyColorScheme(const QString& schemeName)
|
|
|
|
{
|
2021-06-20 22:54:16 +08:00
|
|
|
highlighterManager.applyColorScheme(highlighter(),schemeName);
|
2021-06-19 22:58:35 +08:00
|
|
|
PColorSchemeItem item = pColorManager->getItem(schemeName,COLOR_SCHEME_ACTIVE_LINE);
|
|
|
|
if (item) {
|
|
|
|
setActiveLineColor(item->background());
|
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName,COLOR_SCHEME_GUTTER);
|
|
|
|
if (item) {
|
|
|
|
gutter().setTextColor(item->foreground());
|
|
|
|
gutter().setColor(item->background());
|
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName,COLOR_SCHEME_FOLD_LINE);
|
|
|
|
if (item) {
|
2021-06-24 22:33:57 +08:00
|
|
|
codeFolding().folderBarLinesColor = item->foreground();
|
2021-06-19 22:58:35 +08:00
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName,COLOR_SCHEME_INDENT_GUIDE_LINE);
|
|
|
|
if (item) {
|
2021-06-24 22:33:57 +08:00
|
|
|
codeFolding().indentGuidesColor = item->foreground();
|
2021-06-19 22:58:35 +08:00
|
|
|
}
|
2021-07-26 00:22:08 +08:00
|
|
|
item = pColorManager->getItem(schemeName,COLOR_SCHEME_ERROR);
|
|
|
|
if (item) {
|
|
|
|
this->mSyntaxErrorColor = item->foreground();
|
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName,COLOR_SCHEME_WARNING);
|
|
|
|
if (item) {
|
|
|
|
this->mSyntaxWarningColor = item->foreground();
|
|
|
|
}
|
2021-08-28 09:01:40 +08:00
|
|
|
item = pColorManager->getItem(schemeName,COLOR_SCHEME_SELECTION);
|
|
|
|
if (item) {
|
|
|
|
setSelectedForeground(item->foreground());
|
|
|
|
setSelectedBackground(item->background());
|
|
|
|
}
|
2021-07-26 00:22:08 +08:00
|
|
|
item = pColorManager->getItem(schemeName,COLOR_SCHEME_ACTIVE_BREAKPOINT);
|
|
|
|
if (item) {
|
|
|
|
this->mActiveBreakpointForegroundColor = item->foreground();
|
|
|
|
this->mActiveBreakpointBackgroundColor = item->background();
|
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName,COLOR_SCHEME_BREAKPOINT);
|
|
|
|
if (item) {
|
|
|
|
this->mBreakpointForegroundColor = item->foreground();
|
2021-07-27 00:14:24 +08:00
|
|
|
this->mBreakpointBackgroundColor = item->background();
|
2021-07-26 00:22:08 +08:00
|
|
|
}
|
2021-08-28 09:01:40 +08:00
|
|
|
//color for code completion popup
|
|
|
|
if (mCompletionPopup) {
|
|
|
|
item = pColorManager->getItem(schemeName, SYNS_AttrFunction);
|
|
|
|
if (item) {
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skFunction,item->foreground());
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skConstructor,item->foreground());
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skDestructor,item->foreground());
|
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName, SYNS_AttrClass);
|
|
|
|
if (item) {
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skClass,item->foreground());
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skTypedef,item->foreground());
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skAlias,item->foreground());
|
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName, SYNS_AttrIdentifier);
|
|
|
|
if (item) {
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skEnumType,item->foreground());
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skEnumClassType,item->foreground());
|
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName, SYNS_AttrVariable);
|
|
|
|
if (item) {
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skVariable,item->foreground());
|
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName, SYNS_AttrLocalVariable);
|
|
|
|
if (item) {
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skLocalVariable,item->foreground());
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skParameter,item->foreground());
|
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName, SYNS_AttrGlobalVariable);
|
|
|
|
if (item) {
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skGlobalVariable,item->foreground());
|
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName, SYNS_AttrGlobalVariable);
|
|
|
|
if (item) {
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skGlobalVariable,item->foreground());
|
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName, SYNS_AttrPreprocessor);
|
|
|
|
if (item) {
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skPreprocessor,item->foreground());
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skEnum,item->foreground());
|
2021-08-29 10:14:07 +08:00
|
|
|
mHeaderCompletionPopup->setSuggestionColor(item->foreground());
|
2021-08-28 09:01:40 +08:00
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName, SYNS_AttrReservedWord);
|
|
|
|
if (item) {
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skKeyword,item->foreground());
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skUserCodeIn,item->foreground());
|
|
|
|
}
|
|
|
|
item = pColorManager->getItem(schemeName, SYNS_AttrString);
|
|
|
|
if (item) {
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skNamespace,item->foreground());
|
|
|
|
mCompletionPopup->colors().insert(StatementKind::skNamespaceAlias,item->foreground());
|
|
|
|
}
|
|
|
|
}
|
2021-06-19 22:58:35 +08:00
|
|
|
this->invalidate();
|
|
|
|
}
|
|
|
|
|
2021-04-11 12:39:22 +08:00
|
|
|
void Editor::updateCaption(const QString& newCaption) {
|
2021-06-19 22:58:35 +08:00
|
|
|
if (mParentPageControl==nullptr) {
|
2021-04-11 12:39:22 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
int index = mParentPageControl->indexOf(this);
|
|
|
|
if (index==-1)
|
|
|
|
return;
|
|
|
|
if (newCaption.isEmpty()) {
|
|
|
|
QString caption = QFileInfo(mFilename).fileName();
|
2021-05-24 00:41:00 +08:00
|
|
|
if (this->modified()) {
|
2021-04-11 12:39:22 +08:00
|
|
|
caption.append("[*]");
|
|
|
|
}
|
|
|
|
mParentPageControl->setTabText(index,caption);
|
|
|
|
} else {
|
|
|
|
mParentPageControl->setTabText(index,newCaption);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|