#include "qcodeedit.h"
#include <QLayout>
#include <QBoxLayout>
#include <QScrollBar>
#include <QTextDocument>
#include <QTextDocumentFragment>
#include <QTextOption>
#include <QTextCursor>
#include <QTextBlock>
#include <QAction>
#include <QApplication>
#include <QHeaderView>
#include "ecombobox.h"
#include "qad_types.h"
#include "ui_qcodeedit.h"

Q_DECLARE_METATYPE(QTextCursor)

QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent) {
	qRegisterMetaType<QTextCursor>();
	ui = new Ui::QCodeEdit();
	ui->setupUi(this);
	ui->widgetSearch->hide();
	ui->comboSearch->installEventFilter(this);
	ui->comboReplace->installEventFilter(this);
	prev_lc = auto_comp_pl = cur_search_ind = pos_press = pos_el_press = -1;
	timer = 0;
	_ignore_focus_out = _destructor = _replacing = false;
	_first = true;
	es_line.format.setBackground(QColor(240, 245, 240));
	es_line.format.setProperty(QTextFormat::FullWidthSelection, true);
	es_cursor.format.setBackground(QColor(220, 255, 200));
	es_bracket.format.setBackground(QColor(180, 238, 180));
	es_bracket.format.setForeground(Qt::red);
	es_search.format.setBackground(QColor(255, 240, 10));
	es_range.format.setBackground(QColor(230, 246, 255));
	es_range.format.setProperty(QTextFormat::FullWidthSelection, true);
	widget_help = new QFrame();
	widget_help->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
	widget_help->setFocusPolicy(Qt::NoFocus);
	widget_help->setFrameShadow(QFrame::Sunken);
	widget_help->setFrameShape(QFrame::StyledPanel);
	widget_help->setLayout(new QBoxLayout(QBoxLayout::TopToBottom));
	widget_help->layout()->setContentsMargins(0, 0, 0, 0);
	for (int i = 0; i < 2; ++i) {
		lbl_help[i] = new IconedLabel();
		lbl_help[i]->setFrameShadow(QFrame::Plain);
		lbl_help[i]->setFrameShape(QFrame::NoFrame);
		lbl_help[i]->setDirection(IconedLabel::RightToLeft);
		widget_help->layout()->addWidget(lbl_help[i]);
	}
	lbl_help[1]->setIcon(QIcon(":/icons/f1.png"));
	lbl_help[1]->setText(tr("Press F1 for details"));
	completer = new QTreeWidget();
	completer->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
	completer->setFocusPolicy(Qt::NoFocus);
	completer->setColumnCount(2);
	completer->setRootIsDecorated(false);
	completer->setHeaderHidden(true);
	completer->header()->setDefaultAlignment(Qt::AlignCenter);
	completer->header()->
#if (QT_VERSION >= 0x050000)
			setSectionResizeMode
#else
			setResizeMode
#endif
				(QHeaderView::ResizeToContents);
	completer->header()->setStretchLastSection(true);

	ui->textCode->setCursorWidth(qMax<int>(qRound(fontHeight() / 10.), 1));
	ui->textLines->viewport()->setAutoFillBackground(false);
	ui->textLines->viewport()->setCursor(Qt::ArrowCursor);
	ui->textLines->setFixedWidth(ui->textLines->fontMetrics().width("  "));
	
	QAction * a = new QAction(this); ui->textCode->addAction(a);
	a->setShortcut(QKeySequence("Shift+Tab"));
	a->setShortcutContext(Qt::WidgetShortcut);
	connect(a, SIGNAL(triggered()), this, SLOT(deindent()));
	a = new QAction(this); ui->textCode->addAction(a);
	a->setShortcut(QKeySequence("Ctrl+D"));
	a->setShortcutContext(Qt::WidgetShortcut);
	connect(a, SIGNAL(triggered()), this, SLOT(deleteLine()));
	a = new QAction(this); ui->textCode->addAction(a);
	a->setShortcut(QKeySequence("Ctrl+Return"));
	a->setShortcutContext(Qt::WidgetShortcut);
	connect(a, SIGNAL(triggered()), this, SLOT(newLine()));
	a = new QAction(this); ui->textCode->addAction(a);
	a->setShortcut(QKeySequence("Ctrl+Up"));
	a->setShortcutContext(Qt::WidgetShortcut);
	connect(a, SIGNAL(triggered()), this, SLOT(scrollUp()));
	a = new QAction(this); ui->textCode->addAction(a);
	a->setShortcut(QKeySequence("Ctrl+Down"));
	a->setShortcutContext(Qt::WidgetShortcut);
	connect(a, SIGNAL(triggered()), this, SLOT(scrollDown()));
	a = new QAction(this); ui->textCode->addAction(a);
	a->setShortcut(QKeySequence("Ctrl+Shift+Return"));
	a->setShortcutContext(Qt::WidgetShortcut);
	connect(a, SIGNAL(triggered()), this, SLOT(newLineBefore()));
	ui->frame->setFocusProxy(ui->textCode);
	QTextOption to = ui->textLines->document()->defaultTextOption();
	to.setAlignment(Qt::AlignTop | Qt::AlignRight);
	ui->textLines->document()->setDefaultTextOption(to);
	/*to = ui->textCode->document()->defaultTextOption();
	to.setFlags(QTextOption::SuppressColors);
	ui->textCode->document()->setDefaultTextOption(to);*/
	setShowSpaces(true);

	a = new QAction(this);
	a->setShortcut(QKeySequence("Ctrl+F"));
	a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
	connect(a, SIGNAL(triggered(bool)), this, SLOT(search_triggered()));
	addAction(a);

	/*a = new QAction(this);
	a->setShortcut(QKeySequence("Esc"));
	a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
	connect(a, SIGNAL(triggered(bool)), this, SLOT(hideSearch()));
	addAction(a);*/

	connect(completer, SIGNAL(itemDoubleClicked(QTreeWidgetItem * ,int)), this, SLOT(commitCompletition()));
	connect(ui->textCode->verticalScrollBar(), SIGNAL(valueChanged(int)), ui->textLines->verticalScrollBar(), SLOT(setValue(int)));
	connect(ui->textCode->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(hideHelp()));
	connect(ui->textCode, SIGNAL(textChanged()), this, SLOT(textEdit_textChanged()));
	connect(ui->textCode, SIGNAL(textChanged()), this, SIGNAL(textChanged()));
	connect(ui->textCode, SIGNAL(cursorPositionChanged()), this, SLOT(textEdit_cursorPositionChanged()));
	connect(ui->textCode, SIGNAL(selectionChanged()), this, SLOT(textEdit_selectionChanged()));
	connect(ui->comboSearch->lineEdit(), SIGNAL(returnPressed()), this, SLOT(searchNext()));
	connect(ui->comboReplace->lineEdit(), SIGNAL(returnPressed()), this, SLOT(on_buttonReplaceSearch_clicked()));
	updateLines();
	
	registerAutoCompletitionClass(-1, QCodeEdit::Keyword, "Words", QIcon(":/icons/code-word.png"));
}


QCodeEdit::~QCodeEdit() {
	_destructor = true;
	delete completer;
	//for (int i = 0; i < 2; ++i)
	//	delete lbl_help[i];
	delete widget_help;
	//delete ui;
}


QTextCursor QCodeEdit::textCursor() const {
	return ui->textCode->textCursor();
}


QTextDocument * QCodeEdit::document() const {
	return ui->textCode->document();
}


void QCodeEdit::setDocument(QTextDocument * doc) {
	if (document()) {
		document()->setProperty("_cursor", QVariant::fromValue(textCursor()));
		document()->setProperty("_vpos", textEdit()->verticalScrollBar()->value());
	}
	ui->textCode->setEnabled(doc);
	ui->textLines->setEnabled(doc);
	documentUnset();
	if (!doc) {
		ui->textCode->setDocument(0);
		documentChanged(0);
		return;
	}
	if (!qobject_cast<QPlainTextDocumentLayout*>(doc->documentLayout()))
		doc->setDocumentLayout(new QPlainTextDocumentLayout(doc));
	ui->textCode->setDocument(doc);
	ui->textCode->setCursorWidth(qMax<int>(qRound(fontHeight() / 10.), 1));
	setShowSpaces(spaces_);
	if (doc->property("_cursor").isValid()) {
		setTextCursor(doc->property("_cursor").value<QTextCursor>());
		textEdit()->verticalScrollBar()->setValue(doc->property("_vpos").toInt());
	}
	documentChanged(doc);
	doc->setDefaultFont(editorFont());
	updateLines();
}


void QCodeEdit::setTextCursor(const QTextCursor & c) {
	ui->textCode->setTextCursor(c);
}


void QCodeEdit::centerCursor() {
	ui->textCode->centerCursor();
	updateLines();
}


void QCodeEdit::insertText(const QString & text) {
	ui->textCode->insertPlainText(text);
	updateLines();
}


void QCodeEdit::appendText(const QString & text) {
	ui->textCode->appendPlainText(text);
	updateLines();
}


void QCodeEdit::setCustomExtraSelection(const QList<QTextEdit::ExtraSelection> & es) {
	es_custom = es;
	applyExtraSelection();
}


QRect QCodeEdit::cursorRect() const {
	return ui->textCode->cursorRect();
}


QRect QCodeEdit::cursorRect(const QTextCursor & cursor) const {
	return ui->textCode->cursorRect(cursor);
}


QString QCodeEdit::text() const {
	return ui->textCode->toPlainText();
}


QStringList QCodeEdit::cursorScope() const {
	return cursor_scope;
}


bool QCodeEdit::showLineNumbers() const {
	return ui->textLines->isVisible();
}


void QCodeEdit::setEditorFont(QFont f) {
	ui->textCode->setFont(f);
	ui->textLines->setFont(f);
}


QFont QCodeEdit::editorFont() const {
	return ui->textCode->font();
}


QPlainTextEdit * QCodeEdit::textEdit() const {
	return ui->textCode;
}


int QCodeEdit::skipRange(const QString & s, int pos, QChar oc, QChar cc, QChar sc) {
	int cnt = 0;
	bool skip = false;
	for (int i = pos - 1; i >= 0; --i) {
		QChar c = s[i];
		if (skip) {skip = false; continue;}
		if (c == sc) {skip = true; continue;}
		if (c == cc) {cnt++; continue;}
		if (c == oc) {cnt--; if (cnt == 0) return i;}
	}
	return -1;
}


int QCodeEdit::skipCWord(const QString & s, int pos) {
	QChar pc(0), c(0);
	for (int i = pos - 1; i >= 0; --i) {
		pc = c;
		c = s[i];
		if (c.isLetterOrNumber() || (c.toLatin1() == '_')) continue;
		if (pc.isLetter() || (pc.toLatin1() == '_')) return i + 1;
		return -1;
	}
	return -1;
}


bool QCodeEdit::matchWritten(QString s, QString w) {
	if (s.isEmpty() || w.isEmpty()) return true;
	if (s.contains(w, Qt::CaseInsensitive)) return true;
	int sp(0);
	for (int i = 0; i < w.size(); ++i, ++sp) {
		if (sp >= s.size()) return false;
		QChar wc(w[i].toLower());
		bool ns = false, bl = true;
		while (sp < s.size()) {
			if (ns || s[sp].toLatin1() == '_') {
				if (s[sp].toLatin1() == '_') {sp++; bl = false; continue;}
				if (s[sp].isLower() && bl) {sp++; continue;}
				if (s[sp].toLower() != wc) return false;
			}
			if (s[sp].toLower() == wc) break;
			ns = true;
			sp++;
		}
		if (sp >= s.size()) return false;
	}
	return true;
}


QChar QCodeEdit::pairChar(QChar c) {
	switch (c.toLatin1()) {
	case '\"': return '\"';
	case '(': return ')';
	case ')': return '(';
	case '[': return ']';
	case ']': return '[';
	default: break;
	}
	return QChar();
}


bool QCodeEdit::eventFilter(QObject * o, QEvent * e) {
	if (_destructor) return QWidget::eventFilter(o, e);
	if (e->type() == QEvent::Destroy) {
		completer->removeEventFilter(this);
		ui->textCode->removeEventFilter(this);
		ui->textCode->viewport()->removeEventFilter(this);
		ui->textLines->viewport()->removeEventFilter(this);
		return QWidget::eventFilter(o, e);
	}
	if (ui->textLines) {
		if (o == ui->textLines->viewport()) {/*
			if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonRelease ||
				e->type() == QEvent::MouseMove || e->type() == QEvent::MouseButtonDblClick) {
#if (QT_VERSION < 0x050000)
				const_cast<QPoint&>(((QMouseEvent*)e)->pos()) = QPoint(0, ((QMouseEvent*)e)->pos().y());
#else
				const_cast<QPointF&>(((QMouseEvent*)e)->localPos()) = QPointF(0, ((QMouseEvent*)e)->localPos().y());
#endif
				QApplication::sendEvent(ui->textCode->viewport(), e);
				return true;
			}*/
			QTextCursor tc;
			int tcpos = 0;
			switch (e->type()) {
			case QEvent::MouseButtonPress:
				if (!isEnabled()) break;
				tc = ui->textCode->cursorForPosition(((QMouseEvent*)e)->pos());
				tc.movePosition(QTextCursor::EndOfLine);
				pos_el_press = tc.anchor();
				tc.movePosition(QTextCursor::StartOfLine);
				pos_press = tc.anchor();
				if (!tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor))
					tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
				ui->textCode->setFocus();
				ui->textCode->setTextCursor(tc);
				return true;
			case QEvent::MouseMove:
				if (!isEnabled()) break;
				tc = ui->textCode->cursorForPosition(((QMouseEvent*)e)->pos());
				tc.movePosition(QTextCursor::StartOfLine);
				if (pos_press == tc.anchor()) {
					if (!tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor))
						tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
					ui->textCode->setTextCursor(tc);
					return true;
				}
				if (pos_press < tc.anchor()) {
					if (!tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor))
						tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
				}
				tcpos = tc.position();
				tc.setPosition(pos_press < tc.anchor() ? pos_press : pos_el_press);
				tc.setPosition(tcpos, QTextCursor::KeepAnchor);
				ui->textCode->setTextCursor(tc);
				return true;
			case QEvent::Wheel:
				if (!isEnabled()) break;
				QApplication::sendEvent(ui->textCode->viewport(), e);
				return true;
			default: break;
			}
		}
	}
	if (o == completer) {
		//qDebug() << o << e;
		if (e->type() == QEvent::WindowActivate)
			_ignore_focus_out = true;
		//qDebug() << e;
		return QWidget::eventFilter(o, e);
	}
	if (o == ui->comboSearch || o == ui->comboReplace) {
		//qDebug() << o << e;
		if (e->type() == QEvent::KeyPress) {
			if (((QKeyEvent * )e)->key() == Qt::Key_Escape) {
				hideHelp();
				if (completer->isVisible())
					completer->hide();
				else
					hideSearch();
			}
		}
		//qDebug() << e;
		return QWidget::eventFilter(o, e);
	}
	if (ui->textCode) {
		if (o == ui->textCode->viewport()) {
			if (e->type() == QEvent::MouseButtonPress) {
				completer->hide();
				hideHelp();
			}
			return QWidget::eventFilter(o, e);
		}
		if (o == ui->textCode) {
			//qDebug() << e;
			QMetaObject::invokeMethod(this, "syncScrolls", Qt::QueuedConnection);
			QKeyEvent * ke;
			QChar kc(0);
			switch (e->type()) {
			case QEvent::ToolTip: {
				QTextCursor tc = ui->textCode->cursorForPosition(((QHelpEvent*)e)->pos());
				tc.select(QTextCursor::WordUnderCursor);
				raiseHelp(tc);
				}
				break;
			case QEvent::KeyPress:
				ke = (QKeyEvent * )e;
				//qDebug() << "key" << ke;
				switch (ke->key()) {
				case Qt::Key_Space:
					if (ke->modifiers().testFlag(Qt::ControlModifier)) {
						invokeAutoCompletition(true);
						return true;
					}
					break;
				case Qt::Key_Escape:
					hideHelp();
					if (completer->isVisible())
						completer->hide();
					else
						hideSearch();
					break;
				case Qt::Key_Up:
					if (completer->isVisible()) {
						previousCompletition();
						return true;
					}
					completer->hide();
					hideHelp();
					if (ke->modifiers().testFlag(Qt::AltModifier)) {
						copyLineUp();
						return true;
					}
					if (ke->modifiers().testFlag(Qt::ControlModifier) && ke->modifiers().testFlag(Qt::ShiftModifier)) {
						moveLineUp();
						return true;
					}
					break;
				case Qt::Key_Down:
					if (completer->isVisible()) {
						nextCompletition();
						return true;
					}
					completer->hide();
					hideHelp();
					if (ke->modifiers().testFlag(Qt::AltModifier)) {
						copyLineDown();
						return true;
					}
					if (ke->modifiers().testFlag(Qt::ControlModifier) && ke->modifiers().testFlag(Qt::ShiftModifier)) {
						moveLineDown();
						return true;
					}
					break;
				case Qt::Key_Home:
				case Qt::Key_End:
				case Qt::Key_PageUp:
				case Qt::Key_PageDown:
					if (completer->isVisible()) {
						qApp->sendEvent(completer, new QKeyEvent(e->type(), ke->key(), ke->modifiers()));
						return true;
					}
					break;
				case Qt::Key_Left:
				case Qt::Key_Right:
				case Qt::Key_Backspace:
				case Qt::Key_Delete:
					if (completer->isVisible())
						QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false));
					break;
				case Qt::Key_Return:
					if (completer->isVisible()) {
						commitCompletition();
						completer->hide();
						return true;
					}
					if (ui->textCode->textCursor().selectedText().isEmpty())
						QMetaObject::invokeMethod(this, "autoIndent", Qt::QueuedConnection);
					break;
				case Qt::Key_Tab:
					if (!ui->textCode->textCursor().selectedText().isEmpty()) {
						if (ke->modifiers().testFlag(Qt::ShiftModifier))
							deindent();
						else
							indent();
						return true;
					}
					break;
				case Qt::Key_D:
					if (ke->modifiers().testFlag(Qt::ControlModifier)) {
						completer->hide();
						return true;
					}
					break;
				default: break;
				}
				if (!ke->text().isEmpty())
					kc = ke->text()[0];
				if (kc == '.') {
					completer->hide();
					QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false));
				} else {
					if ((kc.isLetterOrNumber() || kc.toLatin1() == '_') && completer->isVisible())
						QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false));
				}
				break;
			case QEvent::FocusOut:
				if (_ignore_focus_out) {
					_ignore_focus_out = false;
					break;
				}
			case QEvent::Hide:
			case QEvent::HideToParent:
			case QEvent::MouseButtonPress:
				//qDebug() << e;
				completer->hide();
				hideHelp();
			default: break;
			}
		}
	}
	return QWidget::eventFilter(o, e);
}


void QCodeEdit::showEvent(QShowEvent * ) {
	if (!_first) return;
	_first = false;
	completer->installEventFilter(this);
	ui->textCode->installEventFilter(this);
	ui->textCode->viewport()->installEventFilter(this);
	ui->textLines->viewport()->installEventFilter(this);
}


void QCodeEdit::timerEvent(QTimerEvent * ) {
	parse();
	emit parseRequest();
	killTimer(timer);
	timer = 0;
}


void QCodeEdit::leaveEvent(QEvent * e) {
	hideHelp();
	QWidget::leaveEvent(e);
}


char antiBracket(char c) {
	switch (c) {
	case '(': return ')';
	case '[': return ']';
	case '{': return '}';
	case '<': return '>';
	case ')': return '(';
	case ']': return '[';
	case '}': return '{';
	case '>': return '<';
	}
	return 0;
}


void QCodeEdit::highlightBrackets() {
	es_brackets.clear();
	QTextCursor stc = ui->textCode->textCursor(), tc;
	QTextEdit::ExtraSelection es;
	stc.setPosition(stc.position());
	QTextCursor::MoveOperation mop[2] = {QTextCursor::Left, QTextCursor::Right};
	QString mbr[2] = {")]}>", "([{<"};
	for (int d = 0; d < 2; ++d) {
		tc = stc;
		tc.movePosition(mop[d], QTextCursor::KeepAnchor);
		if (!tc.selectedText().isEmpty()) {
			char ch = tc.selectedText()[0].toLatin1();
			if (mbr[d].contains(ch)) {
				es = es_bracket;
				es.cursor = tc;
				es_brackets << es;
				QTextCursor ftc = tc;
				int bcnt = 1; char fch = antiBracket(ch);
				while (bcnt > 0) {
					ftc.setPosition(ftc.position());
					if (!ftc.movePosition(mop[d], QTextCursor::KeepAnchor)) break;
					//qDebug() << tc.selectedText();
					if (ftc.selectedText().isEmpty()) break;
					if (ftc.selectedText()[0].toLatin1() == ch)  ++bcnt;
					if (ftc.selectedText()[0].toLatin1() == fch) --bcnt;
				}
				if (bcnt == 0) {
					es.cursor = ftc;
					es_brackets << es;
					es.format = es_range.format;
					es.cursor.setPosition(tc.position(), QTextCursor::KeepAnchor);
					if (!es.cursor.selection().isEmpty())
						es_brackets << es;
				}
			}
		}
	}
}


void QCodeEdit::applyExtraSelection() {
	ui->textCode->setExtraSelections(QList<QTextEdit::ExtraSelection>() << es_line << es_selected
									 << es_custom << es_brackets << es_search_list << es_cursor);
}


void QCodeEdit::nextCompletition() {
	int ci = completer->currentIndex().row();
	if (ci >= completer->topLevelItemCount() - 1) return;
	if (completer->topLevelItem(ci + 1)->flags().testFlag(Qt::ItemIsSelectable))
		completer->setCurrentItem(completer->topLevelItem(ci + 1));
	else {
		if (ci >= completer->topLevelItemCount() - 2) return;
		completer->setCurrentItem(completer->topLevelItem(ci + 2));
	}
}


void QCodeEdit::previousCompletition() {
	int ci = completer->currentIndex().row();
	if (ci <= 0) return;
	if (completer->topLevelItem(ci - 1)->flags().testFlag(Qt::ItemIsSelectable))
		completer->setCurrentItem(completer->topLevelItem(ci - 1));
	else {
		if (ci <= 1) return;
		completer->setCurrentItem(completer->topLevelItem(ci - 2));
	}
}


void QCodeEdit::clearSearch() {
	es_search_list.clear();
	applyExtraSelection();
}


void QCodeEdit::moveToSearch() {
	if (es_search_list.isEmpty()) return;
	if (cur_search_ind < 0) cur_search_ind += es_search_list.size();
	if (cur_search_ind >= es_search_list.size()) cur_search_ind = 0;
	if (cur_search_ind < 0 || cur_search_ind >= es_search_list.size()) return;
	ui->textCode->setTextCursor(es_search_list[cur_search_ind].cursor);
}


int QCodeEdit::searchIndFromCursor() {
	if (es_search_list.isEmpty()) return -1;
	int ci = ui->textCode->textCursor().anchor();
	for (int i = 0; i < es_search_list.size(); ++i)
		if (es_search_list[i].cursor.anchor() > ci)
			return i - 1;
	return -1;
}


void QCodeEdit::searchAll() {
	QString st = ui->comboSearch->currentText();
	es_search_list.clear();
	if (!st.isEmpty() && !ui->widgetSearch->isHidden()) {
		QTextDocument::FindFlags ff = 0;
		if (ui->buttonSearchCase->isChecked()) ff |= QTextDocument::FindCaseSensitively;
		if (ui->buttonSearchWord->isChecked()) ff |= QTextDocument::FindWholeWords;
		QTextCursor tc(ui->textCode->document()->begin());
		QTextEdit::ExtraSelection es = es_search;
		while (true) {
			tc = ui->textCode->document()->find(st, tc, ff);
			if (tc.isNull()) break;
			es.cursor = tc;
			es_search_list << es;
		}
	}
	applyExtraSelection();
	QString ss;
	if (es_search_list.isEmpty())
		ss = "color: rgb(180, 0, 0);";
	ui->comboSearch->lineEdit()->setStyleSheet(ss);
}


void QCodeEdit::search_triggered() {
	QTextCursor tc = ui->textCode->textCursor();
	QString st = tc.selectedText();
	if (st.isEmpty()) {
		tc.select(QTextCursor::WordUnderCursor);
		st = tc.selectedText();
	}
	search(st);
	//QMetaObject::invokeMethod(ui->comboSearch->lineEdit(), "returnPressed");
	if (ui->comboSearch->findText(st) < 0)
		ui->comboSearch->insertItem(0, st);
}


void QCodeEdit::syncScrolls() {
	ui->textLines->verticalScrollBar()->setValue(ui->textCode->verticalScrollBar()->value());
	ui->textLines->setHorizontalScrollBarPolicy(ui->textCode->horizontalScrollBar()->isVisible() ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
	//qDebug() << "!!!";
}


void QCodeEdit::scrollUp() {
	ui->textCode->verticalScrollBar()->setValue(ui->textCode->verticalScrollBar()->value() - 1);
}


void QCodeEdit::scrollDown() {
	ui->textCode->verticalScrollBar()->setValue(ui->textCode->verticalScrollBar()->value() + 1);
}


void QCodeEdit::deleteLine() {
	QTextCursor tc = ui->textCode->textCursor();
	tc.movePosition(QTextCursor::EndOfLine);
	tc.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
	bool md = true;
	if (!tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor)) {
		tc.movePosition(QTextCursor::StartOfLine);
		tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
		tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
		md = false;
	}
	tc.removeSelectedText();
	tc.movePosition(QTextCursor::StartOfLine);
	if (md) tc.movePosition(QTextCursor::Down);
	ui->textCode->setTextCursor(tc);
}


void QCodeEdit::copyLineUp() {
	QTextCursor tc = ui->textCode->textCursor();
	int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se;
	QString st_ = tc.selection().toPlainText();
	if (st_.endsWith("\n")) {
		st_.chop(1);
		se--; se_--;
	}
	tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position();
	tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position();
	tc.setPosition(ss); tc.setPosition(se, QTextCursor::KeepAnchor);
	bool ins_nl = false;
	if (!tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor))
		ins_nl = true;
	QString l = tc.selectedText();
	tc.beginEditBlock();
	tc.setPosition(ss);
	if (ins_nl)
		l.append("\n");
	tc.insertText(l);
	tc.setPosition(ss_);
	tc.setPosition(se_, QTextCursor::KeepAnchor);
	tc.endEditBlock();
	ui->textCode->setTextCursor(tc);
}


void QCodeEdit::copyLineDown() {
	QTextCursor tc = ui->textCode->textCursor();
	int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se;
	QString st_ = tc.selection().toPlainText();
	if (st_.endsWith("\n")) {
		st_.chop(1);
		se--; se_--;
	}
	tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position();
	tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position();
	tc.setPosition(ss); tc.setPosition(se, QTextCursor::KeepAnchor);
	bool ins_nl = false;
	if (!tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor))
		ins_nl = true;
	QString l = tc.selectedText();
	tc.beginEditBlock();
	tc.setPosition(ss);
	ss_ += l.size(); se_ += l.size();
	if (ins_nl) {
		l.append("\n");
		ss_++; se_++;
	}
	tc.insertText(l);
	tc.setPosition(ss_);
	tc.setPosition(se_, QTextCursor::KeepAnchor);
	tc.endEditBlock();
	ui->textCode->setTextCursor(tc);
}


void QCodeEdit::moveLineUp() {
	QTextCursor tc = ui->textCode->textCursor();
	int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se;
	QString st_ = tc.selection().toPlainText();
	if (st_.endsWith("\n")) {
		st_.chop(1);
		se--; se_--;
	}
	tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position();
	tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position();
	tc.setPosition(ss);
	if (!tc.movePosition(QTextCursor::Up))
		return;
	tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor);
	QString l = tc.selectedText();
	ss -= l.size(); se -= l.size();
	ss_ -= l.size(); se_ -= l.size();
	tc.beginEditBlock();
	tc.removeSelectedText();
	tc.setPosition(se);
	bool de = false;
	if (!tc.movePosition(QTextCursor::Right)) {
		l.prepend("\n");
		de = true;
	}
	tc.insertText(l);
	if (de) {
		tc.movePosition(QTextCursor::End);
		tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
		tc.removeSelectedText();
	}
	tc.setPosition(ss_);
	tc.setPosition(se_, QTextCursor::KeepAnchor);
	tc.endEditBlock();
	ui->textCode->setTextCursor(tc);
}


void QCodeEdit::moveLineDown() {
	QTextCursor tc = ui->textCode->textCursor();
	int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se;
	QString st_ = tc.selection().toPlainText();
	if (st_.endsWith("\n")) {
		st_.chop(1);
		se--; se_--;
	}
	tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position();
	tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position();
	tc.setPosition(se);
	if (!tc.movePosition(QTextCursor::Right))
		return;
	bool de = false;
	if (!tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor)) {
		tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
		de = true;
	}
	QString l = tc.selectedText();
	tc.beginEditBlock();
	tc.removeSelectedText();
	tc.setPosition(ss);
	if (de) l += "\n";
	tc.insertText(l);
	if (de) {
		tc.movePosition(QTextCursor::End);
		tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
		tc.removeSelectedText();
	}
	ss += l.size(); se += l.size();
	ss_ += l.size(); se_ += l.size();
	tc.setPosition(ss_);
	tc.setPosition(se_, QTextCursor::KeepAnchor);
	tc.endEditBlock();
	ui->textCode->setTextCursor(tc);
}


void QCodeEdit::indent() {
	QTextCursor tc = ui->textCode->textCursor();
	int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se;
	QString st_ = tc.selection().toPlainText();
	if (st_.endsWith("\n")) {
		st_.chop(1);
		se--; se_--;
	}
	tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position();
	tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position();
	tc.setPosition(ss);
	while (tc.position() < se_) {
		tc.insertText("\t");
		se_++;
		tc.movePosition(QTextCursor::StartOfLine);
		if (!tc.movePosition(QTextCursor::Down))
			break;
	}
	tc.setPosition(ss_ + 1);
	tc.setPosition(se_, QTextCursor::KeepAnchor);
	ui->textCode->setTextCursor(tc);
}


void QCodeEdit::deindent() {
	QTextCursor tc = ui->textCode->textCursor();
	int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se;
	QString st_ = tc.selection().toPlainText();
	if (st_.endsWith("\n")) {
		st_.chop(1);
		se--; se_--;
	}
	tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position();
	tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position();
	tc.setPosition(ss);
	bool first = true;
	while (tc.position() < se_) {
		tc.movePosition(QTextCursor::StartOfLine);
		tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
		int rs = 0;
		if (tc.selectedText() == "\t") {
			tc.removeSelectedText();
			rs = 1;
		} else {
			for (int i = 0; i < 4; ++i) {
				tc.movePosition(QTextCursor::StartOfLine);
				tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
				if (tc.selectedText() == " ") {
					tc.removeSelectedText();
					rs++;
				}
			}
		}
		if (first) {
			first = false;
			ss_ -= rs;
		}
		se_ -= rs;
		tc.movePosition(QTextCursor::StartOfLine);
		if (!tc.movePosition(QTextCursor::Down))
			break;
	}
	tc.setPosition(ss_);
	tc.setPosition(se_, QTextCursor::KeepAnchor);
	ui->textCode->setTextCursor(tc);
}


void QCodeEdit::autoIndent() {
	QTextCursor tc = ui->textCode->textCursor(), stc = tc;
	tc.movePosition(QTextCursor::StartOfLine);
	if (!tc.movePosition(QTextCursor::Up)) return;
	tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor);
	QString line = tc.selectedText(), tabs;
	int i = 0;
	for (; i < line.size(); ++i)
		if (!line[i].isSpace()) {
			tabs = line.left(i);
			break;
		}
	if (i >= line.size())
		tabs = line.left(line.size() - 1);
	int nt = qMax<int>(0, line.count(QChar('{')) - line.count(QChar('}')));
	tabs.append(QString("\t").repeated(nt));
	if (tabs.isEmpty()) return;
	stc.insertText(tabs);
	ui->textCode->setTextCursor(stc);
}


void QCodeEdit::scrollToTop() {
	prev_lc = -1;
	updateLines();
	ui->textCode->verticalScrollBar()->setValue(0);
	ui->textLines->verticalScrollBar()->setValue(0);
}


void QCodeEdit::newLine() {
	QTextCursor tc = ui->textCode->textCursor();
	tc.movePosition(QTextCursor::EndOfLine);
	tc.insertText("\n");
	ui->textCode->setTextCursor(tc);
	QMetaObject::invokeMethod(this, "autoIndent", Qt::QueuedConnection);
}


void QCodeEdit::newLineBefore() {
	QTextCursor tc = ui->textCode->textCursor();
	tc.movePosition(QTextCursor::StartOfLine);
	tc.insertText("\n");
	tc.movePosition(QTextCursor::Up);
	ui->textCode->setTextCursor(tc);
	QMetaObject::invokeMethod(this, "autoIndent", Qt::QueuedConnection);
}


void QCodeEdit::setFocus() {
	ui->textCode->setFocus();
}


void QCodeEdit::setText(const QString & t) {
	ui->textCode->setPlainText(t);
}


void QCodeEdit::updateLines() {
	if (timer > 0) killTimer(timer);
	timer = startTimer(500);
	ui->textCode->setTabStopWidth(ui->textCode->fontMetrics().width("    "));
	int lc = ui->textCode->document()->lineCount();
	if (prev_lc == lc) return;
	prev_lc = lc;
	ui->textLines->setFixedWidth(ui->textLines->fontMetrics().width(QString(" %1").arg(lc)));
	ui->textLines->clear();
	for (int i = 1; i <= lc; ++i)
		ui->textLines->appendPlainText(QString("%1").arg(i));
	ui->textLines->verticalScrollBar()->setValue(ui->textCode->verticalScrollBar()->value());
}


QString QCodeEdit::selectArg(QString s, int arg) {
	if (!s.contains('(') || arg < 0) return s;
	QString ss = s.left(s.indexOf('('));
	s.remove(0, ss.size());
	if (s.startsWith('(')) s.remove(0, 1);
	if (s.endsWith(')')) s.chop(1);
	QStringList al = s.split(",");
	QString ret = ss + "(";
	for (int i = 0; i < al.size(); ++i) {
		if (i > 0) ret += ", ";
		if (i == arg) ret += "<span style=\"font-weight:600;\">";
		ret += al[i].trimmed();
		if (i == arg) ret += "</span>";
	}
	ret += ")";
	return ret;
}


void QCodeEdit::raiseHelp(QTextCursor tc, int arg) {
	bool ok;
	QPair<QStringList, QString> scope = getScope(tc, &ok);
	QString st = tc.selectedText();
	if (arg >= 0) st = scope.second;
	if (!ok || st.isEmpty()) {
		hideHelp();
		return;
	}
	ok = false;
	ACList acl(autoCompletitionList(scope.first, scope.second));
	foreach (const ACPair & i, acl) {
		foreach (const StringsPair & s, i.second) {
			QString ts = s.second;
			//qDebug() << ts << st;
			if (ts != st) {
				if (ts.startsWith(st)) {
					ts.remove(0, st.size());
					ts = ts.trimmed();
					if (!ts.isEmpty()) {
						if (ts[0] != '(')
							continue;
					}
				} else
					continue;
			}
			//qDebug() << s.second << st;
			ACClass acc = ac_classes.value(i.first);
			lbl_help[0]->setIcon(acc.icon);
			lbl_help[0]->setText(QString("<html><body>[%1]  %2  %3</html></body>").arg(acc.name, s.first, selectArg(s.second, arg)));
			ok = true;
			break;
		}
		if (ok) break;
	}
	if (!ok) {
		hideHelp();
		return;
	}
	//qDebug() << "help found" << tc.selectionStart() << tc.selectionEnd();
	es_cursor.cursor = tc;
	applyExtraSelection();
	//tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
	lbl_help[0]->setFont(font());
	qApp->processEvents();
	widget_help->resize(widget_help->sizeHint());
	qApp->processEvents();
	QRect whr = ui->textCode->cursorRect(tc);
	whr.setWidth(ui->textCode->fontMetrics().width(st));
	QPoint whp;
	whp.setX(whr.left() - whr.width() - (widget_help->width() - whr.width()) / 2);
	whp.setY(whr.top() - widget_help->height() - (fontHeight() / 3));
	//qDebug() << whr << whp << widget_help->width() << ", " << st;
	widget_help->move(ui->textCode->viewport()->mapToGlobal(whp));
	widget_help->show();
	widget_help->raise();
	cursor_scope = scope.first;
	cursor_scope << scope.second;
	//qDebug() << "tooltip" << st;
}


void QCodeEdit::hideHelp() {
	widget_help->hide();
	es_cursor.cursor = QTextCursor();
	cursor_scope.clear();
	applyExtraSelection();
}


QTextCursor QCodeEdit::functionStart(QTextCursor tc, int * arg) {
	QString doc = ui->textCode->toPlainText();
	int bcnt = 0, a = 0, i = -1;
	for (i = tc.position() - 1; i >= 0; --i) {
		if (doc[i] == ')') bcnt++;
		if (doc[i] == '(') {
			if (bcnt == 0)
				break;
			else
				bcnt--;
		}
		//if (doc[i] == '(') bcnt--;
		if (doc[i] == ',' && bcnt == 0) a++;
	}
	if (i < 0) return QTextCursor();
	if (arg) *arg = a;
	QTextCursor ret(ui->textCode->document());
	ret.setPosition(i);
	//qDebug() << "found" << i << a;
	return ret;
}


QCodeEdit::ACList QCodeEdit::wordsCompletitionList(const QString & written) const {
	QCodeEdit::ACList ret;
	if (!written.isEmpty()) {
		QTextCursor tc = QTextCursor(ui->textCode->document()->begin()), stc;
		QStringList acwl;
		tc = QTextCursor(ui->textCode->document()->begin());
		while (true) {
			tc = ui->textCode->document()->find(written, tc);
			if (tc.isNull()) break;
			stc = tc;
			stc.movePosition(QTextCursor::Left);
			stc.select(QTextCursor::WordUnderCursor);
			if (!stc.selectedText().isEmpty() && stc.selectedText().trimmed() != written)
				acwl << stc.selectedText();
		}
		acwl.removeDuplicates();
		ACPair acl;
		acl.first = -1;
		foreach (const QString & s, acwl)
			acl.second << StringsPair("", s);
		ret << acl;
	}
	return ret;
}


QPair<QStringList, QString> QCodeEdit::getScope(QTextCursor tc, bool * ok) {
	QPair<QStringList, QString> ret;
	QTextCursor stc = tc;
	if (tc.isNull()) {
		completer->hide();
		if (ok) *ok = false;
		return ret;
	}
	int line = tc.block().firstLineNumber();
	if (completer->isVisible()) {
		if (auto_comp_pl != line) {
			completer->hide();
			auto_comp_pl = line;
			if (ok) *ok = false;
			return ret;
		}
	}
	QString doc = ui->textCode->toPlainText();
	auto_comp_pl = line;
	completer->clear();
	int spos = tc.position(), cpos = spos;
	tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
	QStringList scope;
	QString written = tc.selectedText().trimmed();
	//qDebug() << "\n*** invokeAutoCompletition ***";
	if (written != "_" && !written.leftJustified(1)[0].isLetterOrNumber()) {
		written.clear();
	} else {
		cpos = skipCWord(doc, spos);
		if (cpos >= 0)
			written = doc.mid(cpos, spos - cpos).trimmed();
	}
	while (cpos >= 0) {
		cpos--;
		//qDebug() << "char =" << doc.mid(cpos, 1);
		if (doc.mid(cpos, 1) != ".") break;
		QChar c = doc.mid(cpos - 1, 1).leftJustified(1)[0];
		int ppos = cpos;
		if (c == '\"' || c == ')' || c == ']') {
			cpos = skipRange(doc, cpos, pairChar(c), c, '\\');
			//qDebug() << "range" << cpos;
			if (cpos < 0) break;
		}
		int npos = skipCWord(doc, cpos);
		if (npos < 0) break;
		scope.push_front(doc.mid(npos, ppos - npos));
		cpos = npos;
	}
	ret.first = scope;
	ret.second = written;
	if (ok) *ok = true;
	return ret;
}


void QCodeEdit::invokeAutoCompletition(bool force) {
	int arg = -1;
	QTextCursor htc = functionStart(ui->textCode->textCursor(), &arg);
	if (!htc.isNull()) {
		//qDebug() << "raise";
		raiseHelp(htc, arg);
	}
	bool ok;
	QPair<QStringList, QString> scope = getScope(ui->textCode->textCursor(), &ok);
	if (!ok) return;
	ACList acl(autoCompletitionList(scope.first, scope.second));
	//qDebug() << written << scope << acl.size();
	if (scope.first.isEmpty() && scope.second.isEmpty() && !force) {
		completer->hide();
		hideHelp();
		return;
	}
	acl << wordsCompletitionList(scope.second);
	QFont bf(font());
	bf.setBold(true);
	foreach (const ACPair & ac, acl) {
		if (ac.second.isEmpty()) continue;
		ACClass acc = ac_classes.value(ac.first);
		QTreeWidgetItem * gi = new QTreeWidgetItem();
		gi->setText(0, acc.name);
		gi->setTextAlignment(0, Qt::AlignCenter);
		gi->setTextAlignment(1, Qt::AlignCenter);
		gi->setFont(0, bf);
		gi->setBackgroundColor(0, Qt::lightGray);
		gi->setFlags(Qt::ItemIsEnabled);
		completer->addTopLevelItem(gi);
		gi->setFirstColumnSpanned(true);
		foreach (const StringsPair & s, ac.second) {
			QTreeWidgetItem * ni = new QTreeWidgetItem();
			ni->setIcon(0, acc.icon);
			ni->setText(0, s.first);
			ni->setText(1, s.second);
			completer->addTopLevelItem(ni);
		}
	}
	if (completer->topLevelItemCount() > 1)
		completer->setCurrentItem(completer->topLevelItem(1));
	if (completer->isHidden())
		completer->move(ui->textCode->mapToGlobal(ui->textCode->cursorRect().bottomRight()));
	if (completer->topLevelItemCount() > 0) {
		completer->setVisible(true);
		//qApp->processEvents();
		int sz = completer->verticalScrollBar()->width();
		for (int i = 0; i < completer->header()->count(); ++i)
			sz += qMax<int>(sz, ((QAbstractItemView*)completer)->sizeHintForColumn(i));
		completer->resize(sz, fontHeight() * 16);
	} else
		completer->hide();
}


void QCodeEdit::commitCompletition() {
	if (completer->currentItem() == 0) return;
	if (!completer->currentItem()->flags().testFlag(Qt::ItemIsSelectable)) return;
	QString ins = completer->currentItem()->text(1), ret = completer->currentItem()->text(0);
	QTextCursor tc = ui->textCode->textCursor(), stc = tc;
	tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
	bool ins_br = true, shifted = false;
	if (!tc.selectedText().isEmpty()) {
//		if (tc.selectedText()[0].isSpace()) {
		if (!tc.selectedText()[0].isLetterOrNumber() && !tc.selectedText()[0].isSpace() && !(tc.selectedText()[0] == '_')) {
			stc.movePosition(QTextCursor::Left);
			shifted = true;
		} else {
			tc.movePosition(QTextCursor::Left);
			tc.movePosition(QTextCursor::EndOfWord);
			tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
		}
		if (!tc.selectedText().isEmpty())
			if (tc.selectedText()[0].toLatin1() == '(')
				ins_br = false;
	}
	if (ins.contains("("))
		ins = ins.left(ins.indexOf("(")) + "()";
	if (!ins_br && ins.endsWith("()"))
		ins.chop(2);
	tc = stc;
	tc.select(QTextCursor::WordUnderCursor);
	if (!tc.selectedText().leftJustified(1)[0].isLetterOrNumber() && !(tc.selectedText().leftJustified(1)[0] == '_')) {
		tc = stc;
		if (shifted)
			tc.movePosition(QTextCursor::Right);
	}
	ui->textCode->setTextCursor(tc);
	ui->textCode->textCursor().insertText(ins);
	tc = ui->textCode->textCursor();
	if (ins_br) {
		if (ret == "void") {
			tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
			if (tc.selectedText() != ";") {
				ui->textCode->textCursor().insertText(";");
				tc.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 2);
			}
		}
		if (ins.endsWith(")") && !completer->currentItem()->text(1).endsWith("()")) {
			tc.movePosition(QTextCursor::Left);
			ui->textCode->setTextCursor(tc);
		}
	} else {
		if (completer->currentItem()->text(1).endsWith(")")) {
			tc.movePosition(QTextCursor::Right);
			ui->textCode->setTextCursor(tc);
		}
		if (completer->currentItem()->text(1).endsWith("()")) {
			tc.movePosition(QTextCursor::Right);
			ui->textCode->setTextCursor(tc);
		}
	}
	completer->hide();
}


void QCodeEdit::textEdit_cursorPositionChanged() {
	es_line.cursor = ui->textCode->textCursor();
	es_line.cursor.select(QTextCursor::LineUnderCursor);
	es_line.cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
	highlightBrackets();
	applyExtraSelection();
}


void QCodeEdit::textEdit_textChanged() {
	if (_replacing) return;
	searchAll();
	updateLines();
}


void QCodeEdit::textEdit_selectionChanged() {
	es_selected.clear();
	QString sf = ui->textCode->textCursor().selectedText();
	if (sf.trimmed().isEmpty() || sf.contains("\n")) {
		applyExtraSelection();
		return;
	}
	QTextCursor tc(ui->textCode->document()->begin());
	QTextEdit::ExtraSelection es;
	es.format.setBackground(QColor(251, 250, 150));
	while (true) {
		tc = ui->textCode->document()->find(sf, tc, QTextDocument::FindCaseSensitively | QTextDocument::FindWholeWords);
		if (tc.isNull()) break;
		es.cursor = tc;
		es_selected << es;
	}
	applyExtraSelection();
}


void QCodeEdit::setShowSpaces(bool yes) {
	spaces_ = yes;
	QTextOption to = ui->textCode->document()->defaultTextOption();
	QTextOption::Flags tof = to.flags();
	if (yes) tof |= QTextOption::ShowTabsAndSpaces;
	else tof &= ~QTextOption::ShowTabsAndSpaces;
	to.setFlags(tof);
	ui->textCode->document()->setDefaultTextOption(to);
}


void QCodeEdit::setShowLineNumbers(bool yes) {
	ui->textLines->setVisible(yes);
}


void QCodeEdit::search(const QString & t) {
	ui->widgetSearch->show();
	ui->comboSearch->setEditText(QString());
	ui->comboSearch->setEditText(t);
	ui->comboSearch->setFocus();
	//searchAll();
	searchNext(false);
}


void QCodeEdit::searchNext(bool next) {
	if (es_search_list.isEmpty())
		return;
	cur_search_ind = searchIndFromCursor() + (next ? 1 : 0);
	moveToSearch();
}


void QCodeEdit::searchPrevious() {
	if (es_search_list.isEmpty())
		return;
	cur_search_ind = searchIndFromCursor() - 1;
	moveToSearch();
}


void QCodeEdit::hideSearch() {
	ui->widgetSearch->hide();
	searchAll();
}


void QCodeEdit::on_comboSearch_currentTextChanged(const QString & t) {
	searchAll();
	searchNext(false);
}


void QCodeEdit::on_buttonReplace_clicked() {
	if (es_search_list.isEmpty() || cur_search_ind < 0 || cur_search_ind >= es_search_list.size()) return;
	if (ui->textCode->textCursor() != es_search_list[cur_search_ind].cursor) return;
	if (ui->textCode->textCursor().selectedText().size() != es_search_list[cur_search_ind].cursor.selectedText().size()) return;
	ui->textCode->textCursor().insertText(ui->comboReplace->currentText());
}


void QCodeEdit::on_buttonReplaceSearch_clicked() {
	on_buttonReplace_clicked();
	searchNext();
}


void QCodeEdit::on_buttonReplaceAll_clicked() {
	_replacing = true;
	QString rt = ui->comboReplace->currentText();
	for (int i = es_search_list.size() - 1; i >= 0; --i)
		es_search_list[i].cursor.insertText(rt);
	_replacing = false;
	textEdit_textChanged();
}
