FreeCAD

Форк
0
/
PythonEditor.cpp 
521 строка · 18.0 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
6
 *   This library is free software; you can redistribute it and/or         *
7
 *   modify it under the terms of the GNU Library General Public           *
8
 *   License as published by the Free Software Foundation; either          *
9
 *   version 2 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         *
14
 *   GNU Library General Public License for more details.                  *
15
 *                                                                         *
16
 *   You should have received a copy of the GNU Library General Public     *
17
 *   License along with this library; see the file COPYING.LIB. If not,    *
18
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
19
 *   Suite 330, Boston, MA  02111-1307, USA                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23
#include "PreCompiled.h"
24
#ifndef _PreComp_
25
# include <QContextMenuEvent>
26
# include <QMenu>
27
# include <QPainter>
28
# include <QShortcut>
29
# include <QTextCursor>
30
#endif
31

32
#include <Base/Parameter.h>
33

34
#include "PythonEditor.h"
35
#include "Application.h"
36
#include "BitmapFactory.h"
37
#include "Macro.h"
38
#include "PythonDebugger.h"
39

40

41
using namespace Gui;
42

43
namespace Gui {
44
struct PythonEditorP
45
{
46
    int   debugLine{-1};
47
    QRect debugRect;
48
    QPixmap breakpoint;
49
    QPixmap debugMarker;
50
    QString filename;
51
    PythonDebugger* debugger;
52
    PythonEditorP()
53
        : breakpoint(BitmapFactory().iconFromTheme("breakpoint").pixmap(16,16)),
54
          debugMarker(BitmapFactory().iconFromTheme("debug-marker").pixmap(16,16))
55
    {
56
        debugger = Application::Instance->macroManager()->debugger();
57
    }
58
};
59
} // namespace Gui
60

61
/* TRANSLATOR Gui::PythonEditor */
62

63
/**
64
 *  Constructs a PythonEditor which is a child of 'parent' and does the
65
 *  syntax highlighting for the Python language.
66
 */
67
PythonEditor::PythonEditor(QWidget* parent)
68
  : TextEditor(parent)
69
{
70
    d = new PythonEditorP();
71
    this->setSyntaxHighlighter(new PythonSyntaxHighlighter(this));
72

73
    // set accelerators
74
    auto comment = new QShortcut(this);
75
    comment->setKey(QKeySequence(QString::fromLatin1("ALT+C")));
76

77
    auto uncomment = new QShortcut(this);
78
    uncomment->setKey(QKeySequence(QString::fromLatin1("ALT+U")));
79

80
    connect(comment, &QShortcut::activated, this, &PythonEditor::onComment);
81
    connect(uncomment, &QShortcut::activated, this, &PythonEditor::onUncomment);
82
}
83

84
/** Destroys the object and frees any allocated resources */
85
PythonEditor::~PythonEditor()
86
{
87
    delete d;
88
}
89

90
void PythonEditor::setFileName(const QString& fn)
91
{
92
    d->filename = fn;
93
}
94

95
void PythonEditor::startDebug()
96
{
97
    if (d->debugger->start()) {
98
        d->debugger->runFile(d->filename);
99
        d->debugger->stop();
100
    }
101
}
102

103
void PythonEditor::toggleBreakpoint()
104
{
105
    QTextCursor cursor = textCursor();
106
    int line = cursor.blockNumber() + 1;
107
    d->debugger->toggleBreakpoint(line, d->filename);
108
    getMarker()->update();
109
}
110

111
void PythonEditor::showDebugMarker(int line)
112
{
113
    d->debugLine = line;
114
    getMarker()->update();
115
    QTextCursor cursor = textCursor();
116
    cursor.movePosition(QTextCursor::StartOfBlock);
117
    int cur = cursor.blockNumber() + 1;
118
    if (cur > line) {
119
        for (int i=line; i<cur; i++)
120
            cursor.movePosition(QTextCursor::Up);
121
    }
122
    else if (cur < line) {
123
        for (int i=cur; i<line; i++)
124
            cursor.movePosition(QTextCursor::Down);
125
    }
126
    setTextCursor(cursor);
127
}
128

129
void PythonEditor::hideDebugMarker()
130
{
131
    d->debugLine = -1;
132
    getMarker()->update();
133
}
134

135
void PythonEditor::drawMarker(int line, int x, int y, QPainter* p)
136
{
137
    Breakpoint bp = d->debugger->getBreakpoint(d->filename);
138
    if (bp.checkLine(line)) {
139
        p->drawPixmap(x, y, d->breakpoint);
140
    }
141
    if (d->debugLine == line) {
142
        p->drawPixmap(x, y+2, d->debugMarker);
143
        d->debugRect = QRect(x, y+2, d->debugMarker.width(), d->debugMarker.height());
144
    }
145
}
146

147
void PythonEditor::contextMenuEvent ( QContextMenuEvent * e )
148
{
149
    QMenu* menu = createStandardContextMenu();
150
    if (!isReadOnly()) {
151
        menu->addSeparator();
152
        QAction* comment = menu->addAction( tr("Comment"), this, &PythonEditor::onComment);
153
        comment->setShortcut(QKeySequence(QString::fromLatin1("ALT+C")));
154
        QAction* uncomment = menu->addAction( tr("Uncomment"), this, &PythonEditor::onUncomment);
155
        uncomment->setShortcut(QKeySequence(QString::fromLatin1("ALT+U")));
156
    }
157

158
    menu->exec(e->globalPos());
159
    delete menu;
160
}
161

162
void PythonEditor::keyPressEvent(QKeyEvent* e)
163
{
164
    /** When the user presses enter the next line should match the current
165
     * indentation unless the line ends in a colon, where the next line
166
     * should have an additional indentation.  Shift+Enter should dedent
167
     * the next block 1 indentation from what it would have been, if possible.
168
     */
169
    if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
170
        bool shiftPressed = e->modifiers() & Qt::ShiftModifier;
171
        ParameterGrp::handle hPrefGrp = getWindowParameter();
172
        int indent = hPrefGrp->GetInt( "IndentSize", 4 );
173
        bool space = hPrefGrp->GetBool( "Spaces", true );
174
        QString ch = space ? QString::fromLatin1(" ")
175
                           : QString::fromLatin1("\t");
176

177
        QTextCursor cursor = textCursor();
178
        QString currentLineText = cursor.block().text();
179
        bool endsWithColon = currentLineText.endsWith(QLatin1Char(':'));
180
        int currentIndentation = 0;
181
        //count spaces/tabs at start of current line
182
        for (auto c : currentLineText) {
183
            if (c == ch) {
184
                currentIndentation++;
185
            } else {
186
                break;
187
            }
188
        }
189
        cursor.insertBlock(); //new line
190
        cursor.movePosition(QTextCursor::StartOfBlock); //carriage return
191
        //Shift+Enter means dedent, but ensure we are not at column 0
192
        if (shiftPressed && currentIndentation >= indent){
193
            currentIndentation -= indent;
194
        }
195
        //insert appropriate number of spaces/tabs to match current indentation
196
        cursor.insertText(QString(currentIndentation, ch[0]));
197
        //if the line ended in a colon, then we need to add another tab or multiple spaces
198
        if (endsWithColon) {
199
            if (space){
200
                cursor.insertText(QString(indent, ch[0])); //4 more spaces by default
201
            } else {
202
                cursor.insertText(ch); //1 more tab
203
            }
204
        }
205
        setTextCursor(cursor);
206
        return; //skip default handler
207
    }
208
    TextEditor::keyPressEvent(e); //wasn't enter key, so let base class handle it
209
}
210

211
void PythonEditor::onComment()
212
{
213
    QTextCursor cursor = textCursor();
214
    int selStart = cursor.selectionStart();
215
    int selEnd = cursor.selectionEnd();
216
    QTextBlock block;
217
    cursor.beginEditBlock();
218
    for (block = document()->begin(); block.isValid(); block = block.next()) {
219
        int pos = block.position();
220
        int off = block.length()-1;
221
        // at least one char of the block is part of the selection
222
        if ( pos >= selStart || pos+off >= selStart) {
223
            if ( pos+1 > selEnd )
224
                break; // end of selection reached
225
            cursor.setPosition(block.position());
226
            cursor.insertText(QLatin1String("#"));
227
                selEnd++;
228
        }
229
    }
230

231
    cursor.endEditBlock();
232
}
233

234
void PythonEditor::onUncomment()
235
{
236
    QTextCursor cursor = textCursor();
237
    int selStart = cursor.selectionStart();
238
    int selEnd = cursor.selectionEnd();
239
    QTextBlock block;
240
    cursor.beginEditBlock();
241
    for (block = document()->begin(); block.isValid(); block = block.next()) {
242
        int pos = block.position();
243
        int off = block.length()-1;
244
        // at least one char of the block is part of the selection
245
        if ( pos >= selStart || pos+off >= selStart) {
246
            if ( pos+1 > selEnd )
247
                break; // end of selection reached
248
            if (block.text().startsWith(QLatin1String("#"))) {
249
                cursor.setPosition(block.position());
250
                cursor.deleteChar();
251
                selEnd--;
252
            }
253
        }
254
    }
255

256
    cursor.endEditBlock();
257
}
258

259
// ------------------------------------------------------------------------
260

261
namespace Gui {
262
class PythonSyntaxHighlighterP
263
{
264
public:
265
    PythonSyntaxHighlighterP()
266
    {
267
        keywords << QLatin1String("and") << QLatin1String("as")
268
                 << QLatin1String("assert") << QLatin1String("break")
269
                 << QLatin1String("class") << QLatin1String("continue")
270
                 << QLatin1String("def") << QLatin1String("del")
271
                 << QLatin1String("elif") << QLatin1String("else")
272
                 << QLatin1String("except") << QLatin1String("exec")
273
                 << QLatin1String("False") << QLatin1String("finally")
274
                 << QLatin1String("for") << QLatin1String("from")
275
                 << QLatin1String("global") << QLatin1String("if")
276
                 << QLatin1String("import") << QLatin1String("in")
277
                 << QLatin1String("is") << QLatin1String("lambda")
278
                 << QLatin1String("None") << QLatin1String("nonlocal")
279
                 << QLatin1String("not") << QLatin1String("or")
280
                 << QLatin1String("pass") << QLatin1String("print")
281
                 << QLatin1String("raise") << QLatin1String("return")
282
                 << QLatin1String("True") << QLatin1String("try")
283
                 << QLatin1String("while") << QLatin1String("with")
284
                 << QLatin1String("yield");
285
    }
286

287
    QStringList keywords;
288
};
289
} // namespace Gui
290

291
/**
292
 * Constructs a Python syntax highlighter.
293
 */
294
PythonSyntaxHighlighter::PythonSyntaxHighlighter(QObject* parent)
295
    : SyntaxHighlighter(parent)
296
{
297
    d = new PythonSyntaxHighlighterP;
298
}
299

300
/** Destroys this object. */
301
PythonSyntaxHighlighter::~PythonSyntaxHighlighter()
302
{
303
    delete d;
304
}
305

306
/**
307
 * Detects all kinds of text to highlight them in the correct color.
308
 */
309
void PythonSyntaxHighlighter::highlightBlock (const QString & text)
310
{
311
  int i = 0;
312
  QChar prev, ch;
313

314
  const int Standard      = 0;     // Standard text
315
  const int Digit         = 1;     // Digits
316
  const int Comment       = 2;     // Comment begins with #
317
  const int Literal1      = 3;     // String literal beginning with "
318
  const int Literal2      = 4;     // Other string literal beginning with '
319
  const int Blockcomment1 = 5;     // Block comments beginning and ending with """
320
  const int Blockcomment2 = 6;     // Other block comments beginning and ending with '''
321
  const int ClassName     = 7;     // Text after the keyword class
322
  const int DefineName    = 8;     // Text after the keyword def
323

324
  int endStateOfLastPara = previousBlockState();
325
  if (endStateOfLastPara < 0 || endStateOfLastPara > maximumUserState())
326
    endStateOfLastPara = Standard;
327

328
  while ( i < text.length() )
329
  {
330
    ch = text.at( i );
331

332
    switch ( endStateOfLastPara )
333
    {
334
    case Standard:
335
      {
336
        switch ( ch.unicode() )
337
        {
338
        case '#':
339
          {
340
            // begin a comment
341
            setFormat( i, 1, this->colorByType(SyntaxHighlighter::Comment));
342
            endStateOfLastPara=Comment;
343
          } break;
344
        case '"':
345
          {
346
            // Begin either string literal or block comment
347
            if ((i>=2) && text.at(i-1) == QLatin1Char('"') &&
348
                text.at(i-2) == QLatin1Char('"'))
349
            {
350
              setFormat( i-2, 3, this->colorByType(SyntaxHighlighter::BlockComment));
351
              endStateOfLastPara=Blockcomment1;
352
            }
353
            else
354
            {
355
              setFormat( i, 1, this->colorByType(SyntaxHighlighter::String));
356
              endStateOfLastPara=Literal1;
357
            }
358
          } break;
359
        case '\'':
360
          {
361
            // Begin either string literal or block comment
362
            if ((i>=2) && text.at(i-1) == QLatin1Char('\'') &&
363
                text.at(i-2) == QLatin1Char('\''))
364
            {
365
              setFormat( i-2, 3, this->colorByType(SyntaxHighlighter::BlockComment));
366
              endStateOfLastPara=Blockcomment2;
367
            }
368
            else
369
            {
370
              setFormat( i, 1, this->colorByType(SyntaxHighlighter::String));
371
              endStateOfLastPara=Literal2;
372
            }
373
          } break;
374
        case ' ':
375
        case '\t':
376
          {
377
            // ignore whitespaces
378
          } break;
379
        case '(': case ')': case '[': case ']':
380
        case '+': case '-': case '*': case '/':
381
        case ':': case '%': case '^': case '~':
382
        case '!': case '=': case '<': case '>': // possibly two characters
383
          {
384
            setFormat(i, 1, this->colorByType(SyntaxHighlighter::Operator));
385
            endStateOfLastPara=Standard;
386
          } break;
387
        default:
388
          {
389
            // Check for normal text
390
            if ( ch.isLetter() || ch == QLatin1Char('_') )
391
            {
392
              QString buffer;
393
              int j=i;
394
              while ( ch.isLetterOrNumber() || ch == QLatin1Char('_') ) {
395
                buffer += ch;
396
                ++j;
397
                if (j >= text.length())
398
                  break; // end of text
399
                ch = text.at(j);
400
              }
401

402
              if ( d->keywords.contains( buffer ) != 0 ) {
403
                if ( buffer == QLatin1String("def"))
404
                  endStateOfLastPara = DefineName;
405
                else if ( buffer == QLatin1String("class"))
406
                  endStateOfLastPara = ClassName;
407

408
                QTextCharFormat keywordFormat;
409
                keywordFormat.setForeground(this->colorByType(SyntaxHighlighter::Keyword));
410
                keywordFormat.setFontWeight(QFont::Bold);
411
                setFormat( i, buffer.length(), keywordFormat);
412
              }
413
              else {
414
                setFormat( i, buffer.length(),this->colorByType(SyntaxHighlighter::Text));
415
              }
416

417
              // increment i
418
              if ( !buffer.isEmpty() )
419
                i = j-1;
420
            }
421
            // this is the beginning of a number
422
            else if ( ch.isDigit() )
423
            {
424
              setFormat(i, 1, this->colorByType(SyntaxHighlighter::Number));
425
              endStateOfLastPara=Digit;
426
            }
427
            // probably an operator
428
            else if ( ch.isSymbol() || ch.isPunct() )
429
            {
430
              setFormat( i, 1, this->colorByType(SyntaxHighlighter::Operator));
431
            }
432
          }
433
        }
434
      } break;
435
    case Comment:
436
      {
437
        setFormat( i, 1, this->colorByType(SyntaxHighlighter::Comment));
438
      } break;
439
    case Literal1:
440
      {
441
        setFormat( i, 1, this->colorByType(SyntaxHighlighter::String));
442
        if ( ch == QLatin1Char('"') )
443
          endStateOfLastPara = Standard;
444
      } break;
445
    case Literal2:
446
      {
447
        setFormat( i, 1, this->colorByType(SyntaxHighlighter::String));
448
        if ( ch == QLatin1Char('\'') )
449
          endStateOfLastPara = Standard;
450
      } break;
451
    case Blockcomment1:
452
      {
453
        setFormat( i, 1, this->colorByType(SyntaxHighlighter::BlockComment));
454
        if ( i>=2 && ch == QLatin1Char('"') &&
455
            text.at(i-1) == QLatin1Char('"') &&
456
            text.at(i-2) == QLatin1Char('"'))
457
          endStateOfLastPara = Standard;
458
      } break;
459
    case Blockcomment2:
460
      {
461
        setFormat( i, 1, this->colorByType(SyntaxHighlighter::BlockComment));
462
        if ( i>=2 && ch == QLatin1Char('\'') &&
463
            text.at(i-1) == QLatin1Char('\'') &&
464
            text.at(i-2) == QLatin1Char('\''))
465
          endStateOfLastPara = Standard;
466
      } break;
467
    case DefineName:
468
      {
469
        if ( ch.isLetterOrNumber() || ch == QLatin1Char(' ') || ch == QLatin1Char('_') )
470
        {
471
          setFormat( i, 1, this->colorByType(SyntaxHighlighter::Defname));
472
        }
473
        else
474
        {
475
          if ( ch.isSymbol() || ch.isPunct() )
476
            setFormat(i, 1, this->colorByType(SyntaxHighlighter::Operator));
477
          endStateOfLastPara = Standard;
478
        }
479
      } break;
480
    case ClassName:
481
      {
482
        if ( ch.isLetterOrNumber() || ch == QLatin1Char(' ') || ch == QLatin1Char('_') )
483
        {
484
          setFormat( i, 1, this->colorByType(SyntaxHighlighter::Classname));
485
        }
486
        else
487
        {
488
          if (ch.isSymbol() || ch.isPunct() )
489
            setFormat( i, 1, this->colorByType(SyntaxHighlighter::Operator));
490
          endStateOfLastPara = Standard;
491
        }
492
      } break;
493
    case Digit:
494
      {
495
        if (ch.isDigit() || ch == QLatin1Char('.'))
496
        {
497
          setFormat( i, 1, this->colorByType(SyntaxHighlighter::Number));
498
        }
499
        else
500
        {
501
          if ( ch.isSymbol() || ch.isPunct() )
502
            setFormat( i, 1, this->colorByType(SyntaxHighlighter::Operator));
503
          endStateOfLastPara = Standard;
504
        }
505
      }break;
506
    }
507

508
    prev = ch;
509
    i++;
510
  }
511

512
  // only block comments can have several lines
513
  if ( endStateOfLastPara != Blockcomment1 && endStateOfLastPara != Blockcomment2 )
514
  {
515
    endStateOfLastPara = Standard ;
516
  }
517

518
  setCurrentBlockState(endStateOfLastPara);
519
}
520

521
#include "moc_PythonEditor.cpp"
522

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.