├── .gitignore
├── EmacsKeys.kms
├── EmacsKeys.pluginspec
├── README.markdown
├── emacskeys.pro
├── emacskeysactions.cpp
├── emacskeysactions.h
├── emacskeyshandler.cpp
├── emacskeyshandler.h
├── emacskeysoptions.ui
├── emacskeysplugin.cpp
├── emacskeysplugin.h
├── killring.cpp
├── killring.h
├── mark.h
├── markring.cpp
├── markring.h
└── plugins.pro.patch
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | Makefile*
3 | .moc
4 | .obj
5 | .uic
6 |
--------------------------------------------------------------------------------
/EmacsKeys.kms:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
231 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
258 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
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 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
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 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
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 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
--------------------------------------------------------------------------------
/EmacsKeys.pluginspec:
--------------------------------------------------------------------------------
1 |
2 | Felix Berger
3 | (C) 2008-2009 Nokia Corporation
4 | (C) 2010 Felix Berger
5 |
6 | Commercial Usage
7 |
8 | Licensees holding valid Qt Commercial licenses may use this plugin in
9 | accordance with the Qt Commercial License Agreement provided with the
10 | Software or, alternatively, in accordance with the terms contained in
11 | a written agreement between you and Nokia.
12 |
13 | GNU Lesser General Public License Usage
14 |
15 | Alternatively, this plugin may be used under the terms of the GNU Lesser
16 | General Public License version 2.1 as published by the Free Software
17 | Foundation. Please review the following information to
18 | ensure the GNU Lesser General Public License version 2.1 requirements
19 | will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
20 | Emacs Keys
21 | http://www.qtsoftware.com
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | EmacsKeys
2 | =========
3 |
4 | EmacsKeys is a plugin that brings Emacs key bindings and functionality to the
5 | Qt Creator IDE.
6 |
7 | Latest Updates
8 | ==============
9 |
10 | For a version that works with version 2.5.2 of Qt Creator, see repository: https://github.com/mrjames313/emacskeys.
11 |
12 | Features
13 | ========
14 |
15 | EmacsKeys provides the following features:
16 |
17 | * EmacsKeys.kms - A Keyboard Mapping Scheme for Qt Creator that can be
18 | imported in Options -> Environment -> Keyboard. It overrides some of the
19 | standard key bindings used in Qt Creator and replaces them with Emacs
20 | ones: C-s, C-x,s, C-x,C-s, C-x,C-w.
21 |
22 | * Kill ring - the Emacs kill ring allows you to maintain a history of your
23 | clipboards content. Caveat: It only works when text is inserted into it with
24 | C-W, M-w, C-k, M-d and M-Backspace.
25 |
26 | * The following keys work as expected: C-n, C-p, C-a, C-e, C-b, C-f, M-b, M-f,
27 | M-d, M-Backspace, C-d, M-<, M->, C-v, M-v, C-Space, C-k, C-y, M-y, C-w, M-w,
28 | C-l, C-@.
29 |
30 | * C-x,b opens the quick open dialog at the bottom left.
31 |
32 | * C-x,C-b switches to the File System view on the left.
33 |
34 | * M-/ triggers the code completion that is triggered by C-Space normally.
35 |
36 | * Mnemonics are removed from some of the menus to allow conflicting Emacs keys
37 | to work.
38 |
39 | Build Instructions
40 | ==================
41 |
42 | * Download the source of Qt Creator and and checkout the branch with the respective version number. For instance, if you download Qt Creator v2.2.1, checkout branch v2.2.1.
43 | * cd src/plugins/
44 | * git clone git://github.com/fberger/emacskeys.git
45 | * git checkout -b local [origin/v2.2.1|origin/v2.0.1]
46 | * patch -p 3 < emacskeys/plugins.pro.patch
47 | * cd ../../
48 | * qmake && make
49 | * bin/qtcreator
50 | * Load EmacsKeys.kms from Options -> Environment -> Keyboard
51 | * Activate EmacsKeys Plugin
52 |
53 | Credit
54 | ======
55 |
56 | The Emacs Key code is based on FakeVim, (C) 2008-2009 Nokia Corporation.
57 |
58 | License
59 | =======
60 |
61 | The code is licensed under the LGPL version 2.1.
62 |
63 |
--------------------------------------------------------------------------------
/emacskeys.pro:
--------------------------------------------------------------------------------
1 | TEMPLATE = lib
2 | TARGET = EmacsKeys
3 |
4 | # CONFIG += single
5 | include(../../libs/cplusplus/cplusplus.pri)
6 | include(../../qtcreatorplugin.pri)
7 | include(../../plugins/projectexplorer/projectexplorer.pri)
8 | include(../../plugins/coreplugin/coreplugin.pri)
9 | include(../../plugins/texteditor/texteditor.pri)
10 | include(../../plugins/texteditor/cppeditor.pri)
11 | include(../../shared/indenter/indenter.pri)
12 |
13 | # DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII
14 | QT += gui
15 |
16 | SOURCES += \
17 | emacskeysactions.cpp \
18 | emacskeyshandler.cpp \
19 | emacskeysplugin.cpp \
20 | killring.cpp \
21 | markring.cpp
22 |
23 | HEADERS += \
24 | emacskeysactions.h \
25 | emacskeyshandler.h \
26 | emacskeysplugin.h \
27 | mark.h \
28 | markring.h \
29 | killring.h \
30 |
31 |
32 | FORMS += \
33 | emacskeysoptions.ui
34 |
35 | OTHER_FILES += EmacsKeys.pluginspec
36 |
--------------------------------------------------------------------------------
/emacskeysactions.cpp:
--------------------------------------------------------------------------------
1 | /**************************************************************************
2 | **
3 | ** GNU Lesser General Public License Usage
4 | **
5 | ** Alternatively, this file may be used under the terms of the GNU Lesser
6 | ** General Public License version 2.1 as published by the Free Software
7 | ** Foundation and appearing in the file LICENSE.LGPL included in the
8 | ** packaging of this file. Please review the following information to
9 | ** ensure the GNU Lesser General Public License version 2.1 requirements
10 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
11 | **
12 | ** If you are unsure which license is appropriate for your use, please
13 | ** contact the sales department at http://www.qtsoftware.com/contact.
14 | **
15 | **************************************************************************/
16 |
17 | #include "emacskeysactions.h"
18 |
19 | // Please do not add any direct dependencies to other Qt Creator code here.
20 | // Instead emit signals and let the EmacsKeysPlugin channel the information to
21 | // Qt Creator. The idea is to keep this file here in a "clean" state that
22 | // allows easy reuse with any QTextEdit or QPlainTextEdit derived class.
23 |
24 |
25 | #include
26 |
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 |
38 | using namespace Core::Utils;
39 |
40 | ///////////////////////////////////////////////////////////////////////
41 | //
42 | // EmacsKeysSettings
43 | //
44 | ///////////////////////////////////////////////////////////////////////
45 |
46 | namespace EmacsKeys {
47 | namespace Internal {
48 |
49 | EmacsKeysSettings::EmacsKeysSettings()
50 | {}
51 |
52 | EmacsKeysSettings::~EmacsKeysSettings()
53 | {
54 | qDeleteAll(m_items);
55 | }
56 |
57 | void EmacsKeysSettings::insertItem(int code, SavedAction *item,
58 | const QString &longName, const QString &shortName)
59 | {
60 | QTC_ASSERT(!m_items.contains(code), qDebug() << code << item->toString(); return);
61 | m_items[code] = item;
62 | if (!longName.isEmpty()) {
63 | m_nameToCode[longName] = code;
64 | m_codeToName[code] = longName;
65 | }
66 | if (!shortName.isEmpty()) {
67 | m_nameToCode[shortName] = code;
68 | }
69 | }
70 |
71 | void EmacsKeysSettings::readSettings(QSettings *settings)
72 | {
73 | foreach (SavedAction *item, m_items)
74 | item->readSettings(settings);
75 | }
76 |
77 | void EmacsKeysSettings::writeSettings(QSettings *settings)
78 | {
79 | foreach (SavedAction *item, m_items)
80 | item->writeSettings(settings);
81 | }
82 |
83 | SavedAction *EmacsKeysSettings::item(int code)
84 | {
85 | QTC_ASSERT(m_items.value(code, 0), qDebug() << "CODE: " << code; return 0);
86 | return m_items.value(code, 0);
87 | }
88 |
89 | SavedAction *EmacsKeysSettings::item(const QString &name)
90 | {
91 | return m_items.value(m_nameToCode.value(name, -1), 0);
92 | }
93 |
94 | EmacsKeysSettings *theEmacsKeysSettings()
95 | {
96 | static EmacsKeysSettings *instance = 0;
97 | if (instance)
98 | return instance;
99 |
100 | instance = new EmacsKeysSettings;
101 |
102 | SavedAction *item = 0;
103 |
104 | const QString group = QLatin1String("EmacsKeys");
105 | item = new SavedAction(instance);
106 | item->setText(QCoreApplication::translate("EmacsKeys::Internal", "Toggle vim-style editing"));
107 | item->setSettingsKey(group, QLatin1String("UseEmacsKeys"));
108 | item->setCheckable(true);
109 | instance->insertItem(ConfigUseEmacsKeys, item);
110 |
111 | item = new SavedAction(instance);
112 | item->setDefaultValue(false);
113 | item->setSettingsKey(group, QLatin1String("StartOfLine"));
114 | item->setCheckable(true);
115 | instance->insertItem(ConfigStartOfLine, item, QLatin1String("startofline"), QLatin1String("sol"));
116 |
117 | item = new SavedAction(instance);
118 | item->setDefaultValue(8);
119 | item->setSettingsKey(group, QLatin1String("TabStop"));
120 | instance->insertItem(ConfigTabStop, item, QLatin1String("tabstop"), QLatin1String("ts"));
121 |
122 | item = new SavedAction(instance);
123 | item->setDefaultValue(false);
124 | item->setSettingsKey(group, QLatin1String("SmartTab"));
125 | instance->insertItem(ConfigSmartTab, item, QLatin1String("smarttab"), QLatin1String("sta"));
126 |
127 | item = new SavedAction(instance);
128 | item->setDefaultValue(true);
129 | item->setSettingsKey(group, QLatin1String("HlSearch"));
130 | item->setCheckable(true);
131 | instance->insertItem(ConfigHlSearch, item, QLatin1String("hlsearch"), QLatin1String("hls"));
132 |
133 | item = new SavedAction(instance);
134 | item->setDefaultValue(8);
135 | item->setSettingsKey(group, QLatin1String("ShiftWidth"));
136 | instance->insertItem(ConfigShiftWidth, item, QLatin1String("shiftwidth"), QLatin1String("sw"));
137 |
138 | item = new SavedAction(instance);
139 | item->setDefaultValue(false);
140 | item->setSettingsKey(group, QLatin1String("ExpandTab"));
141 | item->setCheckable(true);
142 | instance->insertItem(ConfigExpandTab, item, QLatin1String("expandtab"), QLatin1String("et"));
143 |
144 | item = new SavedAction(instance);
145 | item->setDefaultValue(false);
146 | item->setSettingsKey(group, QLatin1String("AutoIndent"));
147 | item->setCheckable(true);
148 | instance->insertItem(ConfigAutoIndent, item, QLatin1String("autoindent"), QLatin1String("ai"));
149 |
150 | item = new SavedAction(instance);
151 | item->setDefaultValue(true);
152 | item->setSettingsKey(group, QLatin1String("IncSearch"));
153 | item->setCheckable(true);
154 | instance->insertItem(ConfigIncSearch, item, QLatin1String("incsearch"), QLatin1String("is"));
155 |
156 | item = new SavedAction(instance);
157 | item->setDefaultValue(QLatin1String("indent,eol,start"));
158 | item->setSettingsKey(group, QLatin1String("Backspace"));
159 | instance->insertItem(ConfigBackspace, item, QLatin1String("backspace"), QLatin1String("bs"));
160 |
161 | item = new SavedAction(instance);
162 | item->setText(QCoreApplication::translate("EmacsKeys::Internal", "EmacsKeys properties..."));
163 | instance->insertItem(SettingsDialog, item);
164 |
165 | return instance;
166 | }
167 |
168 | SavedAction *theEmacsKeysSetting(int code)
169 | {
170 | return theEmacsKeysSettings()->item(code);
171 | }
172 |
173 | } // namespace Internal
174 | } // namespace EmacsKeys
175 |
--------------------------------------------------------------------------------
/emacskeysactions.h:
--------------------------------------------------------------------------------
1 | /**************************************************************************
2 | **
3 | ** GNU Lesser General Public License Usage
4 | **
5 | ** Alternatively, this file may be used under the terms of the GNU Lesser
6 | ** General Public License version 2.1 as published by the Free Software
7 | ** Foundation and appearing in the file LICENSE.LGPL included in the
8 | ** packaging of this file. Please review the following information to
9 | ** ensure the GNU Lesser General Public License version 2.1 requirements
10 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
11 | **
12 | ** If you are unsure which license is appropriate for your use, please
13 | ** contact the sales department at http://www.qtsoftware.com/contact.
14 | **
15 | **************************************************************************/
16 |
17 | #ifndef EMACSKEYS_ACTIONS_H
18 | #define EMACSKEYS_ACTIONS_H
19 |
20 | #include
21 |
22 | #include
23 | #include
24 | #include
25 |
26 | namespace EmacsKeys {
27 | namespace Internal {
28 |
29 | enum EmacsKeysSettingsCode
30 | {
31 | ConfigUseEmacsKeys,
32 | ConfigStartOfLine,
33 | ConfigHlSearch,
34 | ConfigTabStop,
35 | ConfigSmartTab,
36 | ConfigShiftWidth,
37 | ConfigExpandTab,
38 | ConfigAutoIndent,
39 | ConfigIncSearch,
40 |
41 | // indent allow backspacing over autoindent
42 | // eol allow backspacing over line breaks (join lines)
43 | // start allow backspacing over the start of insert; CTRL-W and CTRL-U
44 | // stop once at the start of insert.
45 | ConfigBackspace,
46 |
47 | // other actions
48 | SettingsDialog,
49 | };
50 |
51 | class EmacsKeysSettings : public QObject
52 | {
53 | public:
54 | EmacsKeysSettings();
55 | ~EmacsKeysSettings();
56 | void insertItem(int code, Core::Utils::SavedAction *item,
57 | const QString &longname = QString(),
58 | const QString &shortname = QString());
59 |
60 | Core::Utils::SavedAction *item(int code);
61 | Core::Utils::SavedAction *item(const QString &name);
62 |
63 | void readSettings(QSettings *settings);
64 | void writeSettings(QSettings *settings);
65 |
66 | private:
67 | QHash m_items;
68 | QHash m_nameToCode;
69 | QHash m_codeToName;
70 | };
71 |
72 | EmacsKeysSettings *theEmacsKeysSettings();
73 | Core::Utils::SavedAction *theEmacsKeysSetting(int code);
74 |
75 | } // namespace Internal
76 | } // namespace EmacsKeys
77 |
78 | #endif // EMACSKEYS_ACTTIONS_H
79 |
--------------------------------------------------------------------------------
/emacskeyshandler.cpp:
--------------------------------------------------------------------------------
1 | /**************************************************************************
2 | **
3 | ** GNU Lesser General Public License Usage
4 | **
5 | ** Alternatively, this file may be used under the terms of the GNU Lesser
6 | ** General Public License version 2.1 as published by the Free Software
7 | ** Foundation and appearing in the file LICENSE.LGPL included in the
8 | ** packaging of this file. Please review the following information to
9 | ** ensure the GNU Lesser General Public License version 2.1 requirements
10 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
11 | **
12 | ** If you are unsure which license is appropriate for your use, please
13 | ** contact the sales department at http://www.qtsoftware.com/contact.
14 | **
15 | **************************************************************************/
16 |
17 | #include "emacskeyshandler.h"
18 |
19 | //
20 | // ATTENTION:
21 | //
22 | // 1 Please do not add any direct dependencies to other Qt Creator code here.
23 | // Instead emit signals and let the EmacsKeysPlugin channel the information to
24 | // Qt Creator. The idea is to keep this keyfile here in a "clean" state that
25 | // allows easy reuse with any QTextEdit or QPlainTextEdit derived class.
26 | //
27 | // 2 There are a few auto tests located in ../../../tests/auto/emacsKeys.
28 | // Commands that are covered there are marked as "// tested" below.
29 | //
30 | // 3 Some conventions:
31 | //
32 | // Use 1 based line numbers and 0 based column numbers. Even though
33 | // the 1 based line are not nice it matches vim's and QTextEdit's 'line'
34 | // concepts.
35 | //
36 | // Do not pass QTextCursor etc around unless really needed. Convert
37 | // early to line/column.
38 | //
39 | // There is always a "current" cursor (m_tc). A current "region of interest"
40 | // spans between m_anchor (== anchor()) and m_tc.position() (== position())
41 | // The value of m_tc.anchor() is not used.
42 | //
43 |
44 | #include
45 |
46 | #include
47 | #include
48 | #include
49 | #include
50 | #include
51 | #include
52 | #include
53 | #include
54 | #include
55 |
56 | #include
57 | #include
58 | #include
59 | #include
60 | #include
61 | #include
62 | #include
63 | #include
64 | #include
65 | #include
66 |
67 | #include "markring.h"
68 | #include "killring.h"
69 |
70 | #define DEBUG_KEY 1
71 | #if DEBUG_KEY
72 | # define KEY_DEBUG(s) qDebug() << s
73 | #else
74 | # define KEY_DEBUG(s)
75 | #endif
76 |
77 | //#define DEBUG_UNDO 1
78 | #if DEBUG_UNDO
79 | # define UNDO_DEBUG(s) qDebug() << << m_tc.document()->revision() << s
80 | #else
81 | # define UNDO_DEBUG(s)
82 | #endif
83 |
84 | using namespace Core::Utils;
85 |
86 | namespace EmacsKeys {
87 | namespace Internal {
88 |
89 | ///////////////////////////////////////////////////////////////////////
90 | //
91 | // EmacsKeysHandler
92 | //
93 | ///////////////////////////////////////////////////////////////////////
94 |
95 | #define StartOfLine QTextCursor::StartOfLine
96 | #define EndOfLine QTextCursor::EndOfLine
97 | #define MoveAnchor QTextCursor::MoveAnchor
98 | #define KeepAnchor QTextCursor::KeepAnchor
99 | #define Up QTextCursor::Up
100 | #define Down QTextCursor::Down
101 | #define Right QTextCursor::Right
102 | #define Left QTextCursor::Left
103 | #define EndOfDocument QTextCursor::End
104 | #define StartOfDocument QTextCursor::Start
105 |
106 | #define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)
107 | #define EDITOR_WIDGET m_textedit ? qobject_cast(m_textedit) : qobject_cast(m_plaintextedit)
108 |
109 | const int ParagraphSeparator = 0x00002029;
110 |
111 | using namespace Qt;
112 |
113 |
114 | enum Mode
115 | {
116 | InsertMode,
117 | CommandMode,
118 | ExMode,
119 | SearchForwardMode,
120 | SearchBackwardMode,
121 | };
122 |
123 | enum SubMode
124 | {
125 | NoSubMode,
126 | ChangeSubMode, // used for c
127 | DeleteSubMode, // used for d
128 | FilterSubMode, // used for !
129 | IndentSubMode, // used for =
130 | RegisterSubMode, // used for "
131 | ReplaceSubMode, // used for R and r
132 | ShiftLeftSubMode, // used for <
133 | ShiftRightSubMode, // used for >
134 | WindowSubMode, // used for Ctrl-w
135 | YankSubMode, // used for y
136 | ZSubMode, // used for z
137 | CapitalZSubMode // used for Z
138 | };
139 |
140 | enum SubSubMode
141 | {
142 | // typically used for things that require one more data item
143 | // and are 'nested' behind a mode
144 | NoSubSubMode,
145 | FtSubSubMode, // used for f, F, t, T
146 | MarkSubSubMode, // used for m
147 | BackTickSubSubMode, // used for `
148 | TickSubSubMode, // used for '
149 | };
150 |
151 | enum VisualMode
152 | {
153 | NoVisualMode,
154 | VisualCharMode,
155 | VisualLineMode,
156 | VisualBlockMode,
157 | };
158 |
159 | enum MoveType
160 | {
161 | MoveExclusive,
162 | MoveInclusive,
163 | MoveLineWise,
164 | };
165 |
166 | QDebug &operator<<(QDebug &ts, const QList &sels)
167 | {
168 | foreach (QTextEdit::ExtraSelection sel, sels)
169 | ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
170 | return ts;
171 | }
172 |
173 | QString quoteUnprintable(const QString &ba)
174 | {
175 | QString res;
176 | for (int i = 0, n = ba.size(); i != n; ++i) {
177 | QChar c = ba.at(i);
178 | if (c.isPrint())
179 | res += c;
180 | else
181 | res += QString("\\x%1").arg(c.unicode(), 2, 16);
182 | }
183 | return res;
184 | }
185 |
186 | enum EventResult
187 | {
188 | EventHandled,
189 | EventUnhandled,
190 | EventPassedToCore
191 | };
192 |
193 | class EmacsKeysHandler::Private
194 | {
195 | public:
196 | Private(EmacsKeysHandler *parent, QWidget *widget);
197 |
198 | EventResult handleEvent(QKeyEvent *ev);
199 | bool wantsOverride(QKeyEvent *ev);
200 | void handleCommand(const QString &cmd); // sets m_tc + handleExCommand
201 | void handleExCommand(const QString &cmd);
202 |
203 | void installEventFilter();
204 | void setupWidget();
205 | void restoreWidget();
206 |
207 | friend class EmacsKeysHandler;
208 | static int shift(int key) { return key + 32; }
209 | static int control(int key) { return key + 256; }
210 |
211 | void init();
212 | EventResult handleKey(int key, int unmodified, const QString &text);
213 | EventResult handleInsertMode(int key, int unmodified, const QString &text);
214 | EventResult handleCommandMode(int key, int unmodified, const QString &text);
215 | EventResult handleRegisterMode(int key, int unmodified, const QString &text);
216 | EventResult handleMiniBufferModes(int key, int unmodified, const QString &text);
217 | bool exactMatch(int key, const QKeySequence& keySequence);
218 | void finishMovement(const QString &text = QString());
219 | void search(const QString &needle, bool forward);
220 | void highlightMatches(const QString &needle);
221 |
222 |
223 | void yankPop(QWidget* view);
224 | void setMark();
225 | void exchangeDotAndMark();
226 | void popToMark();
227 | void copy();
228 | void cut();
229 | void yank();
230 | void killLine();
231 | void killWord();
232 | void backwardKillWord();
233 | /*
234 | void charactersInserted(int line, int column, const QString& text);
235 | */
236 |
237 | int mvCount() const { return m_mvcount.isEmpty() ? 1 : m_mvcount.toInt(); }
238 | int opCount() const { return m_opcount.isEmpty() ? 1 : m_opcount.toInt(); }
239 | int count() const { return mvCount() * opCount(); }
240 | int leftDist() const { return m_tc.position() - m_tc.block().position(); }
241 | int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
242 | bool atEndOfLine() const
243 | { return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
244 |
245 | int lastPositionInDocument() const;
246 | int firstPositionInLine(int line) const; // 1 based line, 0 based pos
247 | int lastPositionInLine(int line) const; // 1 based line, 0 based pos
248 | int lineForPosition(int pos) const; // 1 based line, 0 based pos
249 |
250 | // all zero-based counting
251 | int cursorLineOnScreen() const;
252 | int linesOnScreen() const;
253 | int columnsOnScreen() const;
254 | int cursorLineInDocument() const;
255 | int cursorColumnInDocument() const;
256 | int linesInDocument() const;
257 | void scrollToLineInDocument(int line);
258 | void scrollUp(int count);
259 | void scrollDown(int count) { scrollUp(-count); }
260 |
261 | // helper functions for indenting
262 | bool isElectricCharacter(QChar c) const
263 | { return c == '{' || c == '}' || c == '#'; }
264 | void indentRegion(QChar lastTyped = QChar());
265 | void shiftRegionLeft(int repeat = 1);
266 | void shiftRegionRight(int repeat = 1);
267 |
268 | void moveToFirstNonBlankOnLine();
269 | void moveToTargetColumn();
270 | void setTargetColumn() {
271 | m_targetColumn = leftDist();
272 | //qDebug() << "TARGET: " << m_targetColumn;
273 | }
274 | void moveToNextWord(bool simple);
275 | void moveToMatchingParanthesis();
276 | void moveToWordBoundary(bool simple, bool forward);
277 |
278 | // to reduce line noise
279 | void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); }
280 | void moveToStartOfLine();
281 | void moveToEndOfLine();
282 | void moveUp(int n = 1) { moveDown(-n); }
283 | void moveDown(int n = 1); // { m_tc.movePosition(Down, MoveAnchor, n); }
284 | void moveRight(int n = 1) { m_tc.movePosition(Right, MoveAnchor, n); }
285 | void moveLeft(int n = 1) { m_tc.movePosition(Left, MoveAnchor, n); }
286 | void setAnchor() { m_anchor = m_tc.position(); }
287 | void setAnchor(int position) { m_anchor = position; }
288 | void setPosition(int position) { m_tc.setPosition(position, MoveAnchor); }
289 |
290 | void handleFfTt(int key);
291 |
292 | // helper function for handleExCommand. return 1 based line index.
293 | int readLineCode(QString &cmd);
294 | void selectRange(int beginLine, int endLine);
295 |
296 | void enterInsertMode();
297 | void enterCommandMode();
298 | void enterExMode();
299 | void showRedMessage(const QString &msg);
300 | void showBlackMessage(const QString &msg);
301 | void notImplementedYet();
302 | void updateMiniBuffer();
303 | void updateSelection();
304 | QWidget *editor() const;
305 | QChar characterAtCursor() const
306 | { return m_tc.document()->characterAt(m_tc.position()); }
307 | void beginEditBlock() { UNDO_DEBUG("BEGIN EDIT BLOCK"); m_tc.beginEditBlock(); }
308 | void endEditBlock() { UNDO_DEBUG("END EDIT BLOCK"); m_tc.endEditBlock(); }
309 | void joinPreviousEditBlock() { UNDO_DEBUG("JOIN EDIT BLOCK"); m_tc.joinPreviousEditBlock(); }
310 |
311 | public:
312 | QTextEdit *m_textedit;
313 | QPlainTextEdit *m_plaintextedit;
314 | bool m_wasReadOnly; // saves read-only state of document
315 |
316 | EmacsKeysHandler *q;
317 | Mode m_mode;
318 | bool m_passing; // let the core see the next event
319 | SubMode m_submode;
320 | SubSubMode m_subsubmode;
321 | int m_subsubdata;
322 | QString m_input;
323 | QTextCursor m_tc;
324 | QTextCursor m_oldTc; // copy from last event to check for external changes
325 | int m_anchor;
326 | QHash m_registers;
327 | int m_register;
328 | QString m_mvcount;
329 | QString m_opcount;
330 | MoveType m_moveType;
331 | MarkRing markRing;
332 |
333 | bool m_fakeEnd;
334 |
335 | bool isSearchMode() const
336 | { return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; }
337 | int m_gflag; // whether current command started with 'g'
338 |
339 | QString m_commandBuffer;
340 | QString m_currentFileName;
341 | QString m_currentMessage;
342 |
343 | bool m_lastSearchForward;
344 | QString m_lastInsertion;
345 |
346 | QString removeSelectedText();
347 | int anchor() const { return m_anchor; }
348 | int position() const { return m_tc.position(); }
349 | QString selectedText() const;
350 |
351 | // undo handling
352 | void undo();
353 | void redo();
354 | QMap m_undoCursorPosition; // revision -> position
355 |
356 | // extra data for '.'
357 | void replay(const QString &text, int count);
358 | void setDotCommand(const QString &cmd) { m_dotCommand = cmd; }
359 | void setDotCommand(const QString &cmd, int n) { m_dotCommand = cmd.arg(n); }
360 | QString m_dotCommand;
361 | bool m_inReplay; // true if we are executing a '.'
362 |
363 | // extra data for ';'
364 | QString m_semicolonCount;
365 | int m_semicolonType; // 'f', 'F', 't', 'T'
366 | int m_semicolonKey;
367 |
368 | // history for '/'
369 | QString lastSearchString() const;
370 | static QStringList m_searchHistory;
371 | int m_searchHistoryIndex;
372 |
373 | // history for ':'
374 | static QStringList m_commandHistory;
375 | int m_commandHistoryIndex;
376 |
377 | // visual line mode
378 | void enterVisualMode(VisualMode visualMode);
379 | void leaveVisualMode();
380 | VisualMode m_visualMode;
381 |
382 | // marks as lines
383 | QHash m_marks;
384 | QString m_oldNeedle;
385 |
386 | // vi style configuration
387 | QVariant config(int code) const { return theEmacsKeysSetting(code)->value(); }
388 | bool hasConfig(int code) const { return config(code).toBool(); }
389 | bool hasConfig(int code, const char *value) const // FIXME
390 | { return config(code).toString().contains(value); }
391 |
392 | // for restoring cursor position
393 | int m_savedYankPosition;
394 | int m_targetColumn;
395 |
396 | int m_cursorWidth;
397 |
398 | // auto-indent
399 | void insertAutomaticIndentation(bool goingDown);
400 | bool removeAutomaticIndentation(); // true if something removed
401 | // number of autoindented characters
402 | int m_justAutoIndented;
403 | void handleStartOfLine();
404 |
405 | void recordJump();
406 | void recordNewUndo();
407 | QList m_jumpListUndo;
408 | QList m_jumpListRedo;
409 |
410 | QList m_searchSelections;
411 |
412 | int yankEndPosition;
413 | int yankStartPosition;
414 | };
415 |
416 | QStringList EmacsKeysHandler::Private::m_searchHistory;
417 | QStringList EmacsKeysHandler::Private::m_commandHistory;
418 |
419 | EmacsKeysHandler::Private::Private(EmacsKeysHandler *parent, QWidget *widget)
420 | {
421 | q = parent;
422 | m_textedit = qobject_cast(widget);
423 | m_plaintextedit = qobject_cast(widget);
424 | init();
425 | }
426 |
427 | void EmacsKeysHandler::Private::init()
428 | {
429 | m_mode = CommandMode;
430 | m_submode = NoSubMode;
431 | m_subsubmode = NoSubSubMode;
432 | m_passing = false;
433 | m_fakeEnd = false;
434 | m_lastSearchForward = true;
435 | m_register = '"';
436 | m_gflag = false;
437 | m_visualMode = NoVisualMode;
438 | m_targetColumn = 0;
439 | m_moveType = MoveInclusive;
440 | m_anchor = 0;
441 | m_savedYankPosition = 0;
442 | m_cursorWidth = EDITOR(cursorWidth());
443 | m_inReplay = false;
444 | m_justAutoIndented = 0;
445 | }
446 |
447 | bool EmacsKeysHandler::Private::wantsOverride(QKeyEvent *ev)
448 | {
449 | const int key = ev->key();
450 | const int mods = ev->modifiers();
451 | KEY_DEBUG("SHORTCUT OVERRIDE" << key << " PASSING: " << m_passing);
452 |
453 | if (key == Key_Escape) {
454 | // Not sure this feels good. People often hit Esc several times
455 | if (m_visualMode == NoVisualMode && m_mode == CommandMode)
456 | return false;
457 | return true;
458 | }
459 |
460 | // We are interested in overriding most Ctrl key combinations
461 | if (mods == Qt::ControlModifier && key >= Key_A && key <= Key_Z && key != Key_X && key != Key_R && key != Key_S) {
462 | // Ctrl-K is special as it is the Core's default notion of QuickOpen
463 | KEY_DEBUG(" NOT PASSING CTRL KEY");
464 | //updateMiniBuffer();
465 | return true;
466 | }
467 |
468 | // Let other shortcuts trigger
469 | return false;
470 | }
471 |
472 | bool EmacsKeysHandler::Private::exactMatch(int key, const QKeySequence& keySequence)
473 | {
474 | return QKeySequence(key).matches(keySequence) == QKeySequence::ExactMatch;
475 | }
476 |
477 |
478 | void EmacsKeysHandler::Private::yankPop(QWidget* view)
479 | {
480 | qDebug() << "yankPop called " << endl;
481 | if (KillRing::instance()->currentYankView() != view) {
482 | qDebug() << "the last previous yank was not in this view" << endl;
483 | // generate beep and return
484 | QApplication::beep();
485 | return;
486 | }
487 |
488 | int position = m_tc.position();
489 | if (position != yankEndPosition) {
490 | qDebug() << "Cursor has been moved in the meantime" << endl;
491 | qDebug() << "yank end position " << yankEndPosition << endl;
492 | QApplication::beep();
493 | return;
494 | }
495 |
496 | QString next(KillRing::instance()->next());
497 | if (!next.isEmpty()) {
498 | qDebug() << "yanking " << next << endl;
499 | beginEditBlock();
500 | m_tc.setPosition(yankStartPosition, QTextCursor::KeepAnchor);
501 | m_tc.removeSelectedText();
502 | m_tc.insertText(next);
503 | yankEndPosition = m_tc.position();
504 | endEditBlock();
505 | }
506 | else {
507 | qDebug() << "killring empty" << endl;
508 | QApplication::beep();
509 | }
510 | }
511 |
512 |
513 |
514 | void EmacsKeysHandler::Private::setMark()
515 | {
516 | qDebug() << "set mark" << endl;
517 | markRing.addMark(m_tc.position());
518 | }
519 |
520 |
521 | void EmacsKeysHandler::Private::exchangeDotAndMark()
522 | {
523 | int position = m_tc.position();
524 | markRing.addMark(position);
525 | popToMark();
526 | }
527 |
528 | void EmacsKeysHandler::Private::popToMark()
529 | {
530 | qDebug() << "pop mark" << endl;
531 | Mark mark(markRing.getPreviousMark());
532 | if (mark.valid) {
533 | m_tc.setPosition(mark.position);
534 | }
535 | else {
536 | QApplication::beep();
537 | }
538 | }
539 |
540 | void EmacsKeysHandler::Private::copy()
541 | {
542 | qDebug() << "emacs copy" << endl;
543 | Mark mark(markRing.getMostRecentMark());
544 | if (mark.valid) {
545 | beginEditBlock();
546 | int position = m_tc.position();
547 | m_tc.setPosition(mark.position, QTextCursor::KeepAnchor);
548 | QApplication::clipboard()->setText(m_tc.selectedText());
549 | m_tc.clearSelection();
550 | m_tc.setPosition(position);
551 | endEditBlock();
552 | }
553 | else {
554 | QApplication::beep();
555 | }
556 | }
557 |
558 | void EmacsKeysHandler::Private::cut()
559 | {
560 | qDebug() << "emacs cut" << endl;
561 | Mark mark(markRing.getMostRecentMark());
562 | if (mark.valid) {
563 | beginEditBlock();
564 | m_tc.setPosition(mark.position, QTextCursor::KeepAnchor);
565 | QApplication::clipboard()->setText(m_tc.selectedText());
566 | m_tc.removeSelectedText();
567 | endEditBlock();
568 | }
569 | else {
570 | QApplication::beep();
571 | }
572 | }
573 |
574 | void EmacsKeysHandler::Private::yank()
575 | {
576 | qDebug() << "emacs yank" << endl;
577 | int position = m_tc.position();
578 | yankStartPosition = position;
579 | EDITOR(paste());
580 | yankEndPosition = m_tc.position();
581 | if (position != yankEndPosition) {
582 | KillRing::instance()->setCurrentYankView(EDITOR_WIDGET);
583 | }
584 | }
585 |
586 | void EmacsKeysHandler::Private::killLine()
587 | {
588 | qDebug() << "kill line" << endl;
589 | beginEditBlock();
590 | int position = m_tc.position();
591 | qDebug() << "current position " << position << endl;
592 | m_tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
593 | if (position == m_tc.position()) {
594 | qDebug() << "at line end" << endl;
595 | // at the end of the line, just delete new line character
596 | m_tc.deleteChar();
597 | } else {
598 | qDebug() << "invoke cut" << endl;
599 | QApplication::clipboard()->setText(m_tc.selectedText());
600 | m_tc.removeSelectedText();
601 | }
602 | endEditBlock();
603 | }
604 |
605 | void EmacsKeysHandler::Private::killWord()
606 | {
607 | qDebug() << "kill word" << endl;
608 | int position = m_tc.position();
609 | qDebug() << "current position " << position << endl;
610 | beginEditBlock();
611 | m_tc.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor);
612 | if (position != m_tc.position()) {
613 | qDebug() << "invoke cut" << endl;
614 | QApplication::clipboard()->setText(m_tc.selectedText());
615 | m_tc.removeSelectedText();
616 | } else {
617 | QApplication::beep();
618 | }
619 | endEditBlock();
620 | }
621 |
622 | void EmacsKeysHandler::Private::backwardKillWord()
623 | {
624 | qDebug() << "backwards kill word" << endl;
625 | int position = m_tc.position();
626 | qDebug() << "current position " << position << endl;
627 | beginEditBlock();
628 | m_tc.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
629 | if (position != m_tc.position()) {
630 | qDebug() << "invoke cut" << endl;
631 | QApplication::clipboard()->setText(m_tc.selectedText());
632 | m_tc.removeSelectedText();
633 | } else {
634 | QApplication::beep();
635 | }
636 | endEditBlock();
637 | }
638 | /*
639 |
640 | void EmacsKeysHandler::Private::charactersInserted(int l, int c, const QString& text)
641 | {
642 | qDebug() << "characters inserted " << text << " " << l << " " << c << endl;
643 | KillRing::instance()->setCurrentYankView(view);
644 | startLine = l;
645 | startColumn = c;
646 | KTextEditor::viewCursorInterface(view)->cursorPositionReal(&endLine,
647 | &endColumn);
648 | }
649 |
650 | */
651 |
652 | EventResult EmacsKeysHandler::Private::handleEvent(QKeyEvent *ev)
653 | {
654 | int key = ev->key();
655 | const int mods = ev->modifiers();
656 | QKeySequence keySequence(ev->key() + ev->modifiers());
657 |
658 | qDebug() << "sequence: " << keySequence << endl;
659 |
660 | if (key == Key_Shift || key == Key_Alt || key == Key_Control
661 | || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
662 | {
663 | KEY_DEBUG("PLAIN MODIFIER");
664 | return EventUnhandled;
665 | }
666 |
667 | // Fake "End of line"
668 | m_tc = EDITOR(textCursor());
669 |
670 | if (m_tc.position() != m_oldTc.position())
671 | setTargetColumn();
672 |
673 | m_tc.setVisualNavigation(true);
674 |
675 | if (m_fakeEnd)
676 | moveRight();
677 |
678 | if ((mods & Qt::ControlModifier) != 0) {
679 | key += 256;
680 | key += 32; // make it lower case
681 | } else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
682 | key += 32;
683 | }
684 |
685 | m_undoCursorPosition[m_tc.document()->revision()] = m_tc.position();
686 |
687 | EventResult result = EventHandled;
688 | if (exactMatch(Qt::CTRL + Qt::Key_N, keySequence)) {
689 | m_tc.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor);
690 | } else if (exactMatch(Qt::CTRL + Qt::Key_P, keySequence)) {
691 | m_tc.movePosition(QTextCursor::Up, QTextCursor::MoveAnchor);
692 | } else if (exactMatch(Qt::CTRL + Qt::Key_A, keySequence)) {
693 | moveToStartOfLine();
694 | } else if (exactMatch(Qt::CTRL + Qt::Key_E, keySequence)) {
695 | moveToEndOfLine();
696 | } else if (exactMatch(Qt::CTRL + Qt::Key_B, keySequence)) {
697 | moveLeft();
698 | } else if (exactMatch(Qt::CTRL + Qt::Key_F, keySequence)) {
699 | moveRight();
700 | } else if (exactMatch(Qt::ALT + Qt::Key_B, keySequence)) {
701 | moveToWordBoundary(false, false);
702 | } else if (exactMatch(Qt::ALT + Qt::Key_F, keySequence)) {
703 | moveToNextWord(false);
704 | } else if (exactMatch(Qt::ALT + Qt::Key_D, keySequence)) {
705 | killWord();
706 | } else if (exactMatch(Qt::ALT + Qt::Key_Backspace, keySequence)) {
707 | backwardKillWord();
708 | } else if (exactMatch(Qt::CTRL + Qt::Key_D, keySequence)) {
709 | m_tc.deleteChar();
710 | } else if (exactMatch(Qt::ALT + Qt::SHIFT + Qt::Key_Less, keySequence)) {
711 | m_tc.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
712 | } else if (exactMatch(Qt::ALT + Qt::SHIFT + Qt::Key_Greater, keySequence)) {
713 | m_tc.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
714 | } else if (exactMatch(Qt::CTRL + Qt::Key_V, keySequence)) {
715 | moveDown(count() * (linesOnScreen() - 2) - cursorLineOnScreen());
716 | scrollToLineInDocument(cursorLineInDocument());
717 | } else if (exactMatch(Qt::ALT + Qt::Key_V, keySequence)) {
718 | moveUp(count() * (linesOnScreen() - 2) + cursorLineOnScreen());
719 | scrollToLineInDocument(cursorLineInDocument() + linesOnScreen() - 2);
720 | } else if (exactMatch(Qt::CTRL + Qt::Key_Space, keySequence)) {
721 | setMark();
722 | } else if (exactMatch(Qt::CTRL + Qt::Key_K, keySequence)) {
723 | killLine();
724 | } else if (exactMatch(Qt::CTRL + Qt::Key_Y, keySequence)) {
725 | yank();
726 | } else if (exactMatch(Qt::ALT + Qt::Key_Y, keySequence)) {
727 | yankPop(EDITOR_WIDGET);
728 | } else if (exactMatch(Qt::CTRL + Qt::Key_W, keySequence)) {
729 | cut();
730 | } else if (exactMatch(Qt::ALT + Qt::Key_W, keySequence)) {
731 | copy();
732 | } else if (QKeySequence(Qt::CTRL + Qt::Key_U, Qt::CTRL + Qt::Key_Space).matches(keySequence) == QKeySequence::ExactMatch) {
733 | popToMark();
734 | } else if (QKeySequence(Qt::CTRL + Qt::Key_X, Qt::Key_X).matches(keySequence) == QKeySequence::ExactMatch) {
735 | exchangeDotAndMark();
736 | } else {
737 | result = EventUnhandled;
738 | }
739 |
740 | m_oldTc = m_tc;
741 | EDITOR(setTextCursor(m_tc));
742 | return result;
743 | }
744 |
745 | void EmacsKeysHandler::Private::installEventFilter()
746 | {
747 | EDITOR(installEventFilter(q));
748 | }
749 |
750 | void EmacsKeysHandler::Private::setupWidget()
751 | {
752 | enterCommandMode();
753 | //EDITOR(setCursorWidth(QFontMetrics(ed->font()).width(QChar('x')));
754 | if (m_textedit) {
755 | m_textedit->setLineWrapMode(QTextEdit::NoWrap);
756 | } else if (m_plaintextedit) {
757 | m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
758 | }
759 | m_wasReadOnly = EDITOR(isReadOnly());
760 | //EDITOR(setReadOnly(true));
761 |
762 | QTextCursor tc = EDITOR(textCursor());
763 | if (tc.hasSelection()) {
764 | int pos = tc.position();
765 | int anc = tc.anchor();
766 | m_marks['<'] = anc;
767 | m_marks['>'] = pos;
768 | m_anchor = anc;
769 | m_visualMode = VisualCharMode;
770 | tc.clearSelection();
771 | EDITOR(setTextCursor(tc));
772 | m_tc = tc; // needed in updateSelection
773 | updateSelection();
774 | }
775 |
776 | //showBlackMessage("vi emulation mode. Type :q to leave. Use , Ctrl-R to trigger run.");
777 | updateMiniBuffer();
778 | }
779 |
780 | void EmacsKeysHandler::Private::restoreWidget()
781 | {
782 | //showBlackMessage(QString());
783 | //updateMiniBuffer();
784 | //EDITOR(removeEventFilter(q));
785 | EDITOR(setReadOnly(m_wasReadOnly));
786 | EDITOR(setCursorWidth(m_cursorWidth));
787 | EDITOR(setOverwriteMode(false));
788 |
789 | if (m_visualMode == VisualLineMode) {
790 | m_tc = EDITOR(textCursor());
791 | int beginLine = lineForPosition(m_marks['<']);
792 | int endLine = lineForPosition(m_marks['>']);
793 | m_tc.setPosition(firstPositionInLine(beginLine), MoveAnchor);
794 | m_tc.setPosition(lastPositionInLine(endLine), KeepAnchor);
795 | EDITOR(setTextCursor(m_tc));
796 | } else if (m_visualMode == VisualCharMode) {
797 | m_tc = EDITOR(textCursor());
798 | m_tc.setPosition(m_marks['<'], MoveAnchor);
799 | m_tc.setPosition(m_marks['>'], KeepAnchor);
800 | EDITOR(setTextCursor(m_tc));
801 | }
802 |
803 | m_visualMode = NoVisualMode;
804 | updateSelection();
805 | }
806 |
807 | EventResult EmacsKeysHandler::Private::handleKey(int key, int unmodified,
808 | const QString &text)
809 | {
810 | qDebug() << "KEY: " << key << text << "POS: " << m_tc.position();
811 | if (m_mode == InsertMode)
812 | return handleInsertMode(key, unmodified, text);
813 | if (m_mode == CommandMode)
814 | return handleCommandMode(key, unmodified, text);
815 | if (m_mode == ExMode || m_mode == SearchForwardMode
816 | || m_mode == SearchBackwardMode)
817 | return handleMiniBufferModes(key, unmodified, text);
818 | return EventUnhandled;
819 | }
820 |
821 | void EmacsKeysHandler::Private::moveDown(int n)
822 | {
823 | #if 0
824 | // does not work for "hidden" documents like in the autotests
825 | m_tc.movePosition(Down, MoveAnchor, n);
826 | #else
827 | const int col = m_tc.position() - m_tc.block().position();
828 | const int lastLine = m_tc.document()->lastBlock().blockNumber();
829 | const int targetLine = qMax(0, qMin(lastLine, m_tc.block().blockNumber() + n));
830 | const QTextBlock &block = m_tc.document()->findBlockByNumber(targetLine);
831 | const int pos = block.position();
832 | setPosition(pos + qMin(block.length() - 1, col));
833 | moveToTargetColumn();
834 | #endif
835 | }
836 |
837 | void EmacsKeysHandler::Private::moveToEndOfLine()
838 | {
839 | #if 0
840 | // does not work for "hidden" documents like in the autotests
841 | m_tc.movePosition(EndOfLine, MoveAnchor);
842 | #else
843 | const QTextBlock &block = m_tc.block();
844 | setPosition(block.position() + block.length() - 1);
845 | #endif
846 | }
847 |
848 | void EmacsKeysHandler::Private::moveToStartOfLine()
849 | {
850 | #if 0
851 | // does not work for "hidden" documents like in the autotests
852 | m_tc.movePosition(StartOfLine, MoveAnchor);
853 | #else
854 | const QTextBlock &block = m_tc.block();
855 | setPosition(block.position());
856 | #endif
857 | }
858 |
859 | void EmacsKeysHandler::Private::finishMovement(const QString &dotCommand)
860 | {
861 | //qDebug() << "ANCHOR: " << position() << anchor();
862 | if (m_submode == FilterSubMode) {
863 | int beginLine = lineForPosition(anchor());
864 | int endLine = lineForPosition(position());
865 | setPosition(qMin(anchor(), position()));
866 | enterExMode();
867 | m_currentMessage.clear();
868 | m_commandBuffer = QString(".,+%1!").arg(qAbs(endLine - beginLine));
869 | m_commandHistory.append(QString());
870 | m_commandHistoryIndex = m_commandHistory.size() - 1;
871 | updateMiniBuffer();
872 | return;
873 | }
874 |
875 | if (m_visualMode != NoVisualMode)
876 | m_marks['>'] = m_tc.position();
877 |
878 | if (m_submode == ChangeSubMode) {
879 | if (m_moveType == MoveInclusive)
880 | moveRight(); // correction
881 | if (anchor() >= position())
882 | m_anchor++;
883 | if (!dotCommand.isEmpty())
884 | setDotCommand("c" + dotCommand);
885 | QString text = removeSelectedText();
886 | //qDebug() << "CHANGING TO INSERT MODE" << text;
887 | m_registers[m_register] = text;
888 | enterInsertMode();
889 | m_submode = NoSubMode;
890 | } else if (m_submode == DeleteSubMode) {
891 | if (m_moveType == MoveInclusive)
892 | moveRight(); // correction
893 | if (anchor() >= position())
894 | m_anchor++;
895 | if (!dotCommand.isEmpty())
896 | setDotCommand("d" + dotCommand);
897 | m_registers[m_register] = removeSelectedText();
898 | m_submode = NoSubMode;
899 | if (atEndOfLine())
900 | moveLeft();
901 | else
902 | setTargetColumn();
903 | } else if (m_submode == YankSubMode) {
904 | m_registers[m_register] = selectedText();
905 | setPosition(m_savedYankPosition);
906 | m_submode = NoSubMode;
907 | } else if (m_submode == ReplaceSubMode) {
908 | m_submode = NoSubMode;
909 | } else if (m_submode == IndentSubMode) {
910 | recordJump();
911 | indentRegion();
912 | m_submode = NoSubMode;
913 | updateMiniBuffer();
914 | } else if (m_submode == ShiftRightSubMode) {
915 | recordJump();
916 | shiftRegionRight(1);
917 | m_submode = NoSubMode;
918 | updateMiniBuffer();
919 | } else if (m_submode == ShiftLeftSubMode) {
920 | recordJump();
921 | shiftRegionLeft(1);
922 | m_submode = NoSubMode;
923 | updateMiniBuffer();
924 | }
925 |
926 | m_moveType = MoveInclusive;
927 | m_mvcount.clear();
928 | m_opcount.clear();
929 | m_gflag = false;
930 | m_register = '"';
931 | m_tc.clearSelection();
932 |
933 | updateSelection();
934 | updateMiniBuffer();
935 | }
936 |
937 | void EmacsKeysHandler::Private::updateSelection()
938 | {
939 | QList selections = m_searchSelections;
940 | if (m_visualMode != NoVisualMode) {
941 | QTextEdit::ExtraSelection sel;
942 | sel.cursor = m_tc;
943 | sel.format = m_tc.blockCharFormat();
944 | #if 0
945 | sel.format.setFontWeight(QFont::Bold);
946 | sel.format.setFontUnderline(true);
947 | #else
948 | sel.format.setForeground(Qt::white);
949 | sel.format.setBackground(Qt::black);
950 | #endif
951 | int cursorPos = m_tc.position();
952 | int anchorPos = m_marks['<'];
953 | //qDebug() << "POS: " << cursorPos << " ANCHOR: " << anchorPos;
954 | if (m_visualMode == VisualCharMode) {
955 | sel.cursor.setPosition(anchorPos, KeepAnchor);
956 | selections.append(sel);
957 | } else if (m_visualMode == VisualLineMode) {
958 | sel.cursor.setPosition(qMin(cursorPos, anchorPos), MoveAnchor);
959 | sel.cursor.movePosition(StartOfLine, MoveAnchor);
960 | sel.cursor.setPosition(qMax(cursorPos, anchorPos), KeepAnchor);
961 | sel.cursor.movePosition(EndOfLine, KeepAnchor);
962 | selections.append(sel);
963 | } else if (m_visualMode == VisualBlockMode) {
964 | QTextCursor tc = m_tc;
965 | tc.setPosition(anchorPos);
966 | tc.movePosition(StartOfLine, MoveAnchor);
967 | QTextBlock anchorBlock = tc.block();
968 | QTextBlock cursorBlock = m_tc.block();
969 | int anchorColumn = anchorPos - anchorBlock.position();
970 | int cursorColumn = cursorPos - cursorBlock.position();
971 | int startColumn = qMin(anchorColumn, cursorColumn);
972 | int endColumn = qMax(anchorColumn, cursorColumn);
973 | int endPos = cursorBlock.position();
974 | while (tc.position() <= endPos) {
975 | if (startColumn < tc.block().length() - 1) {
976 | int last = qMin(tc.block().length() - 1, endColumn);
977 | int len = last - startColumn + 1;
978 | sel.cursor = tc;
979 | sel.cursor.movePosition(Right, MoveAnchor, startColumn);
980 | sel.cursor.movePosition(Right, KeepAnchor, len);
981 | selections.append(sel);
982 | }
983 | tc.movePosition(Down, MoveAnchor, 1);
984 | }
985 | }
986 | }
987 | //qDebug() << "SELECTION: " << selections;
988 | emit q->selectionChanged(selections);
989 | }
990 |
991 | void EmacsKeysHandler::Private::updateMiniBuffer()
992 | {
993 | QString msg;
994 | if (m_passing) {
995 | msg = "-- PASSING -- ";
996 | } else if (!m_currentMessage.isEmpty()) {
997 | msg = m_currentMessage;
998 | } else if (m_mode == CommandMode && m_visualMode != NoVisualMode) {
999 | if (m_visualMode == VisualCharMode) {
1000 | msg = "-- VISUAL --";
1001 | } else if (m_visualMode == VisualLineMode) {
1002 | msg = "-- VISUAL LINE --";
1003 | } else if (m_visualMode == VisualBlockMode) {
1004 | msg = "-- VISUAL BLOCK --";
1005 | }
1006 | } else if (m_mode == InsertMode) {
1007 | if (m_submode == ReplaceSubMode)
1008 | msg = "-- REPLACE --";
1009 | else
1010 | msg = "-- INSERT --";
1011 | } else {
1012 | if (m_mode == SearchForwardMode)
1013 | msg += '/';
1014 | else if (m_mode == SearchBackwardMode)
1015 | msg += '?';
1016 | else if (m_mode == ExMode)
1017 | msg += ':';
1018 | foreach (QChar c, m_commandBuffer) {
1019 | if (c.unicode() < 32) {
1020 | msg += '^';
1021 | msg += QChar(c.unicode() + 64);
1022 | } else {
1023 | msg += c;
1024 | }
1025 | }
1026 | if (!msg.isEmpty() && m_mode != CommandMode)
1027 | msg += QChar(10073); // '|'; // FIXME: Use a real "cursor"
1028 | }
1029 |
1030 | emit q->commandBufferChanged(msg);
1031 |
1032 | int linesInDoc = linesInDocument();
1033 | int l = cursorLineInDocument();
1034 | QString status;
1035 | QString pos = tr("%1,%2").arg(l + 1).arg(cursorColumnInDocument() + 1);
1036 | status += tr("%1").arg(pos, -10);
1037 | // FIXME: physical "-" logical
1038 | if (linesInDoc != 0) {
1039 | status += tr("%1").arg(l * 100 / linesInDoc, 4);
1040 | status += "%";
1041 | } else {
1042 | status += "All";
1043 | }
1044 | emit q->statusDataChanged(status);
1045 | }
1046 |
1047 | void EmacsKeysHandler::Private::showRedMessage(const QString &msg)
1048 | {
1049 | //qDebug() << "MSG: " << msg;
1050 | m_currentMessage = msg;
1051 | updateMiniBuffer();
1052 | }
1053 |
1054 | void EmacsKeysHandler::Private::showBlackMessage(const QString &msg)
1055 | {
1056 | //qDebug() << "MSG: " << msg;
1057 | m_commandBuffer = msg;
1058 | updateMiniBuffer();
1059 | }
1060 |
1061 | void EmacsKeysHandler::Private::notImplementedYet()
1062 | {
1063 | qDebug() << "Not implemented in EmacsKeys";
1064 | showRedMessage(tr("Not implemented in EmacsKeys"));
1065 | updateMiniBuffer();
1066 | }
1067 |
1068 | EventResult EmacsKeysHandler::Private::handleCommandMode(int key, int unmodified,
1069 | const QString &text)
1070 | {
1071 | EventResult handled = EventHandled;
1072 |
1073 | if (m_submode == WindowSubMode) {
1074 | emit q->windowCommandRequested(key);
1075 | m_submode = NoSubMode;
1076 | } else if (m_submode == RegisterSubMode) {
1077 | m_register = key;
1078 | m_submode = NoSubMode;
1079 | } else if (m_submode == ChangeSubMode && key == 'c') { // tested
1080 | moveDown(count() - 1);
1081 | moveToEndOfLine();
1082 | moveLeft();
1083 | setAnchor();
1084 | moveToStartOfLine();
1085 | setTargetColumn();
1086 | moveUp(count() - 1);
1087 | m_moveType = MoveLineWise;
1088 | m_lastInsertion.clear();
1089 | setDotCommand("%1cc", count());
1090 | finishMovement();
1091 | } else if (m_submode == DeleteSubMode && key == 'd') { // tested
1092 | moveToStartOfLine();
1093 | setTargetColumn();
1094 | setAnchor();
1095 | moveDown(count());
1096 | m_moveType = MoveLineWise;
1097 | setDotCommand("%1dd", count());
1098 | finishMovement();
1099 | } else if (m_submode == YankSubMode && key == 'y') {
1100 | moveToStartOfLine();
1101 | setAnchor();
1102 | moveDown(count());
1103 | m_moveType = MoveLineWise;
1104 | finishMovement("y");
1105 | } else if (m_submode == ShiftLeftSubMode && key == '<') {
1106 | setAnchor();
1107 | moveDown(count() - 1);
1108 | m_moveType = MoveLineWise;
1109 | setDotCommand("%1<<", count());
1110 | finishMovement();
1111 | } else if (m_submode == ShiftRightSubMode && key == '>') {
1112 | setAnchor();
1113 | moveDown(count() - 1);
1114 | m_moveType = MoveLineWise;
1115 | setDotCommand("%1>>", count());
1116 | finishMovement();
1117 | } else if (m_submode == IndentSubMode && key == '=') {
1118 | setAnchor();
1119 | moveDown(count() - 1);
1120 | m_moveType = MoveLineWise;
1121 | setDotCommand("%1==", count());
1122 | finishMovement();
1123 | } else if (m_submode == ZSubMode) {
1124 | //qDebug() << "Z_MODE " << cursorLineInDocument() << linesOnScreen();
1125 | if (key == Key_Return || key == 't') { // cursor line to top of window
1126 | if (!m_mvcount.isEmpty())
1127 | setPosition(firstPositionInLine(count()));
1128 | scrollUp(- cursorLineOnScreen());
1129 | if (key == Key_Return)
1130 | moveToFirstNonBlankOnLine();
1131 | finishMovement();
1132 | } else if (key == '.' || key == 'z') { // cursor line to center of window
1133 | if (!m_mvcount.isEmpty())
1134 | setPosition(firstPositionInLine(count()));
1135 | scrollUp(linesOnScreen() / 2 - cursorLineOnScreen());
1136 | if (key == '.')
1137 | moveToFirstNonBlankOnLine();
1138 | finishMovement();
1139 | } else if (key == '-' || key == 'b') { // cursor line to bottom of window
1140 | if (!m_mvcount.isEmpty())
1141 | setPosition(firstPositionInLine(count()));
1142 | scrollUp(linesOnScreen() - cursorLineOnScreen());
1143 | if (key == '-')
1144 | moveToFirstNonBlankOnLine();
1145 | finishMovement();
1146 | } else {
1147 | qDebug() << "IGNORED Z_MODE " << key << text;
1148 | }
1149 | m_submode = NoSubMode;
1150 | } else if (m_submode == CapitalZSubMode) {
1151 | // Recognize ZZ and ZQ as aliases for ":x" and ":q!".
1152 | m_submode = NoSubMode;
1153 | if (key == 'Z')
1154 | handleCommand("x");
1155 | else if (key == 'Q')
1156 | handleCommand("q!");
1157 | } else if (m_subsubmode == FtSubSubMode) {
1158 | m_semicolonType = m_subsubdata;
1159 | m_semicolonKey = key;
1160 | handleFfTt(key);
1161 | m_subsubmode = NoSubSubMode;
1162 | finishMovement(QString("%1%2%3")
1163 | .arg(count())
1164 | .arg(QChar(m_semicolonType))
1165 | .arg(QChar(m_semicolonKey)));
1166 | } else if (m_submode == ReplaceSubMode) {
1167 | if (count() <= (rightDist() + atEndOfLine()) && text.size() == 1
1168 | && (text.at(0).isPrint() || text.at(0).isSpace())) {
1169 | if (atEndOfLine())
1170 | moveLeft();
1171 | setAnchor();
1172 | moveRight(count());
1173 | QString rem = removeSelectedText();
1174 | m_tc.insertText(QString(count(), text.at(0)));
1175 | m_moveType = MoveExclusive;
1176 | setDotCommand("%1r" + text, count());
1177 | }
1178 | setTargetColumn();
1179 | m_submode = NoSubMode;
1180 | finishMovement();
1181 | } else if (m_subsubmode == MarkSubSubMode) {
1182 | m_marks[key] = m_tc.position();
1183 | m_subsubmode = NoSubSubMode;
1184 | } else if (m_subsubmode == BackTickSubSubMode
1185 | || m_subsubmode == TickSubSubMode) {
1186 | if (m_marks.contains(key)) {
1187 | setPosition(m_marks[key]);
1188 | if (m_subsubmode == TickSubSubMode)
1189 | moveToFirstNonBlankOnLine();
1190 | finishMovement();
1191 | } else {
1192 | showRedMessage(tr("E20: Mark '%1' not set").arg(text));
1193 | }
1194 | m_subsubmode = NoSubSubMode;
1195 | } else if (key >= '0' && key <= '9') {
1196 | if (key == '0' && m_mvcount.isEmpty()) {
1197 | moveToStartOfLine();
1198 | setTargetColumn();
1199 | finishMovement();
1200 | } else {
1201 | m_mvcount.append(QChar(key));
1202 | }
1203 | } else if (key == '^') {
1204 | moveToFirstNonBlankOnLine();
1205 | finishMovement();
1206 | } else if (0 && key == ',') {
1207 | // FIXME: emacsKeys uses ',' by itself, so it is incompatible
1208 | m_subsubmode = FtSubSubMode;
1209 | // HACK: toggle 'f' <-> 'F', 't' <-> 'T'
1210 | m_subsubdata = m_semicolonType ^ 32;
1211 | handleFfTt(m_semicolonKey);
1212 | m_subsubmode = NoSubSubMode;
1213 | finishMovement();
1214 | } else if (key == ';') {
1215 | m_subsubmode = FtSubSubMode;
1216 | m_subsubdata = m_semicolonType;
1217 | handleFfTt(m_semicolonKey);
1218 | m_subsubmode = NoSubSubMode;
1219 | finishMovement();
1220 | } else if (key == ':') {
1221 | enterExMode();
1222 | m_currentMessage.clear();
1223 | m_commandBuffer.clear();
1224 | if (m_visualMode != NoVisualMode)
1225 | m_commandBuffer = "'<,'>";
1226 | m_commandHistory.append(QString());
1227 | m_commandHistoryIndex = m_commandHistory.size() - 1;
1228 | updateMiniBuffer();
1229 | } else if (key == '/' || key == '?') {
1230 | if (hasConfig(ConfigIncSearch)) {
1231 | // re-use the core dialog.
1232 | emit q->findRequested(key == '?');
1233 | } else {
1234 | // FIXME: make core find dialog sufficiently flexible to
1235 | // produce the "default vi" behaviour too. For now, roll our own.
1236 | enterExMode(); // to get the cursor disabled
1237 | m_currentMessage.clear();
1238 | m_mode = (key == '/') ? SearchForwardMode : SearchBackwardMode;
1239 | m_commandBuffer.clear();
1240 | m_searchHistory.append(QString());
1241 | m_searchHistoryIndex = m_searchHistory.size() - 1;
1242 | updateMiniBuffer();
1243 | }
1244 | } else if (key == '`') {
1245 | m_subsubmode = BackTickSubSubMode;
1246 | } else if (key == '#' || key == '*') {
1247 | // FIXME: That's not proper vim behaviour
1248 | m_tc.select(QTextCursor::WordUnderCursor);
1249 | QString needle = "\\<" + m_tc.selection().toPlainText() + "\\>";
1250 | m_searchHistory.append(needle);
1251 | m_lastSearchForward = (key == '*');
1252 | updateMiniBuffer();
1253 | search(needle, m_lastSearchForward);
1254 | recordJump();
1255 | } else if (key == '\'') {
1256 | m_subsubmode = TickSubSubMode;
1257 | } else if (key == '|') {
1258 | moveToStartOfLine();
1259 | moveRight(qMin(count(), rightDist()) - 1);
1260 | setTargetColumn();
1261 | finishMovement();
1262 | } else if (key == '!' && m_visualMode == NoVisualMode) {
1263 | m_submode = FilterSubMode;
1264 | } else if (key == '!' && m_visualMode != NoVisualMode) {
1265 | enterExMode();
1266 | m_currentMessage.clear();
1267 | m_commandBuffer = "'<,'>!";
1268 | m_commandHistory.append(QString());
1269 | m_commandHistoryIndex = m_commandHistory.size() - 1;
1270 | updateMiniBuffer();
1271 | } else if (key == '"') {
1272 | m_submode = RegisterSubMode;
1273 | } else if (unmodified == Key_Return) {
1274 | moveToStartOfLine();
1275 | moveDown();
1276 | moveToFirstNonBlankOnLine();
1277 | finishMovement();
1278 | } else if (key == '-') {
1279 | moveToStartOfLine();
1280 | moveUp();
1281 | moveToFirstNonBlankOnLine();
1282 | finishMovement();
1283 | } else if (key == Key_Home) {
1284 | moveToStartOfLine();
1285 | setTargetColumn();
1286 | finishMovement();
1287 | } else if (key == '$' || key == Key_End) {
1288 | int submode = m_submode;
1289 | moveToEndOfLine();
1290 | m_moveType = MoveExclusive;
1291 | setTargetColumn();
1292 | if (submode == NoSubMode)
1293 | m_targetColumn = -1;
1294 | finishMovement("$");
1295 | } else if (key == ',') {
1296 | // FIXME: use some other mechanism
1297 | //m_passing = true;
1298 | m_passing = !m_passing;
1299 | updateMiniBuffer();
1300 | } else if (key == '.') {
1301 | qDebug() << "REPEATING" << quoteUnprintable(m_dotCommand);
1302 | QString savedCommand = m_dotCommand;
1303 | m_dotCommand.clear();
1304 | replay(savedCommand, count());
1305 | enterCommandMode();
1306 | m_dotCommand = savedCommand;
1307 | } else if (key == '<' && m_visualMode == NoVisualMode) {
1308 | m_submode = ShiftLeftSubMode;
1309 | } else if (key == '<' && m_visualMode != NoVisualMode) {
1310 | shiftRegionLeft(1);
1311 | leaveVisualMode();
1312 | } else if (key == '>' && m_visualMode == NoVisualMode) {
1313 | m_submode = ShiftRightSubMode;
1314 | } else if (key == '>' && m_visualMode != NoVisualMode) {
1315 | shiftRegionRight(1);
1316 | leaveVisualMode();
1317 | } else if (key == '=' && m_visualMode == NoVisualMode) {
1318 | m_submode = IndentSubMode;
1319 | } else if (key == '=' && m_visualMode != NoVisualMode) {
1320 | indentRegion();
1321 | leaveVisualMode();
1322 | } else if (key == '%') {
1323 | m_moveType = MoveExclusive;
1324 | moveToMatchingParanthesis();
1325 | finishMovement();
1326 | } else if (key == 'a') {
1327 | enterInsertMode();
1328 | m_lastInsertion.clear();
1329 | if (!atEndOfLine())
1330 | moveRight();
1331 | updateMiniBuffer();
1332 | } else if (key == 'A') {
1333 | enterInsertMode();
1334 | moveToEndOfLine();
1335 | setDotCommand("A");
1336 | m_lastInsertion.clear();
1337 | } else if (key == control('a')) {
1338 | // FIXME: eat it to prevent the global "select all" shortcut to trigger
1339 | } else if (key == 'b') {
1340 | m_moveType = MoveExclusive;
1341 | moveToWordBoundary(false, false);
1342 | finishMovement();
1343 | } else if (key == 'B') {
1344 | m_moveType = MoveExclusive;
1345 | moveToWordBoundary(true, false);
1346 | finishMovement();
1347 | } else if (key == 'c' && m_visualMode == NoVisualMode) {
1348 | setAnchor();
1349 | m_submode = ChangeSubMode;
1350 | } else if (key == 'c' && m_visualMode == VisualCharMode) {
1351 | leaveVisualMode();
1352 | m_submode = ChangeSubMode;
1353 | finishMovement();
1354 | } else if (key == 'C') {
1355 | setAnchor();
1356 | moveToEndOfLine();
1357 | m_registers[m_register] = removeSelectedText();
1358 | enterInsertMode();
1359 | setDotCommand("C");
1360 | finishMovement();
1361 | } else if (key == control('c')) {
1362 | showBlackMessage("Type Alt-v,Alt-v to quit EmacsKeys mode");
1363 | } else if (key == 'd' && m_visualMode == NoVisualMode) {
1364 | if (atEndOfLine())
1365 | moveLeft();
1366 | setAnchor();
1367 | m_opcount = m_mvcount;
1368 | m_mvcount.clear();
1369 | m_submode = DeleteSubMode;
1370 | } else if ((key == 'd' || key == 'x') && m_visualMode == VisualCharMode) {
1371 | leaveVisualMode();
1372 | m_submode = DeleteSubMode;
1373 | finishMovement();
1374 | } else if ((key == 'd' || key == 'x') && m_visualMode == VisualLineMode) {
1375 | leaveVisualMode();
1376 | int beginLine = lineForPosition(m_marks['<']);
1377 | int endLine = lineForPosition(m_marks['>']);
1378 | selectRange(beginLine, endLine);
1379 | m_registers[m_register] = removeSelectedText();
1380 | } else if (key == 'D') {
1381 | setAnchor();
1382 | m_submode = DeleteSubMode;
1383 | moveDown(qMax(count() - 1, 0));
1384 | m_moveType = MoveExclusive;
1385 | moveToEndOfLine();
1386 | setDotCommand("D");
1387 | finishMovement();
1388 | } else if (key == control('d')) {
1389 | int sline = cursorLineOnScreen();
1390 | // FIXME: this should use the "scroll" option, and "count"
1391 | moveDown(linesOnScreen() / 2);
1392 | handleStartOfLine();
1393 | scrollToLineInDocument(cursorLineInDocument() - sline);
1394 | finishMovement();
1395 | } else if (key == 'e') { // tested
1396 | m_moveType = MoveInclusive;
1397 | moveToWordBoundary(false, true);
1398 | finishMovement();
1399 | } else if (key == 'E') {
1400 | m_moveType = MoveInclusive;
1401 | moveToWordBoundary(true, true);
1402 | finishMovement();
1403 | } else if (key == control('e')) {
1404 | // FIXME: this should use the "scroll" option, and "count"
1405 | if (cursorLineOnScreen() == 0)
1406 | moveDown(1);
1407 | scrollDown(1);
1408 | finishMovement();
1409 | } else if (key == 'f') {
1410 | m_subsubmode = FtSubSubMode;
1411 | m_moveType = MoveInclusive;
1412 | m_subsubdata = key;
1413 | } else if (key == 'F') {
1414 | m_subsubmode = FtSubSubMode;
1415 | m_moveType = MoveExclusive;
1416 | m_subsubdata = key;
1417 | } else if (key == 'g') {
1418 | if (m_gflag) {
1419 | m_gflag = false;
1420 | m_tc.setPosition(firstPositionInLine(1), KeepAnchor);
1421 | handleStartOfLine();
1422 | finishMovement();
1423 | } else {
1424 | m_gflag = true;
1425 | }
1426 | } else if (key == 'G') {
1427 | int n = m_mvcount.isEmpty() ? linesInDocument() : count();
1428 | m_tc.setPosition(firstPositionInLine(n), KeepAnchor);
1429 | handleStartOfLine();
1430 | finishMovement();
1431 | } else if (key == 'h' || key == Key_Left
1432 | || key == Key_Backspace || key == control('h')) {
1433 | int n = qMin(count(), leftDist());
1434 | if (m_fakeEnd && m_tc.block().length() > 1)
1435 | ++n;
1436 | moveLeft(n);
1437 | setTargetColumn();
1438 | finishMovement("h");
1439 | } else if (key == 'H') {
1440 | m_tc = EDITOR(cursorForPosition(QPoint(0, 0)));
1441 | moveDown(qMax(count() - 1, 0));
1442 | handleStartOfLine();
1443 | finishMovement();
1444 | } else if (key == 'i' || key == Key_Insert) {
1445 | setDotCommand("i"); // setDotCommand("%1i", count());
1446 | enterInsertMode();
1447 | updateMiniBuffer();
1448 | if (atEndOfLine())
1449 | moveLeft();
1450 | } else if (key == 'I') {
1451 | setDotCommand("I"); // setDotCommand("%1I", count());
1452 | enterInsertMode();
1453 | if (m_gflag)
1454 | moveToStartOfLine();
1455 | else
1456 | moveToFirstNonBlankOnLine();
1457 | m_tc.clearSelection();
1458 | } else if (key == control('i')) {
1459 | if (!m_jumpListRedo.isEmpty()) {
1460 | m_jumpListUndo.append(position());
1461 | setPosition(m_jumpListRedo.takeLast());
1462 | }
1463 | } else if (key == 'j' || key == Key_Down) {
1464 | if (m_submode == NoSubMode || m_submode == ZSubMode
1465 | || m_submode == CapitalZSubMode || m_submode == RegisterSubMode) {
1466 | moveDown(count());
1467 | } else {
1468 | m_moveType = MoveLineWise;
1469 | moveToStartOfLine();
1470 | setAnchor();
1471 | moveDown(count() + 1);
1472 | }
1473 | finishMovement("j");
1474 | } else if (key == 'J') {
1475 | if (m_submode == NoSubMode) {
1476 | for (int i = qMax(count(), 2) - 1; --i >= 0; ) {
1477 | moveToEndOfLine();
1478 | setAnchor();
1479 | moveRight();
1480 | while (characterAtCursor() == ' ')
1481 | moveRight();
1482 | removeSelectedText();
1483 | if (!m_gflag)
1484 | m_tc.insertText(" ");
1485 | }
1486 | if (!m_gflag)
1487 | moveLeft();
1488 | }
1489 | } else if (key == 'k' || key == Key_Up) {
1490 | if (m_submode == NoSubMode || m_submode == ZSubMode
1491 | || m_submode == CapitalZSubMode || m_submode == RegisterSubMode) {
1492 | moveUp(count());
1493 | } else {
1494 | m_moveType = MoveLineWise;
1495 | moveToStartOfLine();
1496 | moveDown();
1497 | setAnchor();
1498 | moveUp(count() + 1);
1499 | }
1500 | finishMovement("k");
1501 | } else if (key == 'l' || key == Key_Right || key == ' ') {
1502 | m_moveType = MoveExclusive;
1503 | moveRight(qMin(count(), rightDist()));
1504 | setTargetColumn();
1505 | finishMovement("l");
1506 | } else if (key == 'L') {
1507 | m_tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()))));
1508 | moveUp(qMax(count(), 1));
1509 | handleStartOfLine();
1510 | finishMovement();
1511 | } else if (key == control('l')) {
1512 | // screen redraw. should not be needed
1513 | } else if (key == 'm') {
1514 | m_subsubmode = MarkSubSubMode;
1515 | } else if (key == 'M') {
1516 | m_tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()) / 2)));
1517 | handleStartOfLine();
1518 | finishMovement();
1519 | } else if (key == 'n') { // FIXME: see comment for '/'
1520 | if (hasConfig(ConfigIncSearch))
1521 | emit q->findNextRequested(false);
1522 | else
1523 | search(lastSearchString(), m_lastSearchForward);
1524 | recordJump();
1525 | } else if (key == 'N') {
1526 | if (hasConfig(ConfigIncSearch))
1527 | emit q->findNextRequested(true);
1528 | else
1529 | search(lastSearchString(), !m_lastSearchForward);
1530 | recordJump();
1531 | } else if (key == 'o' || key == 'O') {
1532 | setDotCommand("%1o", count());
1533 | enterInsertMode();
1534 | moveToFirstNonBlankOnLine();
1535 | if (key == 'O')
1536 | moveUp();
1537 | moveToEndOfLine();
1538 | m_tc.insertText("\n");
1539 | insertAutomaticIndentation(key == 'o');
1540 | } else if (key == control('o')) {
1541 | if (!m_jumpListUndo.isEmpty()) {
1542 | m_jumpListRedo.append(position());
1543 | setPosition(m_jumpListUndo.takeLast());
1544 | }
1545 | } else if (key == 'p' || key == 'P') {
1546 | QString text = m_registers[m_register];
1547 | int n = text.count(QChar('\n'));
1548 | //qDebug() << "REGISTERS: " << m_registers << "MOVE: " << m_moveType;
1549 | //qDebug() << "LINES: " << n << text << m_register;
1550 | if (n > 0) {
1551 | moveToStartOfLine();
1552 | m_targetColumn = 0;
1553 | for (int i = count(); --i >= 0; ) {
1554 | if (key == 'p')
1555 | moveDown();
1556 | m_tc.insertText(text);
1557 | moveUp(n);
1558 | }
1559 | moveToFirstNonBlankOnLine();
1560 | } else {
1561 | m_targetColumn = 0;
1562 | for (int i = count(); --i >= 0; ) {
1563 | if (key == 'p')
1564 | moveRight();
1565 | m_tc.insertText(text);
1566 | moveLeft();
1567 | }
1568 | }
1569 | setDotCommand("%1p", count());
1570 | finishMovement();
1571 | } else if (key == 'r') {
1572 | m_submode = ReplaceSubMode;
1573 | setDotCommand("r");
1574 | } else if (key == 'R') {
1575 | // FIXME: right now we repeat the insertion count() times,
1576 | // but not the deletion
1577 | m_lastInsertion.clear();
1578 | enterInsertMode();
1579 | m_submode = ReplaceSubMode;
1580 | setDotCommand("R");
1581 | } else if (key == control('r')) {
1582 | redo();
1583 | } else if (key == 's') {
1584 | if (atEndOfLine())
1585 | moveLeft();
1586 | setAnchor();
1587 | moveRight(qMin(count(), rightDist()));
1588 | m_registers[m_register] = removeSelectedText();
1589 | setDotCommand("s"); // setDotCommand("%1s", count());
1590 | m_opcount.clear();
1591 | m_mvcount.clear();
1592 | enterInsertMode();
1593 | } else if (key == 't') {
1594 | m_moveType = MoveInclusive;
1595 | m_subsubmode = FtSubSubMode;
1596 | m_subsubdata = key;
1597 | } else if (key == 'T') {
1598 | m_moveType = MoveExclusive;
1599 | m_subsubmode = FtSubSubMode;
1600 | m_subsubdata = key;
1601 | } else if (key == 'u') {
1602 | undo();
1603 | } else if (key == control('u')) {
1604 | int sline = cursorLineOnScreen();
1605 | // FIXME: this should use the "scroll" option, and "count"
1606 | moveUp(linesOnScreen() / 2);
1607 | handleStartOfLine();
1608 | scrollToLineInDocument(cursorLineInDocument() - sline);
1609 | finishMovement();
1610 | } else if (key == 'v') {
1611 | enterVisualMode(VisualCharMode);
1612 | } else if (key == 'V') {
1613 | enterVisualMode(VisualLineMode);
1614 | } else if (key == control('v')) {
1615 | enterVisualMode(VisualBlockMode);
1616 | } else if (key == 'w') { // tested
1617 | // Special case: "cw" and "cW" work the same as "ce" and "cE" if the
1618 | // cursor is on a non-blank.
1619 | if (m_submode == ChangeSubMode) {
1620 | moveToWordBoundary(false, true);
1621 | m_moveType = MoveInclusive;
1622 | } else {
1623 | moveToNextWord(false);
1624 | m_moveType = MoveExclusive;
1625 | }
1626 | finishMovement("w");
1627 | } else if (key == 'W') {
1628 | if (m_submode == ChangeSubMode) {
1629 | moveToWordBoundary(true, true);
1630 | m_moveType = MoveInclusive;
1631 | } else {
1632 | moveToNextWord(true);
1633 | m_moveType = MoveExclusive;
1634 | }
1635 | finishMovement("W");
1636 | } else if (key == control('w')) {
1637 | m_submode = WindowSubMode;
1638 | } else if (key == 'x' && m_visualMode == NoVisualMode) { // = "dl"
1639 | m_moveType = MoveExclusive;
1640 | if (atEndOfLine())
1641 | moveLeft();
1642 | setAnchor();
1643 | m_submode = DeleteSubMode;
1644 | moveRight(qMin(count(), rightDist()));
1645 | setDotCommand("%1x", count());
1646 | finishMovement();
1647 | } else if (key == 'X') {
1648 | if (leftDist() > 0) {
1649 | setAnchor();
1650 | moveLeft(qMin(count(), leftDist()));
1651 | m_registers[m_register] = removeSelectedText();
1652 | }
1653 | finishMovement();
1654 | } else if (key == 'y' && m_visualMode == NoVisualMode) {
1655 | m_savedYankPosition = m_tc.position();
1656 | if (atEndOfLine())
1657 | moveLeft();
1658 | setAnchor();
1659 | m_submode = YankSubMode;
1660 | } else if (key == 'y' && m_visualMode == VisualLineMode) {
1661 | int beginLine = lineForPosition(m_marks['<']);
1662 | int endLine = lineForPosition(m_marks['>']);
1663 | selectRange(beginLine, endLine);
1664 | m_registers[m_register] = selectedText();
1665 | setPosition(qMin(position(), anchor()));
1666 | moveToStartOfLine();
1667 | leaveVisualMode();
1668 | updateSelection();
1669 | } else if (key == 'Y') {
1670 | moveToStartOfLine();
1671 | setAnchor();
1672 | moveDown(count());
1673 | m_moveType = MoveLineWise;
1674 | finishMovement();
1675 | } else if (key == 'z') {
1676 | m_submode = ZSubMode;
1677 | } else if (key == 'Z') {
1678 | m_submode = CapitalZSubMode;
1679 | } else if (key == '~' && !atEndOfLine()) {
1680 | setAnchor();
1681 | moveRight(qMin(count(), rightDist()));
1682 | QString str = removeSelectedText();
1683 | for (int i = str.size(); --i >= 0; ) {
1684 | QChar c = str.at(i);
1685 | str[i] = c.isUpper() ? c.toLower() : c.toUpper();
1686 | }
1687 | m_tc.insertText(str);
1688 | } else if (key == Key_PageDown || key == control('f')) {
1689 | moveDown(count() * (linesOnScreen() - 2) - cursorLineOnScreen());
1690 | scrollToLineInDocument(cursorLineInDocument());
1691 | handleStartOfLine();
1692 | finishMovement();
1693 | } else if (key == Key_PageUp || key == control('b')) {
1694 | moveUp(count() * (linesOnScreen() - 2) + cursorLineOnScreen());
1695 | scrollToLineInDocument(cursorLineInDocument() + linesOnScreen() - 2);
1696 | handleStartOfLine();
1697 | finishMovement();
1698 | } else if (key == Key_Delete) {
1699 | setAnchor();
1700 | moveRight(qMin(1, rightDist()));
1701 | removeSelectedText();
1702 | } else if (key == Key_Escape) {
1703 | if (m_visualMode != NoVisualMode) {
1704 | leaveVisualMode();
1705 | } else if (m_submode != NoSubMode) {
1706 | m_submode = NoSubMode;
1707 | m_subsubmode = NoSubSubMode;
1708 | finishMovement();
1709 | }
1710 | } else {
1711 | qDebug() << "IGNORED IN COMMAND MODE: " << key << text
1712 | << " VISUAL: " << m_visualMode;
1713 | handled = EventUnhandled;
1714 | }
1715 |
1716 | return handled;
1717 | }
1718 |
1719 | EventResult EmacsKeysHandler::Private::handleInsertMode(int key, int,
1720 | const QString &text)
1721 | {
1722 | if (key == Key_Escape || key == 27 || key == control('c')) {
1723 | // start with '1', as one instance was already physically inserted
1724 | // while typing
1725 | QString data = m_lastInsertion;
1726 | for (int i = 1; i < count(); ++i) {
1727 | m_tc.insertText(m_lastInsertion);
1728 | data += m_lastInsertion;
1729 | }
1730 | moveLeft(qMin(1, leftDist()));
1731 | setTargetColumn();
1732 | m_dotCommand += m_lastInsertion;
1733 | m_dotCommand += QChar(27);
1734 | recordNewUndo();
1735 | enterCommandMode();
1736 | } else if (key == Key_Insert) {
1737 | if (m_submode == ReplaceSubMode) {
1738 | EDITOR(setCursorWidth(m_cursorWidth));
1739 | EDITOR(setOverwriteMode(false));
1740 | m_submode = NoSubMode;
1741 | } else {
1742 | EDITOR(setCursorWidth(m_cursorWidth));
1743 | EDITOR(setOverwriteMode(true));
1744 | m_submode = ReplaceSubMode;
1745 | }
1746 | } else if (key == Key_Left) {
1747 | moveLeft(count());
1748 | m_lastInsertion.clear();
1749 | } else if (key == Key_Down) {
1750 | removeAutomaticIndentation();
1751 | m_submode = NoSubMode;
1752 | moveDown(count());
1753 | m_lastInsertion.clear();
1754 | } else if (key == Key_Up) {
1755 | removeAutomaticIndentation();
1756 | m_submode = NoSubMode;
1757 | moveUp(count());
1758 | m_lastInsertion.clear();
1759 | } else if (key == Key_Right) {
1760 | moveRight(count());
1761 | m_lastInsertion.clear();
1762 | } else if (key == Key_Return) {
1763 | m_submode = NoSubMode;
1764 | m_tc.insertBlock();
1765 | m_lastInsertion += "\n";
1766 | insertAutomaticIndentation(true);
1767 | } else if (key == Key_Backspace || key == control('h')) {
1768 | if (!removeAutomaticIndentation())
1769 | if (!m_lastInsertion.isEmpty() || hasConfig(ConfigBackspace, "start")) {
1770 | m_tc.deletePreviousChar();
1771 | m_lastInsertion.chop(1);
1772 | }
1773 | } else if (key == Key_Delete) {
1774 | m_tc.deleteChar();
1775 | m_lastInsertion.clear();
1776 | } else if (key == Key_PageDown || key == control('f')) {
1777 | removeAutomaticIndentation();
1778 | moveDown(count() * (linesOnScreen() - 2));
1779 | m_lastInsertion.clear();
1780 | } else if (key == Key_PageUp || key == control('b')) {
1781 | removeAutomaticIndentation();
1782 | moveUp(count() * (linesOnScreen() - 2));
1783 | m_lastInsertion.clear();
1784 | } else if (key == Key_Tab && hasConfig(ConfigExpandTab)) {
1785 | QString str = QString(theEmacsKeysSetting(ConfigTabStop)->value().toInt(), ' ');
1786 | m_lastInsertion.append(str);
1787 | m_tc.insertText(str);
1788 | } else if (key >= control('a') && key <= control('z')) {
1789 | // ignore these
1790 | } else if (!text.isEmpty()) {
1791 | m_lastInsertion.append(text);
1792 | if (m_submode == ReplaceSubMode) {
1793 | if (atEndOfLine())
1794 | m_submode = NoSubMode;
1795 | else
1796 | m_tc.deleteChar();
1797 | }
1798 | m_tc.insertText(text);
1799 | if (0 && hasConfig(ConfigAutoIndent) && isElectricCharacter(text.at(0))) {
1800 | const QString leftText = m_tc.block().text()
1801 | .left(m_tc.position() - 1 - m_tc.block().position());
1802 | if (leftText.simplified().isEmpty())
1803 | indentRegion(text.at(0));
1804 | }
1805 |
1806 | if (!m_inReplay)
1807 | emit q->completionRequested();
1808 | } else {
1809 | return EventUnhandled;
1810 | }
1811 | updateMiniBuffer();
1812 | return EventHandled;
1813 | }
1814 |
1815 | EventResult EmacsKeysHandler::Private::handleMiniBufferModes(int key, int unmodified,
1816 | const QString &text)
1817 | {
1818 | Q_UNUSED(text)
1819 |
1820 | if (key == Key_Escape || key == control('c')) {
1821 | m_commandBuffer.clear();
1822 | enterCommandMode();
1823 | updateMiniBuffer();
1824 | } else if (key == Key_Backspace) {
1825 | if (m_commandBuffer.isEmpty()) {
1826 | enterCommandMode();
1827 | } else {
1828 | m_commandBuffer.chop(1);
1829 | }
1830 | updateMiniBuffer();
1831 | } else if (key == Key_Left) {
1832 | // FIXME:
1833 | if (!m_commandBuffer.isEmpty())
1834 | m_commandBuffer.chop(1);
1835 | updateMiniBuffer();
1836 | } else if (unmodified == Key_Return && m_mode == ExMode) {
1837 | if (!m_commandBuffer.isEmpty()) {
1838 | m_commandHistory.takeLast();
1839 | m_commandHistory.append(m_commandBuffer);
1840 | handleExCommand(m_commandBuffer);
1841 | leaveVisualMode();
1842 | }
1843 | } else if (unmodified == Key_Return && isSearchMode()) {
1844 | if (!m_commandBuffer.isEmpty()) {
1845 | m_searchHistory.takeLast();
1846 | m_searchHistory.append(m_commandBuffer);
1847 | m_lastSearchForward = (m_mode == SearchForwardMode);
1848 | search(lastSearchString(), m_lastSearchForward);
1849 | recordJump();
1850 | }
1851 | enterCommandMode();
1852 | updateMiniBuffer();
1853 | } else if ((key == Key_Up || key == Key_PageUp) && isSearchMode()) {
1854 | // FIXME: This and the three cases below are wrong as vim
1855 | // takes only matching entries in the history into account.
1856 | if (m_searchHistoryIndex > 0) {
1857 | --m_searchHistoryIndex;
1858 | showBlackMessage(m_searchHistory.at(m_searchHistoryIndex));
1859 | }
1860 | } else if ((key == Key_Up || key == Key_PageUp) && m_mode == ExMode) {
1861 | if (m_commandHistoryIndex > 0) {
1862 | --m_commandHistoryIndex;
1863 | showBlackMessage(m_commandHistory.at(m_commandHistoryIndex));
1864 | }
1865 | } else if ((key == Key_Down || key == Key_PageDown) && isSearchMode()) {
1866 | if (m_searchHistoryIndex < m_searchHistory.size() - 1) {
1867 | ++m_searchHistoryIndex;
1868 | showBlackMessage(m_searchHistory.at(m_searchHistoryIndex));
1869 | }
1870 | } else if ((key == Key_Down || key == Key_PageDown) && m_mode == ExMode) {
1871 | if (m_commandHistoryIndex < m_commandHistory.size() - 1) {
1872 | ++m_commandHistoryIndex;
1873 | showBlackMessage(m_commandHistory.at(m_commandHistoryIndex));
1874 | }
1875 | } else if (key == Key_Tab) {
1876 | m_commandBuffer += QChar(9);
1877 | updateMiniBuffer();
1878 | } else if (QChar(key).isPrint()) {
1879 | m_commandBuffer += QChar(key);
1880 | updateMiniBuffer();
1881 | } else {
1882 | qDebug() << "IGNORED IN MINIBUFFER MODE: " << key << text;
1883 | return EventUnhandled;
1884 | }
1885 | return EventHandled;
1886 | }
1887 |
1888 | // 1 based.
1889 | int EmacsKeysHandler::Private::readLineCode(QString &cmd)
1890 | {
1891 | //qDebug() << "CMD: " << cmd;
1892 | if (cmd.isEmpty())
1893 | return -1;
1894 | QChar c = cmd.at(0);
1895 | cmd = cmd.mid(1);
1896 | if (c == '.')
1897 | return cursorLineInDocument() + 1;
1898 | if (c == '$')
1899 | return linesInDocument();
1900 | if (c == '\'' && !cmd.isEmpty()) {
1901 | int mark = m_marks.value(cmd.at(0).unicode());
1902 | if (!mark) {
1903 | showRedMessage(tr("E20: Mark '%1' not set").arg(cmd.at(0)));
1904 | cmd = cmd.mid(1);
1905 | return -1;
1906 | }
1907 | cmd = cmd.mid(1);
1908 | return lineForPosition(mark);
1909 | }
1910 | if (c == '-') {
1911 | int n = readLineCode(cmd);
1912 | return cursorLineInDocument() + 1 - (n == -1 ? 1 : n);
1913 | }
1914 | if (c == '+') {
1915 | int n = readLineCode(cmd);
1916 | return cursorLineInDocument() + 1 + (n == -1 ? 1 : n);
1917 | }
1918 | if (c == '\'' && !cmd.isEmpty()) {
1919 | int pos = m_marks.value(cmd.at(0).unicode(), -1);
1920 | //qDebug() << " MARK: " << cmd.at(0) << pos << lineForPosition(pos);
1921 | if (pos == -1) {
1922 | showRedMessage(tr("E20: Mark '%1' not set").arg(cmd.at(0)));
1923 | cmd = cmd.mid(1);
1924 | return -1;
1925 | }
1926 | cmd = cmd.mid(1);
1927 | return lineForPosition(pos);
1928 | }
1929 | if (c.isDigit()) {
1930 | int n = c.unicode() - '0';
1931 | while (!cmd.isEmpty()) {
1932 | c = cmd.at(0);
1933 | if (!c.isDigit())
1934 | break;
1935 | cmd = cmd.mid(1);
1936 | n = n * 10 + (c.unicode() - '0');
1937 | }
1938 | //qDebug() << "N: " << n;
1939 | return n;
1940 | }
1941 | // not parsed
1942 | cmd = c + cmd;
1943 | return -1;
1944 | }
1945 |
1946 | void EmacsKeysHandler::Private::selectRange(int beginLine, int endLine)
1947 | {
1948 | if (beginLine == -1)
1949 | beginLine = cursorLineInDocument();
1950 | if (endLine == -1)
1951 | endLine = cursorLineInDocument();
1952 | if (beginLine > endLine)
1953 | qSwap(beginLine, endLine);
1954 | setAnchor(firstPositionInLine(beginLine));
1955 | if (endLine == linesInDocument())
1956 | setPosition(lastPositionInLine(endLine));
1957 | else
1958 | setPosition(firstPositionInLine(endLine + 1));
1959 | }
1960 |
1961 | void EmacsKeysHandler::Private::handleCommand(const QString &cmd)
1962 | {
1963 | m_tc = EDITOR(textCursor());
1964 | handleExCommand(cmd);
1965 | EDITOR(setTextCursor(m_tc));
1966 | }
1967 |
1968 | void EmacsKeysHandler::Private::handleExCommand(const QString &cmd0)
1969 | {
1970 | QString cmd = cmd0;
1971 | if (cmd.startsWith(QLatin1Char('%')))
1972 | cmd = "1,$" + cmd.mid(1);
1973 |
1974 | int beginLine = -1;
1975 | int endLine = -1;
1976 |
1977 | int line = readLineCode(cmd);
1978 | if (line != -1)
1979 | beginLine = line;
1980 |
1981 | if (cmd.startsWith(',')) {
1982 | cmd = cmd.mid(1);
1983 | line = readLineCode(cmd);
1984 | if (line != -1)
1985 | endLine = line;
1986 | }
1987 |
1988 | //qDebug() << "RANGE: " << beginLine << endLine << cmd << cmd0 << m_marks;
1989 |
1990 | static QRegExp reQuit("^qa?!?$");
1991 | static QRegExp reDelete("^d( (.*))?$");
1992 | static QRegExp reHistory("^his(tory)?( (.*))?$");
1993 | static QRegExp reNormal("^norm(al)?( (.*))?$");
1994 | static QRegExp reSet("^set?( (.*))?$");
1995 | static QRegExp reWrite("^[wx]q?a?!?( (.*))?$");
1996 | static QRegExp reSubstitute("^s(.)(.*)\\1(.*)\\1([gi]*)");
1997 |
1998 | if (cmd.isEmpty()) {
1999 | setPosition(firstPositionInLine(beginLine));
2000 | showBlackMessage(QString());
2001 | enterCommandMode();
2002 | } else if (reQuit.indexIn(cmd) != -1) { // :q
2003 | showBlackMessage(QString());
2004 | if (cmd.contains(QChar('a')))
2005 | q->quitAllRequested(cmd.contains(QChar('!')));
2006 | else
2007 | q->quitRequested(cmd.contains(QChar('!')));
2008 | } else if (reDelete.indexIn(cmd) != -1) { // :d
2009 | selectRange(beginLine, endLine);
2010 | QString reg = reDelete.cap(2);
2011 | QString text = removeSelectedText();
2012 | if (!reg.isEmpty())
2013 | m_registers[reg.at(0).unicode()] = text;
2014 | } else if (reWrite.indexIn(cmd) != -1) { // :w and :x
2015 | enterCommandMode();
2016 | bool noArgs = (beginLine == -1);
2017 | if (beginLine == -1)
2018 | beginLine = 0;
2019 | if (endLine == -1)
2020 | endLine = linesInDocument();
2021 | //qDebug() << "LINES: " << beginLine << endLine;
2022 | int indexOfSpace = cmd.indexOf(QChar(' '));
2023 | QString prefix;
2024 | if (indexOfSpace < 0)
2025 | prefix = cmd;
2026 | else
2027 | prefix = cmd.left(indexOfSpace);
2028 | bool forced = prefix.contains(QChar('!'));
2029 | bool quit = prefix.contains(QChar('q')) || prefix.contains(QChar('x'));
2030 | bool quitAll = quit && prefix.contains(QChar('a'));
2031 | QString fileName = reWrite.cap(2);
2032 | if (fileName.isEmpty())
2033 | fileName = m_currentFileName;
2034 | QFile file1(fileName);
2035 | bool exists = file1.exists();
2036 | if (exists && !forced && !noArgs) {
2037 | showRedMessage(tr("File '%1' exists (add ! to override)").arg(fileName));
2038 | } else if (file1.open(QIODevice::ReadWrite)) {
2039 | file1.close();
2040 | QTextCursor tc = m_tc;
2041 | selectRange(beginLine, endLine);
2042 | QString contents = selectedText();
2043 | m_tc = tc;
2044 | qDebug() << "LINES: " << beginLine << endLine;
2045 | bool handled = false;
2046 | emit q->writeFileRequested(&handled, fileName, contents);
2047 | // nobody cared, so act ourselves
2048 | if (!handled) {
2049 | //qDebug() << "HANDLING MANUAL SAVE TO " << fileName;
2050 | QFile::remove(fileName);
2051 | QFile file2(fileName);
2052 | if (file2.open(QIODevice::ReadWrite)) {
2053 | QTextStream ts(&file2);
2054 | ts << contents;
2055 | } else {
2056 | showRedMessage(tr("Cannot open file '%1' for writing").arg(fileName));
2057 | }
2058 | }
2059 | // check result by reading back
2060 | QFile file3(fileName);
2061 | file3.open(QIODevice::ReadOnly);
2062 | QByteArray ba = file3.readAll();
2063 | showBlackMessage(tr("\"%1\" %2 %3L, %4C written")
2064 | .arg(fileName).arg(exists ? " " : " [New] ")
2065 | .arg(ba.count('\n')).arg(ba.size()));
2066 | if (quitAll)
2067 | q->quitAllRequested(forced);
2068 | else if (quit)
2069 | q->quitRequested(forced);
2070 | } else {
2071 | showRedMessage(tr("Cannot open file '%1' for reading").arg(fileName));
2072 | }
2073 | } else if (cmd.startsWith("r ")) { // :r
2074 | m_currentFileName = cmd.mid(2);
2075 | QFile file(m_currentFileName);
2076 | file.open(QIODevice::ReadOnly);
2077 | QTextStream ts(&file);
2078 | QString data = ts.readAll();
2079 | EDITOR(setPlainText(data));
2080 | enterCommandMode();
2081 | showBlackMessage(tr("\"%1\" %2L, %3C")
2082 | .arg(m_currentFileName).arg(data.count('\n')).arg(data.size()));
2083 | } else if (cmd.startsWith(QLatin1Char('!'))) {
2084 | selectRange(beginLine, endLine);
2085 | QString command = cmd.mid(1).trimmed();
2086 | QString text = removeSelectedText();
2087 | QProcess proc;
2088 | proc.start(cmd.mid(1));
2089 | proc.waitForStarted();
2090 | proc.write(text.toUtf8());
2091 | proc.closeWriteChannel();
2092 | proc.waitForFinished();
2093 | QString result = QString::fromUtf8(proc.readAllStandardOutput());
2094 | m_tc.insertText(result);
2095 | leaveVisualMode();
2096 | setPosition(firstPositionInLine(beginLine));
2097 | enterCommandMode();
2098 | //qDebug() << "FILTER: " << command;
2099 | showBlackMessage(tr("%n lines filtered", 0, text.count('\n')));
2100 | } else if (cmd.startsWith(QLatin1Char('>'))) {
2101 | m_anchor = firstPositionInLine(beginLine);
2102 | setPosition(firstPositionInLine(endLine));
2103 | shiftRegionRight(1);
2104 | leaveVisualMode();
2105 | enterCommandMode();
2106 | showBlackMessage(tr("%n lines >ed %1 time", 0, (endLine - beginLine + 1)).arg(1));
2107 | } else if (cmd == "red" || cmd == "redo") { // :redo
2108 | redo();
2109 | enterCommandMode();
2110 | updateMiniBuffer();
2111 | } else if (reNormal.indexIn(cmd) != -1) { // :normal
2112 | enterCommandMode();
2113 | //qDebug() << "REPLAY: " << reNormal.cap(3);
2114 | replay(reNormal.cap(3), 1);
2115 | } else if (reSubstitute.indexIn(cmd) != -1) { // :substitute
2116 | QString needle = reSubstitute.cap(2);
2117 | const QString replacement = reSubstitute.cap(3);
2118 | QString flags = reSubstitute.cap(4);
2119 | const bool startOfLineOnly = needle.startsWith('^');
2120 | if (startOfLineOnly)
2121 | needle.remove(0, 1);
2122 | needle.replace('$', '\n');
2123 | needle.replace("\\\n", "\\$");
2124 | QRegExp pattern(needle);
2125 | if (flags.contains('i'))
2126 | pattern.setCaseSensitivity(Qt::CaseInsensitive);
2127 | const bool global = flags.contains('g');
2128 | beginEditBlock();
2129 | for (int line = beginLine; line <= endLine; ++line) {
2130 | const int start = firstPositionInLine(line);
2131 | const int end = lastPositionInLine(line);
2132 | for (int position = start; position <= end && position >= start; ) {
2133 | position = pattern.indexIn(m_tc.document()->toPlainText(), position);
2134 | if (startOfLineOnly && position != start)
2135 | break;
2136 | if (position != -1) {
2137 | m_tc.setPosition(position);
2138 | m_tc.movePosition(QTextCursor::NextCharacter,
2139 | KeepAnchor, pattern.matchedLength());
2140 | QString text = m_tc.selectedText();
2141 | if (text.endsWith(ParagraphSeparator)) {
2142 | text = replacement + "\n";
2143 | } else {
2144 | text.replace(ParagraphSeparator, "\n");
2145 | text.replace(pattern, replacement);
2146 | }
2147 | m_tc.removeSelectedText();
2148 | m_tc.insertText(text);
2149 | }
2150 | if (!global)
2151 | break;
2152 | }
2153 | }
2154 | endEditBlock();
2155 | enterCommandMode();
2156 | } else if (reSet.indexIn(cmd) != -1) { // :set
2157 | showBlackMessage(QString());
2158 | QString arg = reSet.cap(2);
2159 | SavedAction *act = theEmacsKeysSettings()->item(arg);
2160 | if (arg.isEmpty()) {
2161 | theEmacsKeysSetting(SettingsDialog)->trigger(QVariant());
2162 | } else if (act && act->value().type() == QVariant::Bool) {
2163 | // boolean config to be switched on
2164 | bool oldValue = act->value().toBool();
2165 | if (oldValue == false)
2166 | act->setValue(true);
2167 | else if (oldValue == true)
2168 | {} // nothing to do
2169 | } else if (act) {
2170 | // non-boolean to show
2171 | showBlackMessage(arg + '=' + act->value().toString());
2172 | } else if (arg.startsWith("no")
2173 | && (act = theEmacsKeysSettings()->item(arg.mid(2)))) {
2174 | // boolean config to be switched off
2175 | bool oldValue = act->value().toBool();
2176 | if (oldValue == true)
2177 | act->setValue(false);
2178 | else if (oldValue == false)
2179 | {} // nothing to do
2180 | } else if (arg.contains('=')) {
2181 | // non-boolean config to set
2182 | int p = arg.indexOf('=');
2183 | act = theEmacsKeysSettings()->item(arg.left(p));
2184 | if (act)
2185 | act->setValue(arg.mid(p + 1));
2186 | } else {
2187 | showRedMessage(tr("E512: Unknown option: ") + arg);
2188 | }
2189 | enterCommandMode();
2190 | updateMiniBuffer();
2191 | } else if (reHistory.indexIn(cmd) != -1) { // :history
2192 | QString arg = reSet.cap(3);
2193 | if (arg.isEmpty()) {
2194 | QString info;
2195 | info += "# command history\n";
2196 | int i = 0;
2197 | foreach (const QString &item, m_commandHistory) {
2198 | ++i;
2199 | info += QString("%1 %2\n").arg(i, -8).arg(item);
2200 | }
2201 | emit q->extraInformationChanged(info);
2202 | } else {
2203 | notImplementedYet();
2204 | }
2205 | enterCommandMode();
2206 | updateMiniBuffer();
2207 | } else {
2208 | enterCommandMode();
2209 | showRedMessage(tr("E492: Not an editor command: ") + cmd0);
2210 | }
2211 | }
2212 |
2213 | static void vimPatternToQtPattern(QString *needle, QTextDocument::FindFlags *flags)
2214 | {
2215 | // FIXME: Rough mapping of a common case
2216 | if (needle->startsWith("\\<") && needle->endsWith("\\>"))
2217 | (*flags) |= QTextDocument::FindWholeWords;
2218 | needle->replace("\\<", ""); // start of word
2219 | needle->replace("\\>", ""); // end of word
2220 | //qDebug() << "NEEDLE " << needle0 << needle;
2221 | }
2222 |
2223 | void EmacsKeysHandler::Private::search(const QString &needle0, bool forward)
2224 | {
2225 | showBlackMessage((forward ? '/' : '?') + needle0);
2226 | QTextCursor orig = m_tc;
2227 | QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
2228 | if (!forward)
2229 | flags |= QTextDocument::FindBackward;
2230 |
2231 | QString needle = needle0;
2232 | vimPatternToQtPattern(&needle, &flags);
2233 |
2234 | if (forward)
2235 | m_tc.movePosition(Right, MoveAnchor, 1);
2236 |
2237 | int oldLine = cursorLineInDocument() - cursorLineOnScreen();
2238 |
2239 | EDITOR(setTextCursor(m_tc));
2240 | if (EDITOR(find(needle, flags))) {
2241 | m_tc = EDITOR(textCursor());
2242 | m_tc.setPosition(m_tc.anchor());
2243 | // making this unconditional feels better, but is not "vim like"
2244 | if (oldLine != cursorLineInDocument() - cursorLineOnScreen())
2245 | scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() / 2);
2246 | highlightMatches(needle);
2247 | } else {
2248 | m_tc.setPosition(forward ? 0 : lastPositionInDocument() - 1);
2249 | EDITOR(setTextCursor(m_tc));
2250 | if (EDITOR(find(needle, flags))) {
2251 | m_tc = EDITOR(textCursor());
2252 | m_tc.setPosition(m_tc.anchor());
2253 | if (oldLine != cursorLineInDocument() - cursorLineOnScreen())
2254 | scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() / 2);
2255 | if (forward)
2256 | showRedMessage(tr("search hit BOTTOM, continuing at TOP"));
2257 | else
2258 | showRedMessage(tr("search hit TOP, continuing at BOTTOM"));
2259 | highlightMatches(needle);
2260 | } else {
2261 | m_tc = orig;
2262 | showRedMessage(tr("E486: Pattern not found: ") + needle);
2263 | highlightMatches(QString());
2264 | }
2265 | }
2266 | }
2267 |
2268 | void EmacsKeysHandler::Private::highlightMatches(const QString &needle0)
2269 | {
2270 | if (!hasConfig(ConfigHlSearch))
2271 | return;
2272 | if (needle0 == m_oldNeedle)
2273 | return;
2274 | m_oldNeedle = needle0;
2275 | m_searchSelections.clear();
2276 |
2277 | if (!needle0.isEmpty()) {
2278 | QTextCursor tc = m_tc;
2279 | tc.movePosition(StartOfDocument, MoveAnchor);
2280 |
2281 | QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
2282 | QString needle = needle0;
2283 | vimPatternToQtPattern(&needle, &flags);
2284 |
2285 |
2286 | EDITOR(setTextCursor(tc));
2287 | while (EDITOR(find(needle, flags))) {
2288 | tc = EDITOR(textCursor());
2289 | QTextEdit::ExtraSelection sel;
2290 | sel.cursor = tc;
2291 | sel.format = tc.blockCharFormat();
2292 | sel.format.setBackground(QColor(177, 177, 0));
2293 | m_searchSelections.append(sel);
2294 | tc.movePosition(Right, MoveAnchor);
2295 | EDITOR(setTextCursor(tc));
2296 | }
2297 | }
2298 | updateSelection();
2299 | }
2300 |
2301 | void EmacsKeysHandler::Private::moveToFirstNonBlankOnLine()
2302 | {
2303 | QTextDocument *doc = m_tc.document();
2304 | const QTextBlock &block = m_tc.block();
2305 | int firstPos = block.position();
2306 | for (int i = firstPos, n = firstPos + block.length(); i < n; ++i) {
2307 | if (!doc->characterAt(i).isSpace()) {
2308 | setPosition(i);
2309 | return;
2310 | }
2311 | }
2312 | setPosition(block.position());
2313 | }
2314 |
2315 | void EmacsKeysHandler::Private::indentRegion(QChar typedChar)
2316 | {
2317 | //int savedPos = anchor();
2318 | int beginLine = lineForPosition(anchor());
2319 | int endLine = lineForPosition(position());
2320 | if (beginLine > endLine)
2321 | qSwap(beginLine, endLine);
2322 |
2323 | int amount = 0;
2324 | emit q->indentRegion(&amount, beginLine, endLine, typedChar);
2325 |
2326 | setPosition(firstPositionInLine(beginLine));
2327 | moveToFirstNonBlankOnLine();
2328 | setTargetColumn();
2329 | setDotCommand("%1==", endLine - beginLine + 1);
2330 | }
2331 |
2332 | void EmacsKeysHandler::Private::shiftRegionRight(int repeat)
2333 | {
2334 | int beginLine = lineForPosition(anchor());
2335 | int endLine = lineForPosition(position());
2336 | if (beginLine > endLine)
2337 | qSwap(beginLine, endLine);
2338 | int len = config(ConfigShiftWidth).toInt() * repeat;
2339 | QString indent(len, ' ');
2340 | int firstPos = firstPositionInLine(beginLine);
2341 |
2342 | for (int line = beginLine; line <= endLine; ++line) {
2343 | setPosition(firstPositionInLine(line));
2344 | m_tc.insertText(indent);
2345 | }
2346 |
2347 | setPosition(firstPos);
2348 | moveToFirstNonBlankOnLine();
2349 | setTargetColumn();
2350 | setDotCommand("%1>>", endLine - beginLine + 1);
2351 | }
2352 |
2353 | void EmacsKeysHandler::Private::shiftRegionLeft(int repeat)
2354 | {
2355 | int beginLine = lineForPosition(anchor());
2356 | int endLine = lineForPosition(position());
2357 | if (beginLine > endLine)
2358 | qSwap(beginLine, endLine);
2359 | int shift = config(ConfigShiftWidth).toInt() * repeat;
2360 | int tab = config(ConfigTabStop).toInt();
2361 | int firstPos = firstPositionInLine(beginLine);
2362 |
2363 | for (int line = beginLine; line <= endLine; ++line) {
2364 | int pos = firstPositionInLine(line);
2365 | setPosition(pos);
2366 | setAnchor(pos);
2367 | QString text = m_tc.block().text();
2368 | int amount = 0;
2369 | int i = 0;
2370 | for (; i < text.size() && amount <= shift; ++i) {
2371 | if (text.at(i) == ' ')
2372 | amount++;
2373 | else if (text.at(i) == '\t')
2374 | amount += tab; // FIXME: take position into consideration
2375 | else
2376 | break;
2377 | }
2378 | setPosition(pos + i);
2379 | text = removeSelectedText();
2380 | setPosition(pos);
2381 | }
2382 |
2383 | setPosition(firstPos);
2384 | moveToFirstNonBlankOnLine();
2385 | setTargetColumn();
2386 | setDotCommand("%1<<", endLine - beginLine + 1);
2387 | }
2388 |
2389 | void EmacsKeysHandler::Private::moveToTargetColumn()
2390 | {
2391 | const QTextBlock &block = m_tc.block();
2392 | int col = m_tc.position() - m_tc.block().position();
2393 | if (col == m_targetColumn)
2394 | return;
2395 | //qDebug() << "CORRECTING COLUMN FROM: " << col << "TO" << m_targetColumn;
2396 | if (m_targetColumn == -1 || m_tc.block().length() <= m_targetColumn)
2397 | m_tc.setPosition(block.position() + block.length() - 1, KeepAnchor);
2398 | else
2399 | m_tc.setPosition(m_tc.block().position() + m_targetColumn, KeepAnchor);
2400 | }
2401 |
2402 | /* if simple is given:
2403 | * class 0: spaces
2404 | * class 1: non-spaces
2405 | * else
2406 | * class 0: spaces
2407 | * class 1: non-space-or-letter-or-number
2408 | * class 2: letter-or-number
2409 | */
2410 | static int charClass(QChar c, bool simple)
2411 | {
2412 | if (simple)
2413 | return c.isSpace() ? 0 : 1;
2414 | if (c.isLetterOrNumber() || c.unicode() == '_')
2415 | return 2;
2416 | return c.isSpace() ? 0 : 1;
2417 | }
2418 |
2419 | void EmacsKeysHandler::Private::moveToWordBoundary(bool simple, bool forward)
2420 | {
2421 | int repeat = count();
2422 | QTextDocument *doc = m_tc.document();
2423 | int n = forward ? lastPositionInDocument() - 1 : 0;
2424 | int lastClass = -1;
2425 | while (true) {
2426 | QChar c = doc->characterAt(m_tc.position() + (forward ? 1 : -1));
2427 | //qDebug() << "EXAMINING: " << c << " AT " << position();
2428 | int thisClass = charClass(c, simple);
2429 | if (thisClass != lastClass && lastClass != 0)
2430 | --repeat;
2431 | if (repeat == -1)
2432 | break;
2433 | lastClass = thisClass;
2434 | if (m_tc.position() == n)
2435 | break;
2436 | forward ? moveRight() : moveLeft();
2437 | }
2438 | setTargetColumn();
2439 | }
2440 |
2441 | void EmacsKeysHandler::Private::handleFfTt(int key)
2442 | {
2443 | // m_subsubmode \in { 'f', 'F', 't', 'T' }
2444 | bool forward = m_subsubdata == 'f' || m_subsubdata == 't';
2445 | int repeat = count();
2446 | QTextDocument *doc = m_tc.document();
2447 | QTextBlock block = m_tc.block();
2448 | int n = block.position();
2449 | if (forward)
2450 | n += block.length();
2451 | int pos = m_tc.position();
2452 | while (true) {
2453 | pos += forward ? 1 : -1;
2454 | if (pos == n)
2455 | break;
2456 | int uc = doc->characterAt(pos).unicode();
2457 | if (uc == ParagraphSeparator)
2458 | break;
2459 | if (uc == key)
2460 | --repeat;
2461 | if (repeat == 0) {
2462 | if (m_subsubdata == 't')
2463 | --pos;
2464 | else if (m_subsubdata == 'T')
2465 | ++pos;
2466 |
2467 | if (forward)
2468 | m_tc.movePosition(Right, KeepAnchor, pos - m_tc.position());
2469 | else
2470 | m_tc.movePosition(Left, KeepAnchor, m_tc.position() - pos);
2471 | break;
2472 | }
2473 | }
2474 | setTargetColumn();
2475 | }
2476 |
2477 | void EmacsKeysHandler::Private::moveToNextWord(bool simple)
2478 | {
2479 | // FIXME: 'w' should stop on empty lines, too
2480 | int repeat = count();
2481 | int n = lastPositionInDocument() - 1;
2482 | int lastClass = charClass(characterAtCursor(), simple);
2483 | while (true) {
2484 | QChar c = characterAtCursor();
2485 | int thisClass = charClass(c, simple);
2486 | if (thisClass != lastClass && thisClass != 0)
2487 | --repeat;
2488 | if (repeat == 0)
2489 | break;
2490 | lastClass = thisClass;
2491 | moveRight();
2492 | if (m_tc.position() == n)
2493 | break;
2494 | }
2495 | setTargetColumn();
2496 | }
2497 |
2498 | void EmacsKeysHandler::Private::moveToMatchingParanthesis()
2499 | {
2500 | bool moved = false;
2501 | bool forward = false;
2502 |
2503 | emit q->moveToMatchingParenthesis(&moved, &forward, &m_tc);
2504 |
2505 | if (moved && forward) {
2506 | if (m_submode == NoSubMode || m_submode == ZSubMode || m_submode == CapitalZSubMode || m_submode == RegisterSubMode)
2507 | m_tc.movePosition(Left, KeepAnchor, 1);
2508 | }
2509 | setTargetColumn();
2510 | }
2511 |
2512 | int EmacsKeysHandler::Private::cursorLineOnScreen() const
2513 | {
2514 | if (!editor())
2515 | return 0;
2516 | QRect rect = EDITOR(cursorRect());
2517 | return rect.y() / rect.height();
2518 | }
2519 |
2520 | int EmacsKeysHandler::Private::linesOnScreen() const
2521 | {
2522 | if (!editor())
2523 | return 1;
2524 | QRect rect = EDITOR(cursorRect());
2525 | return EDITOR(height()) / rect.height();
2526 | }
2527 |
2528 | int EmacsKeysHandler::Private::columnsOnScreen() const
2529 | {
2530 | if (!editor())
2531 | return 1;
2532 | QRect rect = EDITOR(cursorRect());
2533 | // qDebug() << "WID: " << EDITOR(width()) << "RECT: " << rect;
2534 | return EDITOR(width()) / rect.width();
2535 | }
2536 |
2537 | int EmacsKeysHandler::Private::cursorLineInDocument() const
2538 | {
2539 | return m_tc.block().blockNumber();
2540 | }
2541 |
2542 | int EmacsKeysHandler::Private::cursorColumnInDocument() const
2543 | {
2544 | return m_tc.position() - m_tc.block().position();
2545 | }
2546 |
2547 | int EmacsKeysHandler::Private::linesInDocument() const
2548 | {
2549 | return m_tc.isNull() ? 0 : m_tc.document()->blockCount();
2550 | }
2551 |
2552 | void EmacsKeysHandler::Private::scrollToLineInDocument(int line)
2553 | {
2554 | // FIXME: works only for QPlainTextEdit
2555 | QScrollBar *scrollBar = EDITOR(verticalScrollBar());
2556 | //qDebug() << "SCROLL: " << scrollBar->value() << line;
2557 | scrollBar->setValue(line);
2558 | }
2559 |
2560 | void EmacsKeysHandler::Private::scrollUp(int count)
2561 | {
2562 | scrollToLineInDocument(cursorLineInDocument() - cursorLineOnScreen() - count);
2563 | }
2564 |
2565 | int EmacsKeysHandler::Private::lastPositionInDocument() const
2566 | {
2567 | QTextBlock block = m_tc.document()->lastBlock();
2568 | return block.position() + block.length();
2569 | }
2570 |
2571 | QString EmacsKeysHandler::Private::lastSearchString() const
2572 | {
2573 | return m_searchHistory.empty() ? QString() : m_searchHistory.back();
2574 | }
2575 |
2576 | QString EmacsKeysHandler::Private::selectedText() const
2577 | {
2578 | QTextCursor tc = m_tc;
2579 | tc.setPosition(m_anchor, KeepAnchor);
2580 | return tc.selection().toPlainText();
2581 | }
2582 |
2583 | int EmacsKeysHandler::Private::firstPositionInLine(int line) const
2584 | {
2585 | return m_tc.document()->findBlockByNumber(line - 1).position();
2586 | }
2587 |
2588 | int EmacsKeysHandler::Private::lastPositionInLine(int line) const
2589 | {
2590 | QTextBlock block = m_tc.document()->findBlockByNumber(line - 1);
2591 | return block.position() + block.length() - 1;
2592 | }
2593 |
2594 | int EmacsKeysHandler::Private::lineForPosition(int pos) const
2595 | {
2596 | QTextCursor tc = m_tc;
2597 | tc.setPosition(pos);
2598 | return tc.block().blockNumber() + 1;
2599 | }
2600 |
2601 | void EmacsKeysHandler::Private::enterVisualMode(VisualMode visualMode)
2602 | {
2603 | setAnchor();
2604 | m_visualMode = visualMode;
2605 | m_marks['<'] = m_tc.position();
2606 | m_marks['>'] = m_tc.position();
2607 | updateMiniBuffer();
2608 | updateSelection();
2609 | }
2610 |
2611 | void EmacsKeysHandler::Private::leaveVisualMode()
2612 | {
2613 | m_visualMode = NoVisualMode;
2614 | updateMiniBuffer();
2615 | updateSelection();
2616 | }
2617 |
2618 | QWidget *EmacsKeysHandler::Private::editor() const
2619 | {
2620 | return m_textedit
2621 | ? static_cast(m_textedit)
2622 | : static_cast(m_plaintextedit);
2623 | }
2624 |
2625 | void EmacsKeysHandler::Private::undo()
2626 | {
2627 | int current = m_tc.document()->revision();
2628 | //endEditBlock();
2629 | EDITOR(undo());
2630 | //beginEditBlock();
2631 | int rev = m_tc.document()->revision();
2632 | if (current == rev)
2633 | showBlackMessage(tr("Already at oldest change"));
2634 | else
2635 | showBlackMessage(QString());
2636 | if (m_undoCursorPosition.contains(rev))
2637 | m_tc.setPosition(m_undoCursorPosition[rev]);
2638 | }
2639 |
2640 | void EmacsKeysHandler::Private::redo()
2641 | {
2642 | int current = m_tc.document()->revision();
2643 | //endEditBlock();
2644 | EDITOR(redo());
2645 | //beginEditBlock();
2646 | int rev = m_tc.document()->revision();
2647 | if (rev == current)
2648 | showBlackMessage(tr("Already at newest change"));
2649 | else
2650 | showBlackMessage(QString());
2651 | if (m_undoCursorPosition.contains(rev))
2652 | m_tc.setPosition(m_undoCursorPosition[rev]);
2653 | }
2654 |
2655 | QString EmacsKeysHandler::Private::removeSelectedText()
2656 | {
2657 | //qDebug() << "POS: " << position() << " ANCHOR: " << anchor() << m_tc.anchor();
2658 | int pos = m_tc.position();
2659 | if (pos == anchor())
2660 | return QString();
2661 | m_tc.setPosition(anchor(), MoveAnchor);
2662 | m_tc.setPosition(pos, KeepAnchor);
2663 | QString from = m_tc.selection().toPlainText();
2664 | m_tc.removeSelectedText();
2665 | setAnchor();
2666 | return from;
2667 | }
2668 |
2669 | void EmacsKeysHandler::Private::enterInsertMode()
2670 | {
2671 | EDITOR(setCursorWidth(m_cursorWidth));
2672 | EDITOR(setOverwriteMode(false));
2673 | m_mode = InsertMode;
2674 | m_lastInsertion.clear();
2675 | }
2676 |
2677 | void EmacsKeysHandler::Private::enterCommandMode()
2678 | {
2679 | EDITOR(setCursorWidth(m_cursorWidth));
2680 | EDITOR(setOverwriteMode(true));
2681 | m_mode = CommandMode;
2682 | }
2683 |
2684 | void EmacsKeysHandler::Private::enterExMode()
2685 | {
2686 | EDITOR(setCursorWidth(0));
2687 | EDITOR(setOverwriteMode(false));
2688 | m_mode = ExMode;
2689 | }
2690 |
2691 | void EmacsKeysHandler::Private::recordJump()
2692 | {
2693 | m_jumpListUndo.append(position());
2694 | m_jumpListRedo.clear();
2695 | UNDO_DEBUG("jumps: " << m_jumpListUndo);
2696 | }
2697 |
2698 | void EmacsKeysHandler::Private::recordNewUndo()
2699 | {
2700 | //endEditBlock();
2701 | UNDO_DEBUG("---- BREAK ----");
2702 | //beginEditBlock();
2703 | }
2704 |
2705 | void EmacsKeysHandler::Private::insertAutomaticIndentation(bool goingDown)
2706 | {
2707 | if (!hasConfig(ConfigAutoIndent))
2708 | return;
2709 | QTextBlock block = goingDown ? m_tc.block().previous() : m_tc.block().next();
2710 | QString text = block.text();
2711 | int pos = 0, n = text.size();
2712 | while (pos < n && text.at(pos).isSpace())
2713 | ++pos;
2714 | text.truncate(pos);
2715 | // FIXME: handle 'smartindent' and 'cindent'
2716 | m_tc.insertText(text);
2717 | m_justAutoIndented = text.size();
2718 | }
2719 |
2720 | bool EmacsKeysHandler::Private::removeAutomaticIndentation()
2721 | {
2722 | if (!hasConfig(ConfigAutoIndent) || m_justAutoIndented == 0)
2723 | return false;
2724 | m_tc.movePosition(StartOfLine, KeepAnchor);
2725 | m_tc.removeSelectedText();
2726 | m_lastInsertion.chop(m_justAutoIndented);
2727 | m_justAutoIndented = 0;
2728 | return true;
2729 | }
2730 |
2731 | void EmacsKeysHandler::Private::handleStartOfLine()
2732 | {
2733 | if (hasConfig(ConfigStartOfLine))
2734 | moveToFirstNonBlankOnLine();
2735 | }
2736 |
2737 | void EmacsKeysHandler::Private::replay(const QString &command, int n)
2738 | {
2739 | //qDebug() << "REPLAY: " << command;
2740 | m_inReplay = true;
2741 | for (int i = n; --i >= 0; ) {
2742 | foreach (QChar c, command) {
2743 | //qDebug() << " REPLAY: " << QString(c);
2744 | handleKey(c.unicode(), c.unicode(), QString(c));
2745 | }
2746 | }
2747 | m_inReplay = false;
2748 | }
2749 |
2750 | ///////////////////////////////////////////////////////////////////////
2751 | //
2752 | // EmacsKeysHandler
2753 | //
2754 | ///////////////////////////////////////////////////////////////////////
2755 |
2756 | EmacsKeysHandler::EmacsKeysHandler(QWidget *widget, QObject *parent)
2757 | : QObject(parent), d(new Private(this, widget))
2758 | {}
2759 |
2760 | EmacsKeysHandler::~EmacsKeysHandler()
2761 | {
2762 | delete d;
2763 | }
2764 |
2765 | bool EmacsKeysHandler::eventFilter(QObject *ob, QEvent *ev)
2766 | {
2767 | bool active = theEmacsKeysSetting(ConfigUseEmacsKeys)->value().toBool();
2768 |
2769 | if (active && ev->type() == QEvent::KeyPress && ob == d->editor()) {
2770 | QKeyEvent *kev = static_cast(ev);
2771 | KEY_DEBUG("KEYPRESS" << kev->key());
2772 | EventResult res = d->handleEvent(kev);
2773 | // returning false core the app see it
2774 | //KEY_DEBUG("HANDLED CODE:" << res);
2775 | //return res != EventPassedToCore;
2776 | //return true;
2777 | return res == EventHandled;
2778 | }
2779 |
2780 | if (active && ev->type() == QEvent::ShortcutOverride && ob == d->editor()) {
2781 | QKeyEvent *kev = static_cast(ev);
2782 | if (d->wantsOverride(kev)) {
2783 | KEY_DEBUG("OVERRIDING SHORTCUT" << kev->key());
2784 | ev->accept(); // accepting means "don't run the shortcuts"
2785 | return true;
2786 | }
2787 | KEY_DEBUG("NO SHORTCUT OVERRIDE" << kev->key());
2788 | return true;
2789 | }
2790 |
2791 | return QObject::eventFilter(ob, ev);
2792 | }
2793 |
2794 | void EmacsKeysHandler::installEventFilter()
2795 | {
2796 | d->installEventFilter();
2797 | }
2798 |
2799 | void EmacsKeysHandler::setupWidget()
2800 | {
2801 | d->setupWidget();
2802 | }
2803 |
2804 | void EmacsKeysHandler::restoreWidget()
2805 | {
2806 | d->restoreWidget();
2807 | }
2808 |
2809 | void EmacsKeysHandler::handleCommand(const QString &cmd)
2810 | {
2811 | d->handleCommand(cmd);
2812 | }
2813 |
2814 | void EmacsKeysHandler::setCurrentFileName(const QString &fileName)
2815 | {
2816 | d->m_currentFileName = fileName;
2817 | }
2818 |
2819 | QWidget *EmacsKeysHandler::widget()
2820 | {
2821 | return d->editor();
2822 | }
2823 |
2824 | } // namespace Internal
2825 | } // namespace EmacsKeys
2826 |
--------------------------------------------------------------------------------
/emacskeyshandler.h:
--------------------------------------------------------------------------------
1 | /**************************************************************************
2 | **
3 | ** GNU Lesser General Public License Usage
4 | **
5 | ** Alternatively, this file may be used under the terms of the GNU Lesser
6 | ** General Public License version 2.1 as published by the Free Software
7 | ** Foundation and appearing in the file LICENSE.LGPL included in the
8 | ** packaging of this file. Please review the following information to
9 | ** ensure the GNU Lesser General Public License version 2.1 requirements
10 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
11 | **
12 | ** If you are unsure which license is appropriate for your use, please
13 | ** contact the sales department at http://www.qtsoftware.com/contact.
14 | **
15 | **************************************************************************/
16 |
17 | #ifndef EMACSKEYS_HANDLER_H
18 | #define EMACSKEYS_HANDLER_H
19 |
20 | #include "emacskeysactions.h"
21 |
22 | #include
23 | #include
24 |
25 | namespace EmacsKeys {
26 | namespace Internal {
27 |
28 | class EmacsKeysHandler : public QObject
29 | {
30 | Q_OBJECT
31 |
32 | public:
33 | EmacsKeysHandler(QWidget *widget, QObject *parent = 0);
34 | ~EmacsKeysHandler();
35 |
36 | QWidget *widget();
37 |
38 | public slots:
39 | void setCurrentFileName(const QString &fileName);
40 |
41 | // This executes an "ex" style command taking context
42 | // information from widget;
43 | void handleCommand(const QString &cmd);
44 |
45 | void installEventFilter();
46 |
47 | // Convenience
48 | void setupWidget();
49 | void restoreWidget();
50 |
51 | signals:
52 | void commandBufferChanged(const QString &msg);
53 | void statusDataChanged(const QString &msg);
54 | void extraInformationChanged(const QString &msg);
55 | void quitRequested(bool force);
56 | void quitAllRequested(bool force);
57 | void selectionChanged(const QList &selection);
58 | void writeFileRequested(bool *handled,
59 | const QString &fileName, const QString &contents);
60 | void moveToMatchingParenthesis(bool *moved, bool *forward, QTextCursor *cursor);
61 | void indentRegion(int *amount, int beginLine, int endLine, QChar typedChar);
62 | void completionRequested();
63 | void windowCommandRequested(int key);
64 | void findRequested(bool reverse);
65 | void findNextRequested(bool reverse);
66 |
67 | public:
68 | class Private;
69 |
70 | private:
71 | bool eventFilter(QObject *ob, QEvent *ev);
72 | friend class Private;
73 | Private *d;
74 | };
75 |
76 | } // namespace Internal
77 | } // namespace EmacsKeys
78 |
79 | #endif // EMACSKEYS_H
80 |
--------------------------------------------------------------------------------
/emacskeysoptions.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | EmacsKeysOptionPage
4 |
5 |
6 |
7 | 0
8 | 0
9 | 394
10 | 322
11 |
12 |
13 |
14 | -
15 |
16 |
17 | Use EmacsKeys
18 |
19 |
20 |
21 | -
22 |
23 |
24 | Vim style settings
25 |
26 |
27 |
-
28 |
29 |
30 | vim's "expandtab" option
31 |
32 |
33 | Expand tabulators:
34 |
35 |
36 |
37 | -
38 |
39 |
40 |
41 |
42 |
43 |
44 | -
45 |
46 |
47 | Highlight search results:
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 |
57 |
58 | -
59 |
60 |
61 | Shift width:
62 |
63 |
64 |
65 | -
66 |
67 |
68 | -
69 |
70 |
71 | Smart tabulators:
72 |
73 |
74 |
75 | -
76 |
77 |
78 |
79 |
80 |
81 |
82 | -
83 |
84 |
85 | Start of line:
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 |
95 |
96 | -
97 |
98 |
99 | vim's "tabstop" option
100 |
101 |
102 | Tabulator size:
103 |
104 |
105 |
106 | -
107 |
108 |
109 | -
110 |
111 |
112 | Backspace:
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
125 |
126 | -
127 |
128 |
129 | VIM's "autoindent" option
130 |
131 |
132 | Automatic indentation:
133 |
134 |
135 |
136 | -
137 |
138 |
139 | Incremental search:
140 |
141 |
142 |
143 | -
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | -
154 |
155 |
-
156 |
157 |
158 | Copy text editor settings
159 |
160 |
161 |
162 | -
163 |
164 |
165 | Set Qt style
166 |
167 |
168 |
169 | -
170 |
171 |
172 | Set plain style
173 |
174 |
175 |
176 | -
177 |
178 |
179 | Qt::Horizontal
180 |
181 |
182 |
183 | 40
184 | 20
185 |
186 |
187 |
188 |
189 |
190 |
191 | -
192 |
193 |
194 | Qt::Vertical
195 |
196 |
197 |
198 | 20
199 | 1
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
--------------------------------------------------------------------------------
/emacskeysplugin.cpp:
--------------------------------------------------------------------------------
1 | /**************************************************************************
2 | **
3 | ** GNU Lesser General Public License Usage
4 | **
5 | ** Alternatively, this file may be used under the terms of the GNU Lesser
6 | ** General Public License version 2.1 as published by the Free Software
7 | ** Foundation and appearing in the file LICENSE.LGPL included in the
8 | ** packaging of this file. Please review the following information to
9 | ** ensure the GNU Lesser General Public License version 2.1 requirements
10 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
11 | **
12 | ** If you are unsure which license is appropriate for your use, please
13 | ** contact the sales department at http://www.qtsoftware.com/contact.
14 | **
15 | **************************************************************************/
16 |
17 | #include "emacskeysplugin.h"
18 |
19 | #include "emacskeyshandler.h"
20 | #include "ui_emacskeysoptions.h"
21 |
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 |
34 | #include
35 | #include
36 |
37 | #include
38 | #include
39 | #include
40 | #include
41 | #include
42 | #include
43 | #include
44 | #include
45 |
46 | #include
47 |
48 | #include
49 | #include
50 |
51 | #include
52 |
53 | #include
54 | #include
55 | #include
56 | #include
57 | #include
58 | #include
59 |
60 | #include
61 | #include
62 | #include
63 | #include
64 | #include
65 | #include
66 |
67 |
68 | using namespace EmacsKeys::Internal;
69 | using namespace TextEditor;
70 | using namespace Core;
71 | using namespace ProjectExplorer;
72 |
73 |
74 | namespace EmacsKeys {
75 | namespace Constants {
76 |
77 | const char * const INSTALL_HANDLER = "TextEditor.EmacsKeysHandler";
78 | const char * const MINI_BUFFER = "TextEditor.EmacsKeysMiniBuffer";
79 |
80 | } // namespace Constants
81 | } // namespace EmacsKeys
82 |
83 |
84 | ///////////////////////////////////////////////////////////////////////
85 | //
86 | // EmacsKeysOptionPage
87 | //
88 | ///////////////////////////////////////////////////////////////////////
89 |
90 | namespace EmacsKeys {
91 | namespace Internal {
92 |
93 | class EmacsKeysOptionPage : public Core::IOptionsPage
94 | {
95 | Q_OBJECT
96 |
97 | public:
98 | EmacsKeysOptionPage() {}
99 |
100 | // IOptionsPage
101 | QString id() const { return QLatin1String("General"); }
102 | QString trName() const { return tr("General"); }
103 | QString category() const { return QLatin1String("EmacsKeys"); }
104 | QString trCategory() const { return tr("EmacsKeys"); }
105 |
106 | QWidget *createPage(QWidget *parent);
107 | void apply() { m_group.apply(ICore::instance()->settings()); }
108 | void finish() { m_group.finish(); }
109 |
110 | private slots:
111 | void copyTextEditorSettings();
112 | void setQtStyle();
113 | void setPlainStyle();
114 |
115 | private:
116 | friend class DebuggerPlugin;
117 | Ui::EmacsKeysOptionPage m_ui;
118 |
119 | Core::Utils::SavedActionSet m_group;
120 | };
121 |
122 | QWidget *EmacsKeysOptionPage::createPage(QWidget *parent)
123 | {
124 | QWidget *w = new QWidget(parent);
125 | m_ui.setupUi(w);
126 |
127 | m_group.clear();
128 | m_group.insert(theEmacsKeysSetting(ConfigUseEmacsKeys),
129 | m_ui.checkBoxUseEmacsKeys);
130 |
131 | m_group.insert(theEmacsKeysSetting(ConfigExpandTab),
132 | m_ui.checkBoxExpandTab);
133 | m_group.insert(theEmacsKeysSetting(ConfigHlSearch),
134 | m_ui.checkBoxHlSearch);
135 | m_group.insert(theEmacsKeysSetting(ConfigShiftWidth),
136 | m_ui.lineEditShiftWidth);
137 |
138 | m_group.insert(theEmacsKeysSetting(ConfigSmartTab),
139 | m_ui.checkBoxSmartTab);
140 | m_group.insert(theEmacsKeysSetting(ConfigStartOfLine),
141 | m_ui.checkBoxStartOfLine);
142 | m_group.insert(theEmacsKeysSetting(ConfigTabStop),
143 | m_ui.lineEditTabStop);
144 | m_group.insert(theEmacsKeysSetting(ConfigBackspace),
145 | m_ui.lineEditBackspace);
146 |
147 | m_group.insert(theEmacsKeysSetting(ConfigAutoIndent),
148 | m_ui.checkBoxAutoIndent);
149 | m_group.insert(theEmacsKeysSetting(ConfigIncSearch),
150 | m_ui.checkBoxIncSearch);
151 |
152 | connect(m_ui.pushButtonCopyTextEditorSettings, SIGNAL(clicked()),
153 | this, SLOT(copyTextEditorSettings()));
154 | connect(m_ui.pushButtonSetQtStyle, SIGNAL(clicked()),
155 | this, SLOT(setQtStyle()));
156 | connect(m_ui.pushButtonSetPlainStyle, SIGNAL(clicked()),
157 | this, SLOT(setPlainStyle()));
158 |
159 | return w;
160 | }
161 |
162 | void EmacsKeysOptionPage::copyTextEditorSettings()
163 | {
164 | TextEditor::TabSettings ts =
165 | TextEditor::TextEditorSettings::instance()->tabSettings();
166 |
167 | m_ui.checkBoxExpandTab->setChecked(ts.m_spacesForTabs);
168 | m_ui.lineEditTabStop->setText(QString::number(ts.m_tabSize));
169 | m_ui.lineEditShiftWidth->setText(QString::number(ts.m_indentSize));
170 | m_ui.checkBoxSmartTab->setChecked(ts.m_smartBackspace);
171 | m_ui.checkBoxAutoIndent->setChecked(ts.m_autoIndent);
172 | // FIXME: Not present in core
173 | //m_ui.checkBoxIncSearch->setChecked(ts.m_incSearch);
174 | }
175 |
176 | void EmacsKeysOptionPage::setQtStyle()
177 | {
178 | m_ui.checkBoxExpandTab->setChecked(true);
179 | m_ui.lineEditTabStop->setText("4");
180 | m_ui.lineEditShiftWidth->setText("4");
181 | m_ui.checkBoxSmartTab->setChecked(true);
182 | m_ui.checkBoxAutoIndent->setChecked(true);
183 | m_ui.checkBoxIncSearch->setChecked(true);
184 | m_ui.lineEditBackspace->setText("indent,eol,start");
185 | }
186 |
187 | void EmacsKeysOptionPage::setPlainStyle()
188 | {
189 | m_ui.checkBoxExpandTab->setChecked(false);
190 | m_ui.lineEditTabStop->setText("8");
191 | m_ui.lineEditShiftWidth->setText("8");
192 | m_ui.checkBoxSmartTab->setChecked(false);
193 | m_ui.checkBoxAutoIndent->setChecked(false);
194 | m_ui.checkBoxIncSearch->setChecked(false);
195 | m_ui.lineEditBackspace->setText(QString());
196 | }
197 |
198 | } // namespace Internal
199 | } // namespace EmacsKeys
200 |
201 |
202 | ///////////////////////////////////////////////////////////////////////
203 | //
204 | // EmacsKeysPluginPrivate
205 | //
206 | ///////////////////////////////////////////////////////////////////////
207 |
208 | namespace EmacsKeys {
209 | namespace Internal {
210 |
211 | class EmacsKeysPluginPrivate : public QObject
212 | {
213 | Q_OBJECT
214 |
215 | public:
216 | EmacsKeysPluginPrivate(EmacsKeysPlugin *);
217 | ~EmacsKeysPluginPrivate();
218 | friend class EmacsKeysPlugin;
219 |
220 | bool initialize();
221 | void shutdown();
222 |
223 | private slots:
224 | void editorOpened(Core::IEditor *);
225 | void editorAboutToClose(Core::IEditor *);
226 |
227 | void setUseEmacsKeys(const QVariant &value);
228 | void quitEmacsKeys();
229 | void triggerCompletions();
230 | void windowCommand(int key);
231 | void find(bool reverse);
232 | void findNext(bool reverse);
233 | void showSettingsDialog();
234 |
235 | void showCommandBuffer(const QString &contents);
236 | void showExtraInformation(const QString &msg);
237 | void changeSelection(const QList &selections);
238 | void writeFile(bool *handled, const QString &fileName, const QString &contents);
239 | void quitFile(bool forced);
240 | void quitAllFiles(bool forced);
241 | void moveToMatchingParenthesis(bool *moved, bool *forward, QTextCursor *cursor);
242 | void indentRegion(int *amount, int beginLine, int endLine, QChar typedChar);
243 |
244 | private:
245 | EmacsKeysPlugin *q;
246 | EmacsKeysOptionPage *m_emacsKeysOptionsPage;
247 | QHash m_editorToHandler;
248 |
249 | void triggerAction(const QString& code);
250 | };
251 |
252 | } // namespace Internal
253 | } // namespace EmacsKeys
254 |
255 | EmacsKeysPluginPrivate::EmacsKeysPluginPrivate(EmacsKeysPlugin *plugin)
256 | {
257 | q = plugin;
258 | m_emacsKeysOptionsPage = 0;
259 | }
260 |
261 | EmacsKeysPluginPrivate::~EmacsKeysPluginPrivate()
262 | {
263 | }
264 |
265 | void EmacsKeysPluginPrivate::shutdown()
266 | {
267 | q->removeObject(m_emacsKeysOptionsPage);
268 | delete m_emacsKeysOptionsPage;
269 | m_emacsKeysOptionsPage = 0;
270 | theEmacsKeysSettings()->writeSettings(Core::ICore::instance()->settings());
271 | delete theEmacsKeysSettings();
272 | }
273 |
274 | bool EmacsKeysPluginPrivate::initialize()
275 | {
276 | Core::ActionManager *actionManager = Core::ICore::instance()->actionManager();
277 | QTC_ASSERT(actionManager, return false);
278 |
279 |
280 |
281 | m_emacsKeysOptionsPage = new EmacsKeysOptionPage;
282 | q->addObject(m_emacsKeysOptionsPage);
283 | theEmacsKeysSettings()->readSettings(Core::ICore::instance()->settings());
284 |
285 | QList globalcontext;
286 | globalcontext << Core::Constants::C_GLOBAL_ID;
287 | Core::Command *cmd = 0;
288 | cmd = actionManager->registerAction(theEmacsKeysSetting(ConfigUseEmacsKeys),
289 | Constants::INSTALL_HANDLER, globalcontext);
290 |
291 | ActionContainer *advancedMenu =
292 | actionManager->actionContainer(Core::Constants::M_EDIT_ADVANCED);
293 | advancedMenu->addAction(cmd, Core::Constants::G_EDIT_EDITOR);
294 |
295 | ActionContainer* actionContainer = actionManager->actionContainer(Core::Constants::M_FILE);
296 | QMenu* menu = actionContainer->menu();
297 | menu->setTitle("F&ile");
298 |
299 | actionContainer = actionManager->actionContainer(Core::Constants::M_EDIT);
300 | menu = actionContainer->menu();
301 | menu->setTitle("Edit");
302 |
303 | QHash replacements;
304 | replacements["&Build"] = "Build";
305 | replacements["&Debug"] = "Debug";
306 | replacements["&Tools"] = "Tools";
307 | replacements["&Window"] = "Window";
308 |
309 | actionContainer = actionManager->actionContainer(Core::Constants::MENU_BAR);
310 | qDebug() << menu->parentWidget()->metaObject()->className() << endl;
311 | QMainWindow* mainWindow = qobject_cast(menu->parentWidget());
312 | foreach(QObject* child, mainWindow->children()) {
313 | if (QMenu* menuChild = qobject_cast(child)) {
314 | if (replacements.contains(menuChild->title())) {
315 | menuChild->setTitle(replacements[menuChild->title()]);
316 | }
317 | }
318 | }
319 |
320 | Command* command = actionManager->command("QtCreator.QuickOpen");
321 | command->setKeySequence(QKeySequence("Ctrl+X,B"));
322 |
323 | command = actionManager->command(TextEditor::Constants::COMPLETE_THIS);
324 | command->setKeySequence(QKeySequence("Alt+/"));
325 |
326 | command = actionManager->command("QtCreator.Sidebar.File System");
327 | command->setKeySequence(QKeySequence("Ctrl+X,Ctrl+B"));
328 |
329 | // EditorManager
330 | QObject *editorManager = Core::ICore::instance()->editorManager();
331 | connect(editorManager, SIGNAL(editorAboutToClose(Core::IEditor*)),
332 | this, SLOT(editorAboutToClose(Core::IEditor*)));
333 | connect(editorManager, SIGNAL(editorOpened(Core::IEditor*)),
334 | this, SLOT(editorOpened(Core::IEditor*)));
335 |
336 | connect(theEmacsKeysSetting(SettingsDialog), SIGNAL(triggered()),
337 | this, SLOT(showSettingsDialog()));
338 | connect(theEmacsKeysSetting(ConfigUseEmacsKeys), SIGNAL(valueChanged(QVariant)),
339 | this, SLOT(setUseEmacsKeys(QVariant)));
340 |
341 | return true;
342 | }
343 |
344 | void EmacsKeysPluginPrivate::showSettingsDialog()
345 | {
346 | Core::ICore::instance()->showOptionsDialog("EmacsKeys", "General");
347 | }
348 |
349 | void EmacsKeysPluginPrivate::triggerAction(const QString& code)
350 | {
351 | Core::ActionManager *am = Core::ICore::instance()->actionManager();
352 | QTC_ASSERT(am, return);
353 | Core::Command *cmd = am->command(code);
354 | QTC_ASSERT(cmd, return);
355 | QAction *action = cmd->action();
356 | QTC_ASSERT(action, return);
357 | action->trigger();
358 | }
359 |
360 | void EmacsKeysPluginPrivate::windowCommand(int key)
361 | {
362 | #define control(n) (256 + n)
363 | QString code;
364 | switch (key) {
365 | case 'c': case 'C': case control('c'):
366 | code = Core::Constants::CLOSE;
367 | break;
368 | case 'n': case 'N': case control('n'):
369 | code = Core::Constants::GOTONEXT;
370 | break;
371 | case 'o': case 'O': case control('o'):
372 | code = Core::Constants::REMOVE_ALL_SPLITS;
373 | code = Core::Constants::REMOVE_CURRENT_SPLIT;
374 | break;
375 | case 'p': case 'P': case control('p'):
376 | code = Core::Constants::GOTOPREV;
377 | break;
378 | case 's': case 'S': case control('s'):
379 | code = Core::Constants::SPLIT;
380 | break;
381 | case 'w': case 'W': case control('w'):
382 | code = Core::Constants::GOTO_OTHER_SPLIT;
383 | break;
384 | }
385 | #undef control
386 | qDebug() << "RUNNING WINDOW COMMAND: " << key << code;
387 | if (code.isEmpty()) {
388 | qDebug() << "UNKNOWN WINDOWS COMMAND: " << key;
389 | return;
390 | }
391 | triggerAction(code);
392 | }
393 |
394 | void EmacsKeysPluginPrivate::find(bool reverse)
395 | {
396 | Q_UNUSED(reverse); // TODO: Creator needs an action for find in reverse.
397 | triggerAction(Find::Constants::FIND_IN_DOCUMENT);
398 | }
399 |
400 | void EmacsKeysPluginPrivate::findNext(bool reverse)
401 | {
402 | if (reverse)
403 | triggerAction(Find::Constants::FIND_PREVIOUS);
404 | else
405 | triggerAction(Find::Constants::FIND_NEXT);
406 | }
407 |
408 | void EmacsKeysPluginPrivate::editorOpened(Core::IEditor *editor)
409 | {
410 | if (!editor)
411 | return;
412 |
413 | QWidget *widget = editor->widget();
414 | if (!widget)
415 | return;
416 |
417 | // we can only handle QTextEdit and QPlainTextEdit
418 | if (!qobject_cast(widget) && !qobject_cast(widget))
419 | return;
420 |
421 | //qDebug() << "OPENING: " << editor << editor->widget()
422 | // << "MODE: " << theEmacsKeysSetting(ConfigUseEmacsKeys)->value();
423 |
424 | EmacsKeysHandler *handler = new EmacsKeysHandler(widget, widget);
425 | m_editorToHandler[editor] = handler;
426 |
427 | connect(handler, SIGNAL(extraInformationChanged(QString)),
428 | this, SLOT(showExtraInformation(QString)));
429 | connect(handler, SIGNAL(commandBufferChanged(QString)),
430 | this, SLOT(showCommandBuffer(QString)));
431 | connect(handler, SIGNAL(quitRequested(bool)),
432 | this, SLOT(quitFile(bool)), Qt::QueuedConnection);
433 | connect(handler, SIGNAL(quitAllRequested(bool)),
434 | this, SLOT(quitAllFiles(bool)), Qt::QueuedConnection);
435 | connect(handler, SIGNAL(writeFileRequested(bool*,QString,QString)),
436 | this, SLOT(writeFile(bool*,QString,QString)));
437 | connect(handler, SIGNAL(selectionChanged(QList)),
438 | this, SLOT(changeSelection(QList)));
439 | connect(handler, SIGNAL(moveToMatchingParenthesis(bool*,bool*,QTextCursor*)),
440 | this, SLOT(moveToMatchingParenthesis(bool*,bool*,QTextCursor*)));
441 | connect(handler, SIGNAL(indentRegion(int*,int,int,QChar)),
442 | this, SLOT(indentRegion(int*,int,int,QChar)));
443 | connect(handler, SIGNAL(completionRequested()),
444 | this, SLOT(triggerCompletions()));
445 | connect(handler, SIGNAL(windowCommandRequested(int)),
446 | this, SLOT(windowCommand(int)));
447 | connect(handler, SIGNAL(findRequested(bool)),
448 | this, SLOT(find(bool)));
449 | connect(handler, SIGNAL(findNextRequested(bool)),
450 | this, SLOT(findNext(bool)));
451 |
452 | handler->setCurrentFileName(editor->file()->fileName());
453 | handler->installEventFilter();
454 |
455 | // pop up the bar
456 | if (theEmacsKeysSetting(ConfigUseEmacsKeys)->value().toBool())
457 | showCommandBuffer("");
458 | }
459 |
460 | void EmacsKeysPluginPrivate::editorAboutToClose(Core::IEditor *editor)
461 | {
462 | //qDebug() << "CLOSING: " << editor << editor->widget();
463 | m_editorToHandler.remove(editor);
464 | }
465 |
466 | void EmacsKeysPluginPrivate::setUseEmacsKeys(const QVariant &value)
467 | {
468 | //qDebug() << "SET USE EMACSKEYS" << value;
469 | bool on = value.toBool();
470 | if (on) {
471 | Core::EditorManager::instance()->showEditorStatusBar(
472 | QLatin1String(Constants::MINI_BUFFER),
473 | "vi emulation mode. Type :q to leave. Use , Ctrl-R to trigger run.",
474 | tr("Quit EmacsKeys"), this, SLOT(quitEmacsKeys()));
475 | foreach (Core::IEditor *editor, m_editorToHandler.keys())
476 | m_editorToHandler[editor]->setupWidget();
477 | } else {
478 | Core::EditorManager::instance()->hideEditorStatusBar(
479 | QLatin1String(Constants::MINI_BUFFER));
480 | foreach (Core::IEditor *editor, m_editorToHandler.keys())
481 | m_editorToHandler[editor]->restoreWidget();
482 | }
483 | }
484 |
485 | void EmacsKeysPluginPrivate::triggerCompletions()
486 | {
487 | EmacsKeysHandler *handler = qobject_cast(sender());
488 | if (!handler)
489 | return;
490 | if (BaseTextEditor *bt = qobject_cast(handler->widget()))
491 | TextEditor::Internal::CompletionSupport::instance()->
492 | autoComplete(bt->editableInterface(), false);
493 | // bt->triggerCompletions();
494 | }
495 |
496 | void EmacsKeysPluginPrivate::quitFile(bool forced)
497 | {
498 | EmacsKeysHandler *handler = qobject_cast(sender());
499 | if (!handler)
500 | return;
501 | QList editors;
502 | editors.append(m_editorToHandler.key(handler));
503 | Core::EditorManager::instance()->closeEditors(editors, !forced);
504 | }
505 |
506 | void EmacsKeysPluginPrivate::quitAllFiles(bool forced)
507 | {
508 | Core::EditorManager::instance()->closeAllEditors(!forced);
509 | }
510 |
511 | void EmacsKeysPluginPrivate::writeFile(bool *handled,
512 | const QString &fileName, const QString &contents)
513 | {
514 | Q_UNUSED(contents);
515 |
516 | EmacsKeysHandler *handler = qobject_cast(sender());
517 | if (!handler)
518 | return;
519 |
520 | Core::IEditor *editor = m_editorToHandler.key(handler);
521 | if (editor && editor->file()->fileName() == fileName) {
522 | // Handle that as a special case for nicer interaction with core
523 | Core::IFile *file = editor->file();
524 | Core::ICore::instance()->fileManager()->blockFileChange(file);
525 | file->save(fileName);
526 | Core::ICore::instance()->fileManager()->unblockFileChange(file);
527 | *handled = true;
528 | }
529 | }
530 |
531 | void EmacsKeysPluginPrivate::moveToMatchingParenthesis(bool *moved, bool *forward,
532 | QTextCursor *cursor)
533 | {
534 | *moved = false;
535 |
536 | bool undoFakeEOL = false;
537 | if (cursor->atBlockEnd() && cursor->block().length() > 1) {
538 | cursor->movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1);
539 | undoFakeEOL = true;
540 | }
541 | TextEditor::TextBlockUserData::MatchType match
542 | = TextEditor::TextBlockUserData::matchCursorForward(cursor);
543 | if (match == TextEditor::TextBlockUserData::Match) {
544 | *moved = true;
545 | *forward = true;
546 | } else {
547 | if (undoFakeEOL)
548 | cursor->movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 1);
549 | if (match == TextEditor::TextBlockUserData::NoMatch) {
550 | // backward matching is according to the character before the cursor
551 | bool undoMove = false;
552 | if (!cursor->atBlockEnd()) {
553 | cursor->movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 1);
554 | undoMove = true;
555 | }
556 | match = TextEditor::TextBlockUserData::matchCursorBackward(cursor);
557 | if (match == TextEditor::TextBlockUserData::Match) {
558 | *moved = true;
559 | *forward = false;
560 | } else if (undoMove) {
561 | cursor->movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1);
562 | }
563 | }
564 | }
565 | }
566 |
567 | void EmacsKeysPluginPrivate::indentRegion(int *amount, int beginLine, int endLine,
568 | QChar typedChar)
569 | {
570 | EmacsKeysHandler *handler = qobject_cast(sender());
571 | if (!handler)
572 | return;
573 |
574 | BaseTextEditor *bt = qobject_cast(handler->widget());
575 | if (!bt)
576 | return;
577 |
578 | TextEditor::TabSettings tabSettings =
579 | TextEditor::TextEditorSettings::instance()->tabSettings();
580 | typedef SharedTools::Indenter Indenter;
581 | Indenter &indenter = Indenter::instance();
582 | indenter.setIndentSize(tabSettings.m_indentSize);
583 | indenter.setTabSize(tabSettings.m_tabSize);
584 |
585 | const QTextDocument *doc = bt->document();
586 | QTextBlock begin = doc->findBlockByNumber(beginLine);
587 | QTextBlock end = doc->findBlockByNumber(endLine);
588 | const TextEditor::TextBlockIterator docStart(doc->begin());
589 | QTextBlock cur = begin;
590 | do {
591 | if (typedChar == 0 && cur.text().simplified().isEmpty()) {
592 | *amount = 0;
593 | if (cur != end) {
594 | QTextCursor cursor(cur);
595 | while (!cursor.atBlockEnd())
596 | cursor.deleteChar();
597 | }
598 | } else {
599 | const TextEditor::TextBlockIterator current(cur);
600 | const TextEditor::TextBlockIterator next(cur.next());
601 | *amount = indenter.indentForBottomLine(current, docStart, next, typedChar);
602 | if (cur != end)
603 | tabSettings.indentLine(cur, *amount);
604 | }
605 | if (cur != end)
606 | cur = cur.next();
607 | } while (cur != end);
608 | }
609 |
610 | void EmacsKeysPluginPrivate::quitEmacsKeys()
611 | {
612 | theEmacsKeysSetting(ConfigUseEmacsKeys)->setValue(false);
613 | }
614 |
615 | void EmacsKeysPluginPrivate::showCommandBuffer(const QString &contents)
616 | {
617 | //qDebug() << "SHOW COMMAND BUFFER" << contents;
618 | Core::EditorManager::instance()->showEditorStatusBar(
619 | QLatin1String(Constants::MINI_BUFFER), contents,
620 | tr("Quit EmacsKeys"), this, SLOT(quitEmacsKeys()));
621 | }
622 |
623 | void EmacsKeysPluginPrivate::showExtraInformation(const QString &text)
624 | {
625 | EmacsKeysHandler *handler = qobject_cast(sender());
626 | if (handler)
627 | QMessageBox::information(handler->widget(), tr("EmacsKeys Information"), text);
628 | }
629 |
630 | void EmacsKeysPluginPrivate::changeSelection
631 | (const QList &selection)
632 | {
633 | if (EmacsKeysHandler *handler = qobject_cast(sender()))
634 | if (BaseTextEditor *bt = qobject_cast(handler->widget()))
635 | bt->setExtraSelections(BaseTextEditor::FakeVimSelection, selection);
636 | }
637 |
638 |
639 | ///////////////////////////////////////////////////////////////////////
640 | //
641 | // EmacsKeysPlugin
642 | //
643 | ///////////////////////////////////////////////////////////////////////
644 |
645 | EmacsKeysPlugin::EmacsKeysPlugin()
646 | : d(new EmacsKeysPluginPrivate(this))
647 | {}
648 |
649 | EmacsKeysPlugin::~EmacsKeysPlugin()
650 | {
651 | delete d;
652 | }
653 |
654 | bool EmacsKeysPlugin::initialize(const QStringList &arguments, QString *errorMessage)
655 | {
656 | Q_UNUSED(arguments);
657 | Q_UNUSED(errorMessage);
658 | return d->initialize();
659 | }
660 |
661 | void EmacsKeysPlugin::shutdown()
662 | {
663 | d->shutdown();
664 | }
665 |
666 | void EmacsKeysPlugin::extensionsInitialized()
667 | {
668 | }
669 |
670 | #include "emacskeysplugin.moc"
671 |
672 | Q_EXPORT_PLUGIN(EmacsKeysPlugin)
673 |
--------------------------------------------------------------------------------
/emacskeysplugin.h:
--------------------------------------------------------------------------------
1 | /**************************************************************************
2 | **
3 | ** GNU Lesser General Public License Usage
4 | **
5 | ** Alternatively, this file may be used under the terms of the GNU Lesser
6 | ** General Public License version 2.1 as published by the Free Software
7 | ** Foundation and appearing in the file LICENSE.LGPL included in the
8 | ** packaging of this file. Please review the following information to
9 | ** ensure the GNU Lesser General Public License version 2.1 requirements
10 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
11 | **
12 | ** If you are unsure which license is appropriate for your use, please
13 | ** contact the sales department at http://www.qtsoftware.com/contact.
14 | **
15 | **************************************************************************/
16 |
17 | #ifndef EMACSKEYSPLUGIN_H
18 | #define EMACSKEYSPLUGIN_H
19 |
20 | #include
21 |
22 | namespace EmacsKeys {
23 | namespace Internal {
24 |
25 | class EmacsKeysHandler;
26 |
27 | class EmacsKeysPluginPrivate;
28 |
29 | class EmacsKeysPlugin : public ExtensionSystem::IPlugin
30 | {
31 | Q_OBJECT
32 |
33 | public:
34 | EmacsKeysPlugin();
35 | ~EmacsKeysPlugin();
36 |
37 | private:
38 | // implementation of ExtensionSystem::IPlugin
39 | bool initialize(const QStringList &arguments, QString *error_message);
40 | void shutdown();
41 | void extensionsInitialized();
42 |
43 | private:
44 | friend class EmacsKeysPluginPrivate;
45 | EmacsKeysPluginPrivate *d;
46 | };
47 |
48 | } // namespace Internal
49 | } // namespace EmacsKeys
50 |
51 | #endif // EMACSKEYSPLUGIN_H
52 |
--------------------------------------------------------------------------------
/killring.cpp:
--------------------------------------------------------------------------------
1 | #include "killring.h"
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | KillRing::KillRing()
8 | : currentView(0), iter(ring.begin()), ignore(false)
9 | {
10 | connect(QApplication::clipboard(), SIGNAL(dataChanged()),
11 | SLOT(clipboardDataChanged()));
12 | }
13 |
14 | KillRing* KillRing::instance()
15 | {
16 | static KillRing* instance;
17 | if (!instance) {
18 | instance = new KillRing();
19 | }
20 | return instance;
21 | }
22 |
23 | void KillRing::ignoreNextClipboardChange()
24 | {
25 | ignore = true;
26 | }
27 |
28 | void KillRing::add(const QString& text)
29 | {
30 | if (text.isEmpty()) {
31 | return;
32 | }
33 | if (ignore) {
34 | ignore = false;
35 | return;
36 | }
37 |
38 | // original emacs implementation does not remove duplicates
39 | ring.removeAll(text);
40 | ring.prepend(text);
41 | // shrink ring to default emacs max size
42 | while (ring.count() > 60) {
43 | ring.pop_back();
44 | }
45 | iter = ring.begin();
46 | }
47 |
48 | QString KillRing::next()
49 | {
50 | if (ring.isEmpty()) {
51 | return QString::null;
52 | }
53 | else if (++iter == ring.end()) {
54 | iter = ring.begin();
55 | }
56 | KillRing::instance()->ignoreNextClipboardChange();
57 | QApplication::clipboard()->setText(*iter);
58 | return *iter;
59 | }
60 |
61 | void KillRing::setCurrentYankView(QWidget* view)
62 | {
63 | currentView = view;
64 | }
65 |
66 | QWidget* KillRing::currentYankView() const
67 | {
68 | return currentView;
69 | }
70 |
71 | void KillRing::clipboardDataChanged()
72 | {
73 | qDebug() << "clipboard changed " << QApplication::clipboard()->text()
74 | << endl;
75 | // TODO handle mouse selection too, optionally
76 | QString text(QApplication::clipboard()->text());
77 | add(text);
78 | }
79 |
--------------------------------------------------------------------------------
/killring.h:
--------------------------------------------------------------------------------
1 | #ifndef KILLRING_H
2 | #define KILLRING_H
3 |
4 | #include
5 | #include
6 |
7 | class QWidget;
8 |
9 | class KillRing : public QObject
10 | {
11 | Q_OBJECT
12 |
13 | public:
14 | KillRing();
15 | void setCurrentYankView(QWidget* view);
16 | QWidget* currentYankView() const;
17 | void add(const QString& text);
18 | QString next();
19 | void ignoreNextClipboardChange();
20 | static KillRing* instance();
21 |
22 | private slots:
23 | void clipboardDataChanged();
24 |
25 | private:
26 | QStringList ring;
27 | QWidget* currentView;
28 | QStringList::ConstIterator iter;
29 | bool ignore;
30 | };
31 |
32 | #endif
33 |
--------------------------------------------------------------------------------
/mark.h:
--------------------------------------------------------------------------------
1 | #ifndef MARK_H
2 | #define MARK_H
3 |
4 | struct Mark
5 | {
6 | Mark(int position)
7 | : valid(true), position(position)
8 | {
9 | }
10 | Mark()
11 | : valid(false)
12 | {
13 | }
14 | bool operator ==(const Mark& mark)
15 | {
16 | return valid == mark.valid && position == mark.position;
17 | }
18 | bool operator !=(const Mark& mark)
19 | {
20 | return !(*this == mark);
21 | }
22 | bool valid;
23 | int position;
24 | };
25 |
26 | #endif
27 |
--------------------------------------------------------------------------------
/markring.cpp:
--------------------------------------------------------------------------------
1 | #include "markring.h"
2 | #include "mark.h"
3 |
4 | MarkRing::MarkRing()
5 | : iter(ring.begin())
6 | {
7 |
8 | }
9 |
10 | void MarkRing::addMark(int position)
11 | {
12 | Mark mark(position);
13 | if (ring.isEmpty() || ring.first() != mark) {
14 | ring.prepend(mark);
15 | }
16 | // shrink ring to default emacs max size
17 | while (ring.count() > 16) {
18 | ring.pop_back();
19 | }
20 | iter = ring.begin();
21 | }
22 |
23 | Mark MarkRing::getPreviousMark()
24 | {
25 | if (ring.isEmpty()) {
26 | return Mark();
27 | }
28 | else if (++iter == ring.end()) {
29 | iter = ring.begin();
30 | }
31 | return *iter;
32 | }
33 |
34 | Mark MarkRing::getMostRecentMark()
35 | {
36 | return ring.isEmpty() ? Mark() : ring.first();
37 | }
38 |
--------------------------------------------------------------------------------
/markring.h:
--------------------------------------------------------------------------------
1 | #ifndef MARKRING_H
2 | #define MARKRING_H
3 |
4 | #include
5 |
6 | #include "mark.h"
7 |
8 |
9 |
10 | class MarkRing
11 | {
12 | public:
13 | MarkRing();
14 | void addMark(int position);
15 | Mark getPreviousMark();
16 | Mark getMostRecentMark();
17 |
18 | private:
19 | QList ring;
20 | QList::Iterator iter;
21 | };
22 |
23 | #endif
24 |
--------------------------------------------------------------------------------
/plugins.pro.patch:
--------------------------------------------------------------------------------
1 | diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro
2 | index 4437f0f..2e51c3b 100644
3 | --- a/src/plugins/plugins.pro
4 | +++ b/src/plugins/plugins.pro
5 | @@ -27,6 +27,7 @@ SUBDIRS = plugin_coreplugin \
6 | plugin_cpaster \
7 | plugin_cmakeprojectmanager \
8 | plugin_fakevim \
9 | + plugin_emacskeys \
10 | plugin_designer \
11 | plugin_resourceeditor \
12 | plugin_genericprojectmanager \
13 | @@ -135,6 +136,11 @@ plugin_fakevim.depends = plugin_projectexplorer
14 | plugin_fakevim.depends += plugin_coreplugin
15 | plugin_fakevim.depends += plugin_cppeditor
16 |
17 | +plugin_emacskeys.subdir = emacskeys
18 | +plugin_emacskeys.depends = plugin_projectexplorer
19 | +plugin_emacskeys.depends += plugin_coreplugin
20 | +plugin_emacskeys.depends += plugin_cppeditor
21 | +
22 | plugin_qtestlib.subdir = qtestlib
23 | plugin_qtestlib.depends = plugin_projectexplorer
24 | plugin_qtestlib.depends += plugin_coreplugin
25 |
--------------------------------------------------------------------------------