1 /**************************************************************************
2 ** This file is part of LiteIDE
3 **
4 ** Copyright (c) 2011-2019 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: markdownedit.cpp
22 // Creator: visualfc <visualfc@gmail.com>
23 
24 #include "markdownedit.h"
25 #include "liteeditorapi/liteeditorapi.h"
26 #include <QMenu>
27 #include <QAction>
28 #include <QToolBar>
29 #include <QTextCursor>
30 #include <QTextBlock>
31 #include <QPlainTextEdit>
32 #include <QTextDocument>
33 #include <QRegExp>
34 #include <QDebug>
35 //lite_memory_check_begin
36 #if defined(WIN32) && defined(_MSC_VER) &&  defined(_DEBUG)
37      #define _CRTDBG_MAP_ALLOC
38      #include <stdlib.h>
39      #include <crtdbg.h>
40      #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
41      #define new DEBUG_NEW
42 #endif
43 //lite_memory_check_end
44 
45 //class Separator : public QAction
46 //{
47 //public:
48 //    Separator(QObject *parent) :
49 //        QAction(parent)
50 //    {
51 //        setSeparator(true);
52 //    }
53 //};
54 
55 //static void updateToolTip(QToolBar *toolBar)
56 //{
57 //    foreach(QAction *act, toolBar->actions()) {
58 //        if (act->isSeparator()) {
59 //            continue;
60 //        }
61 //        if (act->toolTip() == act->text() && !act->shortcut().isEmpty()) {
62 //            act->setToolTip(QString("%1 (%2)").arg(act->text()).arg(act->shortcut().toString()));
63 //        }
64 //    }
65 //}
66 
MarkdownEdit(LiteApi::IApplication * app,LiteApi::IEditor * editor,QObject * parent)67 MarkdownEdit::MarkdownEdit(LiteApi::IApplication *app, LiteApi::IEditor *editor, QObject *parent) :
68     QObject(parent), m_liteApp(app)
69 {
70     m_editor = LiteApi::getTextEditor(editor);
71     if (!m_editor) {
72         return;
73     }
74     m_ed = LiteApi::getPlainTextEdit(editor);
75     if (!m_ed) {
76         return;
77     }
78 
79     m_editor->setLineWrap(true);
80 
81     LiteApi::IActionContext *actionContext = m_liteApp->actionManager()->getActionContext(this,"Markdown");
82 
83     QAction *h1 = new QAction(QIcon("icon:markdown/images/h1.png"),tr("Header (h1)"),this);
84     actionContext->regAction(h1,"Header1","Ctrl+1");
85 
86     QAction *h2 = new QAction(QIcon("icon:markdown/images/h2.png"),tr("Header (h2)"),this);
87     actionContext->regAction(h2,"Header2","Ctrl+2");
88 
89     QAction *h3 = new QAction(QIcon("icon:markdown/images/h3.png"),tr("Header (h3)"),this);
90     actionContext->regAction(h3,"Header3","Ctrl+3");
91 
92     QAction *h4 = new QAction(QIcon("icon:markdown/images/h4.png"),tr("Header (h4)"),this);
93     actionContext->regAction(h4,"Header4","Ctrl+4");
94 
95     QAction *h5 = new QAction(QIcon("icon:markdown/images/h5.png"),tr("Header (h5)"),this);
96     actionContext->regAction(h5,"Header5","Ctrl+5");
97 
98     QAction *h6 = new QAction(QIcon("icon:markdown/images/h6.png"),tr("Header (h6)"),this);
99     actionContext->regAction(h6,"Header6","Ctrl+6");
100 
101     QAction *bold = new QAction(QIcon("icon:markdown/images/bold.png"),tr("Bold"),this);
102     actionContext->regAction(bold,"Bold",QKeySequence::Bold);
103 
104     QAction *italic = new QAction(QIcon("icon:markdown/images/italic.png"),tr("Italic"),this);
105     actionContext->regAction(italic,"Italic",QKeySequence::Italic);
106 
107     QAction *code = new QAction(QIcon("icon:markdown/images/code.png"),tr("Inline Code"),this);
108     actionContext->regAction(code,"InlineCode","Ctrl+K");
109 
110     QAction *link = new QAction(QIcon("icon:markdown/images/link.png"),tr("Link"),this);
111     actionContext->regAction(link,"Link","Ctrl+Shift+L");
112 
113     QAction *image = new QAction(QIcon("icon:markdown/images/image.png"),tr("Image"),this);
114     actionContext->regAction(image,"Image","Ctrl+Shift+I");
115 
116     QAction *ul = new QAction(QIcon("icon:markdown/images/ul.png"),tr("Unordered List"),this);
117     actionContext->regAction(ul,"UnorderedList","Ctrl+Shift+U");
118 
119     QAction *ol = new QAction(QIcon("icon:markdown/images/ol.png"),tr("Ordered List"),this);
120     actionContext->regAction(ol,"OrderedList","Ctrl+Shift+O");
121 
122     QAction *bq = new QAction(QIcon("icon:markdown/images/quote.png"),tr("Blockquote"),this);
123     actionContext->regAction(bq,"Blockquote","Ctrl+Shift+Q");
124 
125     QAction *hr = new QAction(QIcon("icon:markdown/images/hr.png"),tr("Horizontal Rule"),this);
126     actionContext->regAction(hr,"HorizontalRule","Ctrl+Shift+H");
127 
128     QMenu *menu = LiteApi::getEditMenu(editor);
129     if (menu) {
130         menu->addSeparator();
131         QMenu *h = menu->addMenu(tr("Heading"));
132         h->addAction(h1);
133         h->addAction(h2);
134         h->addAction(h3);
135         h->addAction(h4);
136         h->addAction(h5);
137         h->addAction(h6);
138         menu->addSeparator();
139         menu->addAction(link);
140         menu->addAction(image);
141         menu->addSeparator();
142         menu->addAction(bold);
143         menu->addAction(italic);
144         menu->addAction(code);
145         menu->addSeparator();
146         menu->addAction(ul);
147         menu->addAction(ol);
148         menu->addSeparator();
149         menu->addAction(bq);
150         menu->addAction(hr);
151     }
152     menu = LiteApi::getContextMenu(editor);
153     if (menu) {
154         QMenu *h = menu->addMenu(tr("Heading"));
155         h->addAction(h1);
156         h->addAction(h2);
157         h->addAction(h3);
158         h->addAction(h4);
159         h->addAction(h5);
160         h->addAction(h6);
161         menu->addSeparator();
162         menu->addAction(link);
163         menu->addAction(image);
164         menu->addSeparator();
165         menu->addAction(bold);
166         menu->addAction(italic);
167         menu->addAction(code);
168         menu->addSeparator();
169         menu->addAction(ul);
170         menu->addAction(ol);
171         menu->addSeparator();
172         menu->addAction(bq);
173         menu->addAction(hr);
174     }
175 
176     QToolBar *toolBar = LiteApi::getEditToolBar(editor);
177     if (toolBar) {
178         toolBar->addSeparator();
179         toolBar->addAction(h1);
180         toolBar->addAction(h2);
181         toolBar->addAction(h3);
182         toolBar->addSeparator();
183         toolBar->addAction(link);
184         toolBar->addAction(image);
185         toolBar->addSeparator();
186         toolBar->addAction(bold);
187         toolBar->addAction(italic);
188         toolBar->addAction(code);
189         toolBar->addSeparator();
190         toolBar->addAction(ul);
191         toolBar->addAction(ol);
192         toolBar->addSeparator();
193         toolBar->addAction(bq);
194         toolBar->addAction(hr);
195         //updateToolTip(toolBar);
196     }
197 
198     connect(editor,SIGNAL(destroyed()),this,SLOT(deleteLater()));
199     connect(h1,SIGNAL(triggered()),this,SLOT(h1()));
200     connect(h2,SIGNAL(triggered()),this,SLOT(h2()));
201     connect(h3,SIGNAL(triggered()),this,SLOT(h3()));
202     connect(h4,SIGNAL(triggered()),this,SLOT(h4()));
203     connect(h5,SIGNAL(triggered()),this,SLOT(h5()));
204     connect(h6,SIGNAL(triggered()),this,SLOT(h6()));
205     connect(bold,SIGNAL(triggered()),this,SLOT(bold()));
206     connect(italic,SIGNAL(triggered()),this,SLOT(italic()));
207     connect(code,SIGNAL(triggered()),this,SLOT(code()));
208     connect(link,SIGNAL(triggered()),this,SLOT(link()));
209     connect(image,SIGNAL(triggered()),this,SLOT(image()));
210     connect(ul,SIGNAL(triggered()),this,SLOT(ul()));
211     connect(ol,SIGNAL(triggered()),this,SLOT(ol()));
212     connect(bq,SIGNAL(triggered()),this,SLOT(bq()));
213     connect(hr,SIGNAL(triggered()),this,SLOT(hr()));
214 
215     //m_ed->installEventFilter(this);
216 }
217 
~MarkdownEdit()218 MarkdownEdit::~MarkdownEdit()
219 {
220 }
221 
222 
223 
insert_head(const QString & tag,bool blockStart)224 void MarkdownEdit::insert_head(const QString &tag, bool blockStart)
225 {
226     QTextCursor cur = m_ed->textCursor();
227     cur.beginEditBlock();
228     if (cur.hasSelection()) {
229         QTextBlock begin = m_ed->document()->findBlock(cur.selectionStart());
230         QTextBlock end = m_ed->document()->findBlock(cur.selectionEnd());
231         if (end.position() == cur.selectionEnd()) {
232             end = end.previous();
233         }
234         QTextBlock block = begin;
235         do {
236             if (block.text().length() > 0) {
237                 if (blockStart) {
238                     cur.setPosition(block.position());
239                 } else {
240                     QString text = block.text();
241                     foreach(QChar c, text) {
242                         if (!c.isSpace()) {
243                             cur.setPosition(block.position()+text.indexOf(c));
244                             break;
245                         }
246                     }
247                 }
248                 cur.insertText(tag);
249             }
250             block = block.next();
251         } while(block.isValid() && block.position() <= end.position());
252     } else {
253         if (blockStart) {
254             cur.setPosition(cur.block().position());
255         } else {
256             QTextBlock block = cur.block();
257             QString text = block.text();
258             foreach(QChar c, text) {
259                 if (!c.isSpace()) {
260                     cur.setPosition(block.position()+text.indexOf(c));
261                     break;
262                 }
263             }
264         }
265         cur.insertText(tag);
266     }
267     cur.endEditBlock();
268     m_ed->setTextCursor(cur);
269 }
270 
mark_selection(const QString & mark)271 void MarkdownEdit::mark_selection(const QString &mark)
272 {
273     mark_selection(mark,mark);
274 }
275 
mark_selection(const QString & mark1,const QString & mark2)276 void MarkdownEdit::mark_selection(const QString &mark1, const QString &mark2)
277 {
278     QTextCursor cur = m_ed->textCursor();
279     cur.beginEditBlock();
280     if (cur.hasSelection()) {
281         QTextBlock begin = m_ed->document()->findBlock(cur.selectionStart());
282         QTextBlock end = m_ed->document()->findBlock(cur.selectionEnd());
283         if (end.position() == cur.selectionEnd()) {
284             end = end.previous();
285         }
286         int n1 = cur.selectionStart();
287         int n2 = cur.selectionEnd();
288         QTextBlock block = begin;
289         do {
290             int c1 = block.position();
291             int c2 = c1+block.text().length();
292             if (block.position() == begin.position() && c1 < n1) {
293                 c1 = n1;
294             }
295             if (c2 > n2) {
296                 c2 = n2;
297             }
298             if (c2 > c1) {
299                 if (!mark1.isEmpty()) {
300                     cur.setPosition(c1);
301                     cur.insertText(mark1);
302                     n2 += mark1.length();
303                 }
304                 if (!mark2.isEmpty()) {
305                     cur.setPosition(c2+mark1.length());
306                     cur.insertText(mark2);
307                     n2 += mark2.length();
308                 }
309             }
310             block = block.next();
311         } while(block.isValid() && block.position() <= end.position());
312     } else {
313         int pos = cur.position();
314         cur.insertText(mark1+mark2);
315         cur.setPosition(pos+mark1.length());
316     }
317     cur.endEditBlock();
318     m_ed->setTextCursor(cur);
319 }
320 
h1()321 void MarkdownEdit::h1()
322 {
323     insert_head("# ");
324 }
325 
h2()326 void MarkdownEdit::h2()
327 {
328     insert_head("## ");
329 }
330 
h3()331 void MarkdownEdit::h3()
332 {
333     insert_head("### ");
334 }
335 
h4()336 void MarkdownEdit::h4()
337 {
338     insert_head("#### ");
339 }
340 
h5()341 void MarkdownEdit::h5()
342 {
343     insert_head("#####");
344 }
345 
h6()346 void MarkdownEdit::h6()
347 {
348     insert_head("###### ");
349 }
350 
bold()351 void MarkdownEdit::bold()
352 {
353     mark_selection("**");
354 }
355 
italic()356 void MarkdownEdit::italic()
357 {
358     mark_selection("_");
359 }
360 
code()361 void MarkdownEdit::code()
362 {
363     mark_selection("`");
364 }
365 
link()366 void MarkdownEdit::link()
367 {
368     QTextCursor cursor = m_ed->textCursor();
369     cursor.beginEditBlock();
370     if (cursor.hasSelection()) {
371         int n1 = cursor.selectionStart();
372         int n2 = cursor.selectionEnd();
373         cursor.setPosition(n1);
374         cursor.insertText("[");
375         cursor.setPosition(n2+1);
376         cursor.insertText("]()");
377         cursor.setPosition(n2+3);
378     } else {
379         int n = cursor.position();
380         cursor.insertText("[]()");
381         cursor.setPosition(n+1);
382     }
383     cursor.endEditBlock();
384     m_ed->setTextCursor(cursor);
385 }
386 
image()387 void MarkdownEdit::image()
388 {
389     QTextCursor cursor = m_ed->textCursor();
390     cursor.beginEditBlock();
391     if (cursor.hasSelection()) {
392         int n1 = cursor.selectionStart();
393         int n2 = cursor.selectionEnd();
394         cursor.setPosition(n1);
395         cursor.insertText("![");
396         cursor.setPosition(n2+2);
397         cursor.insertText("]()");
398         cursor.setPosition(n2+4);
399     } else {
400         int n = cursor.position();
401         cursor.insertText("![]()");
402         cursor.setPosition(n+2);
403     }
404     cursor.endEditBlock();
405     m_ed->setTextCursor(cursor);
406 }
407 
ul()408 void MarkdownEdit::ul()
409 {
410     insert_head("* ",false);
411 }
412 
ol()413 void MarkdownEdit::ol()
414 {
415     insert_head("1. ",false);
416 }
417 
bq()418 void MarkdownEdit::bq()
419 {
420     insert_head("> ");
421 }
422 
hr()423 void MarkdownEdit::hr()
424 {
425     QTextCursor cursor = m_ed->textCursor();
426     if (cursor.hasSelection()) {
427         cursor.setPosition(cursor.selectionEnd());
428     }
429     cursor.insertText("\n***\n");
430     m_ed->setTextCursor(cursor);
431 }
432 
gotoLine(int line,int column)433 void MarkdownEdit::gotoLine(int line, int column)
434 {
435     const int blockNumber = line;
436     const QTextBlock &block = m_ed->document()->findBlockByLineNumber(blockNumber);
437     if (block.isValid()) {
438         QTextCursor cursor(block);
439         if (column > 0) {
440             cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column);
441         } else {
442             int pos = cursor.position();
443             while (m_ed->document()->characterAt(pos).category() == QChar::Separator_Space) {
444                 ++pos;
445             }
446             cursor.setPosition(pos);
447         }
448         m_ed->setTextCursor(cursor);
449         m_ed->ensureCursorVisible();
450     }
451 }
452 
eventFilter(QObject * obj,QEvent * event)453 bool MarkdownEdit::eventFilter(QObject *obj, QEvent *event)
454 {
455     if (obj == m_ed && event->type() == QEvent::KeyPress) {
456         QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
457         if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
458 //            QObject::eventFilter(obj, event);
459 //            QTextCursor cur = m_ed->textCursor();
460 //            QTextBlock prev = cur.block().previous();
461 //            if (prev.isValid()) {
462 //                QString text = prev.text();
463 //                qDebug() << text;
464 //                if (text.length() > 2 && text.at(1) == ' ' && QString("*+-").contains(text.at(0))) {
465 //                    cur.insertText(text.left(2));
466 //                }
467 //            }
468 //            m_ed->setTextCursor(cur);
469             return false;
470         }
471     }
472     return QObject::eventFilter(obj, event);
473 }
474 
475