1 /**************************************************************************
2 ** This file is part of LiteIDE
3 **
4 ** Copyright (c) 2011-2013 LiteIDE. All rights reserved.
5 **
6 ** This library is free software; you can redistribute it and/or
7 ** modify it under the terms of the GNU Lesser General Public
8 ** License as published by the Free Software Foundation; either
9 ** version 2.1 of the License, or (at your option) any later version.
10 **
11 ** This library is distributed in the hope that it will be useful,
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 ** Lesser General Public License for more details.
15 **
16 ** In addition, as a special exception,  that plugins developed for LiteIDE,
17 ** are allowed to remain closed sourced and can be distributed under any license .
18 ** These rights are included in the file LGPL_EXCEPTION.txt in this package.
19 **
20 **************************************************************************/
21 // Module: syntaxtexteditor.cpp
22 // Creator: visualfc <visualfc@gmail.com>
23 
24 #include <QtGui>
25 
26 #include "syntaxtexteditor.h"
27 #include "golanghighlighter.h"
28 #include "QCompleter"
29 //lite_memory_check_begin
30 #if defined(WIN32) && defined(_MSC_VER) &&  defined(_DEBUG)
31      #define _CRTDBG_MAP_ALLOC
32      #include <stdlib.h>
33      #include <crtdbg.h>
34      #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
35      #define new DEBUG_NEW
36 #endif
37 //lite_memory_check_end
38 
39 
SyntaxTextEditor(QWidget * parent)40 SyntaxTextEditor::SyntaxTextEditor(QWidget *parent) :
41         QPlainTextEdit(parent),editCompleter(0)
42 {
43     setAttribute(Qt::WA_DeleteOnClose);
44     setLineWrapMode(QPlainTextEdit::NoWrap);
45 
46     isUntitled = true;
47     editorArea = new SyntaxTextEditorArea(this);
48     connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateAreaWidth(int)));
49     connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateEditorArea(QRect,int)));
50     connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
51 
52     setCursorWidth(2);
53 
54     updateAreaWidth(0);
55     highlightCurrentLine();
56     this->autoBlock = true;
57     this->autoIndent = true;
58     this->autoWord = false;
59 }
60 
reload()61 void SyntaxTextEditor::reload()
62 {
63     QFile file(curFile);
64     if (!file.open(QFile::ReadOnly | QFile::Text)) {
65         QMessageBox::warning(this, tr("LiteIDE"),
66                              tr("Cannot read file %1:\n%2.")
67                              .arg(curFile)
68                              .arg(file.errorString()));
69         return;
70     }
71 
72     QApplication::setOverrideCursor(Qt::WaitCursor);
73     QTextCodec *codec = QTextCodec::codecForName("UTF-8");
74     setPlainText(codec->toUnicode(file.readAll()));
75     /*
76     QTextCursor cursor(document());
77     cursor.beginEditBlock();
78     cursor.select(QTextCursor::Document);
79     cursor.insertText(codec->toUnicode(file.readAll()));
80     cursor.endEditBlock();
81     */
82     QApplication::restoreOverrideCursor();
83 }
84 
newFile()85 void SyntaxTextEditor::newFile()
86 {
87     static int sequenceNumber = 1;
88 
89     isUntitled = true;
90     curFile = tr("document%1.go").arg(sequenceNumber++);
91     curText = curFile + "[*]";
92     setWindowTitle(curText);
93 }
94 
loadFile(const QString & fileName)95 bool SyntaxTextEditor::loadFile(const QString &fileName)
96 {
97     QFile file(fileName);
98     if (!file.open(QFile::ReadOnly | QFile::Text)) {
99         QMessageBox::warning(this, tr("LiteIDE"),
100                              tr("Cannot read file %1:\n%2.")
101                              .arg(fileName)
102                              .arg(file.errorString()));
103         return false;
104     }
105 
106     QApplication::setOverrideCursor(Qt::WaitCursor);
107     QTextCodec *codec = QTextCodec::codecForName("UTF-8");
108     setPlainText(codec->toUnicode(file.readAll()));
109     QApplication::restoreOverrideCursor();
110 
111     setCurrentFile(fileName);
112 
113     return true;
114 }
115 
save()116 bool SyntaxTextEditor::save()
117 {
118     if (isUntitled) {
119         return saveAs();
120     } else {
121         return saveFile(curFile);
122     }
123 }
124 
saveAs()125 bool SyntaxTextEditor::saveAs()
126 {
127     QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"),
128                                                     curFile);
129     if (fileName.isEmpty())
130         return false;
131 
132     return saveFile(fileName);
133 }
134 
data() const135 QByteArray SyntaxTextEditor::data() const
136 {
137     QTextCodec *codec = QTextCodec::codecForName("UTF-8");
138     return codec->fromUnicode(toPlainText());
139 }
140 
saveFile(const QString & fileName)141 bool SyntaxTextEditor::saveFile(const QString &fileName)
142 {
143     if (!this->document()->isModified())
144         return true;
145 
146     QFile file(fileName);
147     if (!file.open(QFile::WriteOnly | QFile::Text)) {
148         QMessageBox::warning(this, tr("LiteIDE"),
149                              tr("Cannot write file %1:\n%2.")
150                              .arg(fileName)
151                              .arg(file.errorString()));
152         return false;
153     }
154 
155     QApplication::setOverrideCursor(Qt::WaitCursor);
156     QTextCodec *codec = QTextCodec::codecForName("UTF-8");
157     file.write(codec->fromUnicode(toPlainText()));
158     QApplication::restoreOverrideCursor();
159     setCurrentFile(fileName);
160     return true;
161 }
162 
userFriendlyCurrentFile()163 QString SyntaxTextEditor::userFriendlyCurrentFile()
164 {
165     return strippedName(curFile);
166 }
167 
closeEvent(QCloseEvent * event)168 void SyntaxTextEditor::closeEvent(QCloseEvent *event)
169 {
170     if (maybeSave()) {
171         event->accept();
172     } else {
173         event->ignore();
174     }
175 }
176 
maybeSave()177 bool SyntaxTextEditor::maybeSave()
178 {
179     if (document()->isModified()) {
180         QMessageBox::StandardButton ret;
181         ret = QMessageBox::warning(this, tr("LiteIDE"),
182                      tr("'%1' has been modified.\n"
183                         "Do you want to save your changes?")
184                      .arg(userFriendlyCurrentFile()),
185                      QMessageBox::Save | QMessageBox::Discard
186                      | QMessageBox::Cancel);
187         if (ret == QMessageBox::Save)
188             return save();
189         else if (ret == QMessageBox::Cancel)
190             return false;
191     }
192     return true;
193 }
194 
setCurrentFile(const QString & fileName)195 void SyntaxTextEditor::setCurrentFile(const QString &fileName)
196 {
197     curFile = QFileInfo(fileName).canonicalFilePath();
198     isUntitled = false;
199     document()->setModified(false);
200     setWindowModified(false);
201     curText = userFriendlyCurrentFile();
202 }
203 
strippedName(const QString & fullFileName)204 QString SyntaxTextEditor::strippedName(const QString &fullFileName)
205 {
206     return QFileInfo(fullFileName).fileName();
207 }
208 
editorAreaWidth()209 int SyntaxTextEditor::editorAreaWidth()
210 {
211     int digits = 1;
212     int max = qMax(1, blockCount());
213     while (max >= 10) {
214         max /= 10;
215         ++digits;
216     }
217 
218     int space = 6 + fontMetrics().width(QLatin1Char('9')) * digits;
219 
220     return space;
221 }
222 
areaPaintEvent(QPaintEvent * event)223 void SyntaxTextEditor::areaPaintEvent(QPaintEvent *event)
224 {
225     QPainter painter(editorArea);
226 
227     painter.fillRect(event->rect(), Qt::lightGray);//lightGray);
228 
229 //![extraAreaPaintEvent_0]
230 
231 //![extraAreaPaintEvent_1]
232     QTextBlock block = firstVisibleBlock();
233     int blockNumber = block.blockNumber();
234     int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
235     int bottom = top + (int) blockBoundingRect(block).height();
236 //![extraAreaPaintEvent_1]
237 
238 //![extraAreaPaintEvent_2]
239     while (block.isValid() && top <= event->rect().bottom()) {
240         if (block.isVisible() && bottom >= event->rect().top()) {
241             QString number = QString::number(blockNumber + 1);
242             painter.setPen(Qt::black);
243             painter.drawText(0, top, editorArea->width()-3, fontMetrics().height(),
244                              Qt::AlignRight, number);
245         }
246 
247         block = block.next();
248         top = bottom;
249         bottom = top + (int) blockBoundingRect(block).height();
250         ++blockNumber;
251     }
252 }
253 
resizeEvent(QResizeEvent * e)254 void SyntaxTextEditor::resizeEvent(QResizeEvent *e)
255 {
256     QPlainTextEdit::resizeEvent(e);
257 
258     QRect cr = contentsRect();
259     editorArea->setGeometry(QRect(cr.left(), cr.top(), editorAreaWidth(), cr.height()));
260 }
261 
updateAreaWidth(int)262 void SyntaxTextEditor::updateAreaWidth(int /* newBlockCount */)
263 {
264     setViewportMargins(editorAreaWidth(), 0, 0, 0);
265 }
266 
updateEditorArea(const QRect & rect,int dy)267 void SyntaxTextEditor::updateEditorArea(const QRect &rect, int dy)
268 {
269     if (dy)
270         editorArea->scroll(0, dy);
271     else
272         editorArea->update(0, rect.y(), editorArea->width(), rect.height());
273 
274     if (rect.contains(viewport()->rect())) {
275         updateAreaWidth(0);
276     }
277 }
278 
setCompleter(SyntaxCompleter * completer)279 void SyntaxTextEditor::setCompleter(SyntaxCompleter *completer)
280 {
281     if (editCompleter)
282         QObject::disconnect(editCompleter, 0, this, 0);
283 
284     editCompleter = completer;
285 
286     if (!editCompleter)
287         return;
288 
289     editCompleter->setFileName(this->curFile);
290     editCompleter->setWidget(this);
291     QObject::connect(editCompleter, SIGNAL(activated(QString)),
292                      this, SLOT(insertCompletion(QString)));
293 }
294 
completer() const295 SyntaxCompleter *SyntaxTextEditor::completer() const
296 {
297     return this->editCompleter;
298 }
299 
300 
wordUnderCursor() const301 QString SyntaxTextEditor::wordUnderCursor() const
302 {
303     QTextCursor tc = textCursor();
304     tc.select(QTextCursor::WordUnderCursor);
305     return tc.selectedText();
306 }
307 
textUnderCursor() const308 QString SyntaxTextEditor::textUnderCursor() const
309 {
310     QTextCursor tc = textCursor();
311     tc.select(QTextCursor::BlockUnderCursor);
312     QString block = tc.selectedText();
313     QStringList all = block.split(QRegExp("[\t| |(|)]"));
314     if (all.isEmpty())
315         return "";
316 
317     return all.last();
318 }
319 
focusInEvent(QFocusEvent * e)320 void SyntaxTextEditor::focusInEvent(QFocusEvent *e)
321 {
322     if (editCompleter)
323          editCompleter->setWidget(this);
324      QPlainTextEdit::focusInEvent(e);
325 }
326 
keyPressEvent(QKeyEvent * e)327 void SyntaxTextEditor::keyPressEvent(QKeyEvent *e)
328 {
329     if (editCompleter && editCompleter->popup()->isVisible()) {
330         // The following keys are forwarded by the completer to the widget
331         switch (e->key()) {
332         case Qt::Key_Enter:
333         case Qt::Key_Return:
334         case Qt::Key_Escape:
335         case Qt::Key_Tab:
336         case Qt::Key_Backtab:
337             e->ignore();
338             return; // let the completer do default behavior
339         default:
340             break;
341         }
342     }
343     if (e->key() == Qt::Key_Tab) {
344         indentText(document(), textCursor(),true);
345         e->accept();
346         return;
347     } else if (e->key() == Qt::Key_Backtab) {
348         indentText(document(),textCursor(),false);
349         e->accept();
350         return;
351     }
352 
353     if ( e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return ) {
354         if (this->autoIndent) {
355             indentEnter(textCursor());
356         } else {
357             e->accept();
358         }
359         emit update();
360         return;
361     }
362 
363     if (this->autoBlock && e->key() == '{') {
364         QTextCursor cursor(this->textCursor());
365         cursor.insertText("{}");
366         cursor.movePosition(QTextCursor::PreviousCharacter);
367         setTextCursor(cursor);
368         e->accept();
369         return;
370     }
371 
372     if (!this->autoWord) {
373         QPlainTextEdit::keyPressEvent(e);
374         return;
375     }
376 
377     bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_E); // CTRL+E
378     if (!editCompleter || !isShortcut) // do not process the shortcut when we have a completer
379         QPlainTextEdit::keyPressEvent(e);
380 
381     const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
382     if (!editCompleter || (ctrlOrShift && e->text().isEmpty()))
383         return;
384 
385     static QString eow("~!@#$%^&*()+{}|:\"<>?,./;'[]\\-= "); // end of word
386     bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
387 
388     QString word = wordUnderCursor();
389     QString text = textUnderCursor();
390 
391     if (editCompleter->underCursor(e->key(),textCursor(), text,word)) {
392         return;
393     }
394 
395     if (!isShortcut && (hasModifier || e->text().isEmpty() || text.length() <= 2
396                         || eow.contains(e->text().right(1)))) {
397         editCompleter->hidePopup();
398         return;
399     }
400     editCompleter->showPopup(word);
401 }
402 
insertCompletion(const QString & completion)403 void SyntaxTextEditor::insertCompletion(const QString& completion)
404 {
405     if (editCompleter->widget() != this)
406         return;
407     QTextCursor tc = textCursor();
408     int extra = completion.length() - editCompleter->completionPrefix().length();
409     tc.movePosition(QTextCursor::Left);
410     tc.movePosition(QTextCursor::EndOfWord);
411     tc.insertText(completion.right(extra));
412     setTextCursor(tc);
413     editCompleter->endCompletion();
414 }
415 
loadConfig(QSettings * settings,const QString & mimeType)416 void SyntaxTextEditor::loadConfig(QSettings *settings, const QString &mimeType)
417 {
418     autoIndent = settings->value(mimeType+"/autoindent",true).toBool();
419     autoBlock = settings->value(mimeType+"/autoblock",true).toBool();
420     curFont.setFamily(settings->value(mimeType+"/family","Courier").toString());
421     curFont.setPointSize(settings->value(mimeType+"/fontsize",12).toInt());
422     setFont(curFont);
423     setTabStopWidth(fontMetrics().width("main"));
424     autoWord = settings->value(mimeType+"/autoword",false).toBool();
425 }
426 
highlightCurrentLine()427 void SyntaxTextEditor::highlightCurrentLine()
428 {
429     QList<QTextEdit::ExtraSelection> extraSelections;
430 
431     if (!isReadOnly()) {
432         QTextEdit::ExtraSelection selection;
433 
434         QColor lineColor = QColor(180,200,200,128);
435 
436         selection.format.setBackground(lineColor);
437         selection.format.setProperty(QTextFormat::FullWidthSelection, true);
438         selection.cursor = textCursor();
439         selection.cursor.clearSelection();
440         extraSelections.append(selection);
441     }
442 
443     setExtraSelections(extraSelections);
444 }
445 
gotoLine(int line,int column)446 void SyntaxTextEditor::gotoLine(int line, int column)
447 {
448     const int blockNumber = line - 1;
449     const QTextBlock &block = document()->findBlockByNumber(blockNumber);
450     if (block.isValid()) {
451         QTextCursor cursor(block);
452         if (column > 0) {
453             cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column);
454         } else {
455             int pos = cursor.position();
456             while (document()->characterAt(pos).category() == QChar::Separator_Space) {
457                 ++pos;
458             }
459             cursor.setPosition(pos);
460         }
461         setTextCursor(cursor);
462         centerCursor();
463     }
464 }
465 
indentBlock(QTextBlock block,bool bIndent)466 void SyntaxTextEditor::indentBlock(QTextBlock block, bool bIndent)
467 {
468     QTextCursor cursor(block);
469     cursor.beginEditBlock();
470     cursor.movePosition(QTextCursor::StartOfBlock);
471     cursor.removeSelectedText();
472     if (bIndent) {
473         cursor.insertText("\t");
474     } else {
475         QString text = block.text();
476         if (!text.isEmpty() && (text.at(0) == '\t' || text.at(0) == ' ')) {
477             cursor.deleteChar();
478         }
479     }
480     cursor.endEditBlock();
481 }
482 
indentCursor(QTextCursor cur,bool bIndent)483 void SyntaxTextEditor::indentCursor(QTextCursor cur, bool bIndent)
484 {
485     if (bIndent) {
486         cur.insertText("\t");
487     } else {
488         QString text = cur.block().text();
489         int pos = cur.position()-cur.block().position()-1;
490         int count = text.count();
491         if (count > 0 && pos >= 0 && pos < count) {
492             if (text.at(pos) == '\t' || text.at(pos) == ' ') {
493                 cur.deletePreviousChar();
494             }
495         }
496     }
497 }
498 
indentText(QTextDocument * doc,QTextCursor cur,bool bIndent)499 void SyntaxTextEditor::indentText(QTextDocument *doc, QTextCursor cur, bool bIndent)
500 {
501     cur.beginEditBlock();
502     if (!cur.hasSelection()) {
503         indentCursor(cur,bIndent);
504     } else {
505         QTextBlock block = doc->findBlock(cur.selectionStart());
506         QTextBlock end = doc->findBlock(cur.selectionEnd());
507         if (!cur.atBlockStart()) {
508             end = end.next();
509         }
510         do {
511             indentBlock(block,bIndent);
512             block = block.next();
513         } while (block.isValid() && block != end);
514     }
515     cur.endEditBlock();
516 }
517 
indentEnter(QTextCursor cur)518 void SyntaxTextEditor::indentEnter(QTextCursor cur)
519 {
520     cur.beginEditBlock();
521     int pos = cur.position()-cur.block().position();
522     QString text = cur.block().text();
523     int i = 0;
524     int tab = 0;
525     int space = 0;
526     QString inText = "\n";
527     while (i < text.size()) {
528         if (!text.at(i).isSpace())
529             break;
530         if (text.at(0) == ' ') {
531             space++;
532         }
533         else if (text.at(0) == '\t') {
534             inText += "\t";
535             tab++;
536         }
537         i++;
538     }
539     text.trimmed();
540     if (!text.isEmpty()) {
541         if (pos >= text.size()) {
542             const QChar ch = text.at(text.size()-1);
543             if (ch == '{' || ch == '(') {
544                 inText += "\t";
545             }
546         } else if (pos == text.size()-1 && text.size() >= 3) {
547             const QChar l = text.at(text.size()-2);
548             const QChar r = text.at(text.size()-1);
549             if ( (l == '{' && r == '}') ||
550                  (l == '(' && r== ')') ) {
551                 cur.insertText(inText);
552                 int pos = cur.position();
553                 cur.insertText(inText);
554                 cur.setPosition(pos);
555                 this->setTextCursor(cur);
556                 cur.insertText("\t");
557                 cur.endEditBlock();
558                 return;
559             }
560         }
561     }
562     cur.insertText(inText);
563     cur.endEditBlock();
564     ensureCursorVisible();
565 }
566 
567