1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "scintillaeditextern.h"
6#include "style/stylecolor.h"
7#include "transceiver/codeeditorreceiver.h"
8
9#include "common/common.h"
10#include "framework/framework.h"
11
12#include <QApplication>
13#include <QFile>
14#include <QPoint>
15#include <QRegularExpression>
16#include <QScrollBar>
17
18#include <bitset>
19
20class ScintillaEditExternPrivate
21{
22 friend class ScintillaEditExtern;
23 ScintillaEditExternPrivate() {}
24 bool isCtrlKeyPressed;
25 bool isLeave;
26 bool isSaveText = false;
27 Scintilla::Position hoverPos = -1;
28 QTimer hoverTimer;
29 QTimer definitionHoverTimer;
30 QString filePath;
31 QString language;
32 newlsp::ProjectKey proKey;
33 Scintilla::Position editInsertPostion = -1;
34 int editInsertCount = 0;
35 QHash<int, QList<AnnotationInfo>> lineAnnotations;
36 QHash<QString, decltype (lineAnnotations)> moduleAnnotations;
37};
38
39ScintillaEditExtern::ScintillaEditExtern(QWidget *parent)
40 : ScintillaEdit (parent)
41 , d(new ScintillaEditExternPrivate)
42{
43 setTabWidth(4);
44 setIndentationGuides(SC_IV_LOOKBOTH);
45 styleSetBack(STYLE_DEFAULT, StyleColor::color(QColor(43,43,43)));
46 for (int i = 0; i < KEYWORDSET_MAX; i ++) {
47 styleSetFore(i, StyleColor::color(QColor(0xdd, 0xdd, 0xdd)));
48 styleSetBack(i, StyleColor::color(QColor(43,43,43)));
49 }
50 setCaretFore(StyleColor::color(QColor(255,255,255)));
51 horizontalScrollBar()->setVisible(false);
52}
53
54ScintillaEditExtern::~ScintillaEditExtern()
55{
56 if (d) {
57 delete d;
58 }
59}
60
61QString ScintillaEditExtern::fileLanguage(const QString &path)
62{
63 using namespace support_file;
64 return Language::id(path);
65}
66
67void ScintillaEditExtern::setFile(const QString &filePath)
68{
69 if (d->filePath == filePath) {
70 return;
71 } else {
72 d->filePath = filePath;
73 }
74
75 QString text;
76 QFile file(d->filePath);
77 if (file.open(QFile::OpenModeFlag::ReadOnly)) {
78 text = file.readAll();
79 file.close();
80 }
81 setText(text.toUtf8());
82 emptyUndoBuffer();
83 setSavePoint();
84
85 setMouseDwellTime(0);
86 QObject::connect(this, &ScintillaEditExtern::marginClicked, this, &ScintillaEditExtern::sciMarginClicked, Qt::UniqueConnection);
87 QObject::connect(this, &ScintillaEditExtern::modified, this, &ScintillaEditExtern::sciModified, Qt::UniqueConnection);
88 QObject::connect(this, &ScintillaEditExtern::dwellStart, this, &ScintillaEditExtern::sciDwellStart, Qt::UniqueConnection);
89 QObject::connect(this, &ScintillaEditExtern::dwellEnd, this, &ScintillaEditExtern::sciDwellEnd, Qt::UniqueConnection);
90 QObject::connect(this, &ScintillaEditExtern::notify, this, &ScintillaEditExtern::sciNotify, Qt::UniqueConnection);
91 QObject::connect(this, &ScintillaEditExtern::updateUi, this, &ScintillaEditExtern::sciUpdateUi, Qt::UniqueConnection);
92
93 QObject::connect(EditorCallProxy::instance(), &EditorCallProxy::toSearchText, this, &ScintillaEditExtern::find);
94 QObject::connect(EditorCallProxy::instance(), &EditorCallProxy::toReplaceText, this, &ScintillaEditExtern::replace);
95
96 QObject::connect(&d->hoverTimer, &QTimer::timeout, &d->hoverTimer, [=](){
97 emit this->hovered(d->hoverPos);
98 d->hoverTimer.stop();
99 }, Qt::UniqueConnection);
100
101 QObject::connect(&d->definitionHoverTimer, &QTimer::timeout, &d->definitionHoverTimer, [=](){
102 emit this->definitionHover(d->hoverPos);
103 d->definitionHoverTimer.stop();
104 }, Qt::UniqueConnection);
105 horizontalScrollBar()->setVisible(true);
106}
107
108void ScintillaEditExtern::updateFile()
109{
110 QString text;
111 QFile file(d->filePath);
112 if (file.open(QFile::OpenModeFlag::ReadOnly)) {
113 text = file.readAll();
114 file.close();
115 }
116 setText(text.toUtf8());
117 emptyUndoBuffer();
118 setSavePoint();
119}
120
121QString ScintillaEditExtern::file() const
122{
123 return d->filePath;
124}
125
126void ScintillaEditExtern::setProjectKey(const newlsp::ProjectKey &key)
127{
128 d->proKey = key;
129}
130
131QString ScintillaEditExtern::workspace() const
132{
133 return QString::fromStdString(d->proKey.workspace);
134}
135
136void ScintillaEditExtern::addDebugPoint(int line)
137{
138 markerAdd(line, StyleSci::Debug);
139}
140
141void ScintillaEditExtern::removeDebugPoint(int line)
142{
143 markerDelete(line, StyleSci::Debug);
144}
145
146newlsp::ProjectKey ScintillaEditExtern::projectKey() const
147{
148 return d->proKey;
149}
150
151QString ScintillaEditExtern::language() const
152{
153 return QString::fromStdString(d->proKey.language);
154}
155
156void ScintillaEditExtern::debugPointAllDelete()
157{
158 markerDeleteAll(StyleSci::Debug);
159}
160
161void ScintillaEditExtern::jumpToLine(int line)
162{
163 int lineOffSet = line - 1;
164 int displayLines = linesOnScreen();
165 setFocus(true);
166 gotoPos(lineEndPosition(lineOffSet));
167 if (displayLines > 0) {
168 int offsetLines = displayLines / 2;
169 setFirstVisibleLine(qMax(0, lineOffSet - offsetLines));
170 }
171 cancel();
172}
173
174void ScintillaEditExtern::jumpToRange(Scintilla::Position start, Scintilla::Position end)
175{
176 jumpToLine(lineFromPosition(end));
177 setSelectionStart(start);
178 setSelectionEnd(end);
179}
180
181void ScintillaEditExtern::runningToLine(int line)
182{
183 int lineOffSet = line - 1;
184
185 markerDeleteAll(StyleSci::Running);
186 markerDeleteAll(StyleSci::RunningLineBackground);
187
188 markerAdd(lineOffSet, StyleSci::Running);
189 markerAdd(lineOffSet, StyleSci::RunningLineBackground);
190}
191
192void ScintillaEditExtern::runningEnd()
193{
194 markerDeleteAll(StyleSci::Running);
195 markerDeleteAll(StyleSci::RunningLineBackground);
196}
197
198void ScintillaEditExtern::saveText()
199{
200 QFile file(d->filePath);
201 if (!file.exists())
202 return;
203
204 if (!file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate)) {
205 return;
206 }
207 d->isSaveText = true;
208 file.write(textRange(0, length()));
209 emit saved(d->filePath);
210 file.close();
211}
212
213
214void ScintillaEditExtern::saveAsText()
215{
216 QFile file(d->filePath);
217 if (!file.open(QFile::ReadWrite | QFile::Text | QFile::Truncate)) {
218 ContextDialog::ok("Can't save current: " + file.errorString());
219 return;
220 }
221 d->isSaveText = true;
222 file.write(textRange(0, length()));
223 emit saved(d->filePath);
224 file.close();
225}
226
227bool ScintillaEditExtern::isSaveText()
228{
229 return d->isSaveText;
230}
231
232void ScintillaEditExtern::cleanIsSaveText()
233{
234 d->isSaveText = false;
235}
236
237bool ScintillaEditExtern::isLeave()
238{
239 return d->isLeave;
240}
241
242void ScintillaEditExtern::replaceRange(Scintilla::Position start,
243 Scintilla::Position end, const QString &text)
244{
245 clearSelections();
246 setSelectionStart(start);
247 setSelectionEnd(end);
248 replaceSel(text.toLatin1());
249 emit replaceed(file(), start, end, text);
250}
251
252QPair<long int, long int> ScintillaEditExtern::findText(long int start, long int end, const QString &text)
253{
254 return ScintillaEdit::findText(SCFIND_NONE, text.toLatin1().data(), start, end);
255}
256
257void ScintillaEditExtern::findText(const QString &srcText, bool reverse)
258{
259 long int currentPos = ScintillaEditExtern::currentPos();
260 long int maxPos = length();
261 long int startPos = reverse ? (currentPos - srcText.length()) : currentPos;
262 long int endPos = reverse ? 0 : maxPos;
263 QPair<int, int> position = ScintillaEditExtern::findText(startPos, endPos, srcText.toLatin1().data());
264 if (position.first >= 0) {
265 ScintillaEditExtern::jumpToRange(position.first, position.second);
266 } else {
267 if (reverse) {
268 if (position.second == 0) {
269 findText(srcText, maxPos, 0);
270 }
271 } else {
272 if (position.second == maxPos) {
273 findText(srcText, 0, maxPos);
274 }
275 }
276 }
277}
278
279void ScintillaEditExtern::findText(const QString &srcText, long int startPos, long int endPos)
280{
281 QPair<int, int> position = ScintillaEditExtern::findText(startPos, endPos, srcText.toLatin1().data());
282 if (position.first >= 0) {
283 ScintillaEditExtern::jumpToRange(position.first, position.second);
284 }
285}
286
287void ScintillaEditExtern::replaceAll(const QString &srcText, const QString &destText)
288{
289 ScintillaEdit::searchAnchor();
290 long int textLength = length();
291 for (long int index = 0; index < textLength;) {
292 QPair<int, int> position = ScintillaEditExtern::findText(index, textLength, srcText.toLatin1().data());
293 if (position.first > -1 && position.first != position.second) {
294 index = position.second;
295 replaceRange(position.first, position.second, destText);
296 }
297
298 if (position.second >= textLength || index < 0)
299 return;
300 }
301}
302
303void ScintillaEditExtern::setLineBackground(int line, const QColor &color)
304{
305 int lineOffSet = line - 1;
306 markerAdd(lineOffSet, StyleSci::CustomLineBackground);
307 markerSetBack(StyleSci::CustomLineBackground, StyleColor::color(color));
308 markerSetAlpha(StyleSci::CustomLineBackground, color.alpha());
309}
310
311void ScintillaEditExtern::delLineBackground(int line)
312{
313 int lineOffSet = line - 1;
314 markerDelete(lineOffSet, StyleSci::CustomLineBackground);
315}
316
317void ScintillaEditExtern::cleanLineBackground()
318{
319 markerDeleteAll(StyleSci::CustomLineBackground);
320}
321
322void ScintillaEditExtern::setAnnotation(int line, const QString &title, const AnnotationInfo &info)
323{
324 int lineOffset = line - 1;
325 if (title.isEmpty()) {
326 if (d->lineAnnotations.keys().contains(lineOffset)) {
327 auto anns = d->lineAnnotations.value(lineOffset);
328 if (anns.contains(info)) {
329 return;
330 } else {
331 anns.insert(0, info);
332 d->lineAnnotations[lineOffset] = anns;
333 }
334 } else {
335 d->lineAnnotations[lineOffset] = {info};
336 }
337 } else {
338 if (d->moduleAnnotations.keys().contains(title)) {
339 auto lineAnns = d->moduleAnnotations.value(title);
340 if(lineAnns.keys().contains(lineOffset)) {
341 auto anns = lineAnns.value(lineOffset);
342 if (anns.contains(info))
343 return;
344 else {
345 anns.insert(0, info);
346 lineAnns[lineOffset] = anns;
347 d->moduleAnnotations[title] = lineAnns;
348 }
349 } else {
350 lineAnns[lineOffset] = {info};
351 d->moduleAnnotations[title] = lineAnns;
352 }
353 } else {
354 decltype (d->lineAnnotations) lineAnns;
355 lineAnns[lineOffset] = {info};
356 d->moduleAnnotations[title] = lineAnns;
357 }
358 }
359
360 sciUpdateAnnotation();
361}
362
363void ScintillaEditExtern::cleanAnnotation(const QString &title)
364{
365 if (title.isEmpty()) {
366 d->lineAnnotations.clear();
367 } else {
368 d->moduleAnnotations.remove(title);
369 }
370
371 sciUpdateAnnotation();
372}
373
374void ScintillaEditExtern::sciModified(Scintilla::ModificationFlags type, Scintilla::Position position,
375 Scintilla::Position length, Scintilla::Position linesAdded,
376 const QByteArray &text, Scintilla::Position line,
377 Scintilla::FoldLevel foldNow, Scintilla::FoldLevel foldPrev)
378{
379 Q_UNUSED(position)
380 Q_UNUSED(length)
381 Q_UNUSED(linesAdded)
382 Q_UNUSED(text)
383 Q_UNUSED(line)
384 Q_UNUSED(foldNow)
385 Q_UNUSED(foldPrev)
386
387 if (file().isEmpty()|| !QFile(file()).exists())
388 return;
389
390 if (bool(type & Scintilla::ModificationFlags::InsertText)) {
391 textInserted(position, length, linesAdded, text, line);
392 }
393
394 if (bool(type & Scintilla::ModificationFlags::DeleteText)) {
395 textDeleted(position, length, linesAdded, text, line);
396 }
397}
398
399void ScintillaEditExtern::sciNotify(Scintilla::NotificationData *data)
400{
401 switch (data->nmhdr.code) {
402 case Scintilla::Notification::IndicatorClick :
403 emit indicClicked(data->position);
404 break;
405 case Scintilla::Notification::IndicatorRelease:
406 emit indicReleased(data->position);
407 break;
408 default:
409 break;
410 }
411}
412
413void ScintillaEditExtern::sciUpdateUi(Scintilla::Update update)
414{
415 Q_UNUSED(update);
416 if (d->hoverTimer.isActive()) {
417 d->hoverTimer.stop();
418 }
419}
420
421void ScintillaEditExtern::sciDwellStart(int x, int y)
422{
423 if (d->hoverPos == -1) {
424 d->hoverPos = positionFromPoint(x, y); // cache position
425 bool isKeyCtrl = QApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
426 if (isKeyCtrl) {
427 d->definitionHoverTimer.start(20);
428 } else {
429 d->hoverTimer.start(500); // 如果间隔较小,导致收发管道溢出最终程序崩溃
430 }
431 }
432}
433
434void ScintillaEditExtern::sciDwellEnd(int x, int y)
435{
436 Q_UNUSED(x)
437 Q_UNUSED(y)
438 if (d->hoverPos != -1) {
439 if (d->definitionHoverTimer.isActive()) {
440 d->definitionHoverTimer.stop();
441 }
442 emit definitionHoverCleaned(d->hoverPos);
443 if (d->hoverTimer.isActive()) {
444 d->hoverTimer.stop();
445 }
446 emit hoverCleaned(d->hoverPos);
447 d->hoverPos = -1; // clean cache postion
448 }
449}
450
451void ScintillaEditExtern::sciUpdateAnnotation()
452{
453 annotationClearAll();
454
455 auto getAnnotationStyles = [=](int roleCode, const QString &srcText) -> QString
456 {
457 QString styles;
458 int styleCode = roleCode - annotationStyleOffset();
459 styles.resize(srcText.size(), styleCode);
460 return styles;
461 };
462
463 auto doSetAnnotation = [=] (const QHash<int, QList<AnnotationInfo>> &lineAnns, const QString &title = "")
464 {
465 QString annHead = "";
466 if (!title.isEmpty()) {
467 annHead += " ";
468 }
469
470 auto lines = lineAnns.keys();
471 for (auto line : lines) {
472 auto anns = lineAnns.value(line);
473 QHash<QString, QStringList> roleAnns;
474 for (int i = 0; i < AnnotationInfo::Role::count(); i++) {
475 QString currRoleDisplay = AnnotationInfo::Role::get()->value(i).display;
476 auto itera = anns.begin();
477 while (itera != anns.end()) {
478 if (itera->role.display == currRoleDisplay) {
479 QStringList annLines;
480 if (roleAnns.keys().contains(itera->role.display)) {
481 annLines = roleAnns.value(itera->role.display);
482 }
483 annLines.push_front(annHead + " " + itera->text);
484 roleAnns[itera->role.display] = annLines;
485 }
486 itera ++;
487 }
488 }
489 auto roleAnnsItera = roleAnns.begin();
490 while (roleAnnsItera != roleAnns.end()) {
491 AnnotationInfo::Role::type_value roleElem;
492 for (auto idx = 0; idx < AnnotationInfo::Role::count(); idx ++) {
493 auto roleVal = AnnotationInfo::Role::value(idx);
494 if (roleVal.display == roleAnnsItera.key()) {
495 roleElem = roleVal;
496 break;
497 }
498 }
499
500 QString lineAnnsText = annHead + roleAnnsItera.key() + ":\n" + roleAnnsItera.value().join("\n");
501 if (!title.isEmpty()) {
502 lineAnnsText = title + ":\n" + lineAnnsText;
503 }
504 QString lineAnnsTextStyles = getAnnotationStyles(roleElem.code, lineAnnsText);
505
506 QString srcAnnsText = annotationText(line);
507 QString srcAnnsTextStyles = annotationStyles(line);
508 if (!srcAnnsText.isEmpty()) {
509 srcAnnsText += "\n";
510 srcAnnsTextStyles.resize(srcAnnsText.size(), srcAnnsTextStyles[srcAnnsText.size() -1]);
511 }
512 QString dstLineAnnsText = srcAnnsText + lineAnnsText;
513 QString dstLineAnnsTextStyles = srcAnnsTextStyles + lineAnnsTextStyles;
514 annotationSetText(line, dstLineAnnsText.toStdString().c_str());
515 annotationSetStyles(line, dstLineAnnsTextStyles.toStdString().c_str());
516 roleAnnsItera ++;
517 }
518 }
519 };
520
521 // global
522 doSetAnnotation(d->lineAnnotations);
523
524 // moduel
525 auto itera = d->moduleAnnotations.begin();
526 while (itera != d->moduleAnnotations.end()) {
527 doSetAnnotation(d->moduleAnnotations.value(itera.key()), itera.key());
528 itera ++;
529 }
530}
531
532void ScintillaEditExtern::keyReleaseEvent(QKeyEvent *event)
533{
534 return ScintillaEdit::keyReleaseEvent(event);
535}
536
537void ScintillaEditExtern::keyPressEvent(QKeyEvent *event)
538{
539 bool isKeyCtrl = QApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
540 bool isKeyS = event->key() == Qt::Key_S;
541 if (isKeyCtrl && isKeyS) {
542 saveText();
543 }
544 return ScintillaEdit::keyPressEvent(event);
545}
546
547void ScintillaEditExtern::sciMarginClicked(Scintilla::Position position, Scintilla::KeyMod modifiers, int margin)
548{
549 Q_UNUSED(modifiers);
550
551 sptr_t line = lineFromPosition(position);
552 if (margin == StyleSci::Margin::LineNumber || margin == StyleSci::Margin::Runtime) {
553 std::bitset<32> flags(markerGet(line));
554 if (!flags[StyleSci::Debug]) {
555 markerAdd(line, StyleSci::Debug);
556 editor.addadDebugPoint(file(), qint64(line + 1)); //line begin 1 from debug point setting
557 editor.addDebugPoint(file(), qint64(line + 1));
558 } else {
559 markerDelete(line, StyleSci::Debug);
560 editor.removedDebugPoint(file(), qint64(line + 1)); //line begin 1 from debug point setting
561 editor.removeDebugPoint(file(), qint64(line + 1));
562 }
563 }
564}
565
566void ScintillaEditExtern::focusInEvent(QFocusEvent *event)
567{
568 focusChanged(true);
569 return ScintillaEdit::focusInEvent(event);
570}
571
572void ScintillaEditExtern::focusOutEvent(QFocusEvent *event)
573{
574 focusChanged(false);
575 callTipCancel();
576 return ScintillaEdit::focusOutEvent(event);
577}
578
579void ScintillaEditExtern::contextMenuEvent(QContextMenuEvent *event)
580{
581 if (selectionStart() == selectionEnd()) {
582 ScintillaEdit::contextMenuEvent(event);
583 } else {
584 emit selectionMenu(event);
585 }
586}
587
588void ScintillaEditExtern::enterEvent(QEvent *event)
589{
590 d->isLeave = false;
591 ScintillaEdit::enterEvent(event);
592}
593
594void ScintillaEditExtern::leaveEvent(QEvent *event)
595{
596 d->isLeave = true;
597 ScintillaEdit::leaveEvent(event);
598}
599
600void ScintillaEditExtern::find(const QString &srcText, int operateType)
601{
602 switch (operateType) {
603 case FindType::Previous:
604 {
605 searchAnchor();
606 findText(srcText, true);
607 break;
608 }
609 case FindType::Next:
610 {
611 searchAnchor();
612 findText(srcText, false);
613 break;
614 }
615 default:
616 break;
617 }
618}
619
620void ScintillaEditExtern::replace(const QString &srcText, const QString &destText, int operateType)
621{
622 switch (operateType) {
623 case RepalceType::Repalce:
624 {
625 QByteArray byteArray = getSelText();
626 if (0 == QString(byteArray).compare(srcText, Qt::CaseInsensitive)) {
627 replaceSel(destText.toLatin1().data());
628 }
629
630 break;
631 }
632 case RepalceType::FindAndReplace:
633 {
634 QByteArray byteArray = getSelText();
635 if (0 == QString(byteArray).compare(srcText, Qt::CaseInsensitive)) {
636 replaceSel(destText.toLatin1().data());
637 }
638
639 searchAnchor();
640 findText(srcText, false);
641 break;
642 }
643 case RepalceType::RepalceAll:
644 {
645 replaceAll(srcText, destText);
646 break;
647 }
648 default:
649 break;
650 }
651}
652