97

Background:

I have used Vim for 15+ years. There is a certain "one"-ness with the editor one achieves. Emacs users also experience this. Notepad users do not.

My current interaction with the Mathematica notebook is at the Notepad level. This is very frustrating because:

  • the Mathematica engine is very powerful

  • I'm so slow that I'm thinking faster than I can type or move, which is weird

Goal:

I want to become much more efficient at interacting with Mathematica. Hitting a productive level in Vim took me 2 books + a month of internalizing key bindings and consciously developing habits. I am willing to invest similar efforts for Mathematica. However, I'd like to invest the effort into the right habits.

Here's a bunch of simple things I can't do (not exhaustive list)

  • % in Vim to move around (), {} [] pairs
  • dw to delete a word
  • / to search + jump around the file
  • hjkl movement keys
  • and much more I'm not even aware of

Questions:

  1. How do I become more efficient at keyboard interaction with Mathematica?
  2. Is using the notebook (I'm using Mathematica 8) the right interface, or should I be setting up some up some type of Mathematica-REPL + editing in Vim + sending it over to the Mathematica-REPL over tcp / unix socket? [Like SLIME for Lisp].
  3. How do Mathematica wizards operate? My goal is NOT to make Mathematica feel like Vim. The goal is to learn how to master / communicate efficiently with Mathematica
  4. Any other tips for becoming more efficient at interacting with Mathematica
rm -rf
  • 88,781
  • 21
  • 293
  • 472

5 Answers5

84

The first step would be to not think of Mathematica's notebook editor as a full-fledged editor, but rather as an interactive interface with the kernel that has some editing capabilities, perhaps at par with notepad. If you don't, you'll always be disappointed.

The Mathematica editor:

As with all editors, you'll have to grok the editor before you can be productive in it (for various definitions of productive). Some shortcuts that will help you in writing code faster or in moving around faster are:

  • Enter matching [], (), {} with CommandOption], CommandOption), CommandOption} respectively. Use the modifications in this answer for shortcuts to [enter Part brackets: 〚〛. With these, matching 〚〛 are entered with CtrlCommand].
  • Using Ctrl. to select groups of expressions. Starting at any point in the expression, repeatedly pressing this will select successive groups in the expression. This might be something that you'll end up using a lot, and it also helps you visualize the precedence, etc.

    As an example, try placing your cursor at foo in the following dummy example and repeatedly press the shortcut. You should see something like below:

    f[a_, b_, c_] := a ~foo~ b + c // bar
    

    enter image description here

    enter image description here

    enter image description here

    enter image description here

  • Use CtrlShift and CtrlShift to extend the selection token-wise right or left.

  • Use CommandK to autocomplete symbols (or show a list of possible symbols) and CommandShiftK to autocomplete with placeholders for the possible arguments. For example, you'll get the following for Plot and you can press Tab to move through them.

    enter image description here

    See this answer to change the shortcut key to something else.

  • Use Command/ to comment/uncomment a selection (that you selected either manually or with Ctrl.)

  • Avoid using subscripts, no matter how high the temptation to make your expressions look more "mathy". Apart from slowing you down, you'll run into several issues if you're not careful, and even then, all bets are off.
  • Ctrl and Ctrl scrolls up/down in steps of 3 lines, but the cursor remains in the same position. This is helpful if you want to quickly look at a previous cell and continue typing (thanks to Halirutan for the tip). However, these keys are by default mapped to other exposé actions on a Mac, so this won't work.
  • Shift and Shift when inside a cell, selects the cell contents to the left or to the right respectively, till it wraps up exactly a line above or below your current line. Shift and Shift when outside a cell, selects the cell up or down. Further pressing up/down selects the previous/next cell and holding Shift while you do that will select multiple cells. You can also select a cell by clicking on its cell-bracket on the right.
  • I don't know of a simple way to exit cell editing mode to cell selection mode (using only the keyboard) other than by repeatedly pressing Shift (or down) till you reach the beginning/end of the cell, followed by up or down (this makes the cursor horizontal, which is cell selection/creation mode). The opposite is easy — you can go from a selected cell to editing it by simply pressing .
  • Jens mentions that a few emacs shortcuts work out of the box. For example, CtrlA/E for moving to the beginning/end of line, CtrlP/N to move up/down a line (this also crosses from cell editing to cell selection mode) and CtrlD to delete forward.
  • You can divide cells at the cursor with CommandShiftD and merge multiple cells with CommandShiftM
  • Learn the various styling shortcuts. See what the shortcuts Command1—9 (1 through 9) do.

This list could go on, so I'll stop here unless I think of something that is very essential to add to the list.

If you also use the notebooks for taking notes, typesetting math, etc., you might also want to learn the various input aliases and other shortcuts, some of which are on this page. The simplest way to find an existing shortcut for a function is to go to the function's documentation page. You can also create your own input aliases (EscEsc syntax) following my answer here.

Also see this answer, especially the link to a PDF at the bottom, which shows how fast one can be if they master the formatting and typesetting shortcuts (those notes were taken in realtime).

The Workbench editor:

Compared to the Mathematica notebook, the Workbench editor is rather inert. There is no connection to a kernel and you cannot evaluate your expressions as you go. The familiar input aliases which were quite handy in your notebook interface cannot be entered here. So you will have to resort to a more verbose form of coding.

The advantage is that the Workbench is based on the Eclipse IDE, and so you can use plugins that were designed for Eclipse with the Workbench. I was recently introduced to the viPlugin for the Workbench by Rolf Mertig, and it turns the Workbench into a modal editor (I haven't used it).

rm -rf
  • 88,781
  • 21
  • 293
  • 472
  • 2
    I believe Ctrl + K and Ctrl + Shift + K should be mentioned. http://mathematica.stackexchange.com/questions/4574/command-completion – Ajasja Jul 14 '12 at 14:57
  • @Ajasja Ah, yes! I forgot to mention them – rm -rf Jul 14 '12 at 14:58
  • 1
    is there a shortcut to switch from cell content editing mode to cell bracket navigation mode? i think this could be a useful addition. – Thies Heidecke Jul 14 '12 at 16:03
  • @ThiesHeidecke Good question. I don't know of an easy way (using keyboard only) other than Shift-Up/Down till you reach the end and then Up/Down (see my edit). If the cell is too big to navigate that way, the simplest way would be to break habit and use the mouse to click the bracket, which selects the cell – rm -rf Jul 14 '12 at 16:21
  • +1 for a fine answer. I'm curious how you use Shift+↑ and Shift+↓ -- that behavior doesn't seem very usable for my style. I find much greater use in Shift+Home and Shift+End. – Mr.Wizard Jul 14 '12 at 17:55
  • @Mr.Wizard I have a laptop, so no home/end keys. Using the trackpad, which is pretty big on a mac, is far easier. I mentioned the shortcuts here, because the OP said he was a vim user and being one myself, I understand his aversion to lifting his hands off the keyboard. If you think Shift-Home/End are better or do something that takes multiple Shift-Ups, feel free to add it to the answer – rm -rf Jul 14 '12 at 17:58
  • Maybe you should mention the fast navigation: Scrolling without moving Ctrl+Up, jumping tokenwise left/right Ctrl+Left when you are editing. – halirutan Jul 14 '12 at 18:09
  • Additionally, Ctrl+Shift+B is very useful. – halirutan Jul 14 '12 at 18:11
  • @halirutan Mr.Wizard added the tokenwise movement. I don't know what Ctrl-Up does. The equivalent Cmd-Up/Down on a mac is the equivalent of Home and End (takes you to the beginning and end of the notebook). Is that the intended action? Ctrl-Up has other OS bound default actions (on a mac). Yes, Ctrl-Shift-B is another that I forgot to add. I will add it in on the next edit (conserving edits) – rm -rf Jul 14 '12 at 18:11
  • 3
    @RM How do most people write large mathematica programs? Notebook? Workbench? Vi + Emacs? –  Jul 14 '12 at 19:02
  • @term-rewritica Basically in whatever they're comfortable in. You can just as easily write your programs in notebooks (with autogenerate m files on) and use the WB for testing and other functionality or use the WB editor for everything. Using pure vim/emacs might be harder for large scale projects (there's something for emacs, but you should ask acl about it in chat), but I can do that for smaller programs like for answers here. – rm -rf Jul 14 '12 at 19:11
  • @R.M Ctrl+Up/Down scrolls through the notebook in 3 lines steps without altering the position of the cursor. So it is easy to start writing something, checking then a completely different position in the notebook and just go on writing, because Mathematica jumps back to the cursor-position. – halirutan Jul 14 '12 at 19:52
  • 1
    (+1) Regarding emacs - note that many emacs shortcuts actually work out of the box in the notebook, e.g., the following Control key combinations: p/n (up down), d (forward-delete), a/e (beginning/end of line)... – Jens Jul 15 '12 at 01:18
  • @term-rewritica Just for reference, there is a plugin bringing emacs shortcuts to eclipse. It's not perfect, but better than nothing. A quick search revealed that there is something similar for Vim. I can't comment on it's usefulness though. – sebhofer Jul 15 '12 at 09:37
  • Many Mathematica shortcuts also work in Eclipse with Wolfram Workbench by looking at the Keyboard shortcuts option. – faysou Jul 17 '12 at 19:20
  • To exit cell editing mode and select a cell bracket via the keyboard, press a direction arrow until you are outside the cell and have the horizontal blinking insertion point for a new cell. I usually just use the down arrow. Now you can press Shift up (or down) arrow to select a cell bracket. While in cell bracket selection mode you can use the up down arrow keys to navigate. – Daniel Flatin Jul 19 '12 at 11:41
  • @Jens only in Mac OS, right? – Andrew Dec 16 '12 at 22:13
  • @AndrewMacFie I can only speak for Mac - so I have to remain agnostic about keyboard shortcuts on the other platforms. – Jens Dec 16 '12 at 23:34
  • @rm-rf not sure if you're aware of this, but if you press Ctrl+. enough times you will select the cell, then the cell group, then the whole notebook. This is on Windows at least. Knowing Wolfram Research, I'm surprised you can't subsequently select the whole mathematica program itself, then the OS, your computer, the objects in your room, your room, your house, etc. – amr May 09 '13 at 23:28
  • 1
    @ThiesHeidecke @rm-rf You could add MenuItem["Select &Cell", FrontEnd\KernelExecute[ToExpression@"SelectionMove[EvaluationNotebook[],#,Cell] & /@ {Next,Previous};"],MenuKey["Right",Modifiers->{"Command"}], MenuEvaluator -> Automatic], MenuItem["Select Cell C&ontents",FrontEnd`KernelExecute[ToExpression@"SelectionMove[EvaluationNotebook[],Before,CellContents];"],MenuKey["Left",Modifiers->{"Command"}], MenuEvaluator->Automatic]toMenuSetup.tr`, where you think it fits. (I've added to Edit menu, nead “Extend Selection”.) Sorry if you have figured it out by yourself already. :-) – akater Jun 19 '14 at 21:27
  • I find on a Windows system that the Emacs shortcuts will not work because of the conflict with traditional key bindings, such as C-p being the print command. – Carl Morris Aug 13 '14 at 22:27
  • @R.M., to save me from reading this entire thread, is there an easy way to delete a token versus single characters? – alancalvitti Dec 20 '16 at 19:29
6

See the file KeyEventTranslations.tr located at

FileNames["KeyEventTranslations.tr", 
  FileNameJoin[{$InstallationDirectory, "SystemFiles", "FrontEnd", 
    "TextResources"}], 2] // First

This file can be used to set up hotkeys as you please. There are basically not restrictions here, in that you can also set a key to cause an evaluation, using KernelEvaluate (example here, by Rolf Mertig). This file is also a great source for FrontEndTokens that you may want to bind to (different) keys. A word of caution: it is probably wise to backup KeyEventTranslations.tr.

The FrontEndTokens of particular interest here are the tokens under

(* Typesetting motion commands *)

Of which I especially like "MovePreviousExpression" and "MoveNextExpression".

These can help you jump over matching brackets. Using them you can also jump from + to + and from comma to comma in a way that respects the structure of the expressions, which I think can be useful. It all seems quite nice, though I would have made it a little differently myself :P (i.e. I would have made it easier to jump between arguments of the same function).

Jacob Akkerboom
  • 12,215
  • 45
  • 79
  • It is indeed useful to look closer to that file. :) You may want to attach this Q&A link to your answer. Personally, I find very useful a shortcut deleting all output cells via "DeleteGeneratedCells" token. – Kuba Jul 19 '13 at 23:24
  • @Kuba I actually reinvestigated that file to see if there were any useful tokens that could make me get your bounty in the question of the syntax errors :). I have made some progress there, and maybe I should be satisfied, but I was going for full automation :). I hope I can show you soon, but I think I will go to sleep now. Goodnight :). – Jacob Akkerboom Jul 19 '13 at 23:29
  • I'm looking forward to see it :) Don't miss the bounty deadline :) Goodnight. – Kuba Jul 19 '13 at 23:31
6

For the record, here are some platform-specific suggestions for enhancing keyboard use.

If you're using Mac OS X, there are many ways you can customize your working environment, and if you spend all day at the keyboard you'll probably already be doing a lot of customizations. Although it's a good idea to master the techniques of individual applications, it's also sensible to find some system-wide tricks - you can then use these wherever you find yourself entering text.

For example, there's a strange little System Utility called KeyRemap4Macbook that lets you control the way the keyboard controls your Mac (desptie the name it works on any Mac, not just MacBooks). It also provides a Ubiquitous Vim Bindings for Normal Mode which gives you some Vim navigation shortcuts in any text field in any application, including Mathematica. It's nothing like The Real Vim, though - you can't do 'count+command' or most of the 10000 other things that those crazy Vimmers love...

Alternatively, you could look at QuickCursor on the Mac App Store. This is an inexpensive little app that lets you edit the contents of a text area in your favourite text editor, outside the current application. For example, instead of writing your text inside a text-area in a web browser, you can, after pressing a shortcut, edit the text in, say, MacVim or BBEdit. When you've finished in MacVim, Save and Close the window, and the edited text re-appears in the browser's text area.

QuickCursor works OK-ish with Mathematica, although you have to select the text you want to edit first. It would work even better if you could customize Mathematica so that Select All selected the current cell's text content, rather than every cell in the document. But that's another question for another day.

Finally, the text-expansion utilities are always useful - as you probably know. Both KeyBoard Maestro and TextExpander let you combine keyboard shortcut with scripts, so that typing specific triggers can do anything from inserting an empty Table construction to running Perl or Python scripts to insert on-the-fly calculations.

cormullion
  • 24,243
  • 4
  • 64
  • 133
5

The past few weeks I've gotten into customizing my Mathematica keybindings so I figured I'd share them and share what I've learned. This all works as stated on my system ($Version is 12.1.1 for Linux x86 (64-bit) (June 19, 2020)) but I have not tested these elsewhere so YMMV.


If you like to use input aliases and find hitting Esc twice inconvienent then add this to your init.m file located in FileNameJoin[{$UserBaseDirectory, "Kernel"}].

SetOptions[SelectedNotebook[], 
 NotebookEventActions -> {{"KeyDown", "\t"} :> KeyBindings`OnTab[]}];

KeyBindingsOnTab[] := Module[{nb, type, range}, nb = SelectedNotebook[]; {type, range} = {"CellSelectionType", "CharacterRange"} /. FrontEndExecute @ FrontEndUndocumentedGetSelectionPacket[nb]; If[type === "ContentSelection", FrontEndExecute @ FrontEndToken[nb, "Tab"]; If[range === ("CharacterRange" /. FrontEndExecute @ FrontEnd`UndocumentedGetSelectionPacket[nb]), (* If hitting tab does nothing then its not for navigation or code completion so we are good to run our code *) If[range[[1]] === range[[2]], FrontEndTokenExecute[nb, "ExpandSelection"] ]; NotebookApply[nb, "\[AliasDelimiter]\[SelectionPlaceholder]\[AliasDelimiter]"] ] ] ];

Now if you type an alias and hit tab it should autoexpand appropriately, no Esc needed.


For the rest of these you'll need to actively modify your KeyEventTranslations.tr and MenuSetup.tr files. This post is a good collection of links for the myriad of ways people have come up with to do this (its also quite the read).

My advice: find the originals on your system, for me they are at FileNameJoin[{$InstallationDirectory, "12.1", "SystemFiles", "FrontEnd", "TextResources", "X"}] the X will change depending on your OS, copy only KeyEventTranslations.tr and MenuSetup.tr to FileNameJoin[{$UserBaseDirectory, "SystemFiles", "FrontEnd", "TextResources", "X"}] (once again the X is OS dependent, you may need to make these directories if they do not already exist). Mathematica gives these files priority over the ones in $InstallationDirectory, you should make changes here so you always have clean copies on your system to default to.

KeyEventTranslations.tr lets you set keybindings, MenuSetup.tr lets you both set keybindings and make additional menus and menu items. Keybindings in MenuSetup.tr have priority over those in KeyEventTranslations.tr. When making a new keybinding, you must comment out other bindings (in both files) that use that key combo, e.g. if you want to attach something to Ctrl + c you must find the lines of code that make Ctrl + c do Copy and comment them out.


Often you must surround some code in brackets. I set bindings for [ to surround the selection (the highlighted code) in these. To do this add this to KeyEventTranslations.tr

Item[
    KeyEvent["[", Modifiers -> {}], 
    KernelExecute @ KeyBindings`OnOpenBracket[],
    MenuEvaluator -> Automatic
]

and this to your init.m

KeyBindings`OnOpenBracket[] := (
    Module[{type, range},
     {type, range} = {"CellSelectionType", "CharacterRange"} /. 
       FrontEndExecute @ FrontEnd`UndocumentedGetSelectionPacket[SelectedNotebook[]];
     If[type === "ContentSelection" && range[[1]] =!= range[[2]],
      NotebookApply[SelectedNotebook[], "[\[SelectionPlaceholder]]", All],
      NotebookWrite[SelectedNotebook[], "["]]
     ];
);

it is easy to make these for {, (, and < as well.


Often you want to do the opposite: to desurround the selection. This is a bit harder. Add this to KeyEventTranslations.tr

Item[
    KeyEvent["Backspace", Modifiers -> {Control, Shift}], 
    KernelExecute @ KeyBindings`Desurround[],
    MenuEvaluator -> Automatic
]

And this to init.m

(* Gets the first character of a box structure, if the first thing is 
not a character, returns None *)
KeyBindings`FirstCharacter[string_String] := First@Characters@string;
KeyBindings`FirstCharacter[RowBox[{first_, ___}]] := 
  KeyBindings`FirstCharacter@first;
KeyBindings`FirstCharacter[_] := None;

(* Gets the last character of a box structure, if the last thing is not a character, returns None *) KeyBindingsLastCharacter[string_String] := Last@Characters@string; KeyBindingsLastCharacter[RowBox[{___, last_}]] := KeyBindingsLastCharacter@last; KeyBindingsLastCharacter[_] := None;

KeyBindingsDesurround[] := Module[{nb, selection, range, first, last}, nb = SelectedNotebook[]; selection = NotebookRead[nb]; range = &quot;CharacterRange&quot; /. FrontEndExecute@FrontEndUndocumentedGetSelectionPacket[nb]; first = KeyBindingsFirstCharacter@selection; last = KeyBindingsLastCharacter@selection; If[(first === "[" && last === "]") || (first === "{" && last === "}") || (first === "(" && last === ")") || (first === "&quot;" && last === "&quot;") || (first === "<" && last === ">") || (first === "|" && last === "|") || (first === "[LeftAssociation]" && last === "[RightAssociation]"), FrontEndExecute@FrontEndToken[SelectedNotebook[], "MoveNext"]; FrontEndExecute@FrontEndToken[SelectedNotebook[], "DeletePrevious"]; SelectionMove[nb, Previous, Character, range[[2]] - range[[1]] - 1]; FrontEndExecute@FrontEndToken[SelectedNotebook[], "DeleteNext"]; SelectionMove[nb, All, Character, range[[2]] - range[[1]] - 2]; ]; If[first === "<" && last === ">", KeyBindings`Desurround[] ]; ];

Theres actually a FrontEndToken, "SelectionUnbracket", that does this, but it doesn't work under many circumstances.


Often I want to delete the cell I am in, or, if between cells, the cells above the cursor. Add this to KeyEventTranslations.tr

Item[
    KeyEvent["r", Modifiers -> {Control}], 
    KernelExecute @ KeyBindings`DeleteCell[],
    MenuEvaluator -> Automatic
]

And this to init.m

KeyBindings`DeleteCell[] := Module[{nb},
    nb = SelectedNotebook[];
    Switch["CellSelectionType" /. FrontEndExecute @ FrontEnd`UndocumentedGetSelectionPacket[nb],
    &quot;ContentSelection&quot;,
    SelectionMove[nb, All, Cell];
    NotebookDelete[nb],

    &quot;BelowCell&quot; | &quot;AboveCell&quot;,
    KeyBindings`SelectUpCell[];
    NotebookDelete[nb],

    &quot;OnCell&quot; | &quot;CellRangeSelection&quot;,
    NotebookDelete[nb]
]

];

KeyBindings`SelectUpCell[] := Module[{nb, content, cell}, nb = SelectedNotebook[]; FrontEndExecute @ FrontEndToken[nb, "MovePreviousLine"]; SelectionMove[nb, All, Cell]; cell = SelectedCells[nb][[1]]; If[SelectionMove[nb, All, CellGroup] === $Failed, SelectionMove[cell, All, Cell]; ]; If[Last @ SelectedCells[nb] =!= cell, SelectionMove[cell, All, Cell]; ]; ];


Ok, these are the last two. These are intended to override Ctrl + Shift + Left and Ctrl + Shift + Right. The big difference is they jump over matching brackets/braces/etc. They are kinda messy and not instant like the builtin bindings feel, but they are fast enough to be usable to me so I will include them here. Add this to KeyEventTranslations.tr

Item[
    KeyEvent["Left", Modifiers -> {Control, Shift}], 
    KernelExecute @ KeyBindings`SelectLeftPrevious[],
    MenuEvaluator -> Automatic
],

Item[ KeyEvent["Right", Modifiers -> {Control, Shift}], KernelExecute @ KeyBindings`SelectRightNext[], MenuEvaluator -> Automatic ]

And this to init.m

KeyBindings`SelectLeftPrevious[] := (
    Quiet @ Module[{nb, range, prevChar, range2},
      nb = SelectedNotebook[];
      range = "CharacterRange" /. 
       FrontEndExecute @ FrontEnd`UndocumentedGetSelectionPacket[nb];
      If[
       range[[1]] =!= range[[2]], 
       FrontEndExecute @ FrontEndToken[nb, "MovePrevious"]
       ];
      FrontEndExecute @ FrontEndToken[nb, "SelectPrevious"];
      prevChar = NotebookRead[nb];
      (*Print@prevChar;*)
      Which[
   (* Is it a backtick or period *)

   prevChar === &quot;`&quot; || prevChar === &quot;.&quot;,
   (* Then do this *)
   (*Print@&quot;backtick or period&quot;;*)

   FrontEndExecute @ FrontEndToken[nb, &quot;SelectPreviousWord&quot;],

   (* Is it a commma *)
   prevChar === &quot;,&quot;,
   (* Then do this *)
   (*Print@&quot;comma&quot;;*)

   KeyBindings`SelectLeftWord[],

   (* Is it a word *)

   MatchQ[prevChar, 
    Except[&quot;]&quot; | &quot;}&quot; | &quot;)&quot; | &quot;\[RightAssociation]&quot; | &quot;&gt;&quot; | &quot;\&quot;&quot; | 
      &quot;\[RightDoubleBracket]&quot;, _String]],
   (* If yes then do this *)
   (*Print@&quot;word&quot;;*)

   FrontEndExecute @ FrontEndToken[nb, &quot;MoveNext&quot;];
   FrontEndExecute @ FrontEndToken[nb, &quot;ExpandSelection&quot;];
   If[
    NotebookRead[nb] == &quot;.&quot;,
    FrontEndExecute @ FrontEndToken[nb, &quot;MovePrevious&quot;];
    FrontEndExecute @ FrontEndToken[nb, &quot;SelectPreviousWord&quot;]
    ];
   If[
    NotebookRead[nb] == &quot; &quot;,
    FrontEndExecute @ FrontEndToken[nb, &quot;SelectPreviousWord&quot;]
    ],

   (* Is it a ] or \[RightDoubleBracket] *)

   prevChar === &quot;]&quot; || prevChar === &quot;\[RightDoubleBracket]&quot;,
   (* If yes then do this *)
   (*Print@&quot;] or \[RightDoubleBracket]&quot;;*)

      FrontEndExecute @ FrontEndToken[nb, &quot;ExpandSelection&quot;];
   FrontEndExecute @ FrontEndToken[nb, &quot;MovePrevious&quot;];
   FrontEndExecute @ FrontEndToken[nb, &quot;SelectNext&quot;];
   If[NotebookRead[nb] === &quot;[&quot;,
    FrontEndExecute @ FrontEndToken[nb, &quot;ExpandSelection&quot;],
    FrontEndExecute @ FrontEndToken[nb, &quot;MoveNext&quot;];
    FrontEndExecute @ FrontEndToken[nb, &quot;MoveNextWord&quot;]
    ],

   (* Is it a box thing *)
   MatchQ[prevChar, Except[_String]],
   (* If yes then do this *)
   (*Print@&quot;box thing&quot;;*)
   Null (* So MMA doesn't get mad on startup *),

   (* Then it must be one of } ) \[RightAssociation] &gt; &quot;*)
   True,
   (*Print@&quot;one of } ) \[RightAssociation] &gt; \&quot;&quot;;*)

   FrontEndExecute @ FrontEndToken[nb, &quot;ExpandSelection&quot;];
   If[
    MatchQ[NotebookRead[nb], &quot;*)&quot; | &quot;|&gt;&quot;],
    FrontEndExecute @ FrontEndToken[nb, &quot;ExpandSelection&quot;]
    ]
   ];
  range2 = 
   &quot;CharacterRange&quot; /. 
    FrontEndExecute @ FrontEnd`UndocumentedGetSelectionPacket[nb];
  SelectionMove[nb, All, Character, range[[2]] - range2 [[1]]]
  ]

);

KeyBindingsSelectRightNext[] := ( Quiet @ Module[{nb, range, nextChar, range2}, nb = SelectedNotebook[]; range = &quot;CharacterRange&quot; /. FrontEndExecute @ FrontEndUndocumentedGetSelectionPacket[nb]; If[ range[[1]] =!= range[[2]], FrontEndExecute @ FrontEndToken[nb, "MoveNext"] ]; FrontEndExecute @ FrontEndToken[nb, "SelectNext"]; nextChar = NotebookRead[nb]; (Print@nextChar;) Which[

   (* Is it a backtick or period *)

   nextChar === &quot;`&quot; || nextChar === &quot;.&quot;,
   (* Then do this *)
   (*Print@&quot;backtick or period&quot;;*)

   FrontEndExecute @ FrontEndToken[nb, &quot;SelectNextWord&quot;],

   (* Is it a commma a space or an indenting newline *)

   nextChar === &quot;,&quot; || nextChar === &quot; &quot; || nextChar === &quot;\[IndentingNewLine]&quot;,
   (* Then do this *)
   (*Print@&quot;comma space or indenting newline&quot;;*)

      KeyBindings`SelectRightWord[],

   (* Is it a word *)

   MatchQ[nextChar, 
    Except[&quot;[&quot; | &quot;{&quot; | &quot;(&quot; | &quot;\[LeftAssociation]&quot; | &quot;&lt;&quot; | &quot;\&quot;&quot; | 
      &quot;\[LeftDoubleBracket]&quot;, _String]],
   (* If yes then do this *)
   (*Print@&quot;word&quot;;*)

   FrontEndExecute @ FrontEndToken[nb, &quot;MoveNext&quot;];
   FrontEndExecute @ FrontEndToken[nb, &quot;ExpandSelection&quot;],

   (* Is it a [ or \[LeftDoubleBracket] *)

   nextChar === &quot;[&quot; || nextChar === &quot;\[LeftDoubleBracket]&quot;,
   (* If yes then do this *)
   (*Print@&quot;[ or \[LeftDoubleBracket]&quot;;*)

   FrontEndExecute @ FrontEndToken[nb, &quot;ExpandSelection&quot;];
   FrontEndExecute @ FrontEndToken[nb, &quot;MoveNext&quot;];
   KeyBindings`SelectLeftWord[],

   (* Is it a box thing *)
   MatchQ[nextChar, Except[_String]],
   (* If yes then do this *)
   (*Print@&quot;box thing&quot;;*)
   Null (* So MMA doesn't get mad on startup *),

   (* Then it must be one of { ( \[LeftAssociation] &lt; &quot; *)
   True,
   (*Print@&quot;one of { ( \[LeftAssociation] &lt; \&quot;&quot;;*)

   FrontEndExecute @ FrontEndToken[nb, &quot;ExpandSelection&quot;];
   If[
    MatchQ[NotebookRead[nb], &quot;(*&quot; | &quot;&lt;|&quot;],
    FrontEndExecute @ FrontEndToken[nb, &quot;ExpandSelection&quot;]
    ]
   ];
  range2 = &quot;CharacterRange&quot; /. 
    FrontEndExecute @ FrontEnd`UndocumentedGetSelectionPacket[nb];
  SelectionMove[nb, Before, CellContents, AutoScroll -&gt; False];
  SelectionMove[nb, Next, Character, range[[1]], AutoScroll -&gt; False];
  SelectionMove[nb, All, Character, range2[[2]] - range [[1]], AutoScroll -&gt; False]
  ]

);

Another difference in behavior is that these will only ever increase the size of the selection, e.g. if you just expanded to the left using Ctrl + Shift + Left, Ctrl + Shift + Right will not undo this expansion, instead it will expand the right side of the selection outward (to the right). This isn't ideal behavior, to fix it one would need a global variable, say $ActiveSideOfSelection, that tracks which side of the selection is the "active" side. I haven't done this (though it wouldn't be that hard to set up).


Ok, thats it for my keybindings, now I'll quickly list the basic functions to write more of these.

  • SelectionMove - Is really more for moving between objects in a notebook like cells and graphics, and not so much for text, for which the only useful ways to use it are (let nb be a NotebookObject)
    • SelectionMove[nb, Previous, Character, n] - move the cursor back n characters
    • SelectionMove[nb, Next, Character, n] - move the cursor forward n characters
    • SelectionMove[nb, All, Character, n] - starting from the left side of the selection, highlight the next n characters
    • SelectionMove[nb, Before, CellContents] - move to the beginning of the cell
  • FrontEnd`UndocumentedGetSelectionPacket - this is immensely useful, call using FrontEndExecute, it inputs a NotebookObject and outputs (among other things) the beginning and ending locations (in characters from the start of the cell) of the selection. It also outputs the 'type' of the selection as "CellSelectionType" the possible values I have found for it are
    • "NoNotebookSelection" - there is nothing in the notebook
    • "AboveCell" - the cursor is between cells and guaranteed to have a cell above it
    • "BelowCell" - the cursor is between cells and guaranteed to have a cell below it (not sure why this and "AboveCell" arn't just called "BetweenCells" or something like that)
    • "OnCell" - the selection is on a cell bracket
    • "CellRangeSelection" - the selection is on a cell group
    • "ContentSelection" - the cursor is in a cell and there may be something highlighted
  • FrontEndToken - call using FrontEndExecute, it inputs a NotebookObject and a string called a FrontEndToken, aside from the obvious ones, the ones I find useful are
    • "CreateInlineCell" - Like hitting Ctrl + (
    • "ExpandSelection" - Like hitting Ctrl + .

Some relevant documentation here and here.


Here is a Github gist with my files. They have other changes from the ones I discussed here, for example, I switched the duties of Ctrl + Enter and Ctrl + . (this way Ctrl + , and Ctrl + . make new rows and columns which makes more sense to me). Most of the changes are marked by comments to make them more visually obvious, also my notes at the top of the files provide some info. init.m also has some auxiliary functions that I made but may not have ended up using. I have also added a menu called Macros in MenuSetup.tr that provide some editing convenience.


6224 is another collection of keybindings, they mostly have to do with cells, so they match well with mine which are mostly text manipulation.

Tanner Legvold
  • 529
  • 3
  • 13
3

It is unfortunate that so few of the front-end internals are exposed through the front-end's API. Hopefully this will change in the near future as part of the front-end's evolution into a worthy competitor to LaTeX for producing scientific documents.

Unfortunately the Wolfram team has a lot of work to do to match the quality of LaTeX's output, or even that of Word 2013 for that matter, given the progress Sargent's team over at Microsoft has made toward introducing high-quality math typesetting (that incorporates for example modern math font technology like OpenType) into the Office product line. (I wonder to what extent Wolfram could leverage the Microsoft RichEdit controls in future versions of Mma (for Windows) in order to avoid the need to re-create this technology or if their decision to switch to Qt precludes them from doing so at this point ... assuming there are not other reasons that would make it impractical for the front-end to use RichEdit controls. The RichEdit controls of course offer a rich API that would facilitate developing VIM-like navigation shortcuts.)

In any event, your question concerns ease of navigation in the current Mma front-end, an issue that has occupied my attention as well. I've found a tool called Autohotkey to be quite useful for creating a "mock version" of vim that can be useful not only for Mma but also other under-endowed editors like the Notepad that you mention.

If this type of workaround interests you, here's a script to get you started: modal VIM script in Autohotkey

StackExchanger
  • 1,511
  • 13
  • 20