From 7eede302ecedce6f2e3a0e28f0c3ee71117eaeb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Fri, 20 Apr 2018 19:13:55 +0200 Subject: [PATCH 1/4] vimode: A Vim Mode for Geany I know what you think - the last thing Geany needed... --- MAINTAINERS | 7 + Makefile.am | 4 + build/geany-plugins.nsi | 1 + build/vimode.m4 | 9 + configure.ac | 1 + po/POTFILES.in | 3 + vimode/AUTHORS | 1 + vimode/COPYING | 340 ++++++ vimode/ChangeLog | 0 vimode/Makefile.am | 4 + vimode/NEWS | 0 vimode/README | 565 +++++++++ vimode/THANKS | 8 + vimode/index.txt | 1657 +++++++++++++++++++++++++++ vimode/src/Makefile.am | 58 + vimode/src/backends/backend-geany.c | 345 ++++++ vimode/src/backends/backend-viw.c | 246 ++++ vimode/src/cmd-params.c | 46 + vimode/src/cmd-params.h | 76 ++ vimode/src/cmd-runner.c | 683 +++++++++++ vimode/src/cmd-runner.h | 28 + vimode/src/cmds/changemode.c | 242 ++++ vimode/src/cmds/changemode.h | 51 + vimode/src/cmds/edit.c | 449 ++++++++ vimode/src/cmds/edit.h | 66 ++ vimode/src/cmds/motion.c | 528 +++++++++ vimode/src/cmds/motion.h | 77 ++ vimode/src/cmds/special.c | 117 ++ vimode/src/cmds/special.h | 38 + vimode/src/cmds/txtobjs.c | 190 +++ vimode/src/cmds/txtobjs.h | 41 + vimode/src/context.h | 62 + vimode/src/excmd-params.h | 37 + vimode/src/excmd-prompt.c | 141 +++ vimode/src/excmd-prompt.h | 30 + vimode/src/excmd-runner.c | 458 ++++++++ vimode/src/excmd-runner.h | 26 + vimode/src/excmds/excmds.c | 66 ++ vimode/src/excmds/excmds.h | 33 + vimode/src/keypress.c | 196 ++++ vimode/src/keypress.h | 38 + vimode/src/sci.c | 33 + vimode/src/sci.h | 43 + vimode/src/utils.c | 221 ++++ vimode/src/utils.h | 36 + vimode/src/vi.c | 383 +++++++ vimode/src/vi.h | 64 ++ 47 files changed, 7748 insertions(+) create mode 100644 build/vimode.m4 create mode 100644 vimode/AUTHORS create mode 100644 vimode/COPYING create mode 100644 vimode/ChangeLog create mode 100644 vimode/Makefile.am create mode 100644 vimode/NEWS create mode 100644 vimode/README create mode 100644 vimode/THANKS create mode 100644 vimode/index.txt create mode 100644 vimode/src/Makefile.am create mode 100644 vimode/src/backends/backend-geany.c create mode 100644 vimode/src/backends/backend-viw.c create mode 100644 vimode/src/cmd-params.c create mode 100644 vimode/src/cmd-params.h create mode 100644 vimode/src/cmd-runner.c create mode 100644 vimode/src/cmd-runner.h create mode 100644 vimode/src/cmds/changemode.c create mode 100644 vimode/src/cmds/changemode.h create mode 100644 vimode/src/cmds/edit.c create mode 100644 vimode/src/cmds/edit.h create mode 100644 vimode/src/cmds/motion.c create mode 100644 vimode/src/cmds/motion.h create mode 100644 vimode/src/cmds/special.c create mode 100644 vimode/src/cmds/special.h create mode 100644 vimode/src/cmds/txtobjs.c create mode 100644 vimode/src/cmds/txtobjs.h create mode 100644 vimode/src/context.h create mode 100644 vimode/src/excmd-params.h create mode 100644 vimode/src/excmd-prompt.c create mode 100644 vimode/src/excmd-prompt.h create mode 100644 vimode/src/excmd-runner.c create mode 100644 vimode/src/excmd-runner.h create mode 100644 vimode/src/excmds/excmds.c create mode 100644 vimode/src/excmds/excmds.h create mode 100644 vimode/src/keypress.c create mode 100644 vimode/src/keypress.h create mode 100644 vimode/src/sci.c create mode 100644 vimode/src/sci.h create mode 100644 vimode/src/utils.c create mode 100644 vimode/src/utils.h create mode 100644 vimode/src/vi.c create mode 100644 vimode/src/vi.h diff --git a/MAINTAINERS b/MAINTAINERS index 1350c55b4..8003b1f52 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -299,6 +299,13 @@ M: Frank Lanitz W: http://plugins.geany.org/updatechecker.html S: Maintained +vimode +P: Jiří Techet +g: @techee +M: Jiří Techet +W: http://plugins.geany.org/vimode.html +S: Maintained + webhelper P: Colomban Wendling g: @b4n diff --git a/Makefile.am b/Makefile.am index ba4cf078b..b5883d6d1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -172,6 +172,10 @@ if ENABLE_UPDATECHECKER SUBDIRS += updatechecker endif +if ENABLE_VIMODE +SUBDIRS += vimode +endif + if ENABLE_WEBHELPER SUBDIRS += webhelper endif diff --git a/build/geany-plugins.nsi b/build/geany-plugins.nsi index 2dddbd6ea..4d92f2536 100644 --- a/build/geany-plugins.nsi +++ b/build/geany-plugins.nsi @@ -197,6 +197,7 @@ Section Uninstall Delete "$INSTDIR\lib\geany\tableconvert.dll" Delete "$INSTDIR\lib\geany\treebrowser.dll" Delete "$INSTDIR\lib\geany\updatechecker.dll" + Delete "$INSTDIR\lib\geany\vimode.dll" Delete "$INSTDIR\lib\geany\webhelper.dll" Delete "$INSTDIR\lib\geany\workbench.dll" Delete "$INSTDIR\lib\geany\xmlsnippets.dll" diff --git a/build/vimode.m4 b/build/vimode.m4 new file mode 100644 index 000000000..bce919c0d --- /dev/null +++ b/build/vimode.m4 @@ -0,0 +1,9 @@ +AC_DEFUN([GP_CHECK_VIMODE], +[ + GP_ARG_DISABLE([Vimode], [auto]) + GP_COMMIT_PLUGIN_STATUS([Vimode]) + AC_CONFIG_FILES([ + vimode/Makefile + vimode/src/Makefile + ]) +]) diff --git a/configure.ac b/configure.ac index 61b7ea769..89637c221 100644 --- a/configure.ac +++ b/configure.ac @@ -70,6 +70,7 @@ GP_CHECK_SPELLCHECK GP_CHECK_TREEBROWSER GP_CHECK_TABLECONVERT GP_CHECK_UPDATECHECKER +GP_CHECK_VIMODE GP_CHECK_WEBHELPER GP_CHECK_WORKBENCH GP_CHECK_XMLSNIPPETS diff --git a/po/POTFILES.in b/po/POTFILES.in index 3609b322e..06f493cc0 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -304,6 +304,9 @@ treebrowser/src/treebrowser.c # UpdateChecker updatechecker/src/updatechecker.c +# ViMode +vimode/src/backends/backend-geany.c + # WebHelper webhelper/src/gwh-enum-types.c webhelper/src/gwh-keybindings.c diff --git a/vimode/AUTHORS b/vimode/AUTHORS new file mode 100644 index 000000000..eb925ec9a --- /dev/null +++ b/vimode/AUTHORS @@ -0,0 +1 @@ +Jiří Techet diff --git a/vimode/COPYING b/vimode/COPYING new file mode 100644 index 000000000..8c4c849e2 --- /dev/null +++ b/vimode/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/vimode/ChangeLog b/vimode/ChangeLog new file mode 100644 index 000000000..e69de29bb diff --git a/vimode/Makefile.am b/vimode/Makefile.am new file mode 100644 index 000000000..52219c06e --- /dev/null +++ b/vimode/Makefile.am @@ -0,0 +1,4 @@ +include $(top_srcdir)/build/vars.auxfiles.mk + +SUBDIRS = src +plugin = vimode diff --git a/vimode/NEWS b/vimode/NEWS new file mode 100644 index 000000000..e69de29bb diff --git a/vimode/README b/vimode/README new file mode 100644 index 000000000..6503b80d2 --- /dev/null +++ b/vimode/README @@ -0,0 +1,565 @@ +====== +Vimode +====== + +.. contents:: + + +About +===== + +Vimode is a Vim-mode plugin for Geany written by a guy who does not use Vim. +Expect problems unexpected by a Vim user and, please, report them. + +Despite the limited Vim knowledge of the author, the plugin tries to be a +reasonably complete Vim mode implementation featuring: + +* normal mode, insert/replace mode, visual mode, line visual mode +* repeated commands (e.g. 10dd - delete 10 lines) +* "motion" commands (e.g. d10l - delete 10 characters to the right) +* "text object" commands (e.g. di( - delete inner contents of parentheses) +* visual mode commands (e.g. ~ to swap case of the selected text) +* basic ex mode commands like :s, including range specifications (e.g. + :5,8s/foo/bar/g - replace foo with bar on lines 5 through 8) +* most basic navigation, selection and text manipulation commands - see the end + of this file for the full list +* command repetition using "." and repeated insert + +It should be relatively easy to add more (non-special) commands so if you run +into something you are missing, please let me know. + +Setup and Configuration +======================= +The plugin can be enabled/disabled from the Plugin Manager of Geany. Once enabled, +it adds a new menu called Vim Mode under the Tools menu with the following items. + +Enable Vim Mode +--------------- +Enables/disables the Vim mode. A keybinding can be assigned to this action so +you can for instance use Geany normally and enable/disable the Vim mode as +needed. + +Insert Mode for Dummies +----------------------- +This makes the insert mode behave like normal Geany, only the escape key allows +you to switch to the Vim command mode. This is basically "Vim for the rest of us" +who want to use the editor in a standard (understand non-vim) way but who like +the idea of having access to Vim commands from time to time. Highly unintrusive +so you basically do not know about the Vim mode normally. + +Start in Insert Mode +-------------------- +By default, the plugin starts in the normal mode. If you enable the "dummies" +mode above, you might also want to enable this option so you do not have to +switch to the insert mode manually when the application starts. + +Behavior +======== +Upon changing mode, the plugin writes the current mode in the status bar. The +caret shape also indicates the current mode: + +* block, not blinking - normal mode +* vertical line, not blinking - visual mode +* blinking vertical line - insert mode +* blinking underscore - replace mode + +When evaluating key presses, the plugin checks if the keypress is a Vim command +or a part of a command in which case the plugin consumes the key press event +and does not propagate it to Geany. When the keypress is not a Vim command, +the plugin sends it to Geany which processes it normally based in its internal +logic. This means that it is still possible to use normal Geany keybindings +with this plugin unless they conflict with a Vim command. + +If autocompletion popups or tooltips are present in insert mode, escape closes +them first without entering the normal mode so you do not enter normal mode +by accident. + +Limitations and Problems +------------------------ +I tried to implement a reasonable subset of Vim commands but please note that +the judgement what is a reasonable set of commands was made by a guy who does not +use Vim. So it is very probable I missed some totally fundamental behavior +of Vim every Vim user would expect to be present. Please report such problems. + +This is an incomplete list of known limitations of the plugin: + +* selection in visual mode does not behave the same way as in Vim - the reason is + that the editor component Geany uses (Scintilla) uses cursor which is always + between characters and not on characters (block cursor which appears to be + on top of a character behaves as if it were before the character). This may lead + to situations when the position of the cursor is off by one. This issue might + be fixed later but it will be tricky. +* undo does not preserve cursor position the same way Vim does - probably fixable +* block visual mode is not implemented - probably possible using Scintilla's + multiple selection feature +* select submode of insert mode is not implemented +* named registers and related commands are not implemented +* Ctrl+X mode is not implemented +* marks are not implemented +* fold commands are not implemented +* most commands starting with "'", "z", and "g" are not implemented +* most ex mode commands are not implemented (excluding basic stuff like search, + replace, saving, etc.) +* despite being mentioned below, none of the commands for quitting (:q, ZZ, etc.) + work because the corresponding function is not in Geany API yet - everything is + ready in the plugin though +* only the 'g' flag is supported in the substitute command +* in search and substitute the regular expressions are based on Scintilla regular + expressions which differ from Vim. Check the Scintilla documentation at + http://www.scintilla.org/ScintillaDoc.html#Searching for more details. + In addition, \c is also supported to allow case-insensitive search. + +FAQ +=== + +Why does Vimode suck so much? +----------------------------- +Well, it simulates the Vim behavior - what did you expect? + +No, stupid, I mean why does your implementation suck so much? +------------------------------------------------------------- +Ah, that's simple - I am not a Vim user. Before writing this plugin, I knew +about 5 Vim commands (after writing this plugin, my knowledge has nearly doubled). +Even though I kind of like the idea behind the editor, my poor brain isn't able +to remember in which mode I currently am and what keypresses I can use. This +means I don't really know how a typical user uses Vim and I probably miss +some very basic things Vim users take for granted. Please report such issues +so I can improve the plugin. + +So why the hell did you write this plugin? +------------------------------------------ +It was the constant whining of Vim users at Prague and Chemnitz Linux Days which +made me write the plugin. And it turned out that writing a Vim editor is +quite a lot of fun - much more than using it. + +Meh, even I could write a better Vim plugin +------------------------------------------- +Great! Submit patches! Fix bugs! All contributions are really welcome. + +Help! I enabled your plugin together with others and the editor is really strange now! +-------------------------------------------------------------------------------------- +This, my friend, is the world of Vim. Welcome! Fortunately, you can easily +disable it using Geany's Plugin Manager. Phew! + +Help! I am a long-time Vim user and can't quit the editor, :q doesn't work! +--------------------------------------------------------------------------- +Geany currently doesn't allow plugins to quit the editor so you have to do +it in a non-vim way. This is where the tricky part starts for Vim users: with your +mouse navigate to the top-right corner of the window to the X button and click +it - behold, the window closes (I know, totally counter-intuitive for Vim users). + +After building the plugin, I noticed a binary called viw, what is it? +--------------------------------------------------------------------- +After I started writing the plugin, I soon realized that in fact, I am writing +a new editor. I nearly didn't use any Geany calls and most of the code just +calls the Scintilla API. At this point I decided to separate the Geany plugin +part from the rest of the code which is completely Geany-independent. And now +I could write a simple editor which just uses Scintilla and GTK (well, +not exactly - the way it builds now it still uses Scintilla from libgeany +but the build could be easily modified to link against statically built +Scintilla without any Geany dependency). So yeah, I'm a real man now, have my own +editor - it only sucks it's a Vim clone. And in fact it turned out to be useful +for the development of the plugin because one can use the viw (Vi Worsened) +editor for testing instead of having to restart Geany all the time. + +So does it mean I could use your code and add Vim support to another editor? +---------------------------------------------------------------------------- +Yes - as long as the editor is based on Scintilla and GTK. If it is, it should +be really simple - just check the "backends" directory how it is done for Geany +and viw. I believe it should still be quite simple to modify the code if +the editor uses Scintilla but doesn't use GTK - the amount of the used GTK code +is very small (most work will be with re-mapping the key event codes to the +other library). If your editor isn't Scintilla-based, you are more or less +doomed - most of the code deals with Scintilla and switching to a different +editor component basically means rewriting the plugin. + +Contact +======= + +Author +------ +Jiří Techet, . + +Bug Reports +----------- +To report bugs, please use the Geany-Plugins GitHub page at +https://github.com/geany/geany-plugins/issues + +License +======= + +Vimode is distributed under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. A copy of this license +can be found in the file COPYING included with the source code of this +program. + +Downloads +========= + +Vimode is part of the combined Geany Plugins release. For more information and +downloads, please visit http://plugins.geany.org/geany-plugins/ + +Source Code +=========== + +The source code is available at:: + + git clone https://github.com/geany/geany-plugins.git + +Implemented Commands +==================== +The rest of the file contains a list of Vim commands which have been at least +partially implemented in the plugin. This is taken from the index.txt Vim help +file which is also present in the root directory of the plugin. If you add +a new command, please do not forget to update the table below.:: + + ============================================================================== + 1. Insert mode insert-index + + tag char action in Insert mode + ----------------------------------------------------------------------- + i_CTRL-@ CTRL-@ insert previously inserted text and stop + insert + i_CTRL-A CTRL-A insert previously inserted text + i_CTRL-C CTRL-C quit insert mode, without checking for + abbreviation, unless 'insertmode' set. + i_CTRL-D CTRL-D delete one shiftwidth of indent in the current + line + i_CTRL-E CTRL-E insert the character which is below the cursor + i_ delete character before the cursor + i_CTRL-H CTRL-H same as + i_ insert a character + i_CTRL-I CTRL-I same as + i_ same as + i_CTRL-J CTRL-J same as + i_ begin new line + i_CTRL-M CTRL-M same as + i_CTRL-O CTRL-O execute a single command and return to insert + mode + i_CTRL-T CTRL-T insert one shiftwidth of indent in current + line + i_CTRL-W CTRL-W delete word before the cursor + i_CTRL-Y CTRL-Y insert the character which is above the cursor + i_ end insert mode (unless 'insertmode' set) + i_CTRL-[ CTRL-[ same as + i_ delete character under the cursor + + i_ cursor one character left + i_ cursor one word left + i_ cursor one word left + i_ cursor one character right + i_ cursor one word right + i_ cursor one word right + i_ cursor one line up + i_ same as + i_ cursor one line down + i_ same as + i_ cursor to start of line + i_ cursor to start of file + i_ cursor past end of line + i_ cursor past end of file + i_ one screenful backward + i_ one screenful forward + i_ toggle Insert/Replace mode + + ============================================================================== + 2. Normal mode normal-index + + CHAR any non-blank character + WORD a sequence of non-blank characters + N a number entered before the command + {motion} a cursor movement command + Nmove the text that is moved over with a {motion} + SECTION a section that possibly starts with '}' instead of '{' + + note: 1 = cursor movement command; 2 = can be undone/redone + + tag char note action in Normal mode + ------------------------------------------------------------------------------ + CTRL-B CTRL-B 1 scroll N screens Backwards + CTRL-D CTRL-D scroll Down N lines (default: half a screen) + CTRL-E CTRL-E scroll N lines upwards (N lines Extra) + CTRL-F CTRL-F 1 scroll N screens Forward + 1 same as "h" + CTRL-H CTRL-H 1 same as "h" + 1 same as "j" + CTRL-J CTRL-J 1 same as "j" + 1 cursor to the first CHAR N lines lower + CTRL-M CTRL-M 1 same as + CTRL-N CTRL-N 1 same as "j" + CTRL-P CTRL-P 1 same as "k" + CTRL-R CTRL-R 2 redo changes which were undone with 'u' + CTRL-U CTRL-U scroll N lines Upwards (default: half a + screen) + CTRL-Y CTRL-Y scroll N lines downwards + + 1 same as "l" + # # 1 search backward for the Nth occurrence of + the ident under the cursor + $ $ 1 cursor to the end of Nth next line + % % 1 find the next (curly/square) bracket on + this line and go to its match, or go to + matching comment bracket, or go to matching + preprocessor directive. + N% {count}% 1 go to N percentage in the file + & & 2 repeat last :s + star * 1 search forward for the Nth occurrence of + the ident under the cursor + + + 1 same as + , , 1 repeat latest f, t, F or T in opposite + direction N times + - - 1 cursor to the first CHAR N lines higher + . . 2 repeat last change with count replaced with + N + / /{pattern} 1 search forward for the Nth occurrence of + {pattern} + / / 1 search forward for {pattern} of last search + count 0 1 cursor to the first char of the line + count 1 prepend to command to give a count + count 2 " + count 3 " + count 4 " + count 5 " + count 6 " + count 7 " + count 8 " + count 9 " + : : 1 start entering an Ex command + ; ; 1 repeat latest f, t, F or T N times + < <{motion} 2 shift Nmove lines one 'shiftwidth' + leftwards + << << 2 shift N lines one 'shiftwidth' leftwards + > >{motion} 2 shift Nmove lines one 'shiftwidth' + rightwards + >> >> 2 shift N lines one 'shiftwidth' rightwards + ? ?{pattern} 1 search backward for the Nth previous + occurrence of {pattern} + ? ? 1 search backward for {pattern} of last search + A A 2 append text after the end of the line N times + B B 1 cursor N WORDS backward + C ["x]C 2 change from the cursor position to the end + of the line, and N-1 more lines [into + register x]; synonym for "c$" + D ["x]D 2 delete the characters under the cursor + until the end of the line and N-1 more + lines [into register x]; synonym for "d$" + E E 1 cursor forward to the end of WORD N + F F{char} 1 cursor to the Nth occurrence of {char} to + the left + G G 1 cursor to line N, default last line + H H 1 cursor to line N from top of screen + I I 2 insert text before the first CHAR on the + line N times + J J 2 Join N lines; default is 2 + L L 1 cursor to line N from bottom of screen + M M 1 cursor to middle line of screen + N N 1 repeat the latest '/' or '?' N times in + opposite direction + O O 2 begin a new line above the cursor and + insert text, repeat N times + P ["x]P 2 put the text [from register x] before the + cursor N times + R R 2 enter replace mode: overtype existing + characters, repeat the entered text N-1 + times + S ["x]S 2 delete N lines [into register x] and start + insert; synonym for "cc". + T T{char} 1 cursor till after Nth occurrence of {char} + to the left + V V start linewise Visual mode + W W 1 cursor N WORDS forward + X ["x]X 2 delete N characters before the cursor [into + register x] + Y ["x]Y yank N lines [into register x]; synonym for + "yy" + ZZ ZZ store current file if modified, and exit + ZQ ZQ exit current file always + ^ ^ 1 cursor to the first CHAR of the line + _ _ 1 cursor to the first CHAR N - 1 lines lower + a a 2 append text after the cursor N times + b b 1 cursor N words backward + c ["x]c{motion} 2 delete Nmove text [into register x] and + start insert + cc ["x]cc 2 delete N lines [into register x] and start + insert + d ["x]d{motion} 2 delete Nmove text [into register x] + dd ["x]dd 2 delete N lines [into register x] + e e 1 cursor forward to the end of word N + f f{char} 1 cursor to Nth occurrence of {char} to the + right + g g{char} extended commands, see g below + h h 1 cursor N chars to the left + i i 2 insert text before the cursor N times + j j 1 cursor N lines downward + k k 1 cursor N lines upward + l l 1 cursor N chars to the right + n n 1 repeat the latest '/' or '?' N times + o o 2 begin a new line below the cursor and + insert text, repeat N times + p ["x]p 2 put the text [from register x] after the + cursor N times + r r{char} 2 replace N chars with {char} + s ["x]s 2 (substitute) delete N characters [into + register x] and start insert + t t{char} 1 cursor till before Nth occurrence of {char} + to the right + u u 2 undo changes + v v start characterwise Visual mode + w w 1 cursor N words forward + x ["x]x 2 delete N characters under and after the + cursor [into register x] + y ["x]y{motion} yank Nmove text [into register x] + yy ["x]yy yank N lines [into register x] + bar | 1 cursor to column N + ~ ~ 2 'tildeop' off: switch case of N characters + under cursor and move the cursor N + characters to the right + 1 same as "G" + 1 same as "gg" + 1 same as "b" + 1 same as "w" + ["x] 2 same as "x" + 1 same as "j" + 1 same as "$" + 1 same as "0" + 2 same as "i" + 1 same as "h" + same as CTRL-F + same as CTRL-B + 1 same as "l" + 1 same as CTRL-F + 1 same as "b" + 1 same as "w" + 1 same as CTRL-B + 1 same as "k" + + ============================================================================== + 2.1 Text objects objects + + These can be used after an operator or in Visual mode to select an object. + + tag command action in op-pending and Visual mode + ------------------------------------------------------------------------------ + v_aquote a" double quoted string + v_a' a' single quoted string + v_a( a( same as ab + v_a) a) same as ab + v_a< a< "a <>" from '<' to the matching '>' + v_a> a> same as a< + v_aB aB "a Block" from "[{" to "]}" (with brackets) + v_a[ a[ "a []" from '[' to the matching ']' + v_a] a] same as a[ + v_a` a` string in backticks + v_ab ab "a block" from "[(" to "])" (with braces) + v_a{ a{ same as aB + v_a} a} same as aB + v_iquote i" double quoted string without the quotes + v_i' i' single quoted string without the quotes + v_i( i( same as ib + v_i) i) same as ib + v_i< i< "inner <>" from '<' to the matching '>' + v_i> i> same as i< + v_iB iB "inner Block" from "[{" and "]}" + v_i[ i[ "inner []" from '[' to the matching ']' + v_i] i] same as i[ + v_i` i` string in backticks without the backticks + v_ib ib "inner block" from "[(" to "])" + v_i{ i{ same as iB + v_i} i} same as iB + + ============================================================================== + 2.4 Commands starting with 'g' g + + tag char note action in Normal mode + ------------------------------------------------------------------------------ + gE gE 1 go backwards to the end of the previous + WORD + gU gU{motion} 2 make Nmove text uppercase + ge ge 1 go backwards to the end of the previous + word + gg gg 1 cursor to line N, default first line + gu gu{motion} 2 make Nmove text lowercase + g~ g~{motion} 2 swap case for Nmove text + + ============================================================================== + 2.5 Commands starting with 'z' z + + tag char note action in Normal mode + ------------------------------------------------------------------------------ + z z redraw, cursor line to top of window, + cursor on first non-blank + z+ z+ cursor on line N (default line below + window), otherwise like "z" + z- z- redraw, cursor line at bottom of window, + cursor on first non-blank + z. z. redraw, cursor line to center of window, + cursor on first non-blank + zb zb redraw, cursor line at bottom of window + zt zt redraw, cursor line at top of window + zz zz redraw, cursor line at center of window + + ============================================================================== + 3. Visual mode visual-index + + Most commands in Visual mode are the same as in Normal mode. The ones listed + here are those that are different. + + tag command note action in Visual mode + ------------------------------------------------------------------------------ + v_CTRL-C CTRL-C stop Visual mode + v_: : start a command-line with the highlighted + lines as a range + v_< < 2 shift the highlighted lines one + 'shiftwidth' left + v_> > 2 shift the highlighted lines one + 'shiftwidth' right + v_C C 2 delete the highlighted lines and start + insert + v_D D 2 delete the highlighted lines + v_J J 2 join the highlighted lines + v_O O Move horizontally to other corner of area. + v_R R 2 delete the highlighted lines and start + insert + v_S S 2 delete the highlighted lines and start + insert + v_U U 2 make highlighted area uppercase + v_V V make Visual mode linewise or stop Visual + mode + v_X X 2 delete the highlighted lines + v_Y Y yank the highlighted lines + v_c c 2 delete highlighted area and start insert + v_d d 2 delete highlighted area + v_o o move cursor to other corner of area + v_r r 2 delete highlighted area and start insert + v_s s 2 delete highlighted area and start insert + v_u u 2 make highlighted area lowercase + v_v v make Visual mode characterwise or stop + Visual mode + v_x x 2 delete the highlighted area + v_y y yank the highlighted area + v_~ ~ 2 swap case for the highlighted area + + ============================================================================== + 5. EX commands ex-cmd-index :index + + This is a brief but complete listing of all the ":" commands, without + mentioning any arguments. The optional part of the command name is inside []. + The commands are sorted on the non-optional part of their name. + + tag command action + ------------------------------------------------------------------------------ + :& :& repeat last ":substitute" + :cquit :cq[uit] quit Vim with an error code + :exit :exi[t] same as ":xit" + :quit :q[uit] quit current window (when one window quit Vim) + :quitall :quita[ll] quit Vim + :qall :qa[ll] quit Vim + :substitute :s[ubstitute] find and replace text + :update :up[date] write buffer if modified + :write :w[rite] write to a file + :wall :wa[ll] write all (changed) buffers + :wq :wq write to a file and quit window or Vim + :wqall :wqa[ll] write all changed buffers and quit Vim + :xit :x[it] write if buffer changed and quit window or Vim + :xall :xa[ll] same as ":wqall" diff --git a/vimode/THANKS b/vimode/THANKS new file mode 100644 index 000000000..fa7a5d0e5 --- /dev/null +++ b/vimode/THANKS @@ -0,0 +1,8 @@ +Various parts of the plugin code were taken from other Geany plugins, from Geany +code or Scintilla. Thanks to all the authors of the corresponding code. + +Thanks to Frank Lanitz for the name of the plugin. + +Thanks to Bill Joy and Bram Moolenaar for creating vi and vim, respectively, so +every bloody editor on the planet has to create a compatibility mode with this +crazy editor to make users happy. diff --git a/vimode/index.txt b/vimode/index.txt new file mode 100644 index 000000000..8e6fb3655 --- /dev/null +++ b/vimode/index.txt @@ -0,0 +1,1657 @@ +index.txt For Vim version 8.0. Last change: 2017 Aug 02 + + + VIM REFERENCE MANUAL by Bram Moolenaar + + index +This file contains a list of all commands for each mode, with a tag and a +short description. The lists are sorted on ASCII value. + +Tip: When looking for certain functionality, use a search command. E.g., +to look for deleting something, use: "/delete". + +1. Insert mode insert-index +2. Normal mode normal-index + 2.1. Text objects objects + 2.2. Window commands CTRL-W + 2.3. Square bracket commands [ + 2.4. Commands starting with 'g' g + 2.5. Commands starting with 'z' z +3. Visual mode visual-index +4. Command-line editing ex-edit-index +5. EX commands ex-cmd-index + +For an overview of options see help.txt option-list. +For an overview of built-in functions see functions. +For a list of Vim variables see vim-variable. +For a complete listing of all help items see help-tags. + +============================================================================== +1. Insert mode insert-index + +tag char action in Insert mode +----------------------------------------------------------------------- +i_CTRL-@ CTRL-@ insert previously inserted text and stop + insert +i_CTRL-A CTRL-A insert previously inserted text + CTRL-B not used i_CTRL-B-gone +i_CTRL-C CTRL-C quit insert mode, without checking for + abbreviation, unless 'insertmode' set. +i_CTRL-D CTRL-D delete one shiftwidth of indent in the current + line +i_CTRL-E CTRL-E insert the character which is below the cursor + CTRL-F not used (but by default it's in 'cinkeys' to + re-indent the current line) +i_CTRL-G_j CTRL-G CTRL-J line down, to column where inserting started +i_CTRL-G_j CTRL-G j line down, to column where inserting started +i_CTRL-G_j CTRL-G line down, to column where inserting started +i_CTRL-G_k CTRL-G CTRL-K line up, to column where inserting started +i_CTRL-G_k CTRL-G k line up, to column where inserting started +i_CTRL-G_k CTRL-G line up, to column where inserting started +i_CTRL-G_u CTRL-G u start new undoable edit +i_CTRL-G_U CTRL-G U don't break undo with next cursor movement +i_ delete character before the cursor +i_digraph {char1}{char2} + enter digraph (only when 'digraph' option set) +i_CTRL-H CTRL-H same as +i_ insert a character +i_CTRL-I CTRL-I same as +i_ same as +i_CTRL-J CTRL-J same as +i_CTRL-K CTRL-K {char1} {char2} + enter digraph +i_CTRL-L CTRL-L when 'insertmode' set: Leave Insert mode +i_ begin new line +i_CTRL-M CTRL-M same as +i_CTRL-N CTRL-N find next match for keyword in front of the + cursor +i_CTRL-O CTRL-O execute a single command and return to insert + mode +i_CTRL-P CTRL-P find previous match for keyword in front of + the cursor +i_CTRL-Q CTRL-Q same as CTRL-V, unless used for terminal + control flow +i_CTRL-R CTRL-R {0-9a-z"%#*:=} + insert the contents of a register +i_CTRL-R_CTRL-R CTRL-R CTRL-R {0-9a-z"%#*:=} + insert the contents of a register literally +i_CTRL-R_CTRL-O CTRL-R CTRL-O {0-9a-z"%#*:=} + insert the contents of a register literally + and don't auto-indent +i_CTRL-R_CTRL-P CTRL-R CTRL-P {0-9a-z"%#*:=} + insert the contents of a register literally + and fix indent. + CTRL-S (used for terminal control flow) +i_CTRL-T CTRL-T insert one shiftwidth of indent in current + line +i_CTRL-U CTRL-U delete all entered characters in the current + line +i_CTRL-V CTRL-V {char} insert next non-digit literally +i_CTRL-V_digit CTRL-V {number} insert three digit decimal number as a single + byte. +i_CTRL-W CTRL-W delete word before the cursor +i_CTRL-X CTRL-X {mode} enter CTRL-X sub mode, see i_CTRL-X_index +i_CTRL-Y CTRL-Y insert the character which is above the cursor +i_CTRL-Z CTRL-Z when 'insertmode' set: suspend Vim +i_ end insert mode (unless 'insertmode' set) +i_CTRL-[ CTRL-[ same as +i_CTRL-\_CTRL-N CTRL-\ CTRL-N go to Normal mode +i_CTRL-\_CTRL-G CTRL-\ CTRL-G go to mode specified with 'insertmode' + CTRL-\ a - z reserved for extensions + CTRL-\ others not used +i_CTRL-] CTRL-] trigger abbreviation +i_CTRL-^ CTRL-^ toggle use of :lmap mappings +i_CTRL-_ CTRL-_ When 'allowrevins' set: change language + (Hebrew, Farsi) {only when compiled with + the +rightleft feature} + + to '~' not used, except '0' and '^' followed by + CTRL-D + +i_0_CTRL-D 0 CTRL-D delete all indent in the current line +i_^_CTRL-D ^ CTRL-D delete all indent in the current line, restore + it in the next line + +i_ delete character under the cursor + + Meta characters (0x80 to 0xff, 128 to 255) + not used + +i_ cursor one character left +i_ cursor one word left +i_ cursor one word left +i_ cursor one character right +i_ cursor one word right +i_ cursor one word right +i_ cursor one line up +i_ same as +i_ cursor one line down +i_ same as +i_ cursor to start of line +i_ cursor to start of file +i_ cursor past end of line +i_ cursor past end of file +i_ one screenful backward +i_ one screenful forward +i_ same as +i_ stop insert mode and display help window +i_ toggle Insert/Replace mode +i_ cursor at mouse click +i_ move window three lines down +i_ move window one page down +i_ move window three lines up +i_ move window one page up +i_ move window six columns left +i_ move window one page left +i_ move window six columns right +i_ move window one page right + +commands in CTRL-X submode i_CTRL-X_index + +i_CTRL-X_CTRL-D CTRL-X CTRL-D complete defined identifiers +i_CTRL-X_CTRL-E CTRL-X CTRL-E scroll up +i_CTRL-X_CTRL-F CTRL-X CTRL-F complete file names +i_CTRL-X_CTRL-I CTRL-X CTRL-I complete identifiers +i_CTRL-X_CTRL-K CTRL-X CTRL-K complete identifiers from dictionary +i_CTRL-X_CTRL-L CTRL-X CTRL-L complete whole lines +i_CTRL-X_CTRL-N CTRL-X CTRL-N next completion +i_CTRL-X_CTRL-O CTRL-X CTRL-O omni completion +i_CTRL-X_CTRL-P CTRL-X CTRL-P previous completion +i_CTRL-X_CTRL-S CTRL-X CTRL-S spelling suggestions +i_CTRL-X_CTRL-T CTRL-X CTRL-T complete identifiers from thesaurus +i_CTRL-X_CTRL-Y CTRL-X CTRL-Y scroll down +i_CTRL-X_CTRL-U CTRL-X CTRL-U complete with 'completefunc' +i_CTRL-X_CTRL-V CTRL-X CTRL-V complete like in : command line +i_CTRL-X_CTRL-] CTRL-X CTRL-] complete tags +i_CTRL-X_s CTRL-X s spelling suggestions +{not available when compiled without the |+insert_expand| feature} + +============================================================================== +2. Normal mode normal-index + +CHAR any non-blank character +WORD a sequence of non-blank characters +N a number entered before the command +{motion} a cursor movement command +Nmove the text that is moved over with a {motion} +SECTION a section that possibly starts with '}' instead of '{' + +note: 1 = cursor movement command; 2 = can be undone/redone + +tag char note action in Normal mode +------------------------------------------------------------------------------ + CTRL-@ not used +CTRL-A CTRL-A 2 add N to number at/after cursor +CTRL-B CTRL-B 1 scroll N screens Backwards +CTRL-C CTRL-C interrupt current (search) command +CTRL-D CTRL-D scroll Down N lines (default: half a screen) +CTRL-E CTRL-E scroll N lines upwards (N lines Extra) +CTRL-F CTRL-F 1 scroll N screens Forward +CTRL-G CTRL-G display current file name and position + 1 same as "h" +CTRL-H CTRL-H 1 same as "h" + 1 go to N newer entry in jump list +CTRL-I CTRL-I 1 same as + 1 same as "j" +CTRL-J CTRL-J 1 same as "j" + CTRL-K not used +CTRL-L CTRL-L redraw screen + 1 cursor to the first CHAR N lines lower +CTRL-M CTRL-M 1 same as +CTRL-N CTRL-N 1 same as "j" +CTRL-O CTRL-O 1 go to N older entry in jump list +CTRL-P CTRL-P 1 same as "k" + CTRL-Q (used for terminal control flow) +CTRL-R CTRL-R 2 redo changes which were undone with 'u' + CTRL-S (used for terminal control flow) +CTRL-T CTRL-T jump to N older Tag in tag list +CTRL-U CTRL-U scroll N lines Upwards (default: half a + screen) +CTRL-V CTRL-V start blockwise Visual mode +CTRL-W CTRL-W {char} window commands, see CTRL-W +CTRL-X CTRL-X 2 subtract N from number at/after cursor +CTRL-Y CTRL-Y scroll N lines downwards +CTRL-Z CTRL-Z suspend program (or start new shell) + CTRL-[ not used +CTRL-\_CTRL-N CTRL-\ CTRL-N go to Normal mode (no-op) +CTRL-\_CTRL-G CTRL-\ CTRL-G go to mode specified with 'insertmode' + CTRL-\ a - z reserved for extensions + CTRL-\ others not used +CTRL-] CTRL-] :ta to ident under cursor +CTRL-^ CTRL-^ edit Nth alternate file (equivalent to + ":e #N") + CTRL-_ not used + + 1 same as "l" +! !{motion}{filter} + 2 filter Nmove text through the {filter} + command +!! !!{filter} 2 filter N lines through the {filter} command +quote "{a-zA-Z0-9.%#:-"} use register {a-zA-Z0-9.%#:-"} for next + delete, yank or put (uppercase to append) + ({.%#:} only work with put) +# # 1 search backward for the Nth occurrence of + the ident under the cursor +$ $ 1 cursor to the end of Nth next line +% % 1 find the next (curly/square) bracket on + this line and go to its match, or go to + matching comment bracket, or go to matching + preprocessor directive. +N% {count}% 1 go to N percentage in the file +& & 2 repeat last :s +' '{a-zA-Z0-9} 1 cursor to the first CHAR on the line with + mark {a-zA-Z0-9} +'' '' 1 cursor to the first CHAR of the line where + the cursor was before the latest jump. +'( '( 1 cursor to the first CHAR on the line of the + start of the current sentence +') ') 1 cursor to the first CHAR on the line of the + end of the current sentence +'< '< 1 cursor to the first CHAR of the line where + highlighted area starts/started in the + current buffer. +'> '> 1 cursor to the first CHAR of the line where + highlighted area ends/ended in the current + buffer. +'[ '[ 1 cursor to the first CHAR on the line of the + start of last operated text or start of put + text +'] '] 1 cursor to the first CHAR on the line of the + end of last operated text or end of put + text +'{ '{ 1 cursor to the first CHAR on the line of the + start of the current paragraph +'} '} 1 cursor to the first CHAR on the line of the + end of the current paragraph +( ( 1 cursor N sentences backward +) ) 1 cursor N sentences forward +star * 1 search forward for the Nth occurrence of + the ident under the cursor ++ + 1 same as +, , 1 repeat latest f, t, F or T in opposite + direction N times +- - 1 cursor to the first CHAR N lines higher +. . 2 repeat last change with count replaced with + N +/ /{pattern} 1 search forward for the Nth occurrence of + {pattern} +/ / 1 search forward for {pattern} of last search +count 0 1 cursor to the first char of the line +count 1 prepend to command to give a count +count 2 " +count 3 " +count 4 " +count 5 " +count 6 " +count 7 " +count 8 " +count 9 " +: : 1 start entering an Ex command +N: {count}: start entering an Ex command with range + from current line to N-1 lines down +; ; 1 repeat latest f, t, F or T N times +< <{motion} 2 shift Nmove lines one 'shiftwidth' + leftwards +<< << 2 shift N lines one 'shiftwidth' leftwards += ={motion} 2 filter Nmove lines through "indent" +== == 2 filter N lines through "indent" +> >{motion} 2 shift Nmove lines one 'shiftwidth' + rightwards +>> >> 2 shift N lines one 'shiftwidth' rightwards +? ?{pattern} 1 search backward for the Nth previous + occurrence of {pattern} +? ? 1 search backward for {pattern} of last search +@ @{a-z} 2 execute the contents of register {a-z} + N times +@: @: repeat the previous ":" command N times +@@ @@ 2 repeat the previous @{a-z} N times +A A 2 append text after the end of the line N times +B B 1 cursor N WORDS backward +C ["x]C 2 change from the cursor position to the end + of the line, and N-1 more lines [into + register x]; synonym for "c$" +D ["x]D 2 delete the characters under the cursor + until the end of the line and N-1 more + lines [into register x]; synonym for "d$" +E E 1 cursor forward to the end of WORD N +F F{char} 1 cursor to the Nth occurrence of {char} to + the left +G G 1 cursor to line N, default last line +H H 1 cursor to line N from top of screen +I I 2 insert text before the first CHAR on the + line N times +J J 2 Join N lines; default is 2 +K K lookup Keyword under the cursor with + 'keywordprg' +L L 1 cursor to line N from bottom of screen +M M 1 cursor to middle line of screen +N N 1 repeat the latest '/' or '?' N times in + opposite direction +O O 2 begin a new line above the cursor and + insert text, repeat N times +P ["x]P 2 put the text [from register x] before the + cursor N times +Q Q switch to "Ex" mode +R R 2 enter replace mode: overtype existing + characters, repeat the entered text N-1 + times +S ["x]S 2 delete N lines [into register x] and start + insert; synonym for "cc". +T T{char} 1 cursor till after Nth occurrence of {char} + to the left +U U 2 undo all latest changes on one line +V V start linewise Visual mode +W W 1 cursor N WORDS forward +X ["x]X 2 delete N characters before the cursor [into + register x] +Y ["x]Y yank N lines [into register x]; synonym for + "yy" +ZZ ZZ store current file if modified, and exit +ZQ ZQ exit current file always +[ [{char} square bracket command (see [ below) + \ not used +] ]{char} square bracket command (see ] below) +^ ^ 1 cursor to the first CHAR of the line +_ _ 1 cursor to the first CHAR N - 1 lines lower +` `{a-zA-Z0-9} 1 cursor to the mark {a-zA-Z0-9} +`( `( 1 cursor to the start of the current sentence +`) `) 1 cursor to the end of the current sentence +`< `< 1 cursor to the start of the highlighted area +`> `> 1 cursor to the end of the highlighted area +`[ `[ 1 cursor to the start of last operated text + or start of putted text +`] `] 1 cursor to the end of last operated text or + end of putted text +`` `` 1 cursor to the position before latest jump +`{ `{ 1 cursor to the start of the current paragraph +`} `} 1 cursor to the end of the current paragraph +a a 2 append text after the cursor N times +b b 1 cursor N words backward +c ["x]c{motion} 2 delete Nmove text [into register x] and + start insert +cc ["x]cc 2 delete N lines [into register x] and start + insert +d ["x]d{motion} 2 delete Nmove text [into register x] +dd ["x]dd 2 delete N lines [into register x] +do do 2 same as ":diffget" +dp dp 2 same as ":diffput" +e e 1 cursor forward to the end of word N +f f{char} 1 cursor to Nth occurrence of {char} to the + right +g g{char} extended commands, see g below +h h 1 cursor N chars to the left +i i 2 insert text before the cursor N times +j j 1 cursor N lines downward +k k 1 cursor N lines upward +l l 1 cursor N chars to the right +m m{A-Za-z} set mark {A-Za-z} at cursor position +n n 1 repeat the latest '/' or '?' N times +o o 2 begin a new line below the cursor and + insert text, repeat N times +p ["x]p 2 put the text [from register x] after the + cursor N times +q q{0-9a-zA-Z"} record typed characters into named register + {0-9a-zA-Z"} (uppercase to append) +q q (while recording) stops recording +q: q: edit : command-line in command-line window +q/ q/ edit / command-line in command-line window +q? q? edit ? command-line in command-line window +r r{char} 2 replace N chars with {char} +s ["x]s 2 (substitute) delete N characters [into + register x] and start insert +t t{char} 1 cursor till before Nth occurrence of {char} + to the right +u u 2 undo changes +v v start characterwise Visual mode +w w 1 cursor N words forward +x ["x]x 2 delete N characters under and after the + cursor [into register x] +y ["x]y{motion} yank Nmove text [into register x] +yy ["x]yy yank N lines [into register x] +z z{char} commands starting with 'z', see z below +{ { 1 cursor N paragraphs backward +bar | 1 cursor to column N +} } 1 cursor N paragraphs forward +~ ~ 2 'tildeop' off: switch case of N characters + under cursor and move the cursor N + characters to the right +~ ~{motion} 'tildeop' on: switch case of Nmove text + 1 same as "G" + 1 same as "gg" + 1 same as "b" + ":ta" to the keyword at the mouse click + 1 same as "w" + same as "CTRL-T" + ["x] 2 same as "x" +N {count} remove the last digit from {count} + 1 same as "j" + 1 same as "$" + same as + open a help window + 1 same as "0" + 2 same as "i" + 1 same as "h" + 1 move cursor to the mouse click position + 2 same as "gP" at the mouse click position + same as CTRL-F + same as CTRL-B + 1 same as "l" + start Visual mode, move cursor to the mouse + click position + 1 same as CTRL-F + 1 same as "b" + same as "*" at the mouse click position + 1 same as "w" + same as "#" at the mouse click position + 1 same as CTRL-B + 2 same as "u" + 1 same as "k" + move window three lines down + move window one page down + move window three lines up + move window one page up + move window six columns left + move window one page left + move window six columns right + move window one page right + +============================================================================== +2.1 Text objects objects + +These can be used after an operator or in Visual mode to select an object. + +tag command action in op-pending and Visual mode +------------------------------------------------------------------------------ +v_aquote a" double quoted string +v_a' a' single quoted string +v_a( a( same as ab +v_a) a) same as ab +v_a< a< "a <>" from '<' to the matching '>' +v_a> a> same as a< +v_aB aB "a Block" from "[{" to "]}" (with brackets) +v_aW aW "a WORD" (with white space) +v_a[ a[ "a []" from '[' to the matching ']' +v_a] a] same as a[ +v_a` a` string in backticks +v_ab ab "a block" from "[(" to "])" (with braces) +v_ap ap "a paragraph" (with white space) +v_as as "a sentence" (with white space) +v_at at "a tag block" (with white space) +v_aw aw "a word" (with white space) +v_a{ a{ same as aB +v_a} a} same as aB +v_iquote i" double quoted string without the quotes +v_i' i' single quoted string without the quotes +v_i( i( same as ib +v_i) i) same as ib +v_i< i< "inner <>" from '<' to the matching '>' +v_i> i> same as i< +v_iB iB "inner Block" from "[{" and "]}" +v_iW iW "inner WORD" +v_i[ i[ "inner []" from '[' to the matching ']' +v_i] i] same as i[ +v_i` i` string in backticks without the backticks +v_ib ib "inner block" from "[(" to "])" +v_ip ip "inner paragraph" +v_is is "inner sentence" +v_it it "inner tag block" +v_iw iw "inner word" +v_i{ i{ same as iB +v_i} i} same as iB + +============================================================================== +2.2 Window commands CTRL-W + +tag command action in Normal mode +------------------------------------------------------------------------------ +CTRL-W_CTRL-B CTRL-W CTRL-B same as "CTRL-W b" +CTRL-W_CTRL-C CTRL-W CTRL-C same as "CTRL-W c" +CTRL-W_CTRL-D CTRL-W CTRL-D same as "CTRL-W d" +CTRL-W_CTRL-F CTRL-W CTRL-F same as "CTRL-W f" + CTRL-W CTRL-G same as "CTRL-W g .." +CTRL-W_CTRL-H CTRL-W CTRL-H same as "CTRL-W h" +CTRL-W_CTRL-I CTRL-W CTRL-I same as "CTRL-W i" +CTRL-W_CTRL-J CTRL-W CTRL-J same as "CTRL-W j" +CTRL-W_CTRL-K CTRL-W CTRL-K same as "CTRL-W k" +CTRL-W_CTRL-L CTRL-W CTRL-L same as "CTRL-W l" +CTRL-W_CTRL-N CTRL-W CTRL-N same as "CTRL-W n" +CTRL-W_CTRL-O CTRL-W CTRL-O same as "CTRL-W o" +CTRL-W_CTRL-P CTRL-W CTRL-P same as "CTRL-W p" +CTRL-W_CTRL-Q CTRL-W CTRL-Q same as "CTRL-W q" +CTRL-W_CTRL-R CTRL-W CTRL-R same as "CTRL-W r" +CTRL-W_CTRL-S CTRL-W CTRL-S same as "CTRL-W s" +CTRL-W_CTRL-T CTRL-W CTRL-T same as "CTRL-W t" +CTRL-W_CTRL-V CTRL-W CTRL-V same as "CTRL-W v" +CTRL-W_CTRL-W CTRL-W CTRL-W same as "CTRL-W w" +CTRL-W_CTRL-X CTRL-W CTRL-X same as "CTRL-W x" +CTRL-W_CTRL-Z CTRL-W CTRL-Z same as "CTRL-W z" +CTRL-W_CTRL-] CTRL-W CTRL-] same as "CTRL-W ]" +CTRL-W_CTRL-^ CTRL-W CTRL-^ same as "CTRL-W ^" +CTRL-W_CTRL-_ CTRL-W CTRL-_ same as "CTRL-W _" +CTRL-W_quote CTRL-W " terminal window: paste register +CTRL-W_+ CTRL-W + increase current window height N lines +CTRL-W_- CTRL-W - decrease current window height N lines +CTRL-W_. CTRL-W . terminal window: type CTRL-W +CTRL-W_: CTRL-W : same as :, edit a command line +CTRL-W_< CTRL-W < decrease current window width N columns +CTRL-W_= CTRL-W = make all windows the same height & width +CTRL-W_> CTRL-W > increase current window width N columns +CTRL-W_H CTRL-W H move current window to the far left +CTRL-W_J CTRL-W J move current window to the very bottom +CTRL-W_K CTRL-W K move current window to the very top +CTRL-W_L CTRL-W L move current window to the far right +CTRL-W_N CTRL-W N terminal window: go to Terminal Normal mode +CTRL-W_P CTRL-W P go to preview window +CTRL-W_R CTRL-W R rotate windows upwards N times +CTRL-W_S CTRL-W S same as "CTRL-W s" +CTRL-W_T CTRL-W T move current window to a new tab page +CTRL-W_W CTRL-W W go to N previous window (wrap around) +CTRL-W_] CTRL-W ] split window and jump to tag under cursor +CTRL-W_^ CTRL-W ^ split current window and edit alternate + file N +CTRL-W__ CTRL-W _ set current window height to N (default: + very high) +CTRL-W_b CTRL-W b go to bottom window +CTRL-W_c CTRL-W c close current window (like :close) +CTRL-W_d CTRL-W d split window and jump to definition under + the cursor +CTRL-W_f CTRL-W f split window and edit file name under the + cursor +CTRL-W_F CTRL-W F split window and edit file name under the + cursor and jump to the line number + following the file name. +CTRL-W_g_CTRL-] CTRL-W g CTRL-] split window and do :tjump to tag under + cursor +CTRL-W_g] CTRL-W g ] split window and do :tselect for tag + under cursor +CTRL-W_g} CTRL-W g } do a :ptjump to the tag under the cursor +CTRL-W_gf CTRL-W g f edit file name under the cursor in a new + tab page +CTRL-W_gF CTRL-W g F edit file name under the cursor in a new + tab page and jump to the line number + following the file name. +CTRL-W_h CTRL-W h go to Nth left window (stop at first window) +CTRL-W_i CTRL-W i split window and jump to declaration of + identifier under the cursor +CTRL-W_j CTRL-W j go N windows down (stop at last window) +CTRL-W_k CTRL-W k go N windows up (stop at first window) +CTRL-W_l CTRL-W l go to Nth right window (stop at last window) +CTRL-W_n CTRL-W n open new window, N lines high +CTRL-W_o CTRL-W o close all but current window (like :only) +CTRL-W_p CTRL-W p go to previous (last accessed) window +CTRL-W_q CTRL-W q quit current window (like :quit) +CTRL-W_r CTRL-W r rotate windows downwards N times +CTRL-W_s CTRL-W s split current window in two parts, new + window N lines high +CTRL-W_t CTRL-W t go to top window +CTRL-W_v CTRL-W v split current window vertically, new window + N columns wide +CTRL-W_w CTRL-W w go to N next window (wrap around) +CTRL-W_x CTRL-W x exchange current window with window N + (default: next window) +CTRL-W_z CTRL-W z close preview window +CTRL-W_bar CTRL-W | set window width to N columns +CTRL-W_} CTRL-W } show tag under cursor in preview window +CTRL-W_ CTRL-W same as "CTRL-W j" +CTRL-W_ CTRL-W same as "CTRL-W k" +CTRL-W_ CTRL-W same as "CTRL-W h" +CTRL-W_ CTRL-W same as "CTRL-W l" + +============================================================================== +2.3 Square bracket commands [ ] + +tag char note action in Normal mode +------------------------------------------------------------------------------ +[_CTRL-D [ CTRL-D jump to first #define found in current and + included files matching the word under the + cursor, start searching at beginning of + current file +[_CTRL-I [ CTRL-I jump to first line in current and included + files that contains the word under the + cursor, start searching at beginning of + current file +[# [# 1 cursor to N previous unmatched #if, #else + or #ifdef +[' [' 1 cursor to previous lowercase mark, on first + non-blank +[( [( 1 cursor N times back to unmatched '(' +[star [* 1 same as "[/" +[` [` 1 cursor to previous lowercase mark +[/ [/ 1 cursor to N previous start of a C comment +[D [D list all defines found in current and + included files matching the word under the + cursor, start searching at beginning of + current file +[I [I list all lines found in current and + included files that contain the word under + the cursor, start searching at beginning of + current file +[P [P 2 same as "[p" +[[ [[ 1 cursor N sections backward +[] [] 1 cursor N SECTIONS backward +[c [c 1 cursor N times backwards to start of change +[d [d show first #define found in current and + included files matching the word under the + cursor, start searching at beginning of + current file +[f [f same as "gf" +[i [i show first line found in current and + included files that contains the word under + the cursor, start searching at beginning of + current file +[m [m 1 cursor N times back to start of member + function +[p [p 2 like "P", but adjust indent to current line +[s [s 1 move to the previous misspelled word +[z [z 1 move to start of open fold +[{ [{ 1 cursor N times back to unmatched '{' +[ [ 2 same as "[p" + +]_CTRL-D ] CTRL-D jump to first #define found in current and + included files matching the word under the + cursor, start searching at cursor position +]_CTRL-I ] CTRL-I jump to first line in current and included + files that contains the word under the + cursor, start searching at cursor position +]# ]# 1 cursor to N next unmatched #endif or #else +]' ]' 1 cursor to next lowercase mark, on first + non-blank +]) ]) 1 cursor N times forward to unmatched ')' +]star ]* 1 same as "]/" +]` ]` 1 cursor to next lowercase mark +]/ ]/ 1 cursor to N next end of a C comment +]D ]D list all #defines found in current and + included files matching the word under the + cursor, start searching at cursor position +]I ]I list all lines found in current and + included files that contain the word under + the cursor, start searching at cursor + position +]P ]P 2 same as "[p" +][ ][ 1 cursor N SECTIONS forward +]] ]] 1 cursor N sections forward +]c ]c 1 cursor N times forward to start of change +]d ]d show first #define found in current and + included files matching the word under the + cursor, start searching at cursor position +]f ]f same as "gf" +]i ]i show first line found in current and + included files that contains the word under + the cursor, start searching at cursor + position +]m ]m 1 cursor N times forward to end of member + function +]p ]p 2 like "p", but adjust indent to current line +]s ]s 1 move to next misspelled word +]z ]z 1 move to end of open fold +]} ]} 1 cursor N times forward to unmatched '}' +] ] 2 same as "]p" + +============================================================================== +2.4 Commands starting with 'g' g + +tag char note action in Normal mode +------------------------------------------------------------------------------ +g_CTRL-A g CTRL-A only when compiled with MEM_PROFILE + defined: dump a memory profile +g_CTRL-G g CTRL-G show information about current cursor + position +g_CTRL-H g CTRL-H start Select block mode +g_CTRL-] g CTRL-] :tjump to the tag under the cursor +g# g# 1 like "#", but without using "\<" and "\>" +g$ g$ 1 when 'wrap' off go to rightmost character of + the current line that is on the screen; + when 'wrap' on go to the rightmost character + of the current screen line +g& g& 2 repeat last ":s" on all lines +g' g'{mark} 1 like ' but without changing the jumplist +g` g`{mark} 1 like ` but without changing the jumplist +gstar g* 1 like "*", but without using "\<" and "\>" +g+ g+ go to newer text state N times +g, g, 1 go to N newer position in change list +g- g- go to older text state N times +g0 g0 1 when 'wrap' off go to leftmost character of + the current line that is on the screen; + when 'wrap' on go to the leftmost character + of the current screen line +g8 g8 print hex value of bytes used in UTF-8 + character under the cursor +g; g; 1 go to N older position in change list +g< g< display previous command output +g? g? 2 Rot13 encoding operator +g?g? g?? 2 Rot13 encode current line +g?g? g?g? 2 Rot13 encode current line +gD gD 1 go to definition of word under the cursor + in current file +gE gE 1 go backwards to the end of the previous + WORD +gH gH start Select line mode +gI gI 2 like "I", but always start in column 1 +gJ gJ 2 join lines without inserting space +gN gN 1,2 find the previous match with the last used + search pattern and Visually select it +gP ["x]gP 2 put the text [from register x] before the + cursor N times, leave the cursor after it +gQ gQ switch to "Ex" mode with Vim editing +gR gR 2 enter Virtual Replace mode +gT gT go to the previous tab page +gU gU{motion} 2 make Nmove text uppercase +gV gV don't reselect the previous Visual area + when executing a mapping or menu in Select + mode +g] g] :tselect on the tag under the cursor +g^ g^ 1 when 'wrap' off go to leftmost non-white + character of the current line that is on + the screen; when 'wrap' on go to the + leftmost non-white character of the current + screen line +g_ g_ 1 cursor to the last CHAR N - 1 lines lower +ga ga print ascii value of character under the + cursor +gd gd 1 go to definition of word under the cursor + in current function +ge ge 1 go backwards to the end of the previous + word +gf gf start editing the file whose name is under + the cursor +gF gF start editing the file whose name is under + the cursor and jump to the line number + following the filename. +gg gg 1 cursor to line N, default first line +gh gh start Select mode +gi gi 2 like "i", but first move to the '^ mark +gj gj 1 like "j", but when 'wrap' on go N screen + lines down +gk gk 1 like "k", but when 'wrap' on go N screen + lines up +gn gn 1,2 find the next match with the last used + search pattern and Visually select it +gm gm 1 go to character at middle of the screenline +go go 1 cursor to byte N in the buffer +gp ["x]gp 2 put the text [from register x] after the + cursor N times, leave the cursor after it +gq gq{motion} 2 format Nmove text +gr gr{char} 2 virtual replace N chars with {char} +gs gs go to sleep for N seconds (default 1) +gt gt go to the next tab page +gu gu{motion} 2 make Nmove text lowercase +gv gv reselect the previous Visual area +gw gw{motion} 2 format Nmove text and keep cursor +netrw-gx gx execute application for file name under the + cursor (only with netrw plugin) +g@ g@{motion} call 'operatorfunc' +g~ g~{motion} 2 swap case for Nmove text +g g 1 same as "gj" +g g 1 same as "g$" +g g 1 same as "g0" +g g same as + g same as +g g same as +g g 1 same as "gk" + +============================================================================== +2.5 Commands starting with 'z' z + +tag char note action in Normal mode +------------------------------------------------------------------------------ +z z redraw, cursor line to top of window, + cursor on first non-blank +zN z{height} redraw, make window {height} lines high +z+ z+ cursor on line N (default line below + window), otherwise like "z" +z- z- redraw, cursor line at bottom of window, + cursor on first non-blank +z. z. redraw, cursor line to center of window, + cursor on first non-blank +z= z= give spelling suggestions +zA zA open a closed fold or close an open fold + recursively +zC zC close folds recursively +zD zD delete folds recursively +zE zE eliminate all folds +zF zF create a fold for N lines +zG zG mark word as good spelled word +zH zH when 'wrap' off scroll half a screenwidth + to the right +zL zL when 'wrap' off scroll half a screenwidth + to the left +zM zM set 'foldlevel' to zero +zN zN set 'foldenable' +zO zO open folds recursively +zR zR set 'foldlevel' to the deepest fold +zW zW mark word as wrong (bad) spelled word +zX zX re-apply 'foldlevel' +z^ z^ cursor on line N (default line above + window), otherwise like "z-" +za za open a closed fold, close an open fold +zb zb redraw, cursor line at bottom of window +zc zc close a fold +zd zd delete a fold +ze ze when 'wrap' off scroll horizontally to + position the cursor at the end (right side) + of the screen +zf zf{motion} create a fold for Nmove text +zg zg mark word as good spelled word +zh zh when 'wrap' off scroll screen N characters + to the right +zi zi toggle 'foldenable' +zj zj 1 move to the start of the next fold +zk zk 1 move to the end of the previous fold +zl zl when 'wrap' off scroll screen N characters + to the left +zm zm subtract one from 'foldlevel' +zn zn reset 'foldenable' +zo zo open fold +zr zr add one to 'foldlevel' +zs zs when 'wrap' off scroll horizontally to + position the cursor at the start (left + side) of the screen +zt zt redraw, cursor line at top of window +zv zv open enough folds to view the cursor line +zw zw mark word as wrong (bad) spelled word +zx zx re-apply 'foldlevel' and do "zv" +zz zz redraw, cursor line at center of window +z z same as "zh" +z z same as "zl" + +============================================================================== +3. Visual mode visual-index + +Most commands in Visual mode are the same as in Normal mode. The ones listed +here are those that are different. + +tag command note action in Visual mode +------------------------------------------------------------------------------ +v_CTRL-\_CTRL-N CTRL-\ CTRL-N stop Visual mode +v_CTRL-\_CTRL-G CTRL-\ CTRL-G go to mode specified with 'insertmode' +v_CTRL-A CTRL-A 2 add N to number in highlighted text +v_CTRL-C CTRL-C stop Visual mode +v_CTRL-G CTRL-G toggle between Visual mode and Select mode +v_ 2 Select mode: delete highlighted area +v_CTRL-H CTRL-H 2 same as +v_CTRL-O CTRL-O switch from Select to Visual mode for one + command +v_CTRL-V CTRL-V make Visual mode blockwise or stop Visual + mode +v_CTRL-X CTRL-X 2 subtract N from number in highlighted text +v_ stop Visual mode +v_CTRL-] CTRL-] jump to highlighted tag +v_! !{filter} 2 filter the highlighted lines through the + external command {filter} +v_: : start a command-line with the highlighted + lines as a range +v_< < 2 shift the highlighted lines one + 'shiftwidth' left +v_= = 2 filter the highlighted lines through the + external program given with the 'equalprg' + option +v_> > 2 shift the highlighted lines one + 'shiftwidth' right +v_b_A A 2 block mode: append same text in all lines, + after the highlighted area +v_C C 2 delete the highlighted lines and start + insert +v_D D 2 delete the highlighted lines +v_b_I I 2 block mode: insert same text in all lines, + before the highlighted area +v_J J 2 join the highlighted lines +v_K K run 'keywordprg' on the highlighted area +v_O O Move horizontally to other corner of area. + Q does not start Ex mode +v_R R 2 delete the highlighted lines and start + insert +v_S S 2 delete the highlighted lines and start + insert +v_U U 2 make highlighted area uppercase +v_V V make Visual mode linewise or stop Visual + mode +v_X X 2 delete the highlighted lines +v_Y Y yank the highlighted lines +v_aquote a" extend highlighted area with a double + quoted string +v_a' a' extend highlighted area with a single + quoted string +v_a( a( same as ab +v_a) a) same as ab +v_a< a< extend highlighted area with a <> block +v_a> a> same as a< +v_aB aB extend highlighted area with a {} block +v_aW aW extend highlighted area with "a WORD" +v_a[ a[ extend highlighted area with a [] block +v_a] a] same as a[ +v_a` a` extend highlighted area with a backtick + quoted string +v_ab ab extend highlighted area with a () block +v_ap ap extend highlighted area with a paragraph +v_as as extend highlighted area with a sentence +v_at at extend highlighted area with a tag block +v_aw aw extend highlighted area with "a word" +v_a{ a{ same as aB +v_a} a} same as aB +v_c c 2 delete highlighted area and start insert +v_d d 2 delete highlighted area +v_g_CTRL-A g CTRL-A 2 add N to number in highlighted text +v_g_CTRL-X g CTRL-X 2 subtract N from number in highlighted text +v_gJ gJ 2 join the highlighted lines without + inserting spaces +v_gq gq 2 format the highlighted lines +v_gv gv exchange current and previous highlighted + area +v_iquote i" extend highlighted area with a double + quoted string (without quotes) +v_i' i' extend highlighted area with a single + quoted string (without quotes) +v_i( i( same as ib +v_i) i) same as ib +v_i< i< extend highlighted area with inner <> block +v_i> i> same as i< +v_iB iB extend highlighted area with inner {} block +v_iW iW extend highlighted area with "inner WORD" +v_i[ i[ extend highlighted area with inner [] block +v_i] i] same as i[ +v_i` i` extend highlighted area with a backtick + quoted string (without the backticks) +v_ib ib extend highlighted area with inner () block +v_ip ip extend highlighted area with inner paragraph +v_is is extend highlighted area with inner sentence +v_it it extend highlighted area with inner tag block +v_iw iw extend highlighted area with "inner word" +v_i{ i{ same as iB +v_i} i} same as iB +v_o o move cursor to other corner of area +v_r r 2 delete highlighted area and start insert +v_s s 2 delete highlighted area and start insert +v_u u 2 make highlighted area lowercase +v_v v make Visual mode characterwise or stop + Visual mode +v_x x 2 delete the highlighted area +v_y y yank the highlighted area +v_~ ~ 2 swap case for the highlighted area + +============================================================================== +4. Command-line editing ex-edit-index + +Get to the command-line with the ':', '!', '/' or '?' commands. +Normal characters are inserted at the current cursor position. +"Completion" below refers to context-sensitive completion. It will complete +file names, tags, commands etc. as appropriate. + +tag command action in Command-line editing mode +------------------------------------------------------------------------------ + CTRL-@ not used +c_CTRL-A CTRL-A do completion on the pattern in front of the + cursor and insert all matches +c_CTRL-B CTRL-B cursor to begin of command-line +c_CTRL-C CTRL-C same as +c_CTRL-D CTRL-D list completions that match the pattern in + front of the cursor +c_CTRL-E CTRL-E cursor to end of command-line +'cedit' CTRL-F default value for 'cedit': opens the + command-line window; otherwise not used +c_CTRL-G CTRL-G next match when 'incsearch' is active +c_ delete the character in front of the cursor +c_digraph {char1} {char2} + enter digraph when 'digraph' is on +c_CTRL-H CTRL-H same as +c_ if 'wildchar' is : Do completion on + the pattern in front of the cursor +c_ same as CTRL-P +c_wildchar 'wildchar' Do completion on the pattern in front of the + cursor (default: ) +c_CTRL-I CTRL-I same as +c_ same as +c_CTRL-J CTRL-J same as +c_CTRL-K CTRL-K {char1} {char2} + enter digraph +c_CTRL-L CTRL-L do completion on the pattern in front of the + cursor and insert the longest common part +c_ execute entered command +c_CTRL-M CTRL-M same as +c_CTRL-N CTRL-N after using 'wildchar' with multiple matches: + go to next match, otherwise: recall older + command-line from history. + CTRL-O not used +c_CTRL-P CTRL-P after using 'wildchar' with multiple matches: + go to previous match, otherwise: recall older + command-line from history. +c_CTRL-Q CTRL-Q same as CTRL-V, unless it's used for terminal + control flow +c_CTRL-R CTRL-R {0-9a-z"%#*:= CTRL-F CTRL-P CTRL-W CTRL-A} + insert the contents of a register or object + under the cursor as if typed +c_CTRL-R_CTRL-R CTRL-R CTRL-R {0-9a-z"%#*:= CTRL-F CTRL-P CTRL-W CTRL-A} + insert the contents of a register or object + under the cursor literally + CTRL-S (used for terminal control flow) +c_CTRL-T CTRL-T previous match when 'incsearch' is active +c_CTRL-U CTRL-U remove all characters +c_CTRL-V CTRL-V insert next non-digit literally, insert three + digit decimal number as a single byte. +c_CTRL-W CTRL-W delete the word in front of the cursor + CTRL-X not used (reserved for completion) + CTRL-Y copy (yank) modeless selection + CTRL-Z not used (reserved for suspend) +c_ abandon command-line without executing it +c_CTRL-[ CTRL-[ same as +c_CTRL-\_CTRL-N CTRL-\ CTRL-N go to Normal mode, abandon command-line +c_CTRL-\_CTRL-G CTRL-\ CTRL-G go to mode specified with 'insertmode', + abandon command-line + CTRL-\ a - d reserved for extensions +c_CTRL-\_e CTRL-\ e {expr} replace the command line with the result of + {expr} + CTRL-\ f - z reserved for extensions + CTRL-\ others not used +c_CTRL-] CTRL-] trigger abbreviation +c_CTRL-^ CTRL-^ toggle use of :lmap mappings +c_CTRL-_ CTRL-_ when 'allowrevins' set: change language + (Hebrew, Farsi) +c_ delete the character under the cursor + +c_ cursor left +c_ cursor one word left +c_ cursor one word left +c_ cursor right +c_ cursor one word right +c_ cursor one word right +c_ recall previous command-line from history that + matches pattern in front of the cursor +c_ recall previous command-line from history +c_ recall next command-line from history that + matches pattern in front of the cursor +c_ recall next command-line from history +c_ cursor to start of command-line +c_ cursor to end of command-line +c_ same as +c_ same as +c_ toggle insert/overstrike mode +c_ cursor at mouse click + +You found it, Arthur! holy-grail :smile + +============================================================================== +5. EX commands ex-cmd-index :index + +This is a brief but complete listing of all the ":" commands, without +mentioning any arguments. The optional part of the command name is inside []. +The commands are sorted on the non-optional part of their name. + +tag command action +------------------------------------------------------------------------------ +:! :! filter lines or execute an external command +:!! :!! repeat last ":!" command +:# :# same as ":number" +:& :& repeat last ":substitute" +:star :* execute contents of a register +:< :< shift lines one 'shiftwidth' left +:= := print the cursor line number +:> :> shift lines one 'shiftwidth' right +:@ :@ execute contents of a register +:@@ :@@ repeat the previous ":@" +:Next :N[ext] go to previous file in the argument list +:Print :P[rint] print lines +:X :X ask for encryption key +:append :a[ppend] append text +:abbreviate :ab[breviate] enter abbreviation +:abclear :abc[lear] remove all abbreviations +:aboveleft :abo[veleft] make split window appear left or above +:all :al[l] open a window for each file in the argument + list +:amenu :am[enu] enter new menu item for all modes +:anoremenu :an[oremenu] enter a new menu for all modes that will not + be remapped +:args :ar[gs] print the argument list +:argadd :arga[dd] add items to the argument list +:argdelete :argd[elete] delete items from the argument list +:argedit :arge[dit] add item to the argument list and edit it +:argdo :argdo do a command on all items in the argument list +:argglobal :argg[lobal] define the global argument list +:arglocal :argl[ocal] define a local argument list +:argument :argu[ment] go to specific file in the argument list +:ascii :as[cii] print ascii value of character under the cursor +:autocmd :au[tocmd] enter or show autocommands +:augroup :aug[roup] select the autocommand group to use +:aunmenu :aun[menu] remove menu for all modes +:buffer :b[uffer] go to specific buffer in the buffer list +:bNext :bN[ext] go to previous buffer in the buffer list +:ball :ba[ll] open a window for each buffer in the buffer list +:badd :bad[d] add buffer to the buffer list +:bdelete :bd[elete] remove a buffer from the buffer list +:behave :be[have] set mouse and selection behavior +:belowright :bel[owright] make split window appear right or below +:bfirst :bf[irst] go to first buffer in the buffer list +:blast :bl[ast] go to last buffer in the buffer list +:bmodified :bm[odified] go to next buffer in the buffer list that has + been modified +:bnext :bn[ext] go to next buffer in the buffer list +:botright :bo[tright] make split window appear at bottom or far right +:bprevious :bp[revious] go to previous buffer in the buffer list +:brewind :br[ewind] go to first buffer in the buffer list +:break :brea[k] break out of while loop +:breakadd :breaka[dd] add a debugger breakpoint +:breakdel :breakd[el] delete a debugger breakpoint +:breaklist :breakl[ist] list debugger breakpoints +:browse :bro[wse] use file selection dialog +:bufdo :bufdo execute command in each listed buffer +:buffers :buffers list all files in the buffer list +:bunload :bun[load] unload a specific buffer +:bwipeout :bw[ipeout] really delete a buffer +:change :c[hange] replace a line or series of lines +:cNext :cN[ext] go to previous error +:cNfile :cNf[ile] go to last error in previous file +:cabbrev :ca[bbrev] like ":abbreviate" but for Command-line mode +:cabclear :cabc[lear] clear all abbreviations for Command-line mode +:caddbuffer :cad[dbuffer] add errors from buffer +:caddexpr :cadde[xpr] add errors from expr +:caddfile :caddf[ile] add error message to current quickfix list +:call :cal[l] call a function +:catch :cat[ch] part of a :try command +:cbottom :cbo[ttom] scroll to the bottom of the quickfix window +:cbuffer :cb[uffer] parse error messages and jump to first error +:cc :cc go to specific error +:cclose :ccl[ose] close quickfix window +:cd :cd change directory +:cdo :cdo execute command in each valid error list entry +:cfdo :cfd[o] execute command in each file in error list +:center :ce[nter] format lines at the center +:cexpr :cex[pr] read errors from expr and jump to first +:cfile :cf[ile] read file with error messages and jump to first +:cfirst :cfir[st] go to the specified error, default first one +:cgetbuffer :cgetb[uffer] get errors from buffer +:cgetexpr :cgete[xpr] get errors from expr +:cgetfile :cg[etfile] read file with error messages +:changes :changes print the change list +:chdir :chd[ir] change directory +:checkpath :che[ckpath] list included files +:checktime :checkt[ime] check timestamp of loaded buffers +:chistory :chi[story] list the error lists +:clast :cla[st] go to the specified error, default last one +:clearjumps :cle[arjumps] clear the jump list +:clist :cl[ist] list all errors +:close :clo[se] close current window +:cmap :cm[ap] like ":map" but for Command-line mode +:cmapclear :cmapc[lear] clear all mappings for Command-line mode +:cmenu :cme[nu] add menu for Command-line mode +:cnext :cn[ext] go to next error +:cnewer :cnew[er] go to newer error list +:cnfile :cnf[ile] go to first error in next file +:cnoremap :cno[remap] like ":noremap" but for Command-line mode +:cnoreabbrev :cnorea[bbrev] like ":noreabbrev" but for Command-line mode +:cnoremenu :cnoreme[nu] like ":noremenu" but for Command-line mode +:copy :co[py] copy lines +:colder :col[der] go to older error list +:colorscheme :colo[rscheme] load a specific color scheme +:command :com[mand] create user-defined command +:comclear :comc[lear] clear all user-defined commands +:compiler :comp[iler] do settings for a specific compiler +:continue :con[tinue] go back to :while +:confirm :conf[irm] prompt user when confirmation required +:copen :cope[n] open quickfix window +:cprevious :cp[revious] go to previous error +:cpfile :cpf[ile] go to last error in previous file +:cquit :cq[uit] quit Vim with an error code +:crewind :cr[ewind] go to the specified error, default first one +:cscope :cs[cope] execute cscope command +:cstag :cst[ag] use cscope to jump to a tag +:cunmap :cu[nmap] like ":unmap" but for Command-line mode +:cunabbrev :cuna[bbrev] like ":unabbrev" but for Command-line mode +:cunmenu :cunme[nu] remove menu for Command-line mode +:cwindow :cw[indow] open or close quickfix window +:delete :d[elete] delete lines +:delmarks :delm[arks] delete marks +:debug :deb[ug] run a command in debugging mode +:debuggreedy :debugg[reedy] read debug mode commands from normal input +:delcommand :delc[ommand] delete user-defined command +:delfunction :delf[unction] delete a user function +:diffupdate :dif[fupdate] update 'diff' buffers +:diffget :diffg[et] remove differences in current buffer +:diffoff :diffo[ff] switch off diff mode +:diffpatch :diffp[atch] apply a patch and show differences +:diffput :diffpu[t] remove differences in other buffer +:diffsplit :diffs[plit] show differences with another file +:diffthis :diffthis make current window a diff window +:digraphs :dig[raphs] show or enter digraphs +:display :di[splay] display registers +:djump :dj[ump] jump to #define +:dl :dl short for :delete with the 'l' flag +:del :del[ete]l short for :delete with the 'l' flag +:dlist :dli[st] list #defines +:doautocmd :do[autocmd] apply autocommands to current buffer +:doautoall :doautoa[ll] apply autocommands for all loaded buffers +:dp :d[elete]p short for :delete with the 'p' flag +:drop :dr[op] jump to window editing file or edit file in + current window +:dsearch :ds[earch] list one #define +:dsplit :dsp[lit] split window and jump to #define +:edit :e[dit] edit a file +:earlier :ea[rlier] go to older change, undo +:echo :ec[ho] echoes the result of expressions +:echoerr :echoe[rr] like :echo, show like an error and use history +:echohl :echoh[l] set highlighting for echo commands +:echomsg :echom[sg] same as :echo, put message in history +:echon :echon same as :echo, but without +:else :el[se] part of an :if command +:elseif :elsei[f] part of an :if command +:emenu :em[enu] execute a menu by name +:endif :en[dif] end previous :if +:endfor :endfo[r] end previous :for +:endfunction :endf[unction] end of a user function +:endtry :endt[ry] end previous :try +:endwhile :endw[hile] end previous :while +:enew :ene[w] edit a new, unnamed buffer +:ex :ex same as ":edit" +:execute :exe[cute] execute result of expressions +:exit :exi[t] same as ":xit" +:exusage :exu[sage] overview of Ex commands +:file :f[ile] show or set the current file name +:files :files list all files in the buffer list +:filetype :filet[ype] switch file type detection on/off +:filter :filt[er] filter output of following command +:find :fin[d] find file in 'path' and edit it +:finally :fina[lly] part of a :try command +:finish :fini[sh] quit sourcing a Vim script +:first :fir[st] go to the first file in the argument list +:fixdel :fix[del] set key code of +:fold :fo[ld] create a fold +:foldclose :foldc[lose] close folds +:folddoopen :foldd[oopen] execute command on lines not in a closed fold +:folddoclosed :folddoc[losed] execute command on lines in a closed fold +:foldopen :foldo[pen] open folds +:for :for for loop +:function :fu[nction] define a user function +:global :g[lobal] execute commands for matching lines +:goto :go[to] go to byte in the buffer +:grep :gr[ep] run 'grepprg' and jump to first match +:grepadd :grepa[dd] like :grep, but append to current list +:gui :gu[i] start the GUI +:gvim :gv[im] start the GUI +:hardcopy :ha[rdcopy] send text to the printer +:help :h[elp] open a help window +:helpclose :helpc[lose] close one help window +:helpfind :helpf[ind] dialog to open a help window +:helpgrep :helpg[rep] like ":grep" but searches help files +:helptags :helpt[ags] generate help tags for a directory +:highlight :hi[ghlight] specify highlighting methods +:hide :hid[e] hide current buffer for a command +:history :his[tory] print a history list +:insert :i[nsert] insert text +:iabbrev :ia[bbrev] like ":abbrev" but for Insert mode +:iabclear :iabc[lear] like ":abclear" but for Insert mode +:if :if execute commands when condition met +:ijump :ij[ump] jump to definition of identifier +:ilist :il[ist] list lines where identifier matches +:imap :im[ap] like ":map" but for Insert mode +:imapclear :imapc[lear] like ":mapclear" but for Insert mode +:imenu :ime[nu] add menu for Insert mode +:inoremap :ino[remap] like ":noremap" but for Insert mode +:inoreabbrev :inorea[bbrev] like ":noreabbrev" but for Insert mode +:inoremenu :inoreme[nu] like ":noremenu" but for Insert mode +:intro :int[ro] print the introductory message +:isearch :is[earch] list one line where identifier matches +:isplit :isp[lit] split window and jump to definition of + identifier +:iunmap :iu[nmap] like ":unmap" but for Insert mode +:iunabbrev :iuna[bbrev] like ":unabbrev" but for Insert mode +:iunmenu :iunme[nu] remove menu for Insert mode +:join :j[oin] join lines +:jumps :ju[mps] print the jump list +:k :k set a mark +:keepalt :keepa[lt] following command keeps the alternate file +:keepmarks :kee[pmarks] following command keeps marks where they are +:keepjumps :keepj[umps] following command keeps jumplist and marks +:keeppatterns :keepp[atterns] following command keeps search pattern history +:lNext :lN[ext] go to previous entry in location list +:lNfile :lNf[ile] go to last entry in previous file +:list :l[ist] print lines +:laddexpr :lad[dexpr] add locations from expr +:laddbuffer :laddb[uffer] add locations from buffer +:laddfile :laddf[ile] add locations to current location list +:last :la[st] go to the last file in the argument list +:language :lan[guage] set the language (locale) +:later :lat[er] go to newer change, redo +:lbottom :lbo[ttom] scroll to the bottom of the location window +:lbuffer :lb[uffer] parse locations and jump to first location +:lcd :lc[d] change directory locally +:lchdir :lch[dir] change directory locally +:lclose :lcl[ose] close location window +:lcscope :lcs[cope] like ":cscope" but uses location list +:ldo :ld[o] execute command in valid location list entries +:lfdo :lfd[o] execute command in each file in location list +:left :le[ft] left align lines +:leftabove :lefta[bove] make split window appear left or above +:let :let assign a value to a variable or option +:lexpr :lex[pr] read locations from expr and jump to first +:lfile :lf[ile] read file with locations and jump to first +:lfirst :lfir[st] go to the specified location, default first one +:lgetbuffer :lgetb[uffer] get locations from buffer +:lgetexpr :lgete[xpr] get locations from expr +:lgetfile :lg[etfile] read file with locations +:lgrep :lgr[ep] run 'grepprg' and jump to first match +:lgrepadd :lgrepa[dd] like :grep, but append to current list +:lhelpgrep :lh[elpgrep] like ":helpgrep" but uses location list +:lhistory :lhi[story] list the location lists +:ll :ll go to specific location +:llast :lla[st] go to the specified location, default last one +:llist :lli[st] list all locations +:lmake :lmak[e] execute external command 'makeprg' and parse + error messages +:lmap :lm[ap] like ":map!" but includes Lang-Arg mode +:lmapclear :lmapc[lear] like ":mapclear!" but includes Lang-Arg mode +:lnext :lne[xt] go to next location +:lnewer :lnew[er] go to newer location list +:lnfile :lnf[ile] go to first location in next file +:lnoremap :ln[oremap] like ":noremap!" but includes Lang-Arg mode +:loadkeymap :loadk[eymap] load the following keymaps until EOF +:loadview :lo[adview] load view for current window from a file +:lockmarks :loc[kmarks] following command keeps marks where they are +:lockvar :lockv[ar] lock variables +:lolder :lol[der] go to older location list +:lopen :lope[n] open location window +:lprevious :lp[revious] go to previous location +:lpfile :lpf[ile] go to last location in previous file +:lrewind :lr[ewind] go to the specified location, default first one +:ls :ls list all buffers +:ltag :lt[ag] jump to tag and add matching tags to the + location list +:lunmap :lu[nmap] like ":unmap!" but includes Lang-Arg mode +:lua :lua execute Lua command +:luado :luad[o] execute Lua command for each line +:luafile :luaf[ile] execute Lua script file +:lvimgrep :lv[imgrep] search for pattern in files +:lvimgrepadd :lvimgrepa[dd] like :vimgrep, but append to current list +:lwindow :lw[indow] open or close location window +:move :m[ove] move lines +:mark :ma[rk] set a mark +:make :mak[e] execute external command 'makeprg' and parse + error messages +:map :map show or enter a mapping +:mapclear :mapc[lear] clear all mappings for Normal and Visual mode +:marks :marks list all marks +:match :mat[ch] define a match to highlight +:menu :me[nu] enter a new menu item +:menutranslate :menut[ranslate] add a menu translation item +:messages :mes[sages] view previously displayed messages +:mkexrc :mk[exrc] write current mappings and settings to a file +:mksession :mks[ession] write session info to a file +:mkspell :mksp[ell] produce .spl spell file +:mkvimrc :mkv[imrc] write current mappings and settings to a file +:mkview :mkvie[w] write view of current window to a file +:mode :mod[e] show or change the screen mode +:mzscheme :mz[scheme] execute MzScheme command +:mzfile :mzf[ile] execute MzScheme script file +:nbclose :nbc[lose] close the current Netbeans session +:nbkey :nb[key] pass a key to Netbeans +:nbstart :nbs[art] start a new Netbeans session +:next :n[ext] go to next file in the argument list +:new :new create a new empty window +:nmap :nm[ap] like ":map" but for Normal mode +:nmapclear :nmapc[lear] clear all mappings for Normal mode +:nmenu :nme[nu] add menu for Normal mode +:nnoremap :nn[oremap] like ":noremap" but for Normal mode +:nnoremenu :nnoreme[nu] like ":noremenu" but for Normal mode +:noautocmd :noa[utocmd] following commands don't trigger autocommands +:noremap :no[remap] enter a mapping that will not be remapped +:nohlsearch :noh[lsearch] suspend 'hlsearch' highlighting +:noreabbrev :norea[bbrev] enter an abbreviation that will not be + remapped +:noremenu :noreme[nu] enter a menu that will not be remapped +:normal :norm[al] execute Normal mode commands +:noswapfile :nos[wapfile] following commands don't create a swap file +:number :nu[mber] print lines with line number +:nunmap :nun[map] like ":unmap" but for Normal mode +:nunmenu :nunme[nu] remove menu for Normal mode +:oldfiles :ol[dfiles] list files that have marks in the viminfo file +:open :o[pen] start open mode (not implemented) +:omap :om[ap] like ":map" but for Operator-pending mode +:omapclear :omapc[lear] remove all mappings for Operator-pending mode +:omenu :ome[nu] add menu for Operator-pending mode +:only :on[ly] close all windows except the current one +:onoremap :ono[remap] like ":noremap" but for Operator-pending mode +:onoremenu :onoreme[nu] like ":noremenu" but for Operator-pending mode +:options :opt[ions] open the options-window +:ounmap :ou[nmap] like ":unmap" but for Operator-pending mode +:ounmenu :ounme[nu] remove menu for Operator-pending mode +:ownsyntax :ow[nsyntax] set new local syntax highlight for this window +:packadd :pa[ckadd] add a plugin from 'packpath' +:packloadall :packl[oadall] load all packages under 'packpath' +:pclose :pc[lose] close preview window +:pedit :ped[it] edit file in the preview window +:perl :pe[rl] execute Perl command +:print :p[rint] print lines +:profdel :profd[el] stop profiling a function or script +:profile :prof[ile] profiling functions and scripts +:promptfind :pro[mptfind] open GUI dialog for searching +:promptrepl :promptr[epl] open GUI dialog for search/replace +:perldo :perld[o] execute Perl command for each line +:pop :po[p] jump to older entry in tag stack +:popup :popu[p] popup a menu by name +:ppop :pp[op] ":pop" in preview window +:preserve :pre[serve] write all text to swap file +:previous :prev[ious] go to previous file in argument list +:psearch :ps[earch] like ":ijump" but shows match in preview window +:ptag :pt[ag] show tag in preview window +:ptNext :ptN[ext] :tNext in preview window +:ptfirst :ptf[irst] :trewind in preview window +:ptjump :ptj[ump] :tjump and show tag in preview window +:ptlast :ptl[ast] :tlast in preview window +:ptnext :ptn[ext] :tnext in preview window +:ptprevious :ptp[revious] :tprevious in preview window +:ptrewind :ptr[ewind] :trewind in preview window +:ptselect :pts[elect] :tselect and show tag in preview window +:put :pu[t] insert contents of register in the text +:pwd :pw[d] print current directory +:py3 :py3 execute Python 3 command +:python3 :python3 same as :py3 +:py3do :py3d[o] execute Python 3 command for each line +:py3file :py3f[ile] execute Python 3 script file +:python :py[thon] execute Python command +:pydo :pyd[o] execute Python command for each line +:pyfile :pyf[ile] execute Python script file +:pyx :pyx execute python_x command +:pythonx :pythonx same as :pyx +:pyxdo :pyxd[o] execute python_x command for each line +:pyxfile :pyxf[ile] execute python_x script file +:quit :q[uit] quit current window (when one window quit Vim) +:quitall :quita[ll] quit Vim +:qall :qa[ll] quit Vim +:read :r[ead] read file into the text +:recover :rec[over] recover a file from a swap file +:redo :red[o] redo one undone change +:redir :redi[r] redirect messages to a file or register +:redraw :redr[aw] force a redraw of the display +:redrawstatus :redraws[tatus] force a redraw of the status line(s) +:registers :reg[isters] display the contents of registers +:resize :res[ize] change current window height +:retab :ret[ab] change tab size +:return :retu[rn] return from a user function +:rewind :rew[ind] go to the first file in the argument list +:right :ri[ght] right align text +:rightbelow :rightb[elow] make split window appear right or below +:ruby :rub[y] execute Ruby command +:rubydo :rubyd[o] execute Ruby command for each line +:rubyfile :rubyf[ile] execute Ruby script file +:rundo :rund[o] read undo information from a file +:runtime :ru[ntime] source vim scripts in 'runtimepath' +:rviminfo :rv[iminfo] read from viminfo file +:substitute :s[ubstitute] find and replace text +:sNext :sN[ext] split window and go to previous file in + argument list +:sandbox :san[dbox] execute a command in the sandbox +:sargument :sa[rgument] split window and go to specific file in + argument list +:sall :sal[l] open a window for each file in argument list +:saveas :sav[eas] save file under another name. +:sbuffer :sb[uffer] split window and go to specific file in the + buffer list +:sbNext :sbN[ext] split window and go to previous file in the + buffer list +:sball :sba[ll] open a window for each file in the buffer list +:sbfirst :sbf[irst] split window and go to first file in the + buffer list +:sblast :sbl[ast] split window and go to last file in buffer + list +:sbmodified :sbm[odified] split window and go to modified file in the + buffer list +:sbnext :sbn[ext] split window and go to next file in the buffer + list +:sbprevious :sbp[revious] split window and go to previous file in the + buffer list +:sbrewind :sbr[ewind] split window and go to first file in the + buffer list +:scriptnames :scr[iptnames] list names of all sourced Vim scripts +:scriptencoding :scripte[ncoding] encoding used in sourced Vim script +:scscope :scs[cope] split window and execute cscope command +:set :se[t] show or set options +:setfiletype :setf[iletype] set 'filetype', unless it was set already +:setglobal :setg[lobal] show global values of options +:setlocal :setl[ocal] show or set options locally +:sfind :sf[ind] split current window and edit file in 'path' +:sfirst :sfir[st] split window and go to first file in the + argument list +:shell :sh[ell] escape to a shell +:simalt :sim[alt] Win32 GUI: simulate Windows ALT key +:sign :sig[n] manipulate signs +:silent :sil[ent] run a command silently +:sleep :sl[eep] do nothing for a few seconds +:slast :sla[st] split window and go to last file in the + argument list +:smagic :sm[agic] :substitute with 'magic' +:smap :smap like ":map" but for Select mode +:smapclear :smapc[lear] remove all mappings for Select mode +:smenu :sme[nu] add menu for Select mode +:smile :smi[le] make the user happy +:snext :sn[ext] split window and go to next file in the + argument list +:snomagic :sno[magic] :substitute with 'nomagic' +:snoremap :snor[emap] like ":noremap" but for Select mode +:snoremenu :snoreme[nu] like ":noremenu" but for Select mode +:sort :sor[t] sort lines +:source :so[urce] read Vim or Ex commands from a file +:spelldump :spelld[ump] split window and fill with all correct words +:spellgood :spe[llgood] add good word for spelling +:spellinfo :spelli[nfo] show info about loaded spell files +:spellrepall :spellr[epall] replace all bad words like last z= +:spellundo :spellu[ndo] remove good or bad word +:spellwrong :spellw[rong] add spelling mistake +:split :sp[lit] split current window +:sprevious :spr[evious] split window and go to previous file in the + argument list +:srewind :sre[wind] split window and go to first file in the + argument list +:stop :st[op] suspend the editor or escape to a shell +:stag :sta[g] split window and jump to a tag +:startinsert :star[tinsert] start Insert mode +:startgreplace :startg[replace] start Virtual Replace mode +:startreplace :startr[eplace] start Replace mode +:stopinsert :stopi[nsert] stop Insert mode +:stjump :stj[ump] do ":tjump" and split window +:stselect :sts[elect] do ":tselect" and split window +:sunhide :sun[hide] same as ":unhide" +:sunmap :sunm[ap] like ":unmap" but for Select mode +:sunmenu :sunme[nu] remove menu for Select mode +:suspend :sus[pend] same as ":stop" +:sview :sv[iew] split window and edit file read-only +:swapname :sw[apname] show the name of the current swap file +:syntax :sy[ntax] syntax highlighting +:syntime :synti[me] measure syntax highlighting speed +:syncbind :sync[bind] sync scroll binding +:t :t same as ":copy" +:tNext :tN[ext] jump to previous matching tag +:tabNext :tabN[ext] go to previous tab page +:tabclose :tabc[lose] close current tab page +:tabdo :tabdo execute command in each tab page +:tabedit :tabe[dit] edit a file in a new tab page +:tabfind :tabf[ind] find file in 'path', edit it in a new tab page +:tabfirst :tabfir[st] go to first tab page +:tablast :tabl[ast] go to last tab page +:tabmove :tabm[ove] move tab page to other position +:tabnew :tabnew edit a file in a new tab page +:tabnext :tabn[ext] go to next tab page +:tabonly :tabo[nly] close all tab pages except the current one +:tabprevious :tabp[revious] go to previous tab page +:tabrewind :tabr[ewind] go to first tab page +:tabs :tabs list the tab pages and what they contain +:tab :tab create new tab when opening new window +:tag :ta[g] jump to tag +:tags :tags show the contents of the tag stack +:tcl :tc[l] execute Tcl command +:tcldo :tcld[o] execute Tcl command for each line +:tclfile :tclf[ile] execute Tcl script file +:tearoff :te[aroff] tear-off a menu +:terminal :ter[minal] open a terminal window +:tfirst :tf[irst] jump to first matching tag +:throw :th[row] throw an exception +:tjump :tj[ump] like ":tselect", but jump directly when there + is only one match +:tlast :tl[ast] jump to last matching tag +:tmapclear :tmapc[lear] remove all mappings for Terminal-Job mode +:tmap :tma[p] like ":map" but for Terminal-Job mode +:tmenu :tm[enu] define menu tooltip +:tnext :tn[ext] jump to next matching tag +:tnoremap :tno[remap] like ":noremap" but for Terminal-Job mode +:topleft :to[pleft] make split window appear at top or far left +:tprevious :tp[revious] jump to previous matching tag +:trewind :tr[ewind] jump to first matching tag +:try :try execute commands, abort on error or exception +:tselect :ts[elect] list matching tags and select one +:tunmap :tunma[p] like ":unmap" but for Terminal-Job mode +:tunmenu :tu[nmenu] remove menu tooltip +:undo :u[ndo] undo last change(s) +:undojoin :undoj[oin] join next change with previous undo block +:undolist :undol[ist] list leafs of the undo tree +:unabbreviate :una[bbreviate] remove abbreviation +:unhide :unh[ide] open a window for each loaded file in the + buffer list +:unlet :unl[et] delete variable +:unlockvar :unlo[ckvar] unlock variables +:unmap :unm[ap] remove mapping +:unmenu :unme[nu] remove menu +:unsilent :uns[ilent] run a command not silently +:update :up[date] write buffer if modified +:vglobal :v[global] execute commands for not matching lines +:version :ve[rsion] print version number and other info +:verbose :verb[ose] execute command with 'verbose' set +:vertical :vert[ical] make following command split vertically +:vimgrep :vim[grep] search for pattern in files +:vimgrepadd :vimgrepa[dd] like :vimgrep, but append to current list +:visual :vi[sual] same as ":edit", but turns off "Ex" mode +:viusage :viu[sage] overview of Normal mode commands +:view :vie[w] edit a file read-only +:vmap :vm[ap] like ":map" but for Visual+Select mode +:vmapclear :vmapc[lear] remove all mappings for Visual+Select mode +:vmenu :vme[nu] add menu for Visual+Select mode +:vnew :vne[w] create a new empty window, vertically split +:vnoremap :vn[oremap] like ":noremap" but for Visual+Select mode +:vnoremenu :vnoreme[nu] like ":noremenu" but for Visual+Select mode +:vsplit :vs[plit] split current window vertically +:vunmap :vu[nmap] like ":unmap" but for Visual+Select mode +:vunmenu :vunme[nu] remove menu for Visual+Select mode +:windo :windo execute command in each window +:write :w[rite] write to a file +:wNext :wN[ext] write to a file and go to previous file in + argument list +:wall :wa[ll] write all (changed) buffers +:while :wh[ile] execute loop for as long as condition met +:winsize :wi[nsize] get or set window size (obsolete) +:wincmd :winc[md] execute a Window (CTRL-W) command +:winpos :winp[os] get or set window position +:wnext :wn[ext] write to a file and go to next file in + argument list +:wprevious :wp[revious] write to a file and go to previous file in + argument list +:wq :wq write to a file and quit window or Vim +:wqall :wqa[ll] write all changed buffers and quit Vim +:wsverb :ws[verb] pass the verb to workshop over IPC +:wundo :wu[ndo] write undo information to a file +:wviminfo :wv[iminfo] write to viminfo file +:xit :x[it] write if buffer changed and quit window or Vim +:xall :xa[ll] same as ":wqall" +:xmapclear :xmapc[lear] remove all mappings for Visual mode +:xmap :xm[ap] like ":map" but for Visual mode +:xmenu :xme[nu] add menu for Visual mode +:xnoremap :xn[oremap] like ":noremap" but for Visual mode +:xnoremenu :xnoreme[nu] like ":noremenu" but for Visual mode +:xunmap :xu[nmap] like ":unmap" but for Visual mode +:xunmenu :xunme[nu] remove menu for Visual mode +:yank :y[ank] yank lines into a register +:z :z print some lines +:~ :~ repeat last ":substitute" diff --git a/vimode/src/Makefile.am b/vimode/src/Makefile.am new file mode 100644 index 000000000..40d90343f --- /dev/null +++ b/vimode/src/Makefile.am @@ -0,0 +1,58 @@ +include $(top_srcdir)/build/vars.build.mk +plugin = vimode + +geanyplugins_LTLIBRARIES = vimode.la + +noinst_PROGRAMS = viw + +vi_srcs = \ + vi.h \ + vi.c \ + keypress.h \ + keypress.c \ + utils.h \ + utils.c \ + sci.h \ + sci.c \ + context.h \ + cmd-params.h \ + cmd-params.c \ + cmd-runner.h \ + cmd-runner.c \ + excmd-params.h \ + excmd-runner.h \ + excmd-runner.c \ + excmd-prompt.h \ + excmd-prompt.c \ + cmds/motion.h \ + cmds/motion.c \ + cmds/txtobjs.h \ + cmds/txtobjs.c \ + cmds/changemode.h \ + cmds/changemode.c \ + cmds/special.h \ + cmds/special.c \ + cmds/edit.h \ + cmds/edit.c \ + excmds/excmds.h \ + excmds/excmds.c + +vimode_la_SOURCES = \ + backends/backend-geany.c \ + $(vi_srcs) + +viw_SOURCES = \ + backends/backend-viw.c \ + $(vi_srcs) + +vimode_la_CPPFLAGS = $(AM_CPPFLAGS) \ + -DG_LOG_DOMAIN=\"Vimode\" +vimode_la_CFLAGS = $(AM_CFLAGS) +vimode_la_LIBADD = $(COMMONLIBS) + +viw_CPPFLAGS = $(AM_CPPFLAGS) \ + -DG_LOG_DOMAIN=\"Vimode\" +viw_CFLAGS = $(AM_CFLAGS) +viw_LDADD = $(COMMONLIBS) + +include $(top_srcdir)/build/cppcheck.mk diff --git a/vimode/src/backends/backend-geany.c b/vimode/src/backends/backend-geany.c new file mode 100644 index 000000000..28937494c --- /dev/null +++ b/vimode/src/backends/backend-geany.c @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "vi.h" + +#include +#include + +#define CONF_GROUP "Settings" +#define CONF_ENABLE_VIM "enable_vim" +#define CONF_START_IN_INSERT "start_in_insert" +#define CONF_INSERT_FOR_DUMMIES "insert_for_dummies" + +GeanyPlugin *geany_plugin; +GeanyData *geany_data; + +PLUGIN_VERSION_CHECK(235) + +PLUGIN_SET_TRANSLATABLE_INFO( + LOCALEDIR, + GETTEXT_PACKAGE, + _("Vimode"), + _("Vim mode for Geany"), + VERSION, + "Jiří Techet " +) + +enum +{ + KB_ENABLE_VIM, + KB_INSERT_FOR_DUMMIES, + KB_COUNT +}; + +struct +{ + GtkWidget *parent_item; + GtkWidget *enable_vim_item; + GtkWidget *insert_for_dummies_item; + GtkWidget *start_in_insert_item; +} menu_items = +{ + NULL, NULL, NULL, NULL +}; + +static gboolean start_in_insert; +static ViCallback cb; + + +static gchar *get_config_filename(void) +{ + return g_build_filename(geany_data->app->configdir, "plugins", PLUGIN, PLUGIN".conf", NULL); +} + + +static void load_config(void) +{ + gchar *filename = get_config_filename(); + GKeyFile *kf = g_key_file_new(); + + if (g_key_file_load_from_file(kf, filename, G_KEY_FILE_NONE, NULL)) + { + vi_set_enabled(utils_get_setting_boolean(kf, CONF_GROUP, CONF_ENABLE_VIM, TRUE)); + vi_set_insert_for_dummies(utils_get_setting_boolean(kf, + CONF_GROUP, CONF_INSERT_FOR_DUMMIES, FALSE)); + start_in_insert = utils_get_setting_boolean(kf, + CONF_GROUP, CONF_START_IN_INSERT, FALSE); + } + + g_key_file_free(kf); + g_free(filename); +} + + +static void save_config(void) +{ + GKeyFile *kf = g_key_file_new(); + gchar *filename = get_config_filename(); + gchar *dirname = g_path_get_dirname(filename); + gchar *data; + gsize length; + + g_key_file_set_boolean(kf, CONF_GROUP, CONF_ENABLE_VIM, vi_get_enabled()); + g_key_file_set_boolean(kf, CONF_GROUP, CONF_INSERT_FOR_DUMMIES, vi_get_insert_for_dummies()); + g_key_file_set_boolean(kf, CONF_GROUP, CONF_START_IN_INSERT, start_in_insert); + + utils_mkdir(dirname, TRUE); + data = g_key_file_to_data(kf, &length, NULL); + g_file_set_contents(filename, data, length, NULL); + + g_free(data); + g_key_file_free(kf); + g_free(filename); + g_free(dirname); +} + + +static void on_enable_vim_mode(void) +{ + gboolean enabled = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_items.enable_vim_item)); + vi_set_enabled(enabled); + vi_set_mode(start_in_insert ? VI_MODE_INSERT : VI_MODE_COMMAND); + if (!enabled) + ui_set_statusbar(FALSE, "Vim Mode Disabled"); + save_config(); +} + + +static gboolean on_enable_vim_mode_kb(GeanyKeyBinding *kb, guint key_id, gpointer data) +{ + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_items.enable_vim_item), + !vi_get_enabled()); + return TRUE; +} + + +static void on_insert_for_dummies(void) +{ + gboolean enabled = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_items.insert_for_dummies_item)); + vi_set_insert_for_dummies(enabled); + ui_set_statusbar(FALSE, _("Insert Mode for Dummies: %s"), enabled ? _("ON") : _("OFF")); + save_config(); +} + + +static gboolean on_insert_for_dummies_kb(GeanyKeyBinding *kb, guint key_id, gpointer data) +{ + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_items.insert_for_dummies_item), + !vi_get_insert_for_dummies()); + return TRUE; +} + + +static void on_start_in_insert(void) +{ + gboolean enabled = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_items.start_in_insert_item)); + start_in_insert = enabled; + save_config(); +} + + +static void on_doc_open(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, + G_GNUC_UNUSED gpointer user_data) +{ + g_return_if_fail(doc != NULL); + vi_set_active_sci(doc->editor->sci); +} + + +static void on_doc_activate(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc, + G_GNUC_UNUSED gpointer user_data) +{ + g_return_if_fail(doc != NULL); + vi_set_active_sci(doc->editor->sci); +} + + +static void on_doc_close(G_GNUC_UNUSED GObject * obj, GeanyDocument * doc, + G_GNUC_UNUSED gpointer user_data) +{ + g_return_if_fail(doc != NULL); + /* This makes sure we don't hold an invalid scintilla inside the plugin and + * that vi_set_active_sci() doesn't crash in plugin_cleanup(). Fortunately + * Geany calls document-close before document-activate in which case this + * wouldn't work correctly. */ + vi_set_active_sci(NULL); +} + + +static gboolean on_editor_notify(GObject *object, GeanyEditor *editor, + SCNotification *nt, gpointer data) +{ + return vi_notify_sci(nt); +} + + +PluginCallback plugin_callbacks[] = { + {"document-open", (GCallback) &on_doc_open, TRUE, NULL}, + {"document-activate", (GCallback) &on_doc_activate, TRUE, NULL}, + {"document-close", (GCallback) &on_doc_close, TRUE, NULL}, + {"editor-notify", (GCallback) &on_editor_notify, TRUE, NULL}, + {NULL, NULL, FALSE, NULL} +}; + + +static const gchar *get_mode_name(ViMode vi_mode) +{ + switch (vi_mode) + { + case VI_MODE_COMMAND: + return "NORMAL"; + break; + case VI_MODE_COMMAND_SINGLE: + return "(insert)"; + break; + case VI_MODE_INSERT: + return "INSERT"; + break; + case VI_MODE_REPLACE: + return "REPLACE"; + break; + case VI_MODE_VISUAL: + return "VISUAL"; + break; + case VI_MODE_VISUAL_LINE: + return "VISUAL LINE"; + break; + case VI_MODE_VISUAL_BLOCK: + return "VISUAL BLOCK"; + break; + } + return ""; +} + + +static void on_mode_change(ViMode mode) +{ + ui_set_statusbar(FALSE, "Vim Mode: -- %s --", get_mode_name(mode)); +} + + +static gboolean on_save(gboolean force) +{ + GeanyDocument *doc = document_get_current(); + if (doc != NULL) + return document_save_file(doc, force); + return TRUE; +} + + +static gboolean on_save_all(gboolean force) +{ + gint i; + gboolean success = TRUE; + foreach_document(i) + success = success && document_save_file(documents[i], force); + return success; +} + + +static void on_quit(gboolean force) +{ + //TODO: we need to extend Geany API for this +} + + +static gboolean on_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data) +{ + GeanyDocument *doc = document_get_current(); + ScintillaObject *sci = doc != NULL ? doc->editor->sci : NULL; + + if (!sci || gtk_window_get_focus(GTK_WINDOW(geany->main_widgets->window)) != GTK_WIDGET(sci)) + return FALSE; + + return vi_notify_key_press(event); +} + + +void plugin_init(GeanyData *data) +{ + GeanyDocument *doc = document_get_current(); + GeanyKeyGroup *group; + GtkWidget *menu; + + load_config(); + + /* menu items and keybindings */ + group = plugin_set_key_group(geany_plugin, "vimode", KB_COUNT, NULL); + + menu_items.parent_item = gtk_menu_item_new_with_mnemonic(_("_Vim Mode")); + gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu), menu_items.parent_item); + + menu = gtk_menu_new (); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_items.parent_item), menu); + + menu_items.enable_vim_item = gtk_check_menu_item_new_with_mnemonic(_("Enable _Vim Mode")); + gtk_container_add(GTK_CONTAINER(menu), menu_items.enable_vim_item); + g_signal_connect((gpointer) menu_items.enable_vim_item, "activate", G_CALLBACK(on_enable_vim_mode), NULL); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_items.enable_vim_item), vi_get_enabled()); + keybindings_set_item_full(group, KB_ENABLE_VIM, 0, 0, "enable_vim", + _("Enable Vim Mode"), NULL, on_enable_vim_mode_kb, NULL, NULL); + + menu_items.insert_for_dummies_item = gtk_check_menu_item_new_with_mnemonic(_("Insert Mode for _Dummies")); + gtk_container_add(GTK_CONTAINER(menu), menu_items.insert_for_dummies_item); + g_signal_connect((gpointer) menu_items.insert_for_dummies_item, "activate", + G_CALLBACK(on_insert_for_dummies), NULL); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_items.insert_for_dummies_item), vi_get_insert_for_dummies()); + keybindings_set_item_full(group, KB_INSERT_FOR_DUMMIES, 0, 0, "insert_for_dummies", + _("Insert Mode for Dummies"), NULL, on_insert_for_dummies_kb, NULL, NULL); + + menu_items.start_in_insert_item = gtk_check_menu_item_new_with_mnemonic(_("Start in _Insert Mode")); + gtk_container_add(GTK_CONTAINER(menu), menu_items.start_in_insert_item); + g_signal_connect((gpointer) menu_items.start_in_insert_item, "activate", + G_CALLBACK(on_start_in_insert), NULL); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_items.start_in_insert_item), start_in_insert); + + gtk_widget_show_all(menu_items.parent_item); + + cb.on_mode_change = on_mode_change; + cb.on_save = on_save; + cb.on_save_all = on_save_all; + cb.on_quit = on_quit; + vi_init(geany_data->main_widgets->window, &cb); + vi_set_mode(start_in_insert ? VI_MODE_INSERT : VI_MODE_COMMAND); + + if (doc) + vi_set_active_sci(doc->editor->sci); + + g_signal_connect(geany_data->main_widgets->window, "key-press-event", + G_CALLBACK(on_key_press_cb), NULL); +} + + +void plugin_cleanup(void) +{ + vi_cleanup(); + gtk_widget_destroy(menu_items.parent_item); + g_signal_handlers_disconnect_by_func(geany_data->main_widgets->window, + G_CALLBACK(on_key_press_cb), NULL); +} + + +void plugin_help(void) +{ + utils_open_browser("http://plugins.geany.org/vimode.html"); +} diff --git a/vimode/src/backends/backend-viw.c b/vimode/src/backends/backend-viw.c new file mode 100644 index 000000000..ce585c02f --- /dev/null +++ b/vimode/src/backends/backend-viw.c @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "vi.h" + +#include + +#define SSM(s, m, w, l) scintilla_send_message((s), (m), (w), (l)) + +static const gchar *fname = NULL; +static ScintillaObject *sci; +static GtkWidget *window; +static GtkWidget *statusbar; + + +static const gchar *get_mode_name(ViMode vi_mode) +{ + switch (vi_mode) + { + case VI_MODE_COMMAND: + return "NORMAL"; + break; + case VI_MODE_COMMAND_SINGLE: + return "(insert)"; + break; + case VI_MODE_INSERT: + return "INSERT"; + break; + case VI_MODE_REPLACE: + return "REPLACE"; + break; + case VI_MODE_VISUAL: + return "VISUAL"; + break; + case VI_MODE_VISUAL_LINE: + return "VISUAL LINE"; + break; + case VI_MODE_VISUAL_BLOCK: + return "VISUAL BLOCK"; + break; + } + return ""; +} + + +static void set_statusbar_text(const gchar *text) +{ + static guint id = 0; + if (id == 0) + id = gtk_statusbar_get_context_id(GTK_STATUSBAR(statusbar), "viw"); + + gtk_statusbar_pop(GTK_STATUSBAR(statusbar), id); + gtk_statusbar_push(GTK_STATUSBAR(statusbar), id, text); +} + + +static void on_mode_change(ViMode mode) +{ + gchar *msg = g_strconcat("-- ", get_mode_name(mode), " --", NULL); + set_statusbar_text(msg); + g_free(msg); +} + + +static gboolean on_save(gboolean force) +{ + gint size; + gchar *buf; + gboolean success; + + if (!SSM(sci, SCI_GETMODIFY, 0, 0) && !force) + return TRUE; + + if (!fname) + { + GtkWidget *dialog = gtk_file_chooser_dialog_new ("Save File", GTK_WINDOW(window), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) + fname = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + gtk_widget_destroy(dialog); + } + + if (!fname) + return FALSE; + + size = SSM(sci, SCI_GETLENGTH, 0, 0) + 1; + buf = g_malloc(size); + SSM(sci, SCI_GETTEXT, size, (sptr_t)buf); + success = g_file_set_contents(fname, buf, size, NULL); + g_free(buf); + + if (success) + SSM(sci, SCI_SETSAVEPOINT, 0, 0); + else + set_statusbar_text("Error saving file"); + + return success; +} + + +static gboolean on_save_all(gboolean force) +{ + return on_save(force); +} + + +static void on_quit(gboolean force) +{ + if (force || !SSM(sci, SCI_GETMODIFY, 0, 0)) + gtk_main_quit(); + else + set_statusbar_text("Save the file before exiting or force (!) the command"); +} + + +static gboolean on_wrong_quit(GtkWidget *widget, GdkEvent *event, gpointer parent_window) +{ + GtkWidget *dialog = gtk_message_dialog_new(parent_window, + GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_YES_NO, + "Really?"); + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), + "Did you really expect you would close a vi-based editor this way?"); + gtk_window_set_title(GTK_WINDOW(dialog), "Huh!"); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return TRUE; +} + + +static gboolean on_key_press_cb(GtkWidget *widget, GdkEventKey *ev, gpointer user_data) +{ + return vi_notify_key_press(ev); +} + + +static void on_sci_notify_cb(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gint scn, + gpointer scnt, gpointer data) +{ + vi_notify_sci(scnt); +} + + +// stolen from Geany +static void use_monospaced_font(void) +{ + gint style, size; + gchar *font_name; + PangoFontDescription *pfd; + + pfd = pango_font_description_from_string("Monospace 10"); + size = pango_font_description_get_size(pfd) / PANGO_SCALE; + font_name = g_strdup_printf("!%s", pango_font_description_get_family(pfd)); + pango_font_description_free(pfd); + + for (style = 0; style <= STYLE_MAX; style++) + { + SSM(sci, SCI_STYLESETFONT, (uptr_t) style, (sptr_t) font_name); + SSM(sci, SCI_STYLESETSIZE, (uptr_t) style, size); + } + + g_free(font_name); +} + + +static void open_file(const gchar *name) +{ + gchar *buf; + gsize len; + if (g_file_get_contents(name, &buf, &len, NULL)) + { + fname = g_strdup(name); + SSM(sci, SCI_ADDTEXT, len, (sptr_t)buf); + SSM(sci, SCI_GOTOPOS, 0, 0); + SSM(sci, SCI_SETSAVEPOINT, 0, 0); + SSM(sci, SCI_EMPTYUNDOBUFFER, 0, 0); + g_free(buf); + } + else + set_statusbar_text("File could not be opened"); +} + + +int main(int argc, char **argv) +{ + GtkWidget *editor, *vbox; + ViCallback cb; + + gtk_init(&argc, &argv); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), "viw - vi worsened"); + g_signal_connect(G_OBJECT(window), "delete-event", G_CALLBACK(on_wrong_quit), window); + + editor = scintilla_new(); + sci = SCINTILLA(editor); + SSM(sci, SCI_SETCODEPAGE, SC_CP_UTF8, 0); + use_monospaced_font(); + //show line number margin + SSM(sci, SCI_SETMARGINWIDTHN, 0, 40); + //hide symbol margin + SSM(sci, SCI_SETMARGINWIDTHN, 1, 0); + if (argc > 1) + open_file(argv[1]); + + statusbar = gtk_statusbar_new(); + vbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), editor, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox), statusbar, FALSE, FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_set_size_request(editor, 640, 480); + + gtk_widget_show_all(window); + gtk_widget_grab_focus(editor); + + // the below is the only setup needed to convert normal editor to a vi editor + cb.on_mode_change = on_mode_change; + cb.on_save = on_save; + cb.on_save_all = on_save_all; + cb.on_quit = on_quit; + vi_init(window, &cb); + vi_set_active_sci(sci); + g_signal_connect(window, "key-press-event", G_CALLBACK(on_key_press_cb), NULL); + g_signal_connect(editor, "sci-notify", G_CALLBACK(on_sci_notify_cb), NULL); + + gtk_main(); + + return 0; +} diff --git a/vimode/src/cmd-params.c b/vimode/src/cmd-params.c new file mode 100644 index 000000000..a7f878997 --- /dev/null +++ b/vimode/src/cmd-params.c @@ -0,0 +1,46 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "cmd-params.h" + +void cmd_params_init(CmdParams *param, ScintillaObject *sci, + gint num, gboolean num_present, GSList *kpl, gboolean is_operator_cmd, + gint sel_start, gint sel_len) +{ + param->sci = sci; + + param->num = num; + param->num_present = num_present; + param->last_kp = g_slist_nth_data(kpl, 0); + param->is_operator_cmd = is_operator_cmd; + + param->sel_start = sel_start; + param->sel_len = sel_len; + param->sel_first_line = SSM(sci, SCI_LINEFROMPOSITION, sel_start, 0); + param->sel_first_line_begin_pos = SSM(sci, SCI_POSITIONFROMLINE, param->sel_first_line, 0); + param->sel_last_line = SSM(sci, SCI_LINEFROMPOSITION, sel_start + sel_len, 0); + param->sel_last_line_end_pos = SSM(sci, SCI_GETLINEENDPOSITION, param->sel_last_line, 0); + + param->pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); + param->line = GET_CUR_LINE(sci); + param->line_end_pos = SSM(sci, SCI_GETLINEENDPOSITION, param->line, 0); + param->line_start_pos = SSM(sci, SCI_POSITIONFROMLINE, param->line, 0); + param->line_num = SSM(sci, SCI_GETLINECOUNT, 0, 0); + param->line_visible_first = SSM(sci, SCI_GETFIRSTVISIBLELINE, 0, 0); + param->line_visible_num = SSM(sci, SCI_LINESONSCREEN, 0, 0); +} diff --git a/vimode/src/cmd-params.h b/vimode/src/cmd-params.h new file mode 100644 index 000000000..47f586a1e --- /dev/null +++ b/vimode/src/cmd-params.h @@ -0,0 +1,76 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __CMD_PARAMS_H__ +#define __CMD_PARAMS_H__ + +#include "sci.h" +#include "keypress.h" +#include "context.h" + +typedef struct +{ + /* current Scintilla object - the same one as in CmdContext, added here + * for convenience */ + ScintillaObject *sci; + + /* number preceding command - defaults to 1 when not present */ + gint num; + /* determines if the command was preceded by number */ + gboolean num_present; + /* last key press */ + KeyPress *last_kp; + /* if running as an "operator" command */ + gboolean is_operator_cmd; + + /* selection start or selection made by movement command */ + gint sel_start; + /* selection length or selection made by movement command */ + gint sel_len; + /* first line of selection */ + gint sel_first_line; + /* the beginning of the first line of selection */ + gint sel_first_line_begin_pos; + /* last line of selection */ + gint sel_last_line; + /* the end of the last line of selection */ + gint sel_last_line_end_pos; + + /* current position in scintilla */ + gint pos; + /* current line in scintilla */ + gint line; + /* position of the end of the current line */ + gint line_end_pos; + /* position of the start of the current line */ + gint line_start_pos; + /* number of lines in document */ + gint line_num; + /* first visible line */ + gint line_visible_first; + /* number of displayed lines */ + gint line_visible_num; +} CmdParams; + +typedef void (*Cmd)(CmdContext *c, CmdParams *p); + +void cmd_params_init(CmdParams *param, ScintillaObject *sci, + gint num, gboolean num_present, GSList *kpl, gboolean is_operator_cmd, + gint sel_start, gint sel_len); + +#endif diff --git a/vimode/src/cmd-runner.c b/vimode/src/cmd-runner.c new file mode 100644 index 000000000..1346da569 --- /dev/null +++ b/vimode/src/cmd-runner.c @@ -0,0 +1,683 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "cmd-runner.h" +#include "utils.h" + +#include "cmds/motion.h" +#include "cmds/txtobjs.h" +#include "cmds/changemode.h" +#include "cmds/edit.h" +#include "cmds/special.h" + +#include + +typedef struct { + Cmd cmd; + guint key1; + guint key2; + guint modif1; + guint modif2; + gboolean param; + gboolean needs_selection; +} CmdDef; + + +#define ARROW_MOTIONS \ + /* left */ \ + {cmd_goto_left, GDK_KEY_Left, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_left, GDK_KEY_KP_Left, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_left, GDK_KEY_leftarrow, 0, 0, 0, FALSE, FALSE}, \ + /* right */ \ + {cmd_goto_right, GDK_KEY_Right, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_right, GDK_KEY_KP_Right, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_right, GDK_KEY_rightarrow, 0, 0, 0, FALSE, FALSE}, \ + /* up */ \ + {cmd_goto_up, GDK_KEY_Up, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_up, GDK_KEY_KP_Up, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_up, GDK_KEY_uparrow, 0, 0, 0, FALSE, FALSE}, \ + /* down */ \ + {cmd_goto_down, GDK_KEY_Down, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_down, GDK_KEY_KP_Down, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_down, GDK_KEY_downarrow, 0, 0, 0, FALSE, FALSE}, \ + /* goto next word */ \ + {cmd_goto_next_word, GDK_KEY_Right, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_next_word, GDK_KEY_Right, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_next_word, GDK_KEY_KP_Right, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_next_word, GDK_KEY_KP_Right, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_next_word, GDK_KEY_rightarrow, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_next_word, GDK_KEY_rightarrow, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + /* goto prev word */ \ + {cmd_goto_previous_word, GDK_KEY_Left, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_previous_word, GDK_KEY_Left, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_previous_word, GDK_KEY_KP_Left, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_previous_word, GDK_KEY_KP_Left, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_previous_word, GDK_KEY_leftarrow, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_previous_word, GDK_KEY_leftarrow, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + /* page up/down */ \ + {cmd_goto_page_up, GDK_KEY_Up, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_page_up, GDK_KEY_KP_Up, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_page_up, GDK_KEY_uparrow, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_page_down, GDK_KEY_Down, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_page_down, GDK_KEY_KP_Down, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_page_down, GDK_KEY_downarrow, 0, GDK_SHIFT_MASK, 0, FALSE, FALSE}, \ + /* END */ + + +#define MOVEMENT_CMDS \ + ARROW_MOTIONS \ + /* left */ \ + {cmd_goto_left, GDK_KEY_h, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_left, GDK_KEY_h, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_left, GDK_KEY_BackSpace, 0, 0, 0, FALSE, FALSE}, \ + /* right */ \ + {cmd_goto_right, GDK_KEY_l, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_right, GDK_KEY_space, 0, 0, 0, FALSE, FALSE}, \ + /* up */ \ + {cmd_goto_up, GDK_KEY_k, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_up, GDK_KEY_p, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_up_nonempty, GDK_KEY_minus, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_up_nonempty, GDK_KEY_KP_Subtract, 0, 0, 0, FALSE, FALSE}, \ + /* down */ \ + {cmd_goto_down, GDK_KEY_j, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_down, GDK_KEY_j, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_down, GDK_KEY_n, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_down_nonempty, GDK_KEY_plus, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_down_nonempty, GDK_KEY_KP_Add, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_down_nonempty, GDK_KEY_m, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_down_nonempty, GDK_KEY_Return, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_down_nonempty, GDK_KEY_KP_Enter, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_down_nonempty, GDK_KEY_ISO_Enter, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_down_one_less_nonempty, GDK_KEY_underscore, 0, 0, 0, FALSE, FALSE}, \ + /* line beginning */ \ + {cmd_goto_line_start, GDK_KEY_0, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_line_start, GDK_KEY_Home, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_line_start_nonempty, GDK_KEY_asciicircum, 0, 0, 0, FALSE, FALSE}, \ + /* line end */ \ + {cmd_goto_line_end, GDK_KEY_dollar, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_line_end, GDK_KEY_End, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_column, GDK_KEY_bar, 0, 0, 0, FALSE, FALSE}, \ + /* find character */ \ + {cmd_goto_next_char, GDK_KEY_f, 0, 0, 0, TRUE, FALSE}, \ + {cmd_goto_prev_char, GDK_KEY_F, 0, 0, 0, TRUE, FALSE}, \ + {cmd_goto_next_char_before, GDK_KEY_t, 0, 0, 0, TRUE, FALSE}, \ + {cmd_goto_prev_char_before, GDK_KEY_T, 0, 0, 0, TRUE, FALSE}, \ + {cmd_goto_char_repeat, GDK_KEY_semicolon, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_char_repeat_opposite, GDK_KEY_comma, 0, 0, 0, FALSE, FALSE}, \ + /* goto line */ \ + {cmd_goto_line_last, GDK_KEY_G, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_line_last, GDK_KEY_End, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_line_last, GDK_KEY_KP_End, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_line, GDK_KEY_g, GDK_KEY_g, 0, 0, FALSE, FALSE}, \ + {cmd_goto_line, GDK_KEY_Home, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_line, GDK_KEY_KP_Home, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_doc_percentage, GDK_KEY_percent, 0, 0, 0, FALSE, FALSE}, \ + /* goto next word */ \ + {cmd_goto_next_word, GDK_KEY_w, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_next_word_space, GDK_KEY_W, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_next_word_end, GDK_KEY_e, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_next_word_end_space, GDK_KEY_E, 0, 0, 0, FALSE, FALSE}, \ + /* goto prev word */ \ + {cmd_goto_previous_word, GDK_KEY_b, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_previous_word_space, GDK_KEY_B, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_previous_word_end, GDK_KEY_g, GDK_KEY_e, 0, 0, FALSE, FALSE}, \ + {cmd_goto_previous_word_end_space, GDK_KEY_g, GDK_KEY_E, 0, 0, FALSE, FALSE}, \ + /* various motions */ \ + {cmd_goto_matching_brace, GDK_KEY_percent, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_screen_top, GDK_KEY_H, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_screen_middle, GDK_KEY_M, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_screen_bottom, GDK_KEY_L, 0, 0, 0, FALSE, FALSE}, \ + /* page up/down */ \ + {cmd_goto_page_down, GDK_KEY_f, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_page_down, GDK_KEY_Page_Down, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_page_down, GDK_KEY_KP_Page_Down, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_page_up, GDK_KEY_b, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_page_up, GDK_KEY_Page_Up, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_page_up, GDK_KEY_KP_Page_Up, 0, 0, 0, FALSE, FALSE}, \ + {cmd_goto_halfpage_down, GDK_KEY_d, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_goto_halfpage_up, GDK_KEY_u, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + /* scrolling */ \ + {cmd_scroll_down, GDK_KEY_e, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_scroll_up, GDK_KEY_y, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, \ + {cmd_scroll_center, GDK_KEY_z, GDK_KEY_z, 0, 0, FALSE, FALSE}, \ + {cmd_scroll_top, GDK_KEY_z, GDK_KEY_t, 0, 0, FALSE, FALSE}, \ + {cmd_scroll_bottom, GDK_KEY_z, GDK_KEY_b, 0, 0, FALSE, FALSE}, \ + {cmd_scroll_center_nonempty, GDK_KEY_z, GDK_KEY_period, 0, 0, FALSE, FALSE}, \ + {cmd_scroll_top_nonempty, GDK_KEY_z, GDK_KEY_Return, 0, 0, FALSE, FALSE}, \ + {cmd_scroll_top_nonempty, GDK_KEY_z, GDK_KEY_KP_Enter, 0, 0, FALSE, FALSE}, \ + {cmd_scroll_top_nonempty, GDK_KEY_z, GDK_KEY_ISO_Enter, 0, 0, FALSE, FALSE}, \ + {cmd_scroll_top_next_nonempty, GDK_KEY_z, GDK_KEY_plus, 0, 0, FALSE, FALSE}, \ + {cmd_scroll_bottom_nonempty, GDK_KEY_z, GDK_KEY_minus, 0, 0, FALSE, FALSE}, \ + /* END */ + + +CmdDef movement_cmds[] = { + MOVEMENT_CMDS + {NULL, 0, 0, 0, 0, FALSE, FALSE} +}; + + +#define OPERATOR_CMDS \ + {cmd_enter_command_cut_sel, GDK_KEY_d, 0, 0, 0, FALSE, TRUE}, \ + {cmd_enter_command_copy_sel, GDK_KEY_y, 0, 0, 0, FALSE, TRUE}, \ + {cmd_enter_insert_cut_sel, GDK_KEY_c, 0, 0, 0, FALSE, TRUE}, \ + {cmd_unindent_sel, GDK_KEY_less, 0, 0, 0, FALSE, TRUE}, \ + {cmd_indent_sel, GDK_KEY_greater, 0, 0, 0, FALSE, TRUE}, \ + {cmd_switch_case, GDK_KEY_g, GDK_KEY_asciitilde, 0, 0, FALSE, TRUE}, \ + {cmd_switch_case, GDK_KEY_asciitilde, 0, 0, 0, FALSE, TRUE}, \ + {cmd_upper_case, GDK_KEY_g, GDK_KEY_U, 0, 0, FALSE, TRUE}, \ + {cmd_lower_case, GDK_KEY_g, GDK_KEY_u, 0, 0, FALSE, TRUE}, \ + /* END */ + + +CmdDef operator_cmds[] = { + OPERATOR_CMDS + {NULL, 0, 0, 0, 0, FALSE, FALSE} +}; + + +#define TEXT_OBJECT_CMDS \ + /* inclusive */ \ + {cmd_select_quotedbl, GDK_KEY_a, GDK_KEY_quotedbl, 0, 0, FALSE, FALSE}, \ + {cmd_select_quoteleft, GDK_KEY_a, GDK_KEY_quoteleft, 0, 0, FALSE, FALSE}, \ + {cmd_select_apostrophe, GDK_KEY_a, GDK_KEY_apostrophe, 0, 0, FALSE, FALSE}, \ + {cmd_select_brace, GDK_KEY_a, GDK_KEY_braceleft, 0, 0, FALSE, FALSE}, \ + {cmd_select_brace, GDK_KEY_a, GDK_KEY_braceright, 0, 0, FALSE, FALSE}, \ + {cmd_select_brace, GDK_KEY_a, GDK_KEY_B, 0, 0, FALSE, FALSE}, \ + {cmd_select_paren, GDK_KEY_a, GDK_KEY_parenleft, 0, 0, FALSE, FALSE}, \ + {cmd_select_paren, GDK_KEY_a, GDK_KEY_parenright, 0, 0, FALSE, FALSE}, \ + {cmd_select_paren, GDK_KEY_a, GDK_KEY_b, 0, 0, FALSE, FALSE}, \ + {cmd_select_less, GDK_KEY_a, GDK_KEY_less, 0, 0, FALSE, FALSE}, \ + {cmd_select_less, GDK_KEY_a, GDK_KEY_greater, 0, 0, FALSE, FALSE}, \ + {cmd_select_bracket, GDK_KEY_a, GDK_KEY_bracketleft, 0, 0, FALSE, FALSE}, \ + {cmd_select_bracket, GDK_KEY_a, GDK_KEY_bracketright, 0, 0, FALSE, FALSE}, \ + /* inner */ \ + {cmd_select_quotedbl_inner, GDK_KEY_i, GDK_KEY_quotedbl, 0, 0, FALSE, FALSE}, \ + {cmd_select_quoteleft_inner, GDK_KEY_i, GDK_KEY_quoteleft, 0, 0, FALSE, FALSE}, \ + {cmd_select_apostrophe_inner, GDK_KEY_i, GDK_KEY_apostrophe, 0, 0, FALSE, FALSE}, \ + {cmd_select_brace_inner, GDK_KEY_i, GDK_KEY_braceleft, 0, 0, FALSE, FALSE}, \ + {cmd_select_brace_inner, GDK_KEY_i, GDK_KEY_braceright, 0, 0, FALSE, FALSE}, \ + {cmd_select_brace_inner, GDK_KEY_i, GDK_KEY_B, 0, 0, FALSE, FALSE}, \ + {cmd_select_paren_inner, GDK_KEY_i, GDK_KEY_parenleft, 0, 0, FALSE, FALSE}, \ + {cmd_select_paren_inner, GDK_KEY_i, GDK_KEY_parenright, 0, 0, FALSE, FALSE}, \ + {cmd_select_paren_inner, GDK_KEY_i, GDK_KEY_b, 0, 0, FALSE, FALSE}, \ + {cmd_select_less_inner, GDK_KEY_i, GDK_KEY_less, 0, 0, FALSE, FALSE}, \ + {cmd_select_less_inner, GDK_KEY_i, GDK_KEY_greater, 0, 0, FALSE, FALSE}, \ + {cmd_select_bracket_inner, GDK_KEY_i, GDK_KEY_bracketleft, 0, 0, FALSE, FALSE}, \ + {cmd_select_bracket_inner, GDK_KEY_i, GDK_KEY_bracketright, 0, 0, FALSE, FALSE}, \ + /* END */ + + +CmdDef text_object_cmds[] = { + TEXT_OBJECT_CMDS + {NULL, 0, 0, 0, 0, FALSE, FALSE} +}; + + +#define EDIT_CMDS \ + /* deletion */ \ + {cmd_delete_char_copy, GDK_KEY_x, 0, 0, 0, FALSE, FALSE}, \ + {cmd_delete_char_copy, GDK_KEY_Delete, 0, 0, 0, FALSE, FALSE}, \ + {cmd_delete_char_copy, GDK_KEY_KP_Delete, 0, 0, 0, FALSE, FALSE}, \ + {cmd_delete_char_back_copy, GDK_KEY_X, 0, 0, 0, FALSE, FALSE}, \ + {cmd_delete_line, GDK_KEY_d, GDK_KEY_d, 0, 0, FALSE, FALSE}, \ + {cmd_clear_right, GDK_KEY_D, 0, 0, 0, FALSE, FALSE}, \ + /* copy/paste */ \ + {cmd_copy_line, GDK_KEY_y, GDK_KEY_y, 0, 0, FALSE, FALSE}, \ + {cmd_copy_line, GDK_KEY_Y, 0, 0, 0, FALSE, FALSE}, \ + {cmd_paste_after, GDK_KEY_p, 0, 0, 0, FALSE, FALSE}, \ + {cmd_paste_before, GDK_KEY_P, 0, 0, 0, FALSE, FALSE}, \ + /* changing text */ \ + {cmd_enter_insert_cut_line, GDK_KEY_c, GDK_KEY_c, 0, 0, FALSE, FALSE}, \ + {cmd_enter_insert_cut_line, GDK_KEY_S, 0, 0, 0, FALSE, FALSE}, \ + {cmd_enter_insert_clear_right, GDK_KEY_C, 0, 0, 0, FALSE, FALSE}, \ + {cmd_enter_insert_delete_char, GDK_KEY_s, 0, 0, 0, FALSE, FALSE}, \ + {cmd_replace_char, GDK_KEY_r, 0, 0, 0, TRUE, FALSE}, \ + {cmd_switch_case, GDK_KEY_asciitilde, 0, 0, 0, FALSE, FALSE}, \ + {cmd_indent, GDK_KEY_greater, GDK_KEY_greater, 0, 0, FALSE, FALSE}, \ + {cmd_unindent, GDK_KEY_less, GDK_KEY_less, 0, 0, FALSE, FALSE}, \ + {cmd_repeat_subst, GDK_KEY_ampersand, 0, 0, 0, FALSE, FALSE}, \ + {cmd_join_lines, GDK_KEY_J, 0, 0, 0, FALSE, FALSE}, \ + /* undo/redo */ \ + {cmd_undo, GDK_KEY_U, 0, 0, 0, FALSE, FALSE}, \ + {cmd_undo, GDK_KEY_u, 0, 0, 0, FALSE, FALSE}, \ + {cmd_redo, GDK_KEY_r, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + +CmdDef edit_cmds[] = { + EDIT_CMDS + OPERATOR_CMDS + {NULL, 0, 0, 0, 0, FALSE, FALSE} +}; + + +#define ENTER_EX_CMDS \ + {cmd_enter_ex, GDK_KEY_colon, 0, 0, 0, FALSE, FALSE}, \ + {cmd_enter_ex, GDK_KEY_slash, 0, 0, 0, FALSE, FALSE}, \ + {cmd_enter_ex, GDK_KEY_KP_Divide, 0, 0, 0, FALSE, FALSE}, \ + {cmd_enter_ex, GDK_KEY_question, 0, 0, 0, FALSE, FALSE}, \ + /* END */ + + +#define SEARCH_CMDS \ + {cmd_search_next, GDK_KEY_n, 0, 0, 0, FALSE, FALSE}, \ + {cmd_search_next, GDK_KEY_N, 0, 0, 0, FALSE, FALSE}, \ + {cmd_search_current_next, GDK_KEY_asterisk, 0, 0, 0, FALSE, FALSE}, \ + {cmd_search_current_next, GDK_KEY_KP_Multiply, 0, 0, 0, FALSE, FALSE}, \ + {cmd_search_current_prev, GDK_KEY_numbersign, 0, 0, 0, FALSE, FALSE}, \ + /* END */ + +CmdDef cmd_mode_cmds[] = { + /* enter insert mode */ + {cmd_enter_insert_after, GDK_KEY_a, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_insert_line_end, GDK_KEY_A, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_insert, GDK_KEY_i, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_insert, GDK_KEY_Insert, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_insert, GDK_KEY_KP_Insert, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_insert_line_start_nonempty, GDK_KEY_I, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_insert_line_start, GDK_KEY_g, GDK_KEY_I, 0, 0, FALSE, FALSE}, + {cmd_enter_insert_next_line, GDK_KEY_o, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_insert_prev_line, GDK_KEY_O, 0, 0, 0, FALSE, FALSE}, + /* enter replace mode */ + {cmd_enter_replace, GDK_KEY_R, 0, 0, 0, FALSE, FALSE}, + /* enter visual mode */ + {cmd_enter_visual, GDK_KEY_v, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_visual_line, GDK_KEY_V, 0, 0, 0, FALSE, FALSE}, + + /* special */ + {cmd_repeat_last_command, GDK_KEY_period, 0, 0, 0, FALSE, FALSE}, + {cmd_repeat_last_command, GDK_KEY_KP_Decimal, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_command, GDK_KEY_Escape, 0, 0, 0, FALSE, FALSE}, + {cmd_nop, GDK_KEY_Insert, 0, 0, 0, FALSE, FALSE}, + {cmd_nop, GDK_KEY_KP_Insert, 0, 0, 0, FALSE, FALSE}, + {cmd_write_exit, GDK_KEY_Z, GDK_KEY_Z, 0, 0, FALSE, FALSE}, + {cmd_force_exit, GDK_KEY_Z, GDK_KEY_Q, 0, 0, FALSE, FALSE}, + + EDIT_CMDS + OPERATOR_CMDS + SEARCH_CMDS + MOVEMENT_CMDS + TEXT_OBJECT_CMDS + ENTER_EX_CMDS + + {NULL, 0, 0, 0, 0, FALSE, FALSE} +}; + + +CmdDef vis_mode_cmds[] = { + {cmd_enter_command, GDK_KEY_Escape, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_command, GDK_KEY_c, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_enter_visual, GDK_KEY_v, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_visual_line, GDK_KEY_V, 0, 0, 0, FALSE, FALSE}, + + {cmd_enter_insert_cut_line_sel, GDK_KEY_C, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_insert_cut_line_sel, GDK_KEY_S, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_insert_cut_line_sel, GDK_KEY_R, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_command_cut_line_sel, GDK_KEY_D, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_command_cut_line_sel, GDK_KEY_X, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_command_cut_sel, GDK_KEY_x, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_command_cut_sel, GDK_KEY_Delete, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_command_cut_sel, GDK_KEY_KP_Delete, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_insert_cut_sel, GDK_KEY_s, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_command_copy_line_sel, GDK_KEY_Y, 0, 0, 0, FALSE, FALSE}, + + {cmd_upper_case, GDK_KEY_U, 0, 0, 0, FALSE, FALSE}, + {cmd_lower_case, GDK_KEY_u, 0, 0, 0, FALSE, FALSE}, + {cmd_join_lines_sel, GDK_KEY_J, 0, 0, 0, FALSE, FALSE}, + {cmd_replace_char_sel, GDK_KEY_r, 0, 0, 0, TRUE, FALSE}, + + {cmd_swap_anchor, GDK_KEY_o, 0, 0, 0, FALSE, FALSE}, + {cmd_swap_anchor, GDK_KEY_O, 0, 0, 0, FALSE, FALSE}, + {cmd_nop, GDK_KEY_Insert, 0, 0, 0, FALSE, FALSE}, + {cmd_nop, GDK_KEY_KP_Insert, 0, 0, 0, FALSE, FALSE}, + + SEARCH_CMDS + MOVEMENT_CMDS + TEXT_OBJECT_CMDS + OPERATOR_CMDS + ENTER_EX_CMDS + + {NULL, 0, 0, 0, 0, FALSE, FALSE} +}; + + +CmdDef ins_mode_cmds[] = { + {cmd_enter_command, GDK_KEY_Escape, 0, 0, 0, FALSE, FALSE}, + {cmd_enter_command, GDK_KEY_c, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_enter_command, GDK_KEY_bracketleft, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_enter_command_single, GDK_KEY_o, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + + {cmd_goto_line_last, GDK_KEY_End, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_goto_line_last, GDK_KEY_KP_End, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_goto_line, GDK_KEY_Home, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_goto_line, GDK_KEY_KP_Home, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + + {cmd_newline, GDK_KEY_m, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_newline, GDK_KEY_j, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_tab, GDK_KEY_i, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + + {cmd_paste_inserted_text, GDK_KEY_a, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_paste_inserted_text_leave_ins, GDK_KEY_at, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + /* it's enough to press Ctrl+2 instead of Ctrl+Shift+2 to get Ctrl+@ */ + {cmd_paste_inserted_text_leave_ins, GDK_KEY_2, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + + {cmd_delete_char, GDK_KEY_Delete, 0, 0, 0, FALSE, FALSE}, + {cmd_delete_char, GDK_KEY_KP_Delete, 0, 0, 0, FALSE, FALSE}, + {cmd_delete_char_back, GDK_KEY_h, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_del_word_left, GDK_KEY_w, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_indent_ins, GDK_KEY_t, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_unindent_ins, GDK_KEY_d, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_copy_char_from_below, GDK_KEY_e, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_copy_char_from_above, GDK_KEY_y, 0, GDK_CONTROL_MASK, 0, FALSE, FALSE}, + {cmd_paste_before, GDK_KEY_r, 0, GDK_CONTROL_MASK, 0, TRUE, FALSE}, + + ARROW_MOTIONS + + {NULL, 0, 0, 0, 0, FALSE, FALSE} +}; + + +static gboolean is_in_cmd_group(CmdDef *cmds, CmdDef *def) +{ + int i; + for (i = 0; cmds[i].cmd != NULL; i++) + { + CmdDef *d = &cmds[i]; + if (def->cmd == d->cmd && def->key1 == d->key1 && def->key2 == d->key2 && + def->modif1 == d->modif1 && def->modif2 == d->modif2 && def->param == d->param) + return TRUE; + } + return FALSE; +} + + +static gboolean key_equals(KeyPress *kp, guint key, guint modif) +{ + return kp->key == key && (kp->modif & modif || kp->modif == modif); +} + + +/* is the current keypress the first character of a 2-keypress command? */ +static gboolean is_cmdpart(GSList *kpl, CmdDef *cmds) +{ + gint i; + KeyPress *curr = g_slist_nth_data(kpl, 0); + + for (i = 0; cmds[i].cmd != NULL; i++) + { + CmdDef *cmd = &cmds[i]; + if ((cmd->key2 != 0 || cmd->param) && key_equals(curr, cmd->key1, cmd->modif1)) + return TRUE; + } + + return FALSE; +} + + +static CmdDef *get_cmd_to_run(GSList *kpl, CmdDef *cmds, gboolean have_selection) +{ + gint i; + KeyPress *curr = g_slist_nth_data(kpl, 0); + KeyPress *prev = g_slist_nth_data(kpl, 1); + GSList *below = g_slist_next(kpl); + ViMode mode = vi_get_mode(); + + if (!kpl) + return NULL; + + // commands such as rc or fc (replace char c, find char c) which are specified + // by the previous character and current character is used as their parameter + if (prev != NULL && !kp_isdigit(prev)) + { + for (i = 0; cmds[i].cmd != NULL; i++) + { + CmdDef *cmd = &cmds[i]; + if (cmd->key2 == 0 && cmd->param && + ((cmd->needs_selection && have_selection) || !cmd->needs_selection) && + key_equals(prev, cmd->key1, cmd->modif1)) + return cmd; + } + } + + // 2-letter commands + if (prev != NULL && !kp_isdigit(prev)) + { + for (i = 0; cmds[i].cmd != NULL; i++) + { + CmdDef *cmd = &cmds[i]; + if (cmd->key2 != 0 && !cmd->param && + ((cmd->needs_selection && have_selection) || !cmd->needs_selection) && + key_equals(curr, cmd->key2, cmd->modif2) && + key_equals(prev, cmd->key1, cmd->modif1)) + return cmd; + } + } + + // 1-letter commands + for (i = 0; cmds[i].cmd != NULL; i++) + { + CmdDef *cmd = &cmds[i]; + if (cmd->key2 == 0 && !cmd->param && + ((cmd->needs_selection && have_selection) || !cmd->needs_selection) && + key_equals(curr, cmd->key1, cmd->modif1)) + { + // now solve some quirks manually + if (curr->key == GDK_KEY_0 && !VI_IS_INSERT(mode)) + { + // 0 jumps to the beginning of line only when not preceded + // by another number in which case we want to add it to the accumulator + if (prev == NULL || !kp_isdigit(prev)) + return cmd; + } + else if (curr->key == GDK_KEY_percent && !VI_IS_INSERT(mode)) + { + // % when preceded by a number jumps to N% of the file, otherwise + // % goes to matching brace + Cmd c = cmd_goto_matching_brace; + gint val = kpl_get_int(below, NULL); + if (val != -1) + c = cmd_goto_doc_percentage; + if (cmd->cmd == c) + return cmd; + } + else if (prev && prev->key == GDK_KEY_g) + { + // takes care of operator commands like g~, gu, gU where we + // have no selection yet so the 2-letter command isn't found + // above and a corresponding 1-letter command ~, u, U exists and + // would be used instead of waiting for the full command + } + else if (is_cmdpart(kpl, text_object_cmds) && + get_cmd_to_run(below, operator_cmds, TRUE)) + { + // if we received "a" or "i", we have to check if there's not + // an operator command below because these can be part of + // text object commands (like a<) and in this case we don't + // want to have "a" or "i" executed yet + } + else + return cmd; + } + } + + return NULL; +} + + +static void perform_cmd(CmdDef *def, CmdContext *ctx) +{ + GSList *top; + gint num; + gint cmd_len = 0; + gboolean num_present; + CmdParams param; + gint orig_pos = SSM(ctx->sci, SCI_GETCURRENTPOS, 0, 0); + gint sel_start, sel_len; + + if (def->key1 != 0) + cmd_len++; + if (def->key2 != 0) + cmd_len++; + if (def->param) + cmd_len++; + top = g_slist_nth(ctx->kpl, cmd_len); + num = kpl_get_int(top, &top); + num_present = num != -1; + + sel_start = SSM(ctx->sci, SCI_GETSELECTIONSTART, 0, 0); + sel_len = SSM(ctx->sci, SCI_GETSELECTIONEND, 0, 0) - sel_start; + cmd_params_init(¶m, ctx->sci, + num_present ? num : 1, num_present, ctx->kpl, FALSE, + sel_start, sel_len); + + SSM(ctx->sci, SCI_BEGINUNDOACTION, 0, 0); + +// if (def->cmd != cmd_undo && def->cmd != cmd_redo) +// SSM(sci, SCI_ADDUNDOACTION, param.pos, UNDO_MAY_COALESCE); + + def->cmd(ctx, ¶m); + + if (VI_IS_COMMAND(vi_get_mode())) + { + gboolean is_text_object_cmd = is_in_cmd_group(text_object_cmds, def); + if (is_text_object_cmd ||is_in_cmd_group(movement_cmds, def)) + { + def = get_cmd_to_run(top, operator_cmds, TRUE); + if (def) + { + gint new_pos = SSM(ctx->sci, SCI_GETCURRENTPOS, 0, 0); + + SET_POS(ctx->sci, orig_pos, FALSE); + + if (is_text_object_cmd) + { + sel_start = param.sel_start; + sel_len = param.sel_len; + } + else + { + sel_start = MIN(new_pos, orig_pos); + sel_len = ABS(new_pos - orig_pos); + } + cmd_params_init(¶m, ctx->sci, + 1, FALSE, top, TRUE, + sel_start, sel_len); + + def->cmd(ctx, ¶m); + } + } + } + + /* mode could have changed after performing command */ + if (VI_IS_COMMAND(vi_get_mode())) + clamp_cursor_pos(ctx->sci); + + SSM(ctx->sci, SCI_ENDUNDOACTION, 0, 0); +} + + +static gboolean perform_repeat_cmd(CmdContext *ctx) +{ + GSList *top = g_slist_next(ctx->kpl); // get behind "." + gint num = kpl_get_int(top, NULL); + CmdDef *def; + gint i; + + def = get_cmd_to_run(ctx->repeat_kpl, edit_cmds, FALSE); + if (!def) + return FALSE; + + num = num == -1 ? 1 : num; + for (i = 0; i < num; i++) + perform_cmd(def, ctx); + + return TRUE; +} + + +static gboolean process_cmd(CmdDef *cmds, CmdContext *ctx) +{ + gboolean consumed; + gboolean performed = FALSE; + ViMode orig_mode = vi_get_mode(); + gboolean have_selection = + SSM(ctx->sci, SCI_GETSELECTIONEND, 0, 0) - SSM(ctx->sci, SCI_GETSELECTIONSTART, 0, 0) > 0; + CmdDef *def = get_cmd_to_run(ctx->kpl, cmds, have_selection); + + consumed = is_cmdpart(ctx->kpl, cmds); + + if (def) + { + if (def->cmd == cmd_repeat_last_command) + { + if (perform_repeat_cmd(ctx)) + { + performed = TRUE; + + g_slist_free_full(ctx->kpl, g_free); + ctx->kpl = NULL; + } + } + else + { + perform_cmd(def, ctx); + performed = TRUE; + + if (is_in_cmd_group(edit_cmds, def)) + { + g_slist_free_full(ctx->repeat_kpl, g_free); + ctx->repeat_kpl = ctx->kpl; + } + else + g_slist_free_full(ctx->kpl, g_free); + ctx->kpl = NULL; + } + } + + consumed = consumed || performed; + + if (performed) + { + if (orig_mode == VI_MODE_COMMAND_SINGLE) + vi_set_mode(VI_MODE_INSERT); + } + else if (!consumed && ctx->kpl) + { + g_free(ctx->kpl->data); + ctx->kpl = g_slist_delete_link(ctx->kpl, ctx->kpl); + } + + return consumed; +} + + +gboolean cmd_perform_cmd(CmdContext *ctx) +{ + return process_cmd(cmd_mode_cmds, ctx); +} + + +gboolean cmd_perform_vis(CmdContext *ctx) +{ + return process_cmd(vis_mode_cmds, ctx); +} + + +gboolean cmd_perform_ins(CmdContext *ctx) +{ + return process_cmd(ins_mode_cmds, ctx); +} diff --git a/vimode/src/cmd-runner.h b/vimode/src/cmd-runner.h new file mode 100644 index 000000000..698ce8560 --- /dev/null +++ b/vimode/src/cmd-runner.h @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_CMD_RUNNER_H__ +#define __VIMODE_CMD_RUNNER_H__ + +#include "context.h" + +gboolean cmd_perform_cmd(CmdContext *ctx); +gboolean cmd_perform_vis(CmdContext *ctx); +gboolean cmd_perform_ins(CmdContext *ctx); + +#endif diff --git a/vimode/src/cmds/changemode.c b/vimode/src/cmds/changemode.c new file mode 100644 index 000000000..269b7fa1e --- /dev/null +++ b/vimode/src/cmds/changemode.c @@ -0,0 +1,242 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "cmds/changemode.h" +#include "utils.h" + + +void cmd_enter_ex(CmdContext *c, CmdParams *p) +{ + c->num = p->num; + vi_enter_ex_mode(); +} + + +void cmd_enter_command(CmdContext *c, CmdParams *p) +{ + if (SSM(p->sci, SCI_AUTOCACTIVE, 0, 0) || SSM(p->sci, SCI_CALLTIPACTIVE, 0, 0)) + SSM(p->sci, SCI_CANCEL, 0, 0); + else + vi_set_mode(VI_MODE_COMMAND); +} + + +void cmd_enter_command_single(CmdContext *c, CmdParams *p) +{ + vi_set_mode(VI_MODE_COMMAND_SINGLE); +} + + +void cmd_enter_visual(CmdContext *c, CmdParams *p) +{ + if (vi_get_mode() == VI_MODE_VISUAL) + { + SSM(p->sci, SCI_SETEMPTYSELECTION, p->pos, 0); + vi_set_mode(VI_MODE_COMMAND); + } + else + vi_set_mode(VI_MODE_VISUAL); +} + + +void cmd_enter_visual_line(CmdContext *c, CmdParams *p) +{ + if (vi_get_mode() == VI_MODE_VISUAL_LINE) + { + SSM(p->sci, SCI_SETEMPTYSELECTION, p->pos, 0); + vi_set_mode(VI_MODE_COMMAND); + } + else + { + vi_set_mode(VI_MODE_VISUAL_LINE); + /* just to force the scintilla notification callback to be called so we can + * select the current line */ + SSM(p->sci, SCI_LINEEND, 0, 0); + } +} + + +void cmd_enter_insert(CmdContext *c, CmdParams *p) +{ + c->num = p->num; + c->newline_insert = FALSE; + vi_set_mode(VI_MODE_INSERT); +} + + +void cmd_enter_replace(CmdContext *c, CmdParams *p) +{ + c->num = p->num; + c->newline_insert = FALSE; + vi_set_mode(VI_MODE_REPLACE); +} + + +void cmd_enter_insert_after(CmdContext *c, CmdParams *p) +{ + cmd_enter_insert(c, p); + if (p->pos < p->line_end_pos) + SSM(p->sci, SCI_CHARRIGHT, 0, 0); +} + + +void cmd_enter_insert_line_start_nonempty(CmdContext *c, CmdParams *p) +{ + goto_nonempty(p->sci, p->line, TRUE); + cmd_enter_insert(c, p); +} + + +void cmd_enter_insert_line_start(CmdContext *c, CmdParams *p) +{ + SET_POS(p->sci, p->line_start_pos, TRUE); + cmd_enter_insert(c, p); +} + + +void cmd_enter_insert_line_end(CmdContext *c, CmdParams *p) +{ + SSM(p->sci, SCI_LINEEND, 0, 0); + cmd_enter_insert(c, p); +} + + +void cmd_enter_insert_next_line(CmdContext *c, CmdParams *p) +{ + SSM(p->sci, SCI_LINEEND, 0, 0); + SSM(p->sci, SCI_NEWLINE, 0, 0); + SSM(p->sci, SCI_DELLINELEFT, 0, 0); + c->num = p->num; + c->newline_insert = TRUE; + vi_set_mode(VI_MODE_INSERT); +} + + +void cmd_enter_insert_prev_line(CmdContext *c, CmdParams *p) +{ + SSM(p->sci, SCI_HOME, 0, 0); + SSM(p->sci, SCI_NEWLINE, 0, 0); + SSM(p->sci, SCI_LINEUP, 0, 0); + c->num = p->num; + c->newline_insert = TRUE; + vi_set_mode(VI_MODE_INSERT); +} + + +static void cut_range_change_mode(CmdContext *c, CmdParams *p, + gint start, gint end, gboolean line_copy, ViMode mode) +{ + c->line_copy = line_copy; + SSM(p->sci, SCI_COPYRANGE, start, end); + SSM(p->sci, SCI_DELETERANGE, start, end - start); + if (mode == VI_MODE_INSERT) + cmd_enter_insert(c, p); + else + vi_set_mode(mode); +} + + +void cmd_enter_insert_clear_right(CmdContext *c, CmdParams *p) +{ + gint new_line = get_line_number_rel(p->sci, p->num - 1); + gint pos = SSM(p->sci, SCI_GETLINEENDPOSITION, new_line, 0); + + cut_range_change_mode(c, p, p->pos, pos, FALSE, VI_MODE_INSERT); +} + + +void cmd_enter_insert_delete_char(CmdContext *c, CmdParams *p) +{ + gint end = NTH(p->sci, p->pos, p->num); + end = end > p->line_end_pos ? p->line_end_pos : end; + + cut_range_change_mode(c, p, p->pos, end, FALSE, VI_MODE_INSERT); +} + + +void cmd_enter_insert_cut_line(CmdContext *c, CmdParams *p) +{ + gint new_line = get_line_number_rel(p->sci, p->num - 1); + gint pos_end = SSM(p->sci, SCI_GETLINEENDPOSITION, new_line, 0); + + cut_range_change_mode(c, p, p->line_start_pos, pos_end, TRUE, VI_MODE_INSERT); +} + + +void cmd_enter_insert_cut_sel(CmdContext *c, CmdParams *p) +{ + gint sel_end_pos = p->sel_start + p->sel_len; + if (p->is_operator_cmd && p->line_end_pos < sel_end_pos) + sel_end_pos = p->line_end_pos; + + cut_range_change_mode(c, p, p->sel_start, sel_end_pos, FALSE, VI_MODE_INSERT); +} + + +void cmd_enter_insert_cut_line_sel(CmdContext *c, CmdParams *p) +{ + gint begin = p->sel_first_line_begin_pos; + gint end = p->sel_last_line_end_pos; + + cut_range_change_mode(c, p, begin, end, TRUE, VI_MODE_INSERT); +} + + +void cmd_enter_command_cut_sel(CmdContext *c, CmdParams *p) +{ + gint sel_end_pos = p->sel_start + p->sel_len; + if (p->is_operator_cmd && p->line_end_pos < sel_end_pos) + sel_end_pos = p->line_end_pos; + + cut_range_change_mode(c, p, p->sel_start, sel_end_pos, FALSE, VI_MODE_COMMAND); +} + + +void cmd_enter_command_cut_line_sel(CmdContext *c, CmdParams *p) +{ + gint begin = p->sel_first_line_begin_pos; + gint end = p->sel_last_line_end_pos; + + cut_range_change_mode(c, p, begin, end, TRUE, VI_MODE_COMMAND); + SET_POS(p->sci, begin, TRUE); +} + + +void cmd_enter_command_copy_sel(CmdContext *c, CmdParams *p) +{ + gint sel_end_pos = p->sel_start + p->sel_len; + if (p->is_operator_cmd && p->line_end_pos < sel_end_pos) + sel_end_pos = p->line_end_pos; + + c->line_copy = FALSE; + SSM(p->sci, SCI_COPYRANGE, p->sel_start, sel_end_pos); + vi_set_mode(VI_MODE_COMMAND); + SET_POS(p->sci, p->sel_start, TRUE); +} + + +void cmd_enter_command_copy_line_sel(CmdContext *c, CmdParams *p) +{ + gint begin = p->sel_first_line_begin_pos; + gint end = p->sel_last_line_end_pos; + + c->line_copy = TRUE; + SSM(p->sci, SCI_COPYRANGE, begin, end); + vi_set_mode(VI_MODE_COMMAND); + SET_POS(p->sci, begin, TRUE); +} diff --git a/vimode/src/cmds/changemode.h b/vimode/src/cmds/changemode.h new file mode 100644 index 000000000..9df57d3b0 --- /dev/null +++ b/vimode/src/cmds/changemode.h @@ -0,0 +1,51 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_CMDS_CHANGEMODE_H__ +#define __VIMODE_CMDS_CHANGEMODE_H__ + +#include "context.h" +#include "cmd-params.h" + +void cmd_enter_ex(CmdContext *c, CmdParams *p); +void cmd_enter_command(CmdContext *c, CmdParams *p); +void cmd_enter_command_single(CmdContext *c, CmdParams *p); +void cmd_enter_visual(CmdContext *c, CmdParams *p); +void cmd_enter_visual_line(CmdContext *c, CmdParams *p); + +void cmd_enter_replace(CmdContext *c, CmdParams *p); + +void cmd_enter_insert(CmdContext *c, CmdParams *p); +void cmd_enter_insert_after(CmdContext *c, CmdParams *p); +void cmd_enter_insert_line_start_nonempty(CmdContext *c, CmdParams *p); +void cmd_enter_insert_line_start(CmdContext *c, CmdParams *p); +void cmd_enter_insert_line_end(CmdContext *c, CmdParams *p); +void cmd_enter_insert_clear_right(CmdContext *c, CmdParams *p); +void cmd_enter_insert_delete_char(CmdContext *c, CmdParams *p); +void cmd_enter_insert_next_line(CmdContext *c, CmdParams *p); +void cmd_enter_insert_prev_line(CmdContext *c, CmdParams *p); +void cmd_enter_insert_cut_line(CmdContext *c, CmdParams *p); +void cmd_enter_insert_cut_sel(CmdContext *c, CmdParams *p); +void cmd_enter_insert_cut_line_sel(CmdContext *c, CmdParams *p); + +void cmd_enter_command_cut_sel(CmdContext *c, CmdParams *p); +void cmd_enter_command_cut_line_sel(CmdContext *c, CmdParams *p); +void cmd_enter_command_copy_sel(CmdContext *c, CmdParams *p); +void cmd_enter_command_copy_line_sel(CmdContext *c, CmdParams *p); + +#endif diff --git a/vimode/src/cmds/edit.c b/vimode/src/cmds/edit.c new file mode 100644 index 000000000..733bfd118 --- /dev/null +++ b/vimode/src/cmds/edit.c @@ -0,0 +1,449 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "cmds/edit.h" +#include "utils.h" + + +static void delete_char(CmdContext *c, CmdParams *p, gboolean yank) +{ + gint end_pos = NTH(p->sci, p->pos, p->num); + end_pos = end_pos > p->line_end_pos ? p->line_end_pos : end_pos; + if (yank) + { + c->line_copy = FALSE; + SSM(p->sci, SCI_COPYRANGE, p->pos, end_pos); + } + SSM(p->sci, SCI_DELETERANGE, p->pos, end_pos - p->pos); +} + + +void cmd_delete_char(CmdContext *c, CmdParams *p) +{ + delete_char(c, p, FALSE); +} + + +void cmd_delete_char_copy(CmdContext *c, CmdParams *p) +{ + delete_char(c, p, TRUE); +} + + +static void delete_char_back(CmdContext *c, CmdParams *p, gboolean yank) +{ + gint start_pos = NTH(p->sci, p->pos, -p->num); + start_pos = start_pos < p->line_start_pos ? p->line_start_pos : start_pos; + if (yank) + { + c->line_copy = FALSE; + SSM(p->sci, SCI_COPYRANGE, start_pos, p->pos); + } + SSM(p->sci, SCI_DELETERANGE, start_pos, p->pos - start_pos); +} + + +void cmd_delete_char_back(CmdContext *c, CmdParams *p) +{ + delete_char_back(c, p, FALSE); +} + + +void cmd_delete_char_back_copy(CmdContext *c, CmdParams *p) +{ + delete_char_back(c, p, TRUE); +} + + +void cmd_clear_right(CmdContext *c, CmdParams *p) +{ + gint new_line = get_line_number_rel(p->sci, p->num - 1); + gint pos = SSM(p->sci, SCI_GETLINEENDPOSITION, new_line, 0); + + c->line_copy = FALSE; + SSM(p->sci, SCI_COPYRANGE, p->pos, pos); + SSM(p->sci, SCI_DELETERANGE, p->pos, pos - p->pos); +} + + +void cmd_delete_line(CmdContext *c, CmdParams *p) +{ + gint num = get_line_number_rel(p->sci, p->num); + gint end = SSM(p->sci, SCI_POSITIONFROMLINE, num, 0); + + c->line_copy = TRUE; + SSM(p->sci, SCI_COPYRANGE, p->line_start_pos, end); + SSM(p->sci, SCI_DELETERANGE, p->line_start_pos, end - p->line_start_pos); +} + + +void cmd_copy_line(CmdContext *c, CmdParams *p) +{ + gint num = get_line_number_rel(p->sci, p->num); + gint end = SSM(p->sci, SCI_POSITIONFROMLINE, num, 0); + + c->line_copy = TRUE; + SSM(p->sci, SCI_COPYRANGE, p->line_start_pos, end); +} + + +void cmd_newline(CmdContext *c, CmdParams *p) +{ + SSM(p->sci, SCI_NEWLINE, 0, 0); +} + + +void cmd_tab(CmdContext *c, CmdParams *p) +{ + SSM(p->sci, SCI_TAB, 0, 0); +} + + +void cmd_del_word_left(CmdContext *c, CmdParams *p) +{ + SSM(p->sci, SCI_DELWORDLEFT, 0, 0); +} + + +void cmd_undo(CmdContext *c, CmdParams *p) +{ + gint i; + for (i = 0; i < p->num; i++) + { + if (!SSM(p->sci, SCI_CANUNDO, 0, 0)) + break; + SSM(p->sci, SCI_UNDO, 0, 0); + } +} + + +void cmd_redo(CmdContext *c, CmdParams *p) +{ + gint i; + for (i = 0; i < p->num; i++) + { + if (!SSM(p->sci, SCI_CANREDO, 0, 0)) + break; + SSM(p->sci, SCI_REDO, 0, 0); + } +} + + +static void paste(CmdContext *c, CmdParams *p, gboolean after) +{ + gint pos; + gint i; + + if (c->line_copy) + { + if (after) + pos = SSM(p->sci, SCI_POSITIONFROMLINE, p->line+1, 0); + else + pos = p->line_start_pos; + } + else + { + pos = p->pos; + if (after && pos < p->line_end_pos) + pos = NEXT(p->sci, pos); + } + + SET_POS(p->sci, pos, TRUE); + for (i = 0; i < p->num; i++) + SSM(p->sci, SCI_PASTE, 0, 0); + if (c->line_copy) + SET_POS(p->sci, pos, TRUE); + else if (!VI_IS_INSERT(vi_get_mode())) + SSM(p->sci, SCI_CHARLEFT, 0, 0); +} + + +void cmd_paste_after(CmdContext *c, CmdParams *p) +{ + paste(c, p, TRUE); +} + + +void cmd_paste_before(CmdContext *c, CmdParams *p) +{ + paste(c, p, FALSE); +} + + +static void join_lines(CmdContext *c, CmdParams *p, gint line, gint num) +{ + for (int i = 0; i < num; i++) + { + gint line_start_pos = SSM(p->sci, SCI_POSITIONFROMLINE, line, 0); + gint line_end_pos = SSM(p->sci, SCI_GETLINEENDPOSITION, line, 0); + gint next_line_end_pos = SSM(p->sci, SCI_GETLINEENDPOSITION, line+1, 0); + gint pos = line_end_pos; + gint pos_start; + + while (g_ascii_isspace(SSM(p->sci, SCI_GETCHARAT, pos, 0)) && pos > line_start_pos) + pos = PREV(p->sci, pos); + if (!g_ascii_isspace(SSM(p->sci, SCI_GETCHARAT, pos, 0))) + pos = NEXT(p->sci, pos); + pos_start = pos; + + pos = line_end_pos; + while (g_ascii_isspace(SSM(p->sci, SCI_GETCHARAT, pos, 0)) && pos < next_line_end_pos) + pos = NEXT(p->sci, pos); + + SSM(p->sci, SCI_DELETERANGE, pos_start, pos - pos_start); + SSM(p->sci, SCI_INSERTTEXT, pos_start, (sptr_t)" "); + } +} + + +void cmd_join_lines(CmdContext *c, CmdParams *p) +{ + gint num = p->num; + if (p->num_present && num > 1) + num--; + join_lines(c, p, p->line, num); +} + + +void cmd_join_lines_sel(CmdContext *c, CmdParams *p) +{ + join_lines(c, p, p->sel_first_line, p->sel_last_line - p->sel_first_line); + vi_set_mode(VI_MODE_COMMAND); +} + + +static void replace_char(ScintillaObject *sci, gint pos, gint num, gint line, + gboolean force_upper, gboolean force_lower, gunichar repl_char) +{ + gint i; + gint max_num; + gint last_pos; + struct Sci_TextRange tr; + gchar *original, *replacement, *repl, *orig; + + max_num = DIFF(sci, pos, SSM(sci, SCI_GETLINEENDPOSITION, line, 0)); + if (line != -1 && num > max_num) + num = max_num; + + max_num = DIFF(sci, pos, SSM(sci, SCI_GETLENGTH, 0, 0)); + if (num > max_num) + num = max_num; + + if (num <= 0) + return; + + last_pos = NTH(sci, pos, num); + original = g_malloc(last_pos - pos + 1); + replacement = g_malloc(6 * num + 1); + repl = replacement; + orig = original; + + tr.chrg.cpMin = pos; + tr.chrg.cpMax = last_pos; + tr.lpstrText = orig; + SSM(sci, SCI_GETTEXTRANGE, 0, (sptr_t)&tr); + + for (i = 0; i < num; i++) + { + gunichar c = g_utf8_get_char(orig); + + if (repl_char == 0) + { + if ((force_upper || g_unichar_islower(c)) && !force_lower) + c = g_unichar_toupper(c); + else if (force_lower || g_unichar_isupper(c)) + c = g_unichar_tolower(c); + } + else + { + GUnicodeBreakType t = g_unichar_break_type(c); + if (t != G_UNICODE_BREAK_CARRIAGE_RETURN && + t != G_UNICODE_BREAK_LINE_FEED) + c = repl_char; + } + + repl += g_unichar_to_utf8(c, repl); + orig = g_utf8_find_next_char(orig, NULL); + + } + *repl = '\0'; + + SSM(sci, SCI_SETTARGETRANGE, pos, last_pos); + SSM(sci, SCI_REPLACETARGET, -1, (sptr_t)replacement); + + if (line != -1) + SET_POS(sci, last_pos, TRUE); + + g_free(original); + g_free(replacement); +} + + +void cmd_replace_char(CmdContext *c, CmdParams *p) +{ + gunichar repl = gdk_keyval_to_unicode(p->last_kp->key); + replace_char(p->sci, p->pos, p->num, p->line, FALSE, FALSE, repl); +} + + +void cmd_replace_char_sel(CmdContext *c, CmdParams *p) +{ + gint num = DIFF(p->sci, p->sel_start, p->sel_start + p->sel_len); + gunichar repl = gdk_keyval_to_unicode(p->last_kp->key); + replace_char(p->sci, p->sel_start, num, -1, FALSE, FALSE, repl); + vi_set_mode(VI_MODE_COMMAND); +} + + +static void switch_case(CmdContext *c, CmdParams *p, + gboolean force_upper, gboolean force_lower) +{ + if (VI_IS_VISUAL(vi_get_mode()) || p->sel_len > 0) + { + gint num = DIFF(p->sci, p->sel_start, p->sel_start + p->sel_len); + replace_char(p->sci, p->sel_start, num, -1, force_upper, force_lower, 0); + vi_set_mode(VI_MODE_COMMAND); + } + else + replace_char(p->sci, p->pos, p->num, p->line, force_upper, force_lower, 0); +} + + +void cmd_switch_case(CmdContext *c, CmdParams *p) +{ + switch_case(c, p, FALSE, FALSE); +} + + +void cmd_lower_case(CmdContext *c, CmdParams *p) +{ + switch_case(c, p, FALSE, TRUE); +} + + +void cmd_upper_case(CmdContext *c, CmdParams *p) +{ + switch_case(c, p, TRUE, FALSE); +} + + +static void indent(ScintillaObject *sci, gboolean unindent, gint pos, gint num, gint indent_num) +{ + gint i; + gint line_start = SSM(sci, SCI_LINEFROMPOSITION, pos, 0); + gint line_count = SSM(sci, SCI_GETLINECOUNT, 0, 0); + gint line_end = line_start + num > line_count ? line_count : line_start + num; + gint end_pos = SSM(sci, SCI_POSITIONFROMLINE, line_end, 0); + + SSM(sci, SCI_HOME, 0, 0); + SSM(sci, SCI_SETSEL, end_pos, pos); + for (i = 0; i < indent_num; i++) + SSM(sci, unindent ? SCI_BACKTAB : SCI_TAB, 0, 0); + goto_nonempty(sci, line_start, TRUE); +} + + +void cmd_indent(CmdContext *c, CmdParams *p) +{ + indent(p->sci, FALSE, p->pos, p->num, 1); +} + + +void cmd_unindent(CmdContext *c, CmdParams *p) +{ + indent(p->sci, TRUE, p->pos, p->num, 1); +} + + +void cmd_indent_sel(CmdContext *c, CmdParams *p) +{ + indent(p->sci, FALSE, p->sel_start, p->sel_last_line - p->sel_first_line + 1, p->num); + vi_set_mode(VI_MODE_COMMAND); +} + + +void cmd_unindent_sel(CmdContext *c, CmdParams *p) +{ + indent(p->sci, TRUE, p->sel_start, p->sel_last_line - p->sel_first_line + 1, p->num); + vi_set_mode(VI_MODE_COMMAND); +} + + +static void indent_ins(CmdContext *c, CmdParams *p, gboolean indent) +{ + gint delta; + SSM(p->sci, SCI_HOME, 0, 0); + SSM(p->sci, indent ? SCI_TAB : SCI_BACKTAB, 0, 0); + delta = SSM(p->sci, SCI_GETLINEENDPOSITION, p->line, 0) - p->line_end_pos; + SET_POS(p->sci, p->pos + delta, TRUE); +} + + +void cmd_indent_ins(CmdContext *c, CmdParams *p) +{ + indent_ins(c, p, TRUE); +} + +void cmd_unindent_ins(CmdContext *c, CmdParams *p) +{ + indent_ins(c, p, FALSE); +} + + +static void copy_char(CmdContext *c, CmdParams *p, gboolean above) +{ + if ((above && p->line > 0) || (!above && p->line < p->line_num - 1)) + { + gint line = above ? p->line - 1 : p->line + 1; + gint col = SSM(p->sci, SCI_GETCOLUMN, p->pos, 0); + gint pos = SSM(p->sci, SCI_FINDCOLUMN, line, col); + gint line_end = SSM(p->sci, SCI_GETLINEENDPOSITION, line, 0); + + if (pos < line_end) + { + gchar txt[MAX_CHAR_SIZE]; + struct Sci_TextRange tr; + + tr.chrg.cpMin = pos; + tr.chrg.cpMax = NEXT(p->sci, pos); + tr.lpstrText = txt; + + SSM(p->sci, SCI_GETTEXTRANGE, 0, (sptr_t)&tr); + SSM(p->sci, SCI_INSERTTEXT, p->pos, (sptr_t)txt); + SET_POS(p->sci, NEXT(p->sci, p->pos), TRUE); + } + } +} + + +void cmd_copy_char_from_above(CmdContext *c, CmdParams *p) +{ + copy_char(c, p, TRUE); +} + + +void cmd_copy_char_from_below(CmdContext *c, CmdParams *p) +{ + copy_char(c, p, FALSE); +} + + +void cmd_repeat_subst(CmdContext *c, CmdParams *p) +{ + perform_substitute(p->sci, c->substitute_text, p->line, p->line, ""); +} diff --git a/vimode/src/cmds/edit.h b/vimode/src/cmds/edit.h new file mode 100644 index 000000000..c365ff408 --- /dev/null +++ b/vimode/src/cmds/edit.h @@ -0,0 +1,66 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_CMDS_EDIT_H__ +#define __VIMODE_CMDS_EDIT_H__ + +#include "context.h" +#include "cmd-params.h" + +void cmd_delete_char(CmdContext *c, CmdParams *p); +void cmd_delete_char_copy(CmdContext *c, CmdParams *p); +void cmd_delete_char_back(CmdContext *c, CmdParams *p); +void cmd_delete_char_back_copy(CmdContext *c, CmdParams *p); + +void cmd_clear_right(CmdContext *c, CmdParams *p); +void cmd_delete_line(CmdContext *c, CmdParams *p); +void cmd_copy_line(CmdContext *c, CmdParams *p); + +void cmd_newline(CmdContext *c, CmdParams *p); +void cmd_tab(CmdContext *c, CmdParams *p); +void cmd_del_word_left(CmdContext *c, CmdParams *p); + +void cmd_undo(CmdContext *c, CmdParams *p); +void cmd_redo(CmdContext *c, CmdParams *p); + +void cmd_paste_after(CmdContext *c, CmdParams *p); +void cmd_paste_before(CmdContext *c, CmdParams *p); + +void cmd_join_lines(CmdContext *c, CmdParams *p); +void cmd_join_lines_sel(CmdContext *c, CmdParams *p); + +void cmd_replace_char(CmdContext *c, CmdParams *p); +void cmd_replace_char_sel(CmdContext *c, CmdParams *p); + +void cmd_switch_case(CmdContext *c, CmdParams *p); +void cmd_lower_case(CmdContext *c, CmdParams *p); +void cmd_upper_case(CmdContext *c, CmdParams *p); + +void cmd_indent(CmdContext *c, CmdParams *p); +void cmd_unindent(CmdContext *c, CmdParams *p); +void cmd_indent_sel(CmdContext *c, CmdParams *p); +void cmd_unindent_sel(CmdContext *c, CmdParams *p); +void cmd_indent_ins(CmdContext *c, CmdParams *p); +void cmd_unindent_ins(CmdContext *c, CmdParams *p); + +void cmd_copy_char_from_above(CmdContext *c, CmdParams *p); +void cmd_copy_char_from_below(CmdContext *c, CmdParams *p); + +void cmd_repeat_subst(CmdContext *c, CmdParams *p); + +#endif diff --git a/vimode/src/cmds/motion.c b/vimode/src/cmds/motion.c new file mode 100644 index 000000000..9a22dfd85 --- /dev/null +++ b/vimode/src/cmds/motion.c @@ -0,0 +1,528 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "cmds/motion.h" +#include "utils.h" + + +void cmd_goto_left(CmdContext *c, CmdParams *p) +{ + gint i; + gint start_pos = p->line_start_pos; + gint pos = p->pos; + for (i = 0; i < p->num && pos > start_pos; i++) + pos = PREV(p->sci, pos); + SET_POS(p->sci, pos, TRUE); +} + + +void cmd_goto_right(CmdContext *c, CmdParams *p) +{ + gint i; + gint pos = p->pos; + for (i = 0; i < p->num && pos < p->line_end_pos; i++) + pos = NEXT(p->sci, pos); + SET_POS(p->sci, pos, TRUE); +} + + +void cmd_goto_up(CmdContext *c, CmdParams *p) +{ + gint one_below, pos; + + if (p->line == 0) + return; + + /* Calling SCI_LINEUP in a loop for num lines leads to visible slow scrolling. + * On the other hand, SCI_LINEUP preserves the value of SCI_CHOOSECARETX + * we want to keep - perform jump to previous line and one final SCI_LINEUP + * which recovers SCI_CHOOSECARETX for us. */ + one_below = p->line - p->num + 1; + one_below = one_below > 0 ? one_below : 1; + pos = SSM(p->sci, SCI_POSITIONFROMLINE, one_below, 0); + SET_POS_NOX(p->sci, pos, FALSE); + SSM(p->sci, SCI_LINEUP, 0, 0); +} + + +void cmd_goto_up_nonempty(CmdContext *c, CmdParams *p) +{ + cmd_goto_up(c, p); + goto_nonempty(p->sci, GET_CUR_LINE(p->sci), TRUE); +} + + +static void goto_down(CmdParams *p, gint num) +{ + gint one_above, pos; + gint last_line = p->line_num - 1; + + if (p->line == last_line) + return; + + /* see cmd_goto_up() for explanation */ + one_above = p->line + num - 1; + one_above = one_above < last_line ? one_above : last_line - 1; + pos = SSM(p->sci, SCI_POSITIONFROMLINE, one_above, 0); + SET_POS_NOX(p->sci, pos, FALSE); + SSM(p->sci, SCI_LINEDOWN, 0, 0); +} + + +void cmd_goto_down(CmdContext *c, CmdParams *p) +{ + goto_down(p, p->num); +} + + +void cmd_goto_down_nonempty(CmdContext *c, CmdParams *p) +{ + goto_down(p, p->num); + goto_nonempty(p->sci, GET_CUR_LINE(p->sci), TRUE); +} + + +void cmd_goto_down_one_less_nonempty(CmdContext *c, CmdParams *p) +{ + if (p->num > 1) + goto_down(p, p->num - 1); + goto_nonempty(p->sci, GET_CUR_LINE(p->sci), TRUE); +} + + +void cmd_goto_page_up(CmdContext *c, CmdParams *p) +{ + gint shift = p->line_visible_num * p->num; + gint new_line = get_line_number_rel(p->sci, -shift); + goto_nonempty(p->sci, new_line, TRUE); +} + + +void cmd_goto_page_down(CmdContext *c, CmdParams *p) +{ + gint shift = p->line_visible_num * p->num; + gint new_line = get_line_number_rel(p->sci, shift); + goto_nonempty(p->sci, new_line, TRUE); +} + + +void cmd_goto_halfpage_up(CmdContext *c, CmdParams *p) +{ + gint shift = p->num_present ? p->num : p->line_visible_num / 2; + gint new_line = get_line_number_rel(p->sci, -shift); + goto_nonempty(p->sci, new_line, TRUE); +} + + +void cmd_goto_halfpage_down(CmdContext *c, CmdParams *p) +{ + gint shift = p->num_present ? p->num : p->line_visible_num / 2; + gint new_line = get_line_number_rel(p->sci, shift); + goto_nonempty(p->sci, new_line, TRUE); +} + + +void cmd_goto_line(CmdContext *c, CmdParams *p) +{ + gint num = p->num > p->line_num ? p->line_num : p->num; + goto_nonempty(p->sci, num - 1, TRUE); +} + + +void cmd_goto_line_last(CmdContext *c, CmdParams *p) +{ + gint num = p->num > p->line_num ? p->line_num : p->num; + if (!p->num_present) + num = p->line_num; + goto_nonempty(p->sci, num - 1, TRUE); +} + + +void cmd_goto_screen_top(CmdContext *c, CmdParams *p) +{ + gint top = p->line_visible_first; + gint count = p->line_visible_num; + gint line = top + p->num; + goto_nonempty(p->sci, line > top + count ? top + count : line, FALSE); +} + + +void cmd_goto_screen_middle(CmdContext *c, CmdParams *p) +{ + goto_nonempty(p->sci, p->line_visible_first + p->line_visible_num/2, FALSE); +} + + +void cmd_goto_screen_bottom(CmdContext *c, CmdParams *p) +{ + gint top = p->line_visible_first; + gint count = p->line_visible_num; + gint line = top + count - p->num; + goto_nonempty(p->sci, line < top ? top : line, FALSE); +} + + +void cmd_goto_doc_percentage(CmdContext *c, CmdParams *p) +{ + if (p->num > 100) + p->num = 100; + + goto_nonempty(p->sci, (p->line_num * p->num) / 100, TRUE); +} + + +static void goto_word_end(CmdContext *c, CmdParams *p, gboolean forward) +{ + gint i; + + if (VI_IS_COMMAND(vi_get_mode())) + SSM(p->sci, SCI_CHARRIGHT, 0, 0); + + for (i = 0; i < p->num; i++) + { + gint orig_pos = SSM(p->sci, SCI_GETCURRENTPOS, 0, 0); + gint pos; + gint line_start_pos; + + SSM(p->sci, forward ? SCI_WORDRIGHTEND : SCI_WORDLEFTEND, 0, 0); + line_start_pos = SSM(p->sci, SCI_POSITIONFROMLINE, GET_CUR_LINE(p->sci), 0); + pos = SSM(p->sci, SCI_GETCURRENTPOS, 0, 0); + if (pos == orig_pos) + break; + /* For some reason, Scintilla treats newlines as word parts and cursor + * is left before/after newline. Repeat again in this case. */ + if (pos == line_start_pos) + SSM(p->sci, forward ? SCI_WORDRIGHTEND : SCI_WORDLEFTEND, 0, 0); + } + + if (VI_IS_COMMAND(vi_get_mode())) + SSM(p->sci, SCI_CHARLEFT, 0, 0); +} + + +static void goto_word_start(CmdContext *c, CmdParams *p, gboolean forward) +{ + gint i; + for (i = 0; i < p->num; i++) + { + gint orig_pos = SSM(p->sci, SCI_GETCURRENTPOS, 0, 0); + gint pos; + gint line_end_pos; + + SSM(p->sci, forward ? SCI_WORDRIGHT : SCI_WORDLEFT, 0, 0); + line_end_pos = SSM(p->sci, SCI_GETLINEENDPOSITION, GET_CUR_LINE(p->sci), 0); + pos = SSM(p->sci, SCI_GETCURRENTPOS, 0, 0); + if (pos == orig_pos) + break; + /* For some reason, Scintilla treats newlines as word parts and cursor + * is left before/after newline. Repeat again in this case. */ + if (pos == line_end_pos) + SSM(p->sci, forward ? SCI_WORDRIGHT : SCI_WORDLEFT, 0, 0); + } +} + + +void cmd_goto_next_word(CmdContext *c, CmdParams *p) +{ + goto_word_start(c, p, TRUE); +} + + +void cmd_goto_previous_word(CmdContext *c, CmdParams *p) +{ + goto_word_start(c, p, FALSE); +} + + +void cmd_goto_next_word_end(CmdContext *c, CmdParams *p) +{ + goto_word_end(c, p, TRUE); +} + + +void cmd_goto_previous_word_end(CmdContext *c, CmdParams *p) +{ + goto_word_end(c, p, FALSE); +} + + +static void use_all_wordchars(ScintillaObject *sci) +{ + guchar wc[512]; + gint i; + gint j = 0; + for (i = 0; i < 256; i++) + { + if (g_ascii_isprint(i) && !g_ascii_isspace(i)) + wc[j++] = i; + } + wc[j] = '\0'; + SSM(sci, SCI_SETWORDCHARS, 0, (sptr_t)wc); +} + + +void cmd_goto_next_word_space(CmdContext *c, CmdParams *p) +{ + guchar wc[512]; + SSM(p->sci, SCI_GETWORDCHARS, 0, (sptr_t)wc); + use_all_wordchars(p->sci); + + goto_word_start(c, p, TRUE); + + SSM(p->sci, SCI_SETWORDCHARS, 0, (sptr_t)wc); +} + + +void cmd_goto_previous_word_space(CmdContext *c, CmdParams *p) +{ + guchar wc[512]; + SSM(p->sci, SCI_GETWORDCHARS, 0, (sptr_t)wc); + use_all_wordchars(p->sci); + + goto_word_start(c, p, FALSE); + + SSM(p->sci, SCI_SETWORDCHARS, 0, (sptr_t)wc); +} + + +void cmd_goto_next_word_end_space(CmdContext *c, CmdParams *p) +{ + guchar wc[512]; + SSM(p->sci, SCI_GETWORDCHARS, 0, (sptr_t)wc); + use_all_wordchars(p->sci); + + goto_word_end(c, p, TRUE); + + SSM(p->sci, SCI_SETWORDCHARS, 0, (sptr_t)wc); +} + + +void cmd_goto_previous_word_end_space(CmdContext *c, CmdParams *p) +{ + guchar wc[512]; + SSM(p->sci, SCI_GETWORDCHARS, 0, (sptr_t)wc); + use_all_wordchars(p->sci); + + goto_word_end(c, p, FALSE); + + SSM(p->sci, SCI_SETWORDCHARS, 0, (sptr_t)wc); +} + + +void cmd_goto_line_start(CmdContext *c, CmdParams *p) +{ + SSM(p->sci, SCI_HOME, 0, 0); +} + + +void cmd_goto_line_start_nonempty(CmdContext *c, CmdParams *p) +{ + goto_nonempty(p->sci, p->line, TRUE); +} + + +void cmd_goto_line_end(CmdContext *c, CmdParams *p) +{ + if (p->num > 1) + goto_down(p, p->num - 1); + SSM(p->sci, SCI_LINEEND, 0, 0); +} + + +void cmd_goto_column(CmdContext *c, CmdParams *p) +{ + gint pos = SSM(p->sci, SCI_FINDCOLUMN, p->line, p->num - 1); + SET_POS(p->sci, pos, TRUE); +} + + +void cmd_goto_matching_brace(CmdContext *c, CmdParams *p) +{ + gint pos = SSM(p->sci, SCI_BRACEMATCH, p->pos, 0); + if (pos != -1) + SET_POS(p->sci, pos, TRUE); +} + + +static void find_char(CmdContext *c, CmdParams *p, gboolean invert) +{ + struct Sci_TextToFind ttf; + gboolean forward; + gint pos = p->pos; + gint i; + + if (!c->search_char) + return; + + forward = c->search_char[0] == 'f' || c->search_char[0] == 't'; + forward = !forward != !invert; + ttf.lpstrText = c->search_char + 1; + + for (i = 0; i < p->num; i++) + { + gint new_pos; + + if (forward) + { + ttf.chrg.cpMin = NEXT(p->sci, pos); + ttf.chrg.cpMax = p->line_end_pos; + } + else + { + ttf.chrg.cpMin = pos; + ttf.chrg.cpMax = p->line_start_pos; + } + + new_pos = SSM(p->sci, SCI_FINDTEXT, 0, (sptr_t)&ttf); + if (new_pos < 0) + break; + pos = new_pos; + } + + if (pos >= 0) + { + if (c->search_char[0] == 't') + pos = PREV(p->sci, pos); + else if (c->search_char[0] == 'T') + pos = NEXT(p->sci, pos); + SET_POS(p->sci, pos, TRUE); + } +} + + +void cmd_goto_next_char(CmdContext *c, CmdParams *p) +{ + g_free(c->search_char); + c->search_char = g_strconcat("f", kp_to_str(p->last_kp), NULL); + find_char(c, p, FALSE); +} + + +void cmd_goto_prev_char(CmdContext *c, CmdParams *p) +{ + g_free(c->search_char); + c->search_char = g_strconcat("F", kp_to_str(p->last_kp), NULL); + find_char(c, p, FALSE); +} + + +void cmd_goto_next_char_before(CmdContext *c, CmdParams *p) +{ + g_free(c->search_char); + c->search_char = g_strconcat("t", kp_to_str(p->last_kp), NULL); + find_char(c, p, FALSE); +} + + +void cmd_goto_prev_char_before(CmdContext *c, CmdParams *p) +{ + g_free(c->search_char); + c->search_char = g_strconcat("T", kp_to_str(p->last_kp), NULL); + find_char(c, p, FALSE); +} + + +void cmd_goto_char_repeat(CmdContext *c, CmdParams *p) +{ + find_char(c, p, FALSE); +} + + +void cmd_goto_char_repeat_opposite(CmdContext *c, CmdParams *p) +{ + find_char(c, p, TRUE); +} + + +void cmd_scroll_up(CmdContext *c, CmdParams *p) +{ + SSM(p->sci, SCI_LINESCROLL, 0, -p->num); +} + + +void cmd_scroll_down(CmdContext *c, CmdParams *p) +{ + SSM(p->sci, SCI_LINESCROLL, 0, p->num); +} + + +static void scroll_to_line(CmdParams *p, gint offset, gboolean nonempty) +{ + gint column = SSM(p->sci, SCI_GETCOLUMN, p->pos, 0); + gint line = p->line; + + if (p->num_present) + line = p->num - 1; + if (nonempty) + goto_nonempty(p->sci, line, FALSE); + else + { + gint pos = SSM(p->sci, SCI_FINDCOLUMN, line, column); + SET_POS_NOX(p->sci, pos, FALSE); + } + SSM(p->sci, SCI_SETFIRSTVISIBLELINE, line + offset, 0); +} + + +void cmd_scroll_center(CmdContext *c, CmdParams *p) +{ + scroll_to_line(p, - p->line_visible_num / 2, FALSE); +} + + +void cmd_scroll_top(CmdContext *c, CmdParams *p) +{ + scroll_to_line(p, 0, FALSE); +} + + +void cmd_scroll_bottom(CmdContext *c, CmdParams *p) +{ + scroll_to_line(p, - p->line_visible_num + 1, FALSE); +} + + +void cmd_scroll_center_nonempty(CmdContext *c, CmdParams *p) +{ + scroll_to_line(p, - p->line_visible_num / 2, TRUE); +} + + +void cmd_scroll_top_nonempty(CmdContext *c, CmdParams *p) +{ + scroll_to_line(p, 0, TRUE); +} + + +void cmd_scroll_top_next_nonempty(CmdContext *c, CmdParams *p) +{ + if (p->num_present) + cmd_scroll_top_nonempty(c, p); + else + { + gint line = p->line_visible_first + p->line_visible_num; + goto_nonempty(p->sci, line, FALSE); + SSM(p->sci, SCI_SETFIRSTVISIBLELINE, line, 0); + } +} + + +void cmd_scroll_bottom_nonempty(CmdContext *c, CmdParams *p) +{ + scroll_to_line(p, - p->line_visible_num + 1, TRUE); +} diff --git a/vimode/src/cmds/motion.h b/vimode/src/cmds/motion.h new file mode 100644 index 000000000..a30c99d3a --- /dev/null +++ b/vimode/src/cmds/motion.h @@ -0,0 +1,77 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_CMDS_MOTION_H__ +#define __VIMODE_CMDS_MOTION_H__ + +#include "context.h" +#include "cmd-params.h" + +void cmd_goto_left(CmdContext *c, CmdParams *p); +void cmd_goto_right(CmdContext *c, CmdParams *p); +void cmd_goto_up(CmdContext *c, CmdParams *p); +void cmd_goto_up_nonempty(CmdContext *c, CmdParams *p); +void cmd_goto_down(CmdContext *c, CmdParams *p); +void cmd_goto_down_nonempty(CmdContext *c, CmdParams *p); +void cmd_goto_down_one_less_nonempty(CmdContext *c, CmdParams *p); + +void cmd_goto_page_up(CmdContext *c, CmdParams *p); +void cmd_goto_page_down(CmdContext *c, CmdParams *p); +void cmd_goto_halfpage_up(CmdContext *c, CmdParams *p); +void cmd_goto_halfpage_down(CmdContext *c, CmdParams *p); +void cmd_goto_line(CmdContext *c, CmdParams *p); +void cmd_goto_line_last(CmdContext *c, CmdParams *p); +void cmd_goto_screen_top(CmdContext *c, CmdParams *p); +void cmd_goto_screen_middle(CmdContext *c, CmdParams *p); +void cmd_goto_screen_bottom(CmdContext *c, CmdParams *p); +void cmd_goto_doc_percentage(CmdContext *c, CmdParams *p); + +void cmd_goto_next_word(CmdContext *c, CmdParams *p); +void cmd_goto_previous_word(CmdContext *c, CmdParams *p); +void cmd_goto_next_word_end(CmdContext *c, CmdParams *p); +void cmd_goto_previous_word_end(CmdContext *c, CmdParams *p); +void cmd_goto_next_word_space(CmdContext *c, CmdParams *p); +void cmd_goto_previous_word_space(CmdContext *c, CmdParams *p); +void cmd_goto_next_word_end_space(CmdContext *c, CmdParams *p); +void cmd_goto_previous_word_end_space(CmdContext *c, CmdParams *p); + +void cmd_goto_line_start(CmdContext *c, CmdParams *p); +void cmd_goto_line_start_nonempty(CmdContext *c, CmdParams *p); +void cmd_goto_line_end(CmdContext *c, CmdParams *p); +void cmd_goto_column(CmdContext *c, CmdParams *p); + +void cmd_goto_matching_brace(CmdContext *c, CmdParams *p); + +void cmd_goto_next_char(CmdContext *c, CmdParams *p); +void cmd_goto_prev_char(CmdContext *c, CmdParams *p); +void cmd_goto_next_char_before(CmdContext *c, CmdParams *p); +void cmd_goto_prev_char_before(CmdContext *c, CmdParams *p); +void cmd_goto_char_repeat(CmdContext *c, CmdParams *p); +void cmd_goto_char_repeat_opposite(CmdContext *c, CmdParams *p); + +void cmd_scroll_up(CmdContext *c, CmdParams *p); +void cmd_scroll_down(CmdContext *c, CmdParams *p); +void cmd_scroll_center(CmdContext *c, CmdParams *p); +void cmd_scroll_top(CmdContext *c, CmdParams *p); +void cmd_scroll_bottom(CmdContext *c, CmdParams *p); +void cmd_scroll_center_nonempty(CmdContext *c, CmdParams *p); +void cmd_scroll_top_nonempty(CmdContext *c, CmdParams *p); +void cmd_scroll_top_next_nonempty(CmdContext *c, CmdParams *p); +void cmd_scroll_bottom_nonempty(CmdContext *c, CmdParams *p); + +#endif diff --git a/vimode/src/cmds/special.c b/vimode/src/cmds/special.c new file mode 100644 index 000000000..7f15aba5f --- /dev/null +++ b/vimode/src/cmds/special.c @@ -0,0 +1,117 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "cmds/special.h" +#include "utils.h" + +#include + + +void cmd_swap_anchor(CmdContext *c, CmdParams *p) +{ + gint anchor = c->sel_anchor; + c->sel_anchor = p->pos; + SET_POS(p->sci, anchor, TRUE); +} + + +void cmd_nop(CmdContext *c, CmdParams *p) +{ + // do nothing +} + + +void cmd_repeat_last_command(CmdContext *c, CmdParams *p) +{ + // handled in a special way - has to be separate from cmd_nop() +} + + +void cmd_paste_inserted_text(CmdContext *c, CmdParams *p) +{ + SSM(p->sci, SCI_ADDTEXT, c->insert_buf_len, (sptr_t) c->insert_buf); +} + + +void cmd_paste_inserted_text_leave_ins(CmdContext *c, CmdParams *p) +{ + cmd_paste_inserted_text(c, p); + vi_set_mode(VI_MODE_COMMAND); +} + + +void cmd_write_exit(CmdContext *c, CmdParams *p) +{ + if (c->cb->on_save(FALSE)) + c->cb->on_quit(FALSE); +} + + +void cmd_force_exit(CmdContext *c, CmdParams *p) +{ + c->cb->on_quit(TRUE); +} + + +void cmd_search_next(CmdContext *c, CmdParams *p) +{ + gboolean invert = FALSE; + gint pos; + + if (p->last_kp->key == GDK_KEY_N) + invert = TRUE; + + pos = perform_search(p->sci, c->search_text, p->num, invert); + if (pos >= 0) + SET_POS(c->sci, pos, TRUE); + +} + + +static void search_current(CmdContext *c, CmdParams *p, gboolean next) +{ + gchar *word = get_current_word(p->sci); + gint pos; + + g_free(c->search_text); + if (!word) + c->search_text = NULL; + else + { + const gchar *prefix = next ? "/" : "?"; + c->search_text = g_strconcat(prefix, word, NULL); + } + g_free(word); + + pos = perform_search(p->sci, c->search_text, p->num, FALSE); + if (pos >= 0) + SET_POS(c->sci, pos, TRUE); + +} + + +void cmd_search_current_next(CmdContext *c, CmdParams *p) +{ + search_current(c, p, TRUE); +} + + +void cmd_search_current_prev(CmdContext *c, CmdParams *p) +{ + search_current(c, p, FALSE); +} diff --git a/vimode/src/cmds/special.h b/vimode/src/cmds/special.h new file mode 100644 index 000000000..9fc8630be --- /dev/null +++ b/vimode/src/cmds/special.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_CMDS_SPECIAL_H__ +#define __VIMODE_CMDS_SPECIAL_H__ + +#include "context.h" +#include "cmd-params.h" + +void cmd_swap_anchor(CmdContext *c, CmdParams *p); +void cmd_nop(CmdContext *c, CmdParams *p); +void cmd_repeat_last_command(CmdContext *c, CmdParams *p); +void cmd_paste_inserted_text(CmdContext *c, CmdParams *p); +void cmd_paste_inserted_text_leave_ins(CmdContext *c, CmdParams *p); + +void cmd_write_exit(CmdContext *c, CmdParams *p); +void cmd_force_exit(CmdContext *c, CmdParams *p); + +void cmd_search_next(CmdContext *c, CmdParams *p); +void cmd_search_current_next(CmdContext *c, CmdParams *p); +void cmd_search_current_prev(CmdContext *c, CmdParams *p); + +#endif diff --git a/vimode/src/cmds/txtobjs.c b/vimode/src/cmds/txtobjs.c new file mode 100644 index 000000000..fa31278ca --- /dev/null +++ b/vimode/src/cmds/txtobjs.c @@ -0,0 +1,190 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "cmds/txtobjs.h" + + +static gint find_upper_level_brace(ScintillaObject *sci, gint pos, gint open_brace, gint close_brace) +{ + while (pos > 0) + { + pos = PREV(sci, pos); + gchar c = SSM(sci, SCI_GETCHARAT, pos, 0); + + if (c == open_brace) + return pos; + else if (c == close_brace) + { + pos = SSM(sci, SCI_BRACEMATCH, pos, 0); + if (pos < 0) + break; + } + } + return -1; +} + + +static gint find_char(ScintillaObject *sci, gint pos, gint ch, gboolean forward) +{ + while (pos > 0) + { + gint last_pos = pos; + gchar c; + + pos = forward ? NEXT(sci, pos) : PREV(sci, pos); + c = SSM(sci, SCI_GETCHARAT, pos, 0); + + if (c == ch) + return pos; + if (pos == last_pos) + break; + } + return -1; +} + + +static void select_brace(CmdContext *c, CmdParams *p, gint open_brace, gint close_brace, gboolean inner) +{ + gint pos = p->pos; + gint start_pos = 0; + gint end_pos = 0; + gint i; + + for (i = 0; i < p->num; i++) + { + if (open_brace == close_brace) + { + start_pos = find_char(p->sci, pos, open_brace, FALSE); + if (start_pos < 0) + return; + end_pos = find_char(p->sci, pos, close_brace, TRUE); + } + else + { + start_pos = find_upper_level_brace(p->sci, pos, open_brace, close_brace); + if (start_pos < 0) + return; + end_pos = SSM(p->sci, SCI_BRACEMATCH, start_pos, 0); + } + + if (end_pos < 0) + return; + + pos = start_pos; + } + + if (inner) + start_pos = NEXT(p->sci, start_pos); + else + end_pos = NEXT(p->sci, end_pos); + + if (VI_IS_VISUAL(vi_get_mode())) + { + c->sel_anchor = start_pos; + SET_POS(p->sci, end_pos, TRUE); + } + else + { + p->sel_start = start_pos; + p->sel_len = end_pos - start_pos; + } +} + + +void cmd_select_quotedbl(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '\"', '\"', FALSE); +} + + +void cmd_select_quoteleft(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '`', '`', FALSE); +} + + +void cmd_select_apostrophe(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '\'', '\'', FALSE); +} + + +void cmd_select_brace(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '{', '}', FALSE); +} + + +void cmd_select_paren(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '(', ')', FALSE); +} + + +void cmd_select_less(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '<', '>', FALSE); +} + + +void cmd_select_bracket(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '[', ']', FALSE); +} + + +void cmd_select_quotedbl_inner(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '\"', '\"', TRUE); +} + + +void cmd_select_quoteleft_inner(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '`', '`', TRUE); +} + + +void cmd_select_apostrophe_inner(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '\'', '\'', TRUE); +} + + +void cmd_select_brace_inner(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '{', '}', TRUE); +} + + +void cmd_select_paren_inner(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '(', ')', TRUE); +} + + +void cmd_select_less_inner(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '<', '>', TRUE); +} + + +void cmd_select_bracket_inner(CmdContext *c, CmdParams *p) +{ + select_brace(c, p, '[', ']', TRUE); +} diff --git a/vimode/src/cmds/txtobjs.h b/vimode/src/cmds/txtobjs.h new file mode 100644 index 000000000..39b96a098 --- /dev/null +++ b/vimode/src/cmds/txtobjs.h @@ -0,0 +1,41 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_CMDS_TXTOBJS_H__ +#define __VIMODE_CMDS_TXTOBJS_H__ + +#include "context.h" +#include "cmd-params.h" + +void cmd_select_quotedbl(CmdContext *c, CmdParams *p); +void cmd_select_quoteleft(CmdContext *c, CmdParams *p); +void cmd_select_apostrophe(CmdContext *c, CmdParams *p); +void cmd_select_brace(CmdContext *c, CmdParams *p); +void cmd_select_paren(CmdContext *c, CmdParams *p); +void cmd_select_less(CmdContext *c, CmdParams *p); +void cmd_select_bracket(CmdContext *c, CmdParams *p); + +void cmd_select_quotedbl_inner(CmdContext *c, CmdParams *p); +void cmd_select_quoteleft_inner(CmdContext *c, CmdParams *p); +void cmd_select_apostrophe_inner(CmdContext *c, CmdParams *p); +void cmd_select_brace_inner(CmdContext *c, CmdParams *p); +void cmd_select_paren_inner(CmdContext *c, CmdParams *p); +void cmd_select_less_inner(CmdContext *c, CmdParams *p); +void cmd_select_bracket_inner(CmdContext *c, CmdParams *p); + +#endif diff --git a/vimode/src/context.h b/vimode/src/context.h new file mode 100644 index 000000000..3d60b39aa --- /dev/null +++ b/vimode/src/context.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_CONTEXT_H__ +#define __VIMODE_CONTEXT_H__ + +#include "vi.h" +#include "sci.h" + +#define INSERT_BUF_LEN 4096 + +typedef struct +{ + /* key presses accumulated over time (e.g. for commands like 100dd) */ + GSList *kpl; + /* kpl of the last edit command used for repeating last command */ + GSList *repeat_kpl; + /* current scintilla object */ + ScintillaObject *sci; + /* callbacks for the backend */ + ViCallback *cb; + + /* the last full search command, including '/' or '?' */ + gchar *search_text; + /* the last full substitute (replace) command of the form 's/pattern/str/flags' */ + gchar *substitute_text; + /* the last full character search command, such as 'fc' or 'Tc' */ + gchar *search_char; + + /* whether the last copy was in line-copy-mode (like yy) or selection mode */ + gboolean line_copy; + /* whether insert mode was entered using 'o' or 'O' - we need to add newlines + * when copying it N times */ + gboolean newline_insert; + + /* selection anchor - selection is between anchor and caret */ + gint sel_anchor; + /* number entered before performing mode-switching command */ + gint num; + + /* buffer used in insert/replace mode to record entered text so it can be + * copied N times when e.g. 'i' is preceded by a number */ + gchar insert_buf[INSERT_BUF_LEN]; + gint insert_buf_len; +} CmdContext; + +#endif diff --git a/vimode/src/excmd-params.h b/vimode/src/excmd-params.h new file mode 100644 index 000000000..360047fe3 --- /dev/null +++ b/vimode/src/excmd-params.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __EXCMD_PARAMS_H__ +#define __EXCMD_PARAMS_H__ + +#include "context.h" + +typedef struct +{ + /* was the command forced with ! mark ? */ + gboolean force; + /* the first parameter of the command */ + const gchar *param1; + /* ex range start and end */ + gint range_from; + gint range_to; +} ExCmdParams; + +typedef void (*ExCmd)(CmdContext *c, ExCmdParams *p); + +#endif diff --git a/vimode/src/excmd-prompt.c b/vimode/src/excmd-prompt.c new file mode 100644 index 000000000..465888647 --- /dev/null +++ b/vimode/src/excmd-prompt.c @@ -0,0 +1,141 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "excmd-prompt.h" +#include "excmd-runner.h" + +#include +#include + +#if ! GTK_CHECK_VERSION (3, 0, 0) && ! defined (gtk_widget_get_allocated_width) +# define gtk_widget_get_allocated_width(w) (GTK_WIDGET (w)->allocation.width) +#endif +#if ! GTK_CHECK_VERSION (3, 0, 0) && ! defined (gtk_widget_get_allocated_height) +# define gtk_widget_get_allocated_height(w) (GTK_WIDGET (w)->allocation.height) +#endif + +#define PROMPT_WIDTH 500 + +static GtkWidget *prompt; +static GtkWidget *entry; +static CmdContext *ctx; + + +static void close_prompt() +{ + gtk_widget_hide(prompt); +} + + +static gboolean on_prompt_key_press_event(GtkWidget *widget, GdkEventKey *event, gpointer dummy) +{ + switch (event->keyval) + { + case GDK_KEY_Escape: + close_prompt(); + return TRUE; + + case GDK_KEY_Tab: + /* avoid leaving the entry */ + return TRUE; + + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + case GDK_KEY_ISO_Enter: + excmd_perform(ctx, gtk_entry_get_text(GTK_ENTRY(entry))); + close_prompt(); + return TRUE; + } + + return FALSE; +} + + +static void on_entry_text_notify(GObject *object, GParamSpec *pspec, gpointer dummy) +{ + const gchar* cmd = gtk_entry_get_text(GTK_ENTRY(entry)); + + if (cmd == NULL || strlen(cmd) == 0) + close_prompt(); +} + + +static void on_prompt_show(GtkWidget *widget, gpointer dummy) +{ + gtk_widget_grab_focus(entry); +} + + +void ex_prompt_init(GtkWidget *parent_window, CmdContext *c) +{ + GtkWidget *frame; + + ctx = c; + + /* prompt */ + prompt = g_object_new(GTK_TYPE_WINDOW, + "decorated", FALSE, + "default-width", PROMPT_WIDTH, + "transient-for", parent_window, + "window-position", GTK_WIN_POS_CENTER_ON_PARENT, + "type-hint", GDK_WINDOW_TYPE_HINT_DIALOG, + "skip-taskbar-hint", TRUE, + "skip-pager-hint", TRUE, + NULL); + g_signal_connect(prompt, "focus-out-event", G_CALLBACK(close_prompt), NULL); + g_signal_connect(prompt, "show", G_CALLBACK(on_prompt_show), NULL); + g_signal_connect(prompt, "key-press-event", G_CALLBACK(on_prompt_key_press_event), NULL); + + frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(prompt), frame); + + entry = gtk_entry_new(); + gtk_container_add(GTK_CONTAINER(frame), entry); + + g_signal_connect(entry, "notify::text", G_CALLBACK(on_entry_text_notify), NULL); + + gtk_widget_show_all(frame); +} + + +static void position_prompt(void) +{ + gint sci_x, sci_y; + gint sci_width = gtk_widget_get_allocated_width(GTK_WIDGET(ctx->sci)); + gint prompt_width = PROMPT_WIDTH > sci_width ? sci_width : PROMPT_WIDTH; + gint prompt_height = gtk_widget_get_allocated_height(GTK_WIDGET(prompt)); + gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(ctx->sci)), &sci_x, &sci_y); + gtk_window_resize(GTK_WINDOW(prompt), prompt_width, prompt_height); + gtk_window_move(GTK_WINDOW(prompt), sci_x + (sci_width - prompt_width) / 2, sci_y); +} + + +void ex_prompt_show(const gchar *val) +{ + gtk_widget_show(prompt); + position_prompt(); + gtk_entry_set_text(GTK_ENTRY(entry), val); + gtk_editable_set_position(GTK_EDITABLE(entry), strlen(val)); +} + + +void ex_prompt_cleanup(void) +{ + gtk_widget_destroy(prompt); +} diff --git a/vimode/src/excmd-prompt.h b/vimode/src/excmd-prompt.h new file mode 100644 index 000000000..906a47eaf --- /dev/null +++ b/vimode/src/excmd-prompt.h @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_EXCMD_PROMPT_H__ +#define __VIMODE_EXCMD_PROMPT_H__ + +#include "context.h" + +#include + +void ex_prompt_init(GtkWidget *parent_window, CmdContext *ctx); +void ex_prompt_cleanup(void); +void ex_prompt_show(const gchar *val); + +#endif diff --git a/vimode/src/excmd-runner.c b/vimode/src/excmd-runner.c new file mode 100644 index 000000000..227b549d6 --- /dev/null +++ b/vimode/src/excmd-runner.c @@ -0,0 +1,458 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "excmd-runner.h" +#include "excmd-params.h" +#include "excmds/excmds.h" +#include "utils.h" + +#include +#include + + +typedef struct { + ExCmd cmd; + const gchar *name; +} ExCmdDef; + + +ExCmdDef ex_cmds[] = { + {excmd_save, "w"}, + {excmd_save, "write"}, + {excmd_save, "up"}, + {excmd_save, "update"}, + + {excmd_save_all, "wall"}, + + {excmd_quit, "q"}, + {excmd_quit, "quit"}, + {excmd_quit, "quita"}, + {excmd_quit, "quitall"}, + {excmd_quit, "qa"}, + {excmd_quit, "qall"}, + {excmd_quit, "cq"}, + {excmd_quit, "cquit"}, + + {excmd_save_quit, "wq"}, + {excmd_save_quit, "x"}, + {excmd_save_quit, "xit"}, + {excmd_save_quit, "exi"}, + {excmd_save_quit, "exit"}, + + {excmd_save_all_quit, "xa"}, + {excmd_save_all_quit, "xall"}, + {excmd_save_all_quit, "wqa"}, + {excmd_save_all_quit, "wqall"}, + {excmd_save_all_quit, "x"}, + {excmd_save_all_quit, "xit"}, + + {excmd_repeat_subst, "s"}, + {excmd_repeat_subst, "substitute"}, + {excmd_repeat_subst, "&"}, + {excmd_repeat_subst_orig_flags, "&&"}, + + {NULL, NULL} +}; + + +typedef enum +{ + TK_END, + TK_EOL, + TK_ERROR, + + TK_PLUS, + TK_MINUS, + TK_NUMBER, + TK_COLON, + TK_SEMICOLON, + TK_DOT, + TK_DOLLAR, + TK_VISUAL_START, + TK_VISUAL_END, + TK_PATTERN, + + TK_STAR, + TK_PERCENT, +} TokenType; + + +typedef enum +{ + ST_START, + ST_AFTER_NUMBER, + ST_BEFORE_END +} State; + + +typedef struct +{ + TokenType type; + gint num; + gchar *str; +} Token; + + +static void init_tk(Token *tk, TokenType type, gint num, gchar *str) +{ + tk->type = type; + tk->num = num; + g_free(tk->str); + tk->str = str; +} + + +static TokenType get_simple_token_type(gchar c) +{ + switch (c) + { + case '+': + return TK_PLUS; + case '-': + return TK_MINUS; + case ';': + return TK_SEMICOLON; + case ',': + return TK_COLON; + case '.': + return TK_DOT; + case '$': + return TK_DOLLAR; + case '*': + return TK_STAR; + case '%': + return TK_PERCENT; + default: + break; + } + return TK_ERROR; +} + + +static void next_token(const gchar **p, Token *tk) +{ + TokenType type; + + while (isspace(**p)) + (*p)++; + + if (**p == '\0') + { + init_tk(tk, TK_EOL, 0, NULL); + return; + } + + if (isdigit(**p)) + { + gint num = 0; + while (isdigit(**p)) + { + num = 10 * num + (**p - '0'); + (*p)++; + } + init_tk(tk, TK_NUMBER, num, NULL); + return; + } + + type = get_simple_token_type(**p); + if (type != TK_ERROR) + { + (*p)++; + init_tk(tk, type, 0, NULL); + return; + } + + if (**p == '/' || **p == '?') + { + gchar c = **p; + gchar begin[2] = {c, '\0'}; + GString *s = g_string_new(begin); + (*p)++; + while (**p != c && **p != '\0') + { + g_string_append_c(s, **p); + (*p)++; + } + if (**p == c) + (*p)++; + init_tk(tk, TK_PATTERN, 0, s->str); + g_string_free(s, FALSE); + return ; + } + + if (**p == '\'') + { + (*p)++; + if (**p == '<') + { + (*p)++; + init_tk(tk, TK_VISUAL_START, 0, NULL); + return; + } + else if (**p == '>') + { + (*p)++; + init_tk(tk, TK_VISUAL_END, 0, NULL); + return; + } + else + { + init_tk(tk, TK_ERROR, 0, NULL); + return; + } + } + + init_tk(tk, TK_END, 0, NULL); + return; +} + + +static gboolean parse_ex_range(const gchar **p, CmdContext *ctx, gint *from, gint *to) +{ + Token *tk = g_new0(Token, 1); + State state = ST_START; + gint num = 0; + gboolean neg = FALSE; + gint count = 0; + gboolean success = TRUE; + + next_token(p, tk); + + while (TRUE) + { + if (state == ST_START) + { + switch (tk->type) + { + case TK_PLUS: + state = ST_AFTER_NUMBER; + break; + case TK_MINUS: + state = ST_AFTER_NUMBER; + neg = !neg; + break; + case TK_NUMBER: + state = ST_AFTER_NUMBER; + num = tk->num - 1; + break; + case TK_DOT: + state = ST_AFTER_NUMBER; + num = GET_CUR_LINE(ctx->sci); + break; + case TK_DOLLAR: + state = ST_AFTER_NUMBER; + num = SSM(ctx->sci, SCI_GETLINECOUNT, 0, 0) - 1; + break; + case TK_VISUAL_START: + { + state = ST_AFTER_NUMBER; + gint min = MIN(ctx->sel_anchor, SSM(ctx->sci, SCI_GETCURRENTPOS, 0, 0)); + num = SSM(ctx->sci, SCI_LINEFROMPOSITION, min, 0); + break; + } + case TK_VISUAL_END: + { + state = ST_AFTER_NUMBER; + gint max = MAX(ctx->sel_anchor, SSM(ctx->sci, SCI_GETCURRENTPOS, 0, 0)); + num = SSM(ctx->sci, SCI_LINEFROMPOSITION, max, 0); + break; + } + case TK_PATTERN: + { + gint pos = perform_search(ctx->sci, tk->str, ctx->num, FALSE); + num = SSM(ctx->sci, SCI_LINEFROMPOSITION, pos, 0); + state = ST_AFTER_NUMBER; + break; + } + + case TK_PERCENT: + state = ST_BEFORE_END; + *to = 0; + num = SSM(ctx->sci, SCI_GETLINECOUNT, 0, 0) - 1; + count++; + break; + case TK_STAR: + { + gint pos = MIN(ctx->sel_anchor, SSM(ctx->sci, SCI_GETCURRENTPOS, 0, 0)); + *to = SSM(ctx->sci, SCI_LINEFROMPOSITION, pos, 0); + pos = MAX(ctx->sel_anchor, SSM(ctx->sci, SCI_GETCURRENTPOS, 0, 0)); + num = SSM(ctx->sci, SCI_LINEFROMPOSITION, pos, 0); + state = ST_BEFORE_END; + count++; + break; + } + + case TK_SEMICOLON: + case TK_COLON: + //we don't have number yet, ignore + break; + default: + goto finish; + } + } + else if (state == ST_AFTER_NUMBER || state == ST_BEFORE_END) + { + if (tk->type == TK_SEMICOLON || tk->type == TK_COLON || + tk->type == TK_END || tk->type == TK_EOL) + { + num = MAX(num, 0); + num = MIN(num, SSM(ctx->sci, SCI_GETLINECOUNT, 0, 0) - 1); + if (tk->type == TK_SEMICOLON || tk->type == TK_EOL) + goto_nonempty(ctx->sci, num, TRUE); + + *from = *to; + *to = num; + + neg = FALSE; + num = 0; + count++; + state = ST_START; + } + else if (state == ST_AFTER_NUMBER) + { + switch (tk->type) + { + case TK_PLUS: + break; + case TK_MINUS: + neg = !neg; + break; + case TK_NUMBER: + num += neg ? -tk->num : tk->num; + neg = FALSE; + break; + default: + goto finish; + } + } + else + goto finish; + } + next_token(p, tk); + } + +finish: + if (tk->type != TK_EOL && tk->type != TK_END) + success = FALSE; + g_free(tk->str); + g_free(tk); + if (count == 0) + *from = *to = GET_CUR_LINE(ctx->sci); + else if (count == 1) + *from = *to; + return success; +} + + +static void perform_simple_ex_cmd(CmdContext *ctx, const gchar *cmd) +{ + ExCmdParams params; + gchar **parts, **part; + gchar *cmd_name = NULL; + gchar *param1 = NULL; + + params.range_from = 0; + params.range_to = 0; + + if (strlen(cmd) < 1) + return; + + if (!parse_ex_range(&cmd, ctx, ¶ms.range_from, ¶ms.range_to)) + return; + + if (g_str_has_prefix(cmd, "s/") || g_str_has_prefix(cmd, "substitute/")) + { + g_free(ctx->substitute_text); + ctx->substitute_text = g_strdup(cmd); + perform_substitute(ctx->sci, cmd, params.range_from, params.range_to, NULL); + return; + } + + parts = g_strsplit(cmd, " ", 0); + + for (part = parts; *part; part++) + { + if (strlen(*part) != 0) + { + if (!cmd_name) + cmd_name = *part; + else if (!param1) + param1 = *part; + } + } + + if (cmd_name) + { + gint i; + + params.param1 = param1; + params.force = FALSE; + if (cmd_name[strlen(cmd_name)-1] == '!') + { + cmd_name[strlen(cmd_name)-1] = '\0'; + params.force = TRUE; + } + + for (i = 0; ex_cmds[i].cmd != NULL; i++) + { + ExCmdDef *def = &ex_cmds[i]; + if (strcmp(def->name, cmd_name) == 0) + { + def->cmd(ctx, ¶ms); + break; + } + } + } + + g_strfreev(parts); +} + + +void excmd_perform(CmdContext *ctx, const gchar *cmd) +{ + guint len = strlen(cmd); + + if (cmd == NULL || len < 1) + return; + + switch (cmd[0]) + { + case ':': + perform_simple_ex_cmd(ctx, cmd + 1); + break; + case '/': + case '?': + { + gint pos; + if (len == 1) + { + if (ctx->search_text && strlen(ctx->search_text) > 1) + ctx->search_text[0] = cmd[0]; + } + else + { + g_free(ctx->search_text); + ctx->search_text = g_strdup(cmd); + } + pos = perform_search(ctx->sci, ctx->search_text, ctx->num, FALSE); + if (pos >= 0) + SET_POS(ctx->sci, pos, TRUE); + break; + } + } +} diff --git a/vimode/src/excmd-runner.h b/vimode/src/excmd-runner.h new file mode 100644 index 000000000..9a77d1eef --- /dev/null +++ b/vimode/src/excmd-runner.h @@ -0,0 +1,26 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_EXCMDS_H__ +#define __VIMODE_EXCMDS_H__ + +#include "context.h" + +void excmd_perform(CmdContext *ctx, const gchar *cmd); + +#endif diff --git a/vimode/src/excmds/excmds.c b/vimode/src/excmds/excmds.c new file mode 100644 index 000000000..7de5b57fe --- /dev/null +++ b/vimode/src/excmds/excmds.c @@ -0,0 +1,66 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "excmds/excmds.h" +#include "utils.h" + +void excmd_save(CmdContext *c, ExCmdParams *p) +{ + c->cb->on_save(p->force); +} + + +void excmd_save_all(CmdContext *c, ExCmdParams *p) +{ + c->cb->on_save_all(p->force); +} + + +void excmd_quit(CmdContext *c, ExCmdParams *p) +{ + c->cb->on_quit(p->force); +} + + +void excmd_save_quit(CmdContext *c, ExCmdParams *p) +{ + if (c->cb->on_save(p->force)) + c->cb->on_quit(p->force); +} + + +void excmd_save_all_quit(CmdContext *c, ExCmdParams *p) +{ + if (c->cb->on_save_all(p->force)) + c->cb->on_quit(p->force); +} + + +void excmd_repeat_subst(CmdContext *c, ExCmdParams *p) +{ + const gchar *flags = p->param1; + if (!flags) + flags = "g"; + perform_substitute(c->sci, c->substitute_text, p->range_from, p->range_to, flags); +} + + +void excmd_repeat_subst_orig_flags(CmdContext *c, ExCmdParams *p) +{ + perform_substitute(c->sci, c->substitute_text, p->range_from, p->range_to, NULL); +} diff --git a/vimode/src/excmds/excmds.h b/vimode/src/excmds/excmds.h new file mode 100644 index 000000000..fa4b1fd1d --- /dev/null +++ b/vimode/src/excmds/excmds.h @@ -0,0 +1,33 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_EXCMDS_EXCMDS_H__ +#define __VIMODE_EXCMDS_EXCMDS_H__ + +#include "excmd-params.h" +#include "context.h" + +void excmd_save(CmdContext *c, ExCmdParams *p); +void excmd_save_all(CmdContext *c, ExCmdParams *p); +void excmd_quit(CmdContext *c, ExCmdParams *p); +void excmd_save_quit(CmdContext *c, ExCmdParams *p); +void excmd_save_all_quit(CmdContext *c, ExCmdParams *p); +void excmd_repeat_subst(CmdContext *c, ExCmdParams *p); +void excmd_repeat_subst_orig_flags(CmdContext *c, ExCmdParams *p); + +#endif diff --git a/vimode/src/keypress.c b/vimode/src/keypress.c new file mode 100644 index 000000000..7b5e197a5 --- /dev/null +++ b/vimode/src/keypress.c @@ -0,0 +1,196 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "keypress.h" +#include "utils.h" + +#include + +KeyPress *kp_from_event_key(GdkEventKey *ev) +{ + guint mask = GDK_MODIFIER_MASK & ~(GDK_SHIFT_MASK | GDK_LOCK_MASK | GDK_CONTROL_MASK); + KeyPress *kp; + + if (ev->state & mask) + return NULL; + + switch (ev->keyval) + { + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Caps_Lock: + case GDK_KEY_Shift_Lock: + case GDK_KEY_Meta_L: + case GDK_KEY_Meta_R: + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Super_L: + case GDK_KEY_Super_R: + case GDK_KEY_Hyper_L: + case GDK_KEY_Hyper_R: + return NULL; + } + + kp = g_new0(KeyPress, 1); + kp->key = ev->keyval; + /* We are interested only in Ctrl presses - Alt is not used in Vim and + * shift is included in letter capitalisation implicitly. The only case + * we are interested in shift is for insert mode shift+arrow keystrokes. */ + switch (ev->keyval) + { + case GDK_KEY_Left: + case GDK_KEY_KP_Left: + case GDK_KEY_leftarrow: + case GDK_KEY_Up: + case GDK_KEY_KP_Up: + case GDK_KEY_uparrow: + case GDK_KEY_Right: + case GDK_KEY_KP_Right: + case GDK_KEY_rightarrow: + case GDK_KEY_Down: + case GDK_KEY_KP_Down: + case GDK_KEY_downarrow: + kp->modif = ev->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK); + break; + default: + kp->modif = ev->state & GDK_CONTROL_MASK; + break; + } + + return kp; +} + + +const gchar *kp_to_str(KeyPress *kp) +{ + static gchar *utf8 = NULL; + gunichar key = gdk_keyval_to_unicode(kp->key); + gint len; + + if (!utf8) + utf8 = g_malloc0(MAX_CHAR_SIZE); + len = g_unichar_to_utf8(key, utf8); + utf8[len] = '\0'; + return utf8; +} + + +static gint kp_todigit(KeyPress *kp) +{ + if (kp->modif != 0) + return -1; + + switch (kp->key) + { + case GDK_KEY_0: + case GDK_KEY_KP_0: + return 0; + case GDK_KEY_1: + case GDK_KEY_KP_1: + return 1; + case GDK_KEY_2: + case GDK_KEY_KP_2: + return 2; + case GDK_KEY_3: + case GDK_KEY_KP_3: + return 3; + case GDK_KEY_4: + case GDK_KEY_KP_4: + return 4; + case GDK_KEY_5: + case GDK_KEY_KP_5: + return 5; + case GDK_KEY_6: + case GDK_KEY_KP_6: + return 6; + case GDK_KEY_7: + case GDK_KEY_KP_7: + return 7; + case GDK_KEY_8: + case GDK_KEY_KP_8: + return 8; + case GDK_KEY_9: + case GDK_KEY_KP_9: + return 9; + } + return -1; +} + + +gboolean kp_isdigit(KeyPress *kp) +{ + return kp_todigit(kp) != -1; +} + + +void kpl_printf(GSList *kpl) +{ + kpl = g_slist_reverse(kpl); + GSList *pos = kpl; + printf("kpl: "); + while (pos != NULL) + { + KeyPress *kp = pos->data; + printf("<%d>%s", kp->key, kp_to_str(kp)); + pos = g_slist_next(pos); + } + printf("\n"); + kpl = g_slist_reverse(kpl); +} + + +gint kpl_get_int(GSList *kpl, GSList **new_kpl) +{ + gint res = 0; + gint i = 0; + GSList *pos = kpl; + GSList *num_list = NULL; + + if (new_kpl != NULL) + *new_kpl = kpl; + + while (pos != NULL) + { + if (kp_isdigit(pos->data)) + num_list = g_slist_prepend(num_list, pos->data); + else + break; + pos = g_slist_next(pos); + } + + if (!num_list) + return -1; + + if (new_kpl != NULL) + *new_kpl = pos; + + pos = num_list; + while (pos != NULL) + { + res = res * 10 + kp_todigit(pos->data); + pos = g_slist_next(pos); + i++; + // some sanity check + if (res > 1000000) + break; + } + + return res; +} diff --git a/vimode/src/keypress.h b/vimode/src/keypress.h new file mode 100644 index 000000000..cc5b7bd55 --- /dev/null +++ b/vimode/src/keypress.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_KEYPRESS_H__ +#define __VIMODE_KEYPRESS_H__ + +#include +#include + +typedef struct +{ + guint key; + guint modif; +} KeyPress; + +KeyPress *kp_from_event_key(GdkEventKey *ev); +const gchar *kp_to_str(KeyPress *kp); +gboolean kp_isdigit(KeyPress *kp); + +gint kpl_get_int(GSList *kpl, GSList **ret); +void kpl_printf(GSList *kpl); + +#endif diff --git a/vimode/src/sci.c b/vimode/src/sci.c new file mode 100644 index 000000000..0783f9a14 --- /dev/null +++ b/vimode/src/sci.c @@ -0,0 +1,33 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "sci.h" + +void _set_current_position(ScintillaObject *sci, gint position, gboolean scroll_to_caret, + gboolean caretx) +{ + if (scroll_to_caret) + SSM(sci, SCI_GOTOPOS, (uptr_t) position, 0); + else + { + SSM(sci, SCI_SETCURRENTPOS, (uptr_t) position, 0); + SSM(sci, SCI_SETANCHOR, (uptr_t) position, 0); /* to avoid creation of a selection */ + } + if (caretx) + SSM(sci, SCI_CHOOSECARETX, 0, 0); +} diff --git a/vimode/src/sci.h b/vimode/src/sci.h new file mode 100644 index 000000000..cbf68e3b5 --- /dev/null +++ b/vimode/src/sci.h @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_SCI_H__ +#define __VIMODE_SCI_H__ + +#include +#include "Scintilla.h" +#include "ScintillaWidget.h" + +#define SSM(s, m, w, l) scintilla_send_message((s), (m), (w), (l)) + +#define NEXT(s, pos) scintilla_send_message((s), SCI_POSITIONAFTER, (pos), 0) +#define PREV(s, pos) scintilla_send_message((s), SCI_POSITIONBEFORE, (pos), 0) +#define NTH(s, pos, rel) scintilla_send_message((s), SCI_POSITIONRELATIVE, (pos), (rel)) +#define DIFF(s, start, end) scintilla_send_message((s), SCI_COUNTCHARACTERS, (start), (end)) + +#define SET_POS(s, pos, scr) _set_current_position((s), (pos), (scr), TRUE) +#define SET_POS_NOX(s, pos, scr) _set_current_position((s), (pos), (scr), FALSE) +#define GET_CUR_LINE(s) scintilla_send_message((s), SCI_LINEFROMPOSITION, \ + SSM((s), SCI_GETCURRENTPOS, 0, 0), 0) + +#define MAX_CHAR_SIZE 16 + +void _set_current_position(ScintillaObject *sci, gint position, gboolean scroll_to_caret, + gboolean caretx); + +#endif diff --git a/vimode/src/utils.c b/vimode/src/utils.c new file mode 100644 index 000000000..b491a2dc1 --- /dev/null +++ b/vimode/src/utils.c @@ -0,0 +1,221 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "utils.h" + +#include + + +void clamp_cursor_pos(ScintillaObject *sci) +{ + gint pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); + gint line = GET_CUR_LINE(sci); + gint start_pos = SSM(sci, SCI_POSITIONFROMLINE, line, 0); + gint end_pos = SSM(sci, SCI_GETLINEENDPOSITION, line, 0); + if (pos == end_pos && pos != start_pos) + SET_POS_NOX(sci, pos-1, FALSE); +} + + +static gchar *get_contents_range(ScintillaObject *sci, gint start, gint end) +{ + struct Sci_TextRange tr; + gchar *text = g_malloc(end - start + 1); + + tr.chrg.cpMin = start; + tr.chrg.cpMax = end; + tr.lpstrText = text; + + SSM(sci, SCI_GETTEXTRANGE, 0, (sptr_t)&tr); + return text; +} + + +gchar *get_current_word(ScintillaObject *sci) +{ + gint start, end, pos; + + if (!sci) + return NULL; + + pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); + start = SSM(sci, SCI_WORDSTARTPOSITION, pos, TRUE); + end = SSM(sci, SCI_WORDENDPOSITION, pos, TRUE); + + if (start == end) + return NULL; + + return get_contents_range(sci, start, end); +} + + +gint perform_search(ScintillaObject *sci, const gchar *search_text, + gint num, gboolean invert) +{ + struct Sci_TextToFind ttf; + gint flags = SCFIND_REGEXP | SCFIND_MATCHCASE; + gint pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); + gint len = SSM(sci, SCI_GETLENGTH, 0, 0); + gboolean forward; + GString *s; + gint i; + + if (!search_text) + return -1; + + s = g_string_new(search_text); + while (TRUE) + { + gchar *p = strstr(s->str, "\\c"); + if (!p) + break; + g_string_erase(s, p - s->str, 2); + flags &= ~SCFIND_MATCHCASE; + } + + forward = s->str[0] == '/'; + forward = !forward != !invert; + ttf.lpstrText = s->str + 1; + + for (i = 0; i < num; i++) + { + gint new_pos; + + if (forward) + { + ttf.chrg.cpMin = pos + 1; + ttf.chrg.cpMax = len; + } + else + { + ttf.chrg.cpMin = pos; + ttf.chrg.cpMax = 0; + } + + new_pos = SSM(sci, SCI_FINDTEXT, flags, (sptr_t)&ttf); + if (new_pos < 0) + { + /* wrap */ + if (forward) + { + ttf.chrg.cpMin = 0; + ttf.chrg.cpMax = pos; + } + else + { + ttf.chrg.cpMin = len; + ttf.chrg.cpMax = pos; + } + + new_pos = SSM(sci, SCI_FINDTEXT, flags, (sptr_t)&ttf); + } + + if (new_pos < 0) + break; + pos = new_pos; + } + + g_string_free(s, TRUE); + return pos; +} + + +void perform_substitute(ScintillaObject *sci, const gchar *cmd, gint from, gint to, + const gchar *flag_override) +{ + gchar *copy = g_strdup(cmd); + gchar *p = copy; + gchar *pattern = NULL; + gchar *repl = NULL; + gchar *flags = NULL; + + if (!cmd) + return; + + while (*p) + { + if (*p == '/' && *(p-1) != '\\') + { + if (!pattern) + pattern = p+1; + else if (!repl) + repl = p+1; + else if (!flags) + flags = p+1; + *p = '\0'; + } + p++; + } + + if (flag_override) + flags = (gchar *)flag_override; + + if (pattern && repl) + { + struct Sci_TextToFind ttf; + gint find_flags = SCFIND_REGEXP | SCFIND_MATCHCASE; + GString *s = g_string_new(pattern); + gboolean all = flags && strstr(flags, "g") != NULL; + + while (TRUE) + { + p = strstr(s->str, "\\c"); + if (!p) + break; + g_string_erase(s, p - s->str, 2); + find_flags &= ~SCFIND_MATCHCASE; + } + + ttf.lpstrText = s->str; + ttf.chrg.cpMin = SSM(sci, SCI_POSITIONFROMLINE, from, 0); + ttf.chrg.cpMax = SSM(sci, SCI_GETLINEENDPOSITION, to, 0); + while (SSM(sci, SCI_FINDTEXT, find_flags, (sptr_t)&ttf) != -1) + { + SSM(sci, SCI_SETTARGETSTART, ttf.chrgText.cpMin, 0); + SSM(sci, SCI_SETTARGETEND, ttf.chrgText.cpMax, 0); + SSM(sci, SCI_REPLACETARGET, -1, (sptr_t)repl); + + if (!all) + break; + } + + g_string_free(s, TRUE); + } + + g_free(copy); +} + + +gint get_line_number_rel(ScintillaObject *sci, gint shift) +{ + gint new_line = GET_CUR_LINE(sci) + shift; + gint lines = SSM(sci, SCI_GETLINECOUNT, 0, 0); + new_line = new_line < 0 ? 0 : new_line; + new_line = new_line > lines ? lines : new_line; + return new_line; +} + +void goto_nonempty(ScintillaObject *sci, gint line, gboolean scroll) +{ + gint line_end_pos = SSM(sci, SCI_GETLINEENDPOSITION, line, 0); + gint pos = SSM(sci, SCI_POSITIONFROMLINE, line, 0); + + while (g_ascii_isspace(SSM(sci, SCI_GETCHARAT, pos, 0)) && pos < line_end_pos) + pos = NEXT(sci, pos); + SET_POS(sci, pos, scroll); +} diff --git a/vimode/src/utils.h b/vimode/src/utils.h new file mode 100644 index 000000000..e2e83d97c --- /dev/null +++ b/vimode/src/utils.h @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_UTILS_H__ +#define __VIMODE_UTILS_H__ + +#include "sci.h" + +gchar *get_current_word(ScintillaObject *sci); + +void clamp_cursor_pos(ScintillaObject *sci); +void goto_nonempty(ScintillaObject *sci, gint line, gboolean scroll); + +gint perform_search(ScintillaObject *sci, const gchar *search_text, + gint num, gboolean invert); +void perform_substitute(ScintillaObject *sci, const gchar *cmd, gint from, gint to, + const gchar *flag_override); + +gint get_line_number_rel(ScintillaObject *sci, gint shift); + +#endif diff --git a/vimode/src/vi.c b/vimode/src/vi.c new file mode 100644 index 000000000..3b13c873b --- /dev/null +++ b/vimode/src/vi.c @@ -0,0 +1,383 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "vi.h" +#include "cmd-runner.h" +#include "utils.h" +#include "keypress.h" +#include "excmd-prompt.h" + +#include + +struct +{ + /* caret style used by Scintilla we can revert to when disabling vi mode */ + gint default_caret_style; + /* caret period used by Scintilla we can revert to when disabling vi mode */ + gint default_caret_period; + + /* whether vi mode is enabled or disabled */ + gboolean vim_enabled; + /* whether insert mode is normal Scintilla ("dummies mode") or normal vim insert mode */ + gboolean insert_for_dummies; + + /* vi mode */ + ViMode vi_mode; +} state = +{ + -1, -1, + TRUE, FALSE, + VI_MODE_COMMAND +}; + +CmdContext ctx = +{ + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + FALSE, FALSE, + 0, 1, + "", 0 +}; + + +ViMode vi_get_mode(void) +{ + return state.vi_mode; +} + + +void vi_enter_ex_mode() +{ + KeyPress *kp = g_slist_nth_data(ctx.kpl, 0); + const gchar *c = kp_to_str(kp); + gchar *val; + if (VI_IS_VISUAL(state.vi_mode) && c[0] == ':') + val = g_strconcat(c, "'<,'>", NULL); + else + val = g_strdup(kp_to_str(kp)); + ex_prompt_show(val); + g_free(val); +} + + +static void repeat_insert(gboolean replace) +{ + ScintillaObject *sci = ctx.sci; + + if (sci && ctx.num > 1 && ctx.insert_buf_len > 0) + { + gint i; + + SSM(sci, SCI_BEGINUNDOACTION, 0, 0); + for (i = 0; i < ctx.num - 1; i++) + { + gint line; + gint line_len; + + if (ctx.newline_insert) + SSM(sci, SCI_NEWLINE, 0, 0); + + line = GET_CUR_LINE(sci); + line_len = SSM(sci, SCI_LINELENGTH, line, 0); + + SSM(sci, SCI_ADDTEXT, ctx.insert_buf_len, (sptr_t) ctx.insert_buf); + + if (replace) + { + gint pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); + gint line_end_pos = SSM(sci, SCI_GETLINEENDPOSITION, line, 0); + gint diff = SSM(sci, SCI_LINELENGTH, line, 0) - line_len; + diff = pos + diff > line_end_pos ? line_end_pos - pos : diff; + SSM(sci, SCI_DELETERANGE, pos, diff); + } + } + SSM(sci, SCI_ENDUNDOACTION, 0, 0); + } + ctx.num = 1; + ctx.insert_buf_len = 0; + ctx.insert_buf[0] = '\0'; + ctx.newline_insert = FALSE; +} + + +void vi_set_mode(ViMode mode) +{ + ScintillaObject *sci = ctx.sci; + ViMode prev_mode = state.vi_mode; + + state.vi_mode = mode; + + if (!sci) + return; + + if (state.default_caret_style == -1) + { + state.default_caret_style = SSM(sci, SCI_GETCARETSTYLE, 0, 0); + state.default_caret_period = SSM(sci, SCI_GETCARETPERIOD, 0, 0); + } + + if (!state.vim_enabled) + { + SSM(sci, SCI_SETCARETSTYLE, state.default_caret_style, 0); + SSM(sci, SCI_SETCARETPERIOD, state.default_caret_period, 0); + return; + } + + if (mode != prev_mode) + ctx.cb->on_mode_change(mode); + + switch (mode) + { + case VI_MODE_COMMAND: + case VI_MODE_COMMAND_SINGLE: + { + gint pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); + if (mode == VI_MODE_COMMAND && VI_IS_INSERT(prev_mode)) + { + repeat_insert(prev_mode == VI_MODE_REPLACE); + + //repeat_insert() can change current position + pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); + gint start_pos = SSM(sci, SCI_POSITIONFROMLINE, GET_CUR_LINE(sci), 0); + if (pos > start_pos) + SET_POS(sci, PREV(sci, pos), FALSE); + } + else if (VI_IS_VISUAL(prev_mode)) + SSM(sci, SCI_SETEMPTYSELECTION, pos, 0); + + SSM(sci, SCI_SETOVERTYPE, 0, 0); + SSM(sci, SCI_SETCARETSTYLE, CARETSTYLE_BLOCK, 0); + SSM(sci, SCI_SETCARETPERIOD, 0, 0); + SSM(sci, SCI_CANCEL, 0, 0); + clamp_cursor_pos(sci); + break; + } + case VI_MODE_INSERT: + case VI_MODE_REPLACE: + if (mode == VI_MODE_INSERT) + SSM(sci, SCI_SETOVERTYPE, 0, 0); + else + SSM(sci, SCI_SETOVERTYPE, 1, 0); + SSM(sci, SCI_SETCARETSTYLE, CARETSTYLE_LINE, 0); + SSM(sci, SCI_SETCARETPERIOD, state.default_caret_period, 0); + ctx.insert_buf_len = 0; + ctx.insert_buf[0] = '\0'; + break; + case VI_MODE_VISUAL: + case VI_MODE_VISUAL_LINE: + case VI_MODE_VISUAL_BLOCK: + SSM(sci, SCI_SETOVERTYPE, 0, 0); + /* Even with block-style caret, scintilla's caret behaves differently + * from how vim behaves - it always behaves as if the caret is before + * the character it's placed on. With visual mode we'd need selection + * to go behind the caret but we cannot achieve this. Visual mode + * simply won't behave as vim's visual mode in this respect. Use + * line caret here which makes it more clear what's being selected. */ + SSM(sci, SCI_SETCARETSTYLE, CARETSTYLE_LINE, 0); + SSM(sci, SCI_SETCARETPERIOD, 0, 0); + ctx.sel_anchor = SSM(sci, SCI_GETCURRENTPOS, 0, 0); + break; + } +} + + +void vi_set_active_sci(ScintillaObject *sci) +{ + if (ctx.sci && state.default_caret_style != -1) + { + SSM(ctx.sci, SCI_SETCARETSTYLE, state.default_caret_style, 0); + SSM(ctx.sci, SCI_SETCARETPERIOD, state.default_caret_period, 0); + } + + ctx.sci = sci; + if (sci) + vi_set_mode(state.vi_mode); +} + + +static gboolean is_printable(GdkEventKey *ev) +{ + guint mask = GDK_MODIFIER_MASK & ~(GDK_SHIFT_MASK | GDK_LOCK_MASK); + + if (ev->state & mask) + return FALSE; + + return g_unichar_isprint(gdk_keyval_to_unicode(ev->keyval)); +} + + +gboolean vi_notify_key_press(GdkEventKey *event) +{ + ScintillaObject *sci = ctx.sci; + gboolean consumed = FALSE; + KeyPress *kp; + + if (!sci || !state.vim_enabled) + return FALSE; + + kp = kp_from_event_key(event); + if (!kp) + return FALSE; + + ctx.kpl = g_slist_prepend(ctx.kpl, kp); + //printf("key: %x, state: %d\n", event->keyval, event->state); + //kpl_printf(ctx.kpl); + //kpl_printf(ctx.prev_kpl); + if (VI_IS_COMMAND(state.vi_mode) || VI_IS_VISUAL(state.vi_mode)) + { + if (VI_IS_COMMAND(state.vi_mode)) + consumed = cmd_perform_cmd(&ctx); + else + consumed = cmd_perform_vis(&ctx); + consumed = consumed || is_printable(event); + } + else //insert, replace mode + { + if (!state.insert_for_dummies || kp->key == GDK_KEY_Escape) + consumed = cmd_perform_ins(&ctx); + } + + return consumed; +} + + +gboolean vi_notify_sci(SCNotification *nt) +{ + ScintillaObject *sci = ctx.sci; + + if (!state.vim_enabled || !sci) + return FALSE; + + if (nt->nmhdr.code == SCN_CHARADDED && VI_IS_INSERT(state.vi_mode)) + { + gchar buf[MAX_CHAR_SIZE]; + gint len = g_unichar_to_utf8(nt->ch, buf); + + if (ctx.insert_buf_len + len + 1 < INSERT_BUF_LEN) + { + gint i; + for (i = 0; i < len; i++) + { + ctx.insert_buf[ctx.insert_buf_len] = buf[i]; + ctx.insert_buf_len++; + } + ctx.insert_buf[ctx.insert_buf_len] = '\0'; + } + } + + if (nt->nmhdr.code == SCN_UPDATEUI && VI_IS_VISUAL(state.vi_mode)) + { + if (state.vi_mode == VI_MODE_VISUAL) + { + gint anchor = SSM(sci, SCI_GETANCHOR, 0, 0); + if (anchor != ctx.sel_anchor) + SSM(sci, SCI_SETANCHOR, ctx.sel_anchor, 0); + } + else if (state.vi_mode == VI_MODE_VISUAL_LINE) + { + gint pos = SSM(sci, SCI_GETCURRENTPOS, 0, 0); + gint anchor_line = SSM(sci, SCI_LINEFROMPOSITION, ctx.sel_anchor, 0); + gint pos_line = SSM(sci, SCI_LINEFROMPOSITION, pos, 0); + gint anchor_linepos, pos_linepos; + + if (pos_line >= anchor_line) + { + anchor_linepos = SSM(sci, SCI_POSITIONFROMLINE, anchor_line, 0); + pos_linepos = SSM(sci, SCI_GETLINEENDPOSITION, pos_line, 0); + } + else + { + anchor_linepos = SSM(sci, SCI_GETLINEENDPOSITION, anchor_line, 0); + pos_linepos = SSM(sci, SCI_POSITIONFROMLINE, pos_line, 0); + } + + /* Scintilla' selection spans from anchor position to caret position. + * This means that unfortunately we have to set the caret position as + * well even though it would be better to have caret independent of + * selection like in vim. TODO: explore the possibility of using + * multiple selections to simulate this behavior */ + if (SSM(sci, SCI_GETANCHOR, 0, 0) != anchor_linepos || pos != pos_linepos) + SSM(sci, SCI_SETSEL, anchor_linepos, pos_linepos); + } + } + + /* This makes sure that when we click behind the end of line in command mode, + * the cursor is not placed BEHIND the last character but ON the last character. + * We want to ignore this when doing selection with mouse as it breaks things. */ + if (VI_IS_COMMAND(state.vi_mode) && + nt->nmhdr.code == SCN_UPDATEUI && nt->updated == SC_UPDATE_SELECTION && + SSM(sci, SCI_GETSELECTIONEND, 0, 0) - SSM(sci, SCI_GETSELECTIONSTART, 0, 0) == 0) + clamp_cursor_pos(sci); + + return FALSE; +} + + +void vi_set_enabled(gboolean enabled) +{ + ViMode mode = enabled ? VI_MODE_COMMAND : VI_MODE_INSERT; + state.vim_enabled = enabled; + vi_set_mode(mode); +} + + +void vi_set_insert_for_dummies(gboolean enabled) +{ + state.insert_for_dummies = enabled; +} + + +gboolean vi_get_enabled(void) +{ + return state.vim_enabled; +} + + +gboolean vi_get_insert_for_dummies(void) +{ + return state.insert_for_dummies; +} + + +static void init_cb(ViCallback *cb) +{ + g_assert(cb->on_mode_change && cb->on_save && cb->on_save_all && cb->on_quit); + + ctx.cb = cb; +} + + +void vi_init(GtkWidget *parent_window, ViCallback *cb) +{ + init_cb(cb); + ex_prompt_init(parent_window, &ctx); +} + + +void vi_cleanup(void) +{ + vi_set_active_sci(NULL); + ex_prompt_cleanup(); + + g_slist_free_full(ctx.kpl, g_free); + g_slist_free_full(ctx.repeat_kpl, g_free); + + g_free(ctx.search_text); + g_free(ctx.substitute_text); + g_free(ctx.search_char); +} diff --git a/vimode/src/vi.h b/vimode/src/vi.h new file mode 100644 index 000000000..ae6012da3 --- /dev/null +++ b/vimode/src/vi.h @@ -0,0 +1,64 @@ +/* + * Copyright 2018 Jiri Techet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VIMODE_VI_H__ +#define __VIMODE_VI_H__ + +#include "sci.h" + +#define VI_IS_VISUAL(m) ((m) == VI_MODE_VISUAL || (m) == VI_MODE_VISUAL_LINE || (m) == VI_MODE_VISUAL_BLOCK) +#define VI_IS_COMMAND(m) ((m) == VI_MODE_COMMAND || (m) == VI_MODE_COMMAND_SINGLE) +#define VI_IS_INSERT(m) ((m) == VI_MODE_INSERT || (m) == VI_MODE_REPLACE) + +typedef enum { + VI_MODE_COMMAND, + VI_MODE_COMMAND_SINGLE, //performing single command from insert mode using Ctrl+O + VI_MODE_VISUAL, + VI_MODE_VISUAL_LINE, + VI_MODE_VISUAL_BLOCK, //not implemented + VI_MODE_INSERT, + VI_MODE_REPLACE, +} ViMode; + +typedef struct +{ + void (*on_mode_change)(ViMode mode); + gboolean (*on_save)(gboolean force); + gboolean (*on_save_all)(gboolean force); + void (*on_quit)(gboolean force); +} ViCallback; + + +void vi_enter_ex_mode(void); +void vi_set_mode(ViMode mode); +ViMode vi_get_mode(void); + +gboolean vi_notify_sci(SCNotification *nt); +gboolean vi_notify_key_press(GdkEventKey *event); + +void vi_init(GtkWidget *parent_window, ViCallback *cb); +void vi_cleanup(void); + +void vi_set_active_sci(ScintillaObject *sci); + +void vi_set_enabled(gboolean enabled); +void vi_set_insert_for_dummies(gboolean enabled); +gboolean vi_get_enabled(void); +gboolean vi_get_insert_for_dummies(void); + +#endif From 46102a8a674716aa4dbb6b324dd100a90dd32a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Sat, 21 Apr 2018 23:06:00 +0200 Subject: [PATCH 2/4] vimode: Only ignore key presses containing Alt The previous mask was too restrictive and didn't contain GDK_MOD2_MASK used for numlock. To avoid similar problems in the future, ignore only Alt keypresses. --- vimode/src/keypress.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vimode/src/keypress.c b/vimode/src/keypress.c index 7b5e197a5..42ac52718 100644 --- a/vimode/src/keypress.c +++ b/vimode/src/keypress.c @@ -23,10 +23,10 @@ KeyPress *kp_from_event_key(GdkEventKey *ev) { - guint mask = GDK_MODIFIER_MASK & ~(GDK_SHIFT_MASK | GDK_LOCK_MASK | GDK_CONTROL_MASK); KeyPress *kp; - if (ev->state & mask) + /* ignore keypresses containing Alt - no Vim command uses it */ + if (ev->state & GDK_MOD1_MASK) return NULL; switch (ev->keyval) From 17833ef60572c228b53553fb6a70158fe96c026b Mon Sep 17 00:00:00 2001 From: pcworld <0188801@gmail.com> Date: Sat, 21 Apr 2018 05:52:38 +0200 Subject: [PATCH 3/4] vimode: % searches for first suitable character :help % specifies: > Find the next item in this line after or under the cursor and jump to > its match. --- vimode/src/cmds/motion.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/vimode/src/cmds/motion.c b/vimode/src/cmds/motion.c index 9a22dfd85..2b8cebba7 100644 --- a/vimode/src/cmds/motion.c +++ b/vimode/src/cmds/motion.c @@ -353,9 +353,17 @@ void cmd_goto_column(CmdContext *c, CmdParams *p) void cmd_goto_matching_brace(CmdContext *c, CmdParams *p) { - gint pos = SSM(p->sci, SCI_BRACEMATCH, p->pos, 0); - if (pos != -1) - SET_POS(p->sci, pos, TRUE); + gint pos = p->pos; + while (pos < p->line_end_pos) + { + gint matching_pos = SSM(p->sci, SCI_BRACEMATCH, pos, 0); + if (matching_pos != -1) + { + SET_POS(p->sci, matching_pos, TRUE); + return; + } + pos++; + } } From e2c9197b2a15dfe4dfaa315bcd16fd5c3f523c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Wed, 25 Apr 2018 15:16:35 +0200 Subject: [PATCH 4/4] vimode: Use dedicated Geany key-press signal instead of key-press-event --- vimode/src/backends/backend-geany.c | 30 +++++++++++++---------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/vimode/src/backends/backend-geany.c b/vimode/src/backends/backend-geany.c index 28937494c..6e4d4c842 100644 --- a/vimode/src/backends/backend-geany.c +++ b/vimode/src/backends/backend-geany.c @@ -193,11 +193,24 @@ static gboolean on_editor_notify(GObject *object, GeanyEditor *editor, } +static gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data) +{ + GeanyDocument *doc = document_get_current(); + ScintillaObject *sci = doc != NULL ? doc->editor->sci : NULL; + + if (!sci || gtk_window_get_focus(GTK_WINDOW(geany->main_widgets->window)) != GTK_WIDGET(sci)) + return FALSE; + + return vi_notify_key_press(event); +} + + PluginCallback plugin_callbacks[] = { {"document-open", (GCallback) &on_doc_open, TRUE, NULL}, {"document-activate", (GCallback) &on_doc_activate, TRUE, NULL}, {"document-close", (GCallback) &on_doc_close, TRUE, NULL}, {"editor-notify", (GCallback) &on_editor_notify, TRUE, NULL}, + {"key-press", (GCallback) &on_key_press, TRUE, NULL}, {NULL, NULL, FALSE, NULL} }; @@ -263,18 +276,6 @@ static void on_quit(gboolean force) } -static gboolean on_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data) -{ - GeanyDocument *doc = document_get_current(); - ScintillaObject *sci = doc != NULL ? doc->editor->sci : NULL; - - if (!sci || gtk_window_get_focus(GTK_WINDOW(geany->main_widgets->window)) != GTK_WIDGET(sci)) - return FALSE; - - return vi_notify_key_press(event); -} - - void plugin_init(GeanyData *data) { GeanyDocument *doc = document_get_current(); @@ -324,9 +325,6 @@ void plugin_init(GeanyData *data) if (doc) vi_set_active_sci(doc->editor->sci); - - g_signal_connect(geany_data->main_widgets->window, "key-press-event", - G_CALLBACK(on_key_press_cb), NULL); } @@ -334,8 +332,6 @@ void plugin_cleanup(void) { vi_cleanup(); gtk_widget_destroy(menu_items.parent_item); - g_signal_handlers_disconnect_by_func(geany_data->main_widgets->window, - G_CALLBACK(on_key_press_cb), NULL); }