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