├── .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 | 55 | 56 | 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 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 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 | 451 | 452 | 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 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 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 | --------------------------------------------------------------------------------