fakevimhandler.cpp 269 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
hjk's avatar
hjk committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
hjk's avatar
hjk committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
hjk's avatar
hjk committed
7
**
hjk's avatar
hjk committed
8
9
10
11
12
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
Eike Ziller's avatar
Eike Ziller committed
13
14
** conditions see http://www.qt.io/licensing.  For further information
** use the contact form at http://www.qt.io/contact-us.
hjk's avatar
hjk committed
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18
19
20
21
22
23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
25
26
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
27
28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30

31
//
32
33
34
35
36
37
38
39
40
41
42
// ATTENTION:
//
// 1 Please do not add any direct dependencies to other Qt Creator code here.
//   Instead emit signals and let the FakeVimPlugin channel the information to
//   Qt Creator. The idea is to keep this file here in a "clean" state that
//   allows easy reuse with any QTextEdit or QPlainTextEdit derived class.
//
// 2 There are a few auto tests located in ../../../tests/auto/fakevim.
//   Commands that are covered there are marked as "// tested" below.
//
// 3 Some conventions:
43
//
44
45
46
//   Use 1 based line numbers and 0 based column numbers. Even though
//   the 1 based line are not nice it matches vim's and QTextEdit's 'line'
//   concepts.
47
//
48
49
50
//   Do not pass QTextCursor etc around unless really needed. Convert
//   early to  line/column.
//
51
52
53
54
//   A QTextCursor is always between characters, whereas vi's cursor is always
//   over a character. FakeVim interprets the QTextCursor to be over the character
//   to the right of the QTextCursor's position().
//
55
//   A current "region of interest"
hjk's avatar
hjk committed
56
57
//   spans between anchor(), (i.e. the character below anchor()), and
//   position(). The character below position() is not included
58
//   if the last movement command was exclusive (MoveExclusive).
hjk's avatar
hjk committed
59
//
60

61
62
#include "fakevimhandler.h"

63
#include "fakevimactions.h"
64
#include "fakevimtr.h"
65

66
#include <utils/hostosinfo.h>
hjk's avatar
hjk committed
67
#include <utils/qtcassert.h>
68

69
70
71
72
73
74
75
76
77
#include <QDebug>
#include <QFile>
#include <QObject>
#include <QPointer>
#include <QProcess>
#include <QRegExp>
#include <QTextStream>
#include <QTimer>
#include <QStack>
hjk's avatar
hjk committed
78

79
#include <QApplication>
80
#include <QClipboard>
81
82
83
84
85
86
87
88
89
#include <QInputMethodEvent>
#include <QKeyEvent>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QScrollBar>
#include <QTextBlock>
#include <QTextCursor>
#include <QTextDocumentFragment>
#include <QTextEdit>
Friedemann Kleint's avatar
Friedemann Kleint committed
90
#include <QMimeData>
91
#include <QSharedPointer>
hjk's avatar
hjk committed
92

93
#include <algorithm>
94
#include <climits>
95
#include <ctype.h>
96

97
98
99
100
101
102
103
//#define DEBUG_KEY  1
#if DEBUG_KEY
#   define KEY_DEBUG(s) qDebug() << s
#else
#   define KEY_DEBUG(s)
#endif

104
105
//#define DEBUG_UNDO  1
#if DEBUG_UNDO
106
#   define UNDO_DEBUG(s) qDebug() << "REV" << revision() << s
107
108
109
110
#else
#   define UNDO_DEBUG(s)
#endif

111
using namespace Utils;
112
113
114
#ifdef FAKEVIM_STANDALONE
using namespace FakeVim::Internal::Utils;
#endif
hjk's avatar
hjk committed
115
116
117
118

namespace FakeVim {
namespace Internal {

hjk's avatar
hjk committed
119
120
121
122
123
124
///////////////////////////////////////////////////////////////////////
//
// FakeVimHandler
//
///////////////////////////////////////////////////////////////////////

hjk's avatar
hjk committed
125
126
127
128
129
130
131
132
133
134
#define StartOfLine     QTextCursor::StartOfLine
#define EndOfLine       QTextCursor::EndOfLine
#define MoveAnchor      QTextCursor::MoveAnchor
#define KeepAnchor      QTextCursor::KeepAnchor
#define Up              QTextCursor::Up
#define Down            QTextCursor::Down
#define Right           QTextCursor::Right
#define Left            QTextCursor::Left
#define EndOfDocument   QTextCursor::End
#define StartOfDocument QTextCursor::Start
Lukas Holecek's avatar
Lukas Holecek committed
135
#define NextBlock       QTextCursor::NextBlock
hjk's avatar
hjk committed
136

137
138
#define ParagraphSeparator QChar::ParagraphSeparator

139
140
#define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)

141
142
#define MetaModifier     // Use HostOsInfo::controlModifier() instead
#define ControlModifier  // Use HostOsInfo::controlModifier() instead
hjk's avatar
hjk committed
143

hjk's avatar
hjk committed
144
typedef QLatin1String _;
hjk's avatar
hjk committed
145

146
/* Clipboard MIME types used by Vim. */
147
148
static const QString vimMimeText = _("_VIM_TEXT");
static const QString vimMimeTextEncoded = _("_VIMENC_TEXT");
149

hjk's avatar
hjk committed
150
151
using namespace Qt;

hjk's avatar
hjk committed
152
153
/*! A \e Mode represents one of the basic modes of operation of FakeVim.
*/
154

hjk's avatar
hjk committed
155
156
157
enum Mode
{
    InsertMode,
158
    ReplaceMode,
hjk's avatar
hjk committed
159
    CommandMode,
160
    ExMode
hjk's avatar
hjk committed
161
};
hjk's avatar
hjk committed
162

163
164
165
166
167
168
169
170
171
enum BlockInsertMode
{
    NoneBlockInsertMode,
    AppendBlockInsertMode,
    AppendToEndOfLineBlockInsertMode,
    InsertBlockInsertMode,
    ChangeBlockInsertMode
};

hjk's avatar
hjk committed
172
173
174
/*! A \e SubMode is used for things that require one more data item
    and are 'nested' behind a \l Mode.
*/
hjk's avatar
hjk committed
175
176
177
enum SubMode
{
    NoSubMode,
hjk's avatar
hjk committed
178
179
180
181
182
183
184
    ChangeSubMode,       // Used for c
    DeleteSubMode,       // Used for d
    FilterSubMode,       // Used for !
    IndentSubMode,       // Used for =
    RegisterSubMode,     // Used for "
    ShiftLeftSubMode,    // Used for <
    ShiftRightSubMode,   // Used for >
Lukas Holecek's avatar
Lukas Holecek committed
185
186
187
    InvertCaseSubMode,   // Used for g~
    DownCaseSubMode,     // Used for gu
    UpCaseSubMode,       // Used for gU
hjk's avatar
hjk committed
188
189
190
191
    WindowSubMode,       // Used for Ctrl-w
    YankSubMode,         // Used for y
    ZSubMode,            // Used for z
    CapitalZSubMode,     // Used for Z
192
193
    ReplaceSubMode,      // Used for r
    MacroRecordSubMode,  // Used for q
194
195
    MacroExecuteSubMode, // Used for @
    CtrlVSubMode         // Used for Ctrl-v in insert mode
hjk's avatar
hjk committed
196
};
hjk's avatar
hjk committed
197

hjk's avatar
hjk committed
198
199
200
/*! A \e SubSubMode is used for things that require one more data item
    and are 'nested' behind a \l SubMode.
*/
hjk's avatar
hjk committed
201
202
203
enum SubSubMode
{
    NoSubSubMode,
204
205
206
207
208
209
210
211
    FtSubSubMode,          // Used for f, F, t, T.
    MarkSubSubMode,        // Used for m.
    BackTickSubSubMode,    // Used for `.
    TickSubSubMode,        // Used for '.
    TextObjectSubSubMode,  // Used for thing like iw, aW, as etc.
    ZSubSubMode,           // Used for zj, zk
    OpenSquareSubSubMode,  // Used for [{, {(, [z
    CloseSquareSubSubMode, // Used for ]}, ]), ]z
212
213
    SearchSubSubMode,
    CtrlVUnicodeSubSubMode // Used for Ctrl-v based unicode input
hjk's avatar
hjk committed
214
215
};

216
217
218
219
220
enum VisualMode
{
    NoVisualMode,
    VisualCharMode,
    VisualLineMode,
221
    VisualBlockMode
222
223
};

hjk's avatar
hjk committed
224
225
226
227
enum MoveType
{
    MoveExclusive,
    MoveInclusive,
228
    MoveLineWise
hjk's avatar
hjk committed
229
230
};

hjk's avatar
hjk committed
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/*!
    \enum RangeMode

    The \e RangeMode serves as a means to define how the "Range" between
    the \l cursor and the \l anchor position is to be interpreted.

    \value RangeCharMode   Entered by pressing \key v. The range includes
                           all characters between cursor and anchor.
    \value RangeLineMode   Entered by pressing \key V. The range includes
                           all lines between the line of the cursor and
                           the line of the anchor.
    \value RangeLineModeExclusice Like \l RangeLineMode, but keeps one
                           newline when deleting.
    \value RangeBlockMode  Entered by pressing \key Ctrl-v. The range includes
                           all characters with line and column coordinates
                           between line and columns coordinates of cursor and
                           anchor.
    \value RangeBlockAndTailMode Like \l RangeBlockMode, but also includes
                           all characters in the affected lines up to the end
                           of these lines.
*/
252
253
254
255
256

enum EventResult
{
    EventHandled,
    EventUnhandled,
257
    EventCancelled, // Event is handled but a sub mode was cancelled.
258
259
260
    EventPassedToCore
};

261
262
263
264
265
266
267
268
269
270
271
272
273
struct CursorPosition
{
    CursorPosition() : line(-1), column(-1) {}
    CursorPosition(int block, int column) : line(block), column(column) {}
    explicit CursorPosition(const QTextCursor &tc)
        : line(tc.block().blockNumber()), column(tc.positionInBlock()) {}
    CursorPosition(const QTextDocument *document, int position)
    {
        QTextBlock block = document->findBlock(position);
        line = block.blockNumber();
        column = position - block.position();
    }
    bool isValid() const { return line >= 0 && column >= 0; }
274
275
    bool operator>(const CursorPosition &other) const
        { return line > other.line || column > other.column; }
276
277
278
279
280
281
282
283
    bool operator==(const CursorPosition &other) const
        { return line == other.line && column == other.column; }
    bool operator!=(const CursorPosition &other) const { return !operator==(other); }

    int line; // Line in document (from 0, folded lines included).
    int column; // Position on line.
};

284
285
286
287
288
QDebug operator<<(QDebug ts, const CursorPosition &pos)
{
    return ts << "(line: " << pos.line << ", column: " << pos.column << ")";
}

289
class Mark
Lukas Holecek's avatar
Lukas Holecek committed
290
{
291
public:
Lukas Holecek's avatar
Lukas Holecek committed
292
    Mark(const CursorPosition &pos = CursorPosition(), const QString &fileName = QString())
293
        : m_position(pos), m_fileName(fileName) {}
Lukas Holecek's avatar
Lukas Holecek committed
294

295
    bool isValid() const { return m_position.isValid(); }
Lukas Holecek's avatar
Lukas Holecek committed
296
297
298

    bool isLocal(const QString &localFileName) const
    {
299
        return m_fileName.isEmpty() || m_fileName == localFileName;
Lukas Holecek's avatar
Lukas Holecek committed
300
301
    }

302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
    /* Return position of mark within given document.
     * If saved line number is too big, mark position is at the end of document.
     * If line number is in document but column is too big, mark position is at the end of line.
     */
    CursorPosition position(const QTextDocument *document) const
    {
        QTextBlock block = document->findBlockByNumber(m_position.line);
        CursorPosition pos;
        if (block.isValid()) {
            pos.line = m_position.line;
            pos.column = qMax(0, qMin(m_position.column, block.length() - 2));
        } else if (document->isEmpty()) {
            pos.line = 0;
            pos.column = 0;
        } else {
            pos.line = document->blockCount() - 1;
            pos.column = qMax(0, document->lastBlock().length() - 2);
        }
        return pos;
    }

    const QString &fileName() const { return m_fileName; }

325
326
    void setFileName(const QString &fileName) { m_fileName = fileName; }

327
328
329
private:
    CursorPosition m_position;
    QString m_fileName;
Lukas Holecek's avatar
Lukas Holecek committed
330
331
332
333
};
typedef QHash<QChar, Mark> Marks;
typedef QHashIterator<QChar, Mark> MarksIterator;

Lukas Holecek's avatar
Lukas Holecek committed
334
335
struct State
{
336
    State() : revision(-1), position(), marks(), lastVisualMode(NoVisualMode),
337
        lastVisualModeInverted(false) {}
338
339
    State(int revision, const CursorPosition &position, const Marks &marks,
        VisualMode lastVisualMode, bool lastVisualModeInverted) : revision(revision),
340
341
        position(position), marks(marks), lastVisualMode(lastVisualMode),
        lastVisualModeInverted(lastVisualModeInverted) {}
342

343
344
    bool isValid() const { return position.isValid(); }

345
    int revision;
346
    CursorPosition position;
Lukas Holecek's avatar
Lukas Holecek committed
347
    Marks marks;
348
    VisualMode lastVisualMode;
349
    bool lastVisualModeInverted;
Lukas Holecek's avatar
Lukas Holecek committed
350
351
};

352
struct Column
353
{
354
    Column(int p, int l) : physical(p), logical(l) {}
hjk's avatar
hjk committed
355
356
    int physical; // Number of characters in the data.
    int logical; // Column on screen.
357
358
};

359
360
361
362
363
QDebug operator<<(QDebug ts, const Column &col)
{
    return ts << "(p: " << col.physical << ", l: " << col.logical << ")";
}

364
365
366
struct Register
{
    Register() : rangemode(RangeCharMode) {}
hjk's avatar
hjk committed
367
    Register(const QString &c) : contents(c), rangemode(RangeCharMode) {}
368
369
370
371
372
    Register(const QString &c, RangeMode m) : contents(c), rangemode(m) {}
    QString contents;
    RangeMode rangemode;
};

hjk's avatar
hjk committed
373
374
375
376
377
QDebug operator<<(QDebug ts, const Register &reg)
{
    return ts << reg.contents;
}

hjk's avatar
hjk committed
378
379
struct SearchData
{
hjk's avatar
hjk committed
380
381
382
383
384
    SearchData()
    {
        forward = true;
        highlightMatches = true;
    }
hjk's avatar
hjk committed
385
386
387
388
389

    QString needle;
    bool forward;
    bool highlightMatches;
};
390

391
// If string begins with given prefix remove it with trailing spaces and return true.
392
static bool eatString(const char *prefix, QString *str)
393
{
394
    if (!str->startsWith(_(prefix)))
395
        return false;
396
    *str = str->mid(strlen(prefix)).trimmed();
397
398
399
    return true;
}

400
static QRegExp vimPatternToQtPattern(QString needle, bool ignoreCaseOption, bool smartCaseOption)
401
{
402
    /* Transformations (Vim regexp -> QRegExp):
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
     *   \a -> [A-Za-z]
     *   \A -> [^A-Za-z]
     *   \h -> [A-Za-z_]
     *   \H -> [^A-Za-z_]
     *   \l -> [a-z]
     *   \L -> [^a-z]
     *   \o -> [0-7]
     *   \O -> [^0-7]
     *   \u -> [A-Z]
     *   \U -> [^A-Z]
     *   \x -> [0-9A-Fa-f]
     *   \X -> [^0-9A-Fa-f]
     *
     *   \< -> \b
     *   \> -> \b
     *   [] -> \[\]
     *   \= -> ?
     *
     *   (...)  <-> \(...\)
     *   {...}  <-> \{...\}
     *   |      <-> \|
     *   ?      <-> \?
     *   +      <-> \+
     *   \{...} -> {...}
     *
     *   \c - set ignorecase for rest
     *   \C - set noignorecase for rest
     */
431
432
433
    // FIXME: Option smartcase should be used only if search was typed by user.
    bool ignorecase = ignoreCaseOption
        && !(smartCaseOption && needle.contains(QRegExp(_("[A-Z]"))));
434
435
436
437
438
    QString pattern;
    pattern.reserve(2 * needle.size());

    bool escape = false;
    bool brace = false;
439
440
    bool embraced = false;
    bool range = false;
441
442
443
444
    bool curly = false;
    foreach (const QChar &c, needle) {
        if (brace) {
            brace = false;
445
            if (c == QLatin1Char(']')) {
446
447
448
                pattern.append(_("\\[\\]"));
                continue;
            }
449
            pattern.append(QLatin1Char('['));
450
451
            escape = true;
            embraced = true;
452
        }
453
454
455
456
        if (embraced) {
            if (range) {
                QChar c2 = pattern[pattern.size() - 2];
                pattern.remove(pattern.size() - 2, 2);
457
458
                pattern.append(c2.toUpper() + QLatin1Char('-') + c.toUpper());
                pattern.append(c2.toLower() + QLatin1Char('-') + c.toLower());
459
460
461
462
                range = false;
            } else if (escape) {
                escape = false;
                pattern.append(c);
463
            } else if (c == QLatin1Char('\\')) {
464
                escape = true;
465
466
            } else if (c == QLatin1Char(']')) {
                pattern.append(QLatin1Char(']'));
467
                embraced = false;
468
            } else if (c == QLatin1Char('-')) {
469
                range = ignorecase && pattern[pattern.size() - 1].isLetter();
470
                pattern.append(QLatin1Char('-'));
471
            } else if (c.isLetter() && ignorecase) {
Orgad Shaneh's avatar
Orgad Shaneh committed
472
                pattern.append(c.toLower()).append(c.toUpper());
473
474
475
            } else {
                pattern.append(c);
            }
476
477
        } else if (QString::fromLatin1("(){}+|?").indexOf(c) != -1) {
            if (c == QLatin1Char('{')) {
478
                curly = escape;
479
            } else if (c == QLatin1Char('}') && curly) {
480
481
482
483
484
485
486
                curly = false;
                escape = true;
            }

            if (escape)
                escape = false;
            else
487
                pattern.append(QLatin1Char('\\'));
488
489
490
491
            pattern.append(c);
        } else if (escape) {
            // escape expression
            escape = false;
492
            if (c == QLatin1Char('<') || c == QLatin1Char('>'))
493
                pattern.append(_("\\b"));
494
            else if (c == QLatin1Char('a'))
495
                pattern.append(_("[a-zA-Z]"));
496
            else if (c == QLatin1Char('A'))
497
                pattern.append(_("[^a-zA-Z]"));
498
            else if (c == QLatin1Char('h'))
499
                pattern.append(_("[A-Za-z_]"));
500
            else if (c == QLatin1Char('H'))
501
                pattern.append(_("[^A-Za-z_]"));
502
503
504
            else if (c == QLatin1Char('c') || c == QLatin1Char('C'))
                ignorecase = (c == QLatin1Char('c'));
            else if (c == QLatin1Char('l'))
505
                pattern.append(_("[a-z]"));
506
            else if (c == QLatin1Char('L'))
507
                pattern.append(_("[^a-z]"));
508
            else if (c == QLatin1Char('o'))
509
                pattern.append(_("[0-7]"));
510
            else if (c == QLatin1Char('O'))
511
                pattern.append(_("[^0-7]"));
512
            else if (c == QLatin1Char('u'))
513
                pattern.append(_("[A-Z]"));
514
            else if (c == QLatin1Char('U'))
515
                pattern.append(_("[^A-Z]"));
516
            else if (c == QLatin1Char('x'))
517
                pattern.append(_("[0-9A-Fa-f]"));
518
            else if (c == QLatin1Char('X'))
519
                pattern.append(_("[^0-9A-Fa-f]"));
520
            else if (c == QLatin1Char('='))
521
522
                pattern.append(_("?"));
            else
523
                pattern.append(QLatin1Char('\\') + c);
524
525
        } else {
            // unescaped expression
526
            if (c == QLatin1Char('\\'))
527
                escape = true;
528
            else if (c == QLatin1Char('['))
529
530
                brace = true;
            else if (c.isLetter() && ignorecase)
531
                pattern.append(QLatin1Char('[') + c.toLower() + c.toUpper() + QLatin1Char(']'));
532
533
534
535
536
            else
                pattern.append(c);
        }
    }
    if (escape)
537
        pattern.append(QLatin1Char('\\'));
538
    else if (brace)
539
        pattern.append(QLatin1Char('['));
540
541
542
543

    return QRegExp(pattern);
}

544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
static bool afterEndOfLine(const QTextDocument *doc, int position)
{
    return doc->characterAt(position) == ParagraphSeparator
        && doc->findBlock(position).length() > 1;
}

static void searchForward(QTextCursor *tc, QRegExp &needleExp, int *repeat)
{
    const QTextDocument *doc = tc->document();
    const int startPos = tc->position();

    // Search from beginning of line so that matched text is the same.
    tc->movePosition(StartOfLine);

    // forward to current position
    *tc = doc->find(needleExp, *tc);
    while (!tc->isNull() && tc->anchor() < startPos) {
        if (!tc->hasSelection())
            tc->movePosition(Right);
        if (tc->atBlockEnd())
Lukas Holecek's avatar
Lukas Holecek committed
564
            tc->movePosition(NextBlock);
565
566
567
568
569
570
571
572
573
574
575
576
        *tc = doc->find(needleExp, *tc);
    }

    if (tc->isNull())
        return;

    --*repeat;

    while (*repeat > 0) {
        if (!tc->hasSelection())
            tc->movePosition(Right);
        if (tc->atBlockEnd())
Lukas Holecek's avatar
Lukas Holecek committed
577
            tc->movePosition(NextBlock);
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
        *tc = doc->find(needleExp, *tc);
        if (tc->isNull())
            return;
        --*repeat;
    }

    if (!tc->isNull() && afterEndOfLine(doc, tc->anchor()))
        tc->movePosition(Left);
}

static void searchBackward(QTextCursor *tc, QRegExp &needleExp, int *repeat)
{
    // Search from beginning of line so that matched text is the same.
    QTextBlock block = tc->block();
    QString line = block.text();

    int i = line.indexOf(needleExp, 0);
    while (i != -1 && i < tc->positionInBlock()) {
        --*repeat;
        i = line.indexOf(needleExp, i + qMax(1, needleExp.matchedLength()));
        if (i == line.size())
            i = -1;
    }

    if (i == tc->positionInBlock())
        --*repeat;

    while (*repeat > 0) {
        block = block.previous();
        if (!block.isValid())
            break;
        line = block.text();
        i = line.indexOf(needleExp, 0);
        while (i != -1) {
            --*repeat;
            i = line.indexOf(needleExp, i + qMax(1, needleExp.matchedLength()));
            if (i == line.size())
                i = -1;
        }
    }

    if (!block.isValid()) {
        *tc = QTextCursor();
        return;
    }

    i = line.indexOf(needleExp, 0);
    while (*repeat < 0) {
        i = line.indexOf(needleExp, i + qMax(1, needleExp.matchedLength()));
        ++*repeat;
    }
    tc->setPosition(block.position() + i);
630
    tc->setPosition(tc->position() + needleExp.matchedLength(), KeepAnchor);
631
632
}

hluk's avatar
hluk committed
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
// Commands [[, []
static void bracketSearchBackward(QTextCursor *tc, const QString &needleExp, int repeat)
{
    QRegExp re(needleExp);
    QTextCursor tc2 = *tc;
    tc2.setPosition(tc2.position() - 1);
    searchBackward(&tc2, re, &repeat);
    if (repeat <= 1)
        tc->setPosition(tc2.isNull() ? 0 : tc2.position(), KeepAnchor);
}

// Commands ][, ]]
// When ]] is used after an operator, then also stops below a '}' in the first column.
static void bracketSearchForward(QTextCursor *tc, const QString &needleExp, int repeat,
                                 bool searchWithCommand)
{
    QRegExp re(searchWithCommand ? QString(_("^\\}|^\\{")) : needleExp);
    QTextCursor tc2 = *tc;
    tc2.setPosition(tc2.position() + 1);
    searchForward(&tc2, re, &repeat);
    if (repeat <= 1) {
        if (tc2.isNull()) {
            tc->setPosition(tc->document()->characterCount() - 1, KeepAnchor);
        } else {
            tc->setPosition(tc2.position() - 1, KeepAnchor);
            if (searchWithCommand && tc->document()->characterAt(tc->position()).unicode() == '}') {
                QTextBlock block = tc->block().next();
                if (block.isValid())
                    tc->setPosition(block.position(), KeepAnchor);
            }
        }
    }
}

667
668
669
670
671
static bool substituteText(QString *text, QRegExp &pattern, const QString &replacement,
    bool global)
{
    bool substituted = false;
    int pos = 0;
672
    int right = -1;
673
674
675
676
    while (true) {
        pos = pattern.indexIn(*text, pos, QRegExp::CaretAtZero);
        if (pos == -1)
            break;
677
678
679
680
681
682
683
684
685
686
687

        // ensure that substitution is advancing towards end of line
        if (right == text->size() - pos) {
            ++pos;
            if (pos == text->size())
                break;
            continue;
        }

        right = text->size() - pos;

688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
        substituted = true;
        QString matched = text->mid(pos, pattern.cap(0).size());
        QString repl;
        bool escape = false;
        // insert captured texts
        for (int i = 0; i < replacement.size(); ++i) {
            const QChar &c = replacement[i];
            if (escape) {
                escape = false;
                if (c.isDigit()) {
                    if (c.digitValue() <= pattern.captureCount())
                        repl += pattern.cap(c.digitValue());
                } else {
                    repl += c;
                }
            } else {
704
                if (c == QLatin1Char('\\'))
705
                    escape = true;
706
                else if (c == QLatin1Char('&'))
707
708
709
710
711
712
                    repl += pattern.cap(0);
                else
                    repl += c;
            }
        }
        text->replace(pos, matched.size(), repl);
713
        pos += (repl.isEmpty() && matched.isEmpty()) ? 1 : repl.size();
714
715
716
717
718
719
720
721

        if (pos >= text->size() || !global)
            break;
    }

    return substituted;
}

722
723
724
static int findUnescaped(QChar c, const QString &line, int from)
{
    for (int i = from; i < line.size(); ++i) {
725
        if (line.at(i) == c && (i == 0 || line.at(i - 1) != QLatin1Char('\\')))
726
727
728
729
730
            return i;
    }
    return -1;
}

731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
static void setClipboardData(const QString &content, RangeMode mode,
    QClipboard::Mode clipboardMode)
{
    QClipboard *clipboard = QApplication::clipboard();
    char vimRangeMode = mode;

    QByteArray bytes1;
    bytes1.append(vimRangeMode);
    bytes1.append(content.toUtf8());

    QByteArray bytes2;
    bytes2.append(vimRangeMode);
    bytes2.append("utf-8");
    bytes2.append('\0');
    bytes2.append(content.toUtf8());

    QMimeData *data = new QMimeData;
    data->setText(content);
    data->setData(vimMimeText, bytes1);
    data->setData(vimMimeTextEncoded, bytes2);
    clipboard->setMimeData(data, clipboardMode);
}

754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
static QByteArray toLocalEncoding(const QString &text)
{
    return HostOsInfo::isWindowsHost() ? QString(text).replace(_("\n"), _("\r\n")).toLocal8Bit()
                                       : text.toLocal8Bit();
}

static QString fromLocalEncoding(const QByteArray &data)
{
    return HostOsInfo::isWindowsHost() ? QString::fromLocal8Bit(data).replace(_("\n"), _("\r\n"))
                                       : QString::fromLocal8Bit(data);
}

static QString getProcessOutput(const QString &command, const QString &input)
{
    QProcess proc;
    proc.start(command);
    proc.waitForStarted();
    proc.write(toLocalEncoding(input));
    proc.closeWriteChannel();

    // FIXME: Process should be interruptable by user.
    //        Solution is to create a QObject for each process and emit finished state.
    proc.waitForFinished();

    return fromLocalEncoding(proc.readAllStandardOutput());
}

781
782
783
784
785
786
787
static const QMap<QString, int> &vimKeyNames()
{
    static QMap<QString, int> k;
    if (!k.isEmpty())
        return k;

    // FIXME: Should be value of mapleader.
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
    k.insert(_("LEADER"), Key_Backslash);

    k.insert(_("SPACE"), Key_Space);
    k.insert(_("TAB"), Key_Tab);
    k.insert(_("NL"), Key_Return);
    k.insert(_("NEWLINE"), Key_Return);
    k.insert(_("LINEFEED"), Key_Return);
    k.insert(_("LF"), Key_Return);
    k.insert(_("CR"), Key_Return);
    k.insert(_("RETURN"), Key_Return);
    k.insert(_("ENTER"), Key_Return);
    k.insert(_("BS"), Key_Backspace);
    k.insert(_("BACKSPACE"), Key_Backspace);
    k.insert(_("ESC"), Key_Escape);
    k.insert(_("BAR"), Key_Bar);
    k.insert(_("BSLASH"), Key_Backslash);
    k.insert(_("DEL"), Key_Delete);
    k.insert(_("DELETE"), Key_Delete);
    k.insert(_("KDEL"), Key_Delete);
    k.insert(_("UP"), Key_Up);
    k.insert(_("DOWN"), Key_Down);
    k.insert(_("LEFT"), Key_Left);
    k.insert(_("RIGHT"), Key_Right);

    k.insert(_("LT"), Key_Less);
813
    k.insert(_("GT"), Key_Greater);
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867

    k.insert(_("F1"), Key_F1);
    k.insert(_("F2"), Key_F2);
    k.insert(_("F3"), Key_F3);
    k.insert(_("F4"), Key_F4);
    k.insert(_("F5"), Key_F5);
    k.insert(_("F6"), Key_F6);
    k.insert(_("F7"), Key_F7);
    k.insert(_("F8"), Key_F8);
    k.insert(_("F9"), Key_F9);
    k.insert(_("F10"), Key_F10);

    k.insert(_("F11"), Key_F11);
    k.insert(_("F12"), Key_F12);
    k.insert(_("F13"), Key_F13);
    k.insert(_("F14"), Key_F14);
    k.insert(_("F15"), Key_F15);
    k.insert(_("F16"), Key_F16);
    k.insert(_("F17"), Key_F17);
    k.insert(_("F18"), Key_F18);
    k.insert(_("F19"), Key_F19);
    k.insert(_("F20"), Key_F20);

    k.insert(_("F21"), Key_F21);
    k.insert(_("F22"), Key_F22);
    k.insert(_("F23"), Key_F23);
    k.insert(_("F24"), Key_F24);
    k.insert(_("F25"), Key_F25);
    k.insert(_("F26"), Key_F26);
    k.insert(_("F27"), Key_F27);
    k.insert(_("F28"), Key_F28);
    k.insert(_("F29"), Key_F29);
    k.insert(_("F30"), Key_F30);

    k.insert(_("F31"), Key_F31);
    k.insert(_("F32"), Key_F32);
    k.insert(_("F33"), Key_F33);
    k.insert(_("F34"), Key_F34);
    k.insert(_("F35"), Key_F35);

    k.insert(_("INSERT"), Key_Insert);
    k.insert(_("INS"), Key_Insert);
    k.insert(_("KINSERT"), Key_Insert);
    k.insert(_("HOME"), Key_Home);
    k.insert(_("END"), Key_End);
    k.insert(_("PAGEUP"), Key_PageUp);
    k.insert(_("PAGEDOWN"), Key_PageDown);

    k.insert(_("KPLUS"), Key_Plus);
    k.insert(_("KMINUS"), Key_Minus);
    k.insert(_("KDIVIDE"), Key_Slash);
    k.insert(_("KMULTIPLY"), Key_Asterisk);
    k.insert(_("KENTER"), Key_Enter);
    k.insert(_("KPOINT"), Key_Period);
868
869
870
871

    return k;
}

872
static bool isOnlyControlModifier(const Qt::KeyboardModifiers &mods)
873
{
874
    return (mods ^ HostOsInfo::controlModifier()) == Qt::NoModifier;
875
876
}

877

878
879
880
Range::Range()
    : beginPos(-1), endPos(-1), rangemode(RangeCharMode)
{}
881

882
883
884
Range::Range(int b, int e, RangeMode m)
    : beginPos(qMin(b, e)), endPos(qMax(b, e)), rangemode(m)
{}
885

886
QString Range::toString() const
hjk's avatar
hjk committed
887
{
888
    return QString::fromLatin1("%1-%2 (mode: %3)").arg(beginPos).arg(endPos)
889
890
891
        .arg(rangemode);
}

892
893
894
895
896
bool Range::isValid() const
{
    return beginPos >= 0 && endPos >= 0;
}

897
898
899
900
901
902
903
QDebug operator<<(QDebug ts, const Range &range)
{
    return ts << '[' << range.beginPos << ',' << range.endPos << ']';
}


ExCommand::ExCommand(const QString &c, const QString &a, const Range &r)
904
    : cmd(c), hasBang(false), args(a), range(r), count(1)
905
{}
hjk's avatar
hjk committed
906

907
908
909
910
911
bool ExCommand::matches(const QString &min, const QString &full) const
{
    return cmd.startsWith(min) && full.startsWith(cmd);
}

hjk's avatar
hjk committed
912
913
QDebug operator<<(QDebug ts, const ExCommand &cmd)
{
914
    return ts << cmd.cmd << ' ' << cmd.args << ' ' << cmd.range;
hjk's avatar
hjk committed
915
916
}

917
QDebug operator<<(QDebug ts, const QList<QTextEdit::ExtraSelection> &sels)
918
{
919
    foreach (const QTextEdit::ExtraSelection &sel, sels)
920
        ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
921
922
    return ts;
}
923

hjk's avatar
hjk committed
924
QString quoteUnprintable(const QString &ba)
hjk's avatar
hjk committed
925
{
hjk's avatar
hjk committed
926
927
    QString res;
    for (int i = 0, n = ba.size(); i != n; ++i) {
928
929
        const QChar c = ba.at(i);
        const int cc = c.unicode();
hjk's avatar
hjk committed
930
931
        if (c.isPrint())
            res += c;
932
        else if (cc == QLatin1Char('\n'))
hjk's avatar
hjk committed
933
            res += _("<CR>");
hjk's avatar
hjk committed
934
        else
935
            res += QString::fromLatin1("\\x%1").arg(c.unicode(), 2, 16, QLatin1Char('0'));
hjk's avatar
hjk committed
936
937
    }
    return res;
hjk's avatar
hjk committed
938
939
}

940
941
942
943
944
static bool startsWithWhitespace(const QString &str, int col)
{
    QTC_ASSERT(str.size() >= col, return false);
    for (int i = 0; i < col; ++i) {
        uint u = str.at(i).unicode();
945
        if (u != QLatin1Char(' ') && u != QLatin1Char('\t'))
946
947
948
949
950
            return false;
    }
    return true;
}

951
inline QString msgMarkNotSet(const QString &text)
952
{
953
    return Tr::tr("Mark \"%1\" not set.").arg(text);
954
955
}

hjk's avatar
hjk committed
956
class Input
hjk's avatar
hjk committed
957
{
hjk's avatar
hjk committed
958
public:
959
    // Remove some extra "information" on Mac.
960
961
962
963
    static Qt::KeyboardModifiers cleanModifier(Qt::KeyboardModifiers m)
    {
        return m & ~Qt::KeypadModifier;
    }
964

965
966
    Input()
        : m_key(0), m_xkey(0), m_modifiers(0) {}
hjk's avatar
hjk committed
967

968
    explicit Input(QChar x)
969
970
971
972
        : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x)
    {
        if (x.isUpper())
            m_modifiers = Qt::ShiftModifier;
973
974
        else if (x.isLower())
            m_key = x.toUpper().unicode();
975
    }
hjk's avatar
hjk committed
976

977
    Input(int k, Qt::KeyboardModifiers m, const QString &t = QString())
978
        : m_key(k), m_modifiers(cleanModifier(m)), m_text(t)
979
    {
980
981
982
983
984
985
986
        if (m_text.size() == 1) {
            QChar x = m_text.at(0);

            // On Mac, QKeyEvent::text() returns non-empty strings for
            // cursor keys. This breaks some of the logic later on
            // relying on text() being empty for "special" keys.
            // FIXME: Check the real conditions.
987
            if (x.unicode() < ' ')
988
                m_text.clear();
989
            else if (x.isLetter())
990
991
                m_key = x.toUpper().unicode();
        }
992
993

        // Set text only if input is ascii key without control modifier.
994
        if (m_text.isEmpty() && k >= 0 && k <= 0x7f && (m & HostOsInfo::controlModifier()) == 0) {
995
            QChar c = QChar::fromLatin1(k);
996
997
998
999
1000
1001
1002
1003
1004
1005
            if (c.isLetter())
                m_text = QString(isShift() ? c.toUpper() : c);
            else if (!isShift())
                m_text = c;
        }

        // Normalize <S-TAB>.
        if (m_key == Qt::Key_Backtab) {
            m_key = Qt::Key_Tab;
            m_modifiers |= Qt::ShiftModifier;
1006
1007
        }

1008
1009
1010
1011
        // m_xkey is only a cache.
        m_xkey = (m_text.size() == 1 ? m_text.at(0).unicode() : m_key);
    }

1012
1013
1014
1015
1016
    bool isValid() const
    {
        return m_key != 0 || !m_text.isNull();
    }

1017
1018
    bool isDigit() const
    {
hluk's avatar
hluk committed
1019
        return m_xkey >= '0' && m_xkey <= '9';
1020
    }
1021

1022
1023
1024
1025
1026
    bool isKey(int c) const
    {
        return !m_modifiers && m_key == c;
    }

1027
1028
1029
1030
1031
1032
1033
    bool isBackspace() const
    {
        return m_key == Key_Backspace || isControl('h');
    }

    bool isReturn() const
    {
1034
        return m_key == QLatin1Char('\n') || m_key == Key_Return || m_key == Key_Enter;
1035
1036
    }

hjk's avatar
hjk committed
1037
1038
1039
1040
1041
1042
    bool isEscape() const
    {
        return isKey(Key_Escape) || isKey(27) || isControl('c')
            || isControl(Key_BracketLeft);
    }

1043
1044
    bool is(int c) const
    {
1045
        return m_xkey == c && !isControl();
1046
1047
    }

1048
1049
    bool isControl() const
    {
1050
        return isOnlyControlModifier(m_modifiers);
1051
1052
    }

1053
1054
    bool isControl(int c) const
    {
1055
        return isControl()
1056
            && (m_xkey == c || m_xkey + 32 == c || m_xkey + 64 == c || m_xkey + 96 == c);
1057
1058
    }

1059
1060
1061
1062
1063
    bool isShift() const
    {
        return m_modifiers & Qt::ShiftModifier;
    }

1064
1065
    bool isShift(int c) const
    {
1066
        return isShift() && m_xkey == c;
1067
1068
    }

1069
    bool operator<(const Input &a) const
1070
    {
1071
1072
1073
1074
        if (m_key != a.m_key)
            return m_key < a.m_key;
        // Text for some mapped key cannot be determined (e.g. <C-J>) so if text is not set for
        // one of compared keys ignore it.
1075
        if (!m_text.isEmpty() && !a.m_text.isEmpty() && m_text != _(" "))
1076
1077
            return m_text < a.m_text;
        return m_modifiers < a.m_modifiers;
1078
1079
    }

1080
    bool operator==(const Input &a) const
1081
    {
1082
        return !(*this < a || a < *this);
1083
1084
    }

1085
1086
    bool operator!=(const Input &a) const { return !operator==(a); }

1087
1088
    QString text() const { return m_text; }

hjk's avatar
hjk committed
1089
1090
1091
1092
1093
    QChar asChar() const
    {
        return (m_text.size() == 1 ? m_text.at(0) : QChar());
    }

1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
    int toInt(bool *ok, int base) const
    {
        const int uc = asChar().unicode();
        int res;
        if ('0' <= uc && uc <= '9')
            res = uc -'0';
        else if ('a' <= uc && uc <= 'z')
            res = 10 + uc - 'a';
        else if ('A' <= uc && uc <= 'Z')
            res = 10 + uc - 'A';
        else
            res = base;
        *ok = res < base;
        return *ok ? res : 0;
    }

1110
1111
    int key() const { return m_key; }

1112
    Qt::KeyboardModifiers modifiers() const { return m_modifiers; }
1113

1114
    // Return raw character for macro recording or dot command.
1115
1116
1117
    QChar raw() const
    {
        if (m_key == Key_Tab)
1118
            return QLatin1Char('\t');
1119
        if (m_key == Key_Return)
1120
            return QLatin1Char('\n');
1121
1122
1123
        if (m_key == Key_Escape)
            return QChar(27);
        return m_xkey;
1124
1125
    }

1126
1127
    QString toString() const
    {
1128
1129
1130
        if (!m_text.isEmpty())
            return QString(m_text).replace(_("<"), _("<LT>"));

1131
        QString key = vimKeyNames().key(m_key);
hluk's avatar
hluk committed
1132
        bool namedKey = !key.isEmpty();
1133

hluk's avatar
hluk committed
1134
        if (!namedKey) {
1135
1136
            if (m_xkey == '<')
                key = _("<LT>");
1137
1138
            else if (m_xkey == '>')
                key = _("<GT>");
1139
1140
            else
                key = QChar(m_xkey);