97
$\begingroup$

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
$\endgroup$
0

5 Answers 5

83
$\begingroup$

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).

$\endgroup$
22
  • 2
    $\begingroup$ I believe Ctrl + K and Ctrl + Shift + K should be mentioned. mathematica.stackexchange.com/questions/4574/command-completion $\endgroup$
    – Ajasja
    Jul 14, 2012 at 14:57
  • 1
    $\begingroup$ is there a shortcut to switch from cell content editing mode to cell bracket navigation mode? i think this could be a useful addition. $\endgroup$ Jul 14, 2012 at 16:03
  • 3
    $\begingroup$ @RM How do most people write large mathematica programs? Notebook? Workbench? Vi + Emacs? $\endgroup$
    – user1602
    Jul 14, 2012 at 19:02
  • 1
    $\begingroup$ (+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)... $\endgroup$
    – Jens
    Jul 15, 2012 at 1:18
  • 1
    $\begingroup$ @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] to MenuSetup.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. :-) $\endgroup$
    – akater
    Jun 19, 2014 at 21:27
6
$\begingroup$

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.

$\endgroup$
3
  • 1
    $\begingroup$ for OS X there's also BetterTouchTool which is free and lets you define arbitrary shortcuts for keyboard, mouse and trackpad actions. eg, shift+cmd+m to switch to mathematica. $\endgroup$
    – acl
    Apr 18, 2013 at 10:56
  • $\begingroup$ @acl Looks good, and free too! $\endgroup$
    – cormullion
    Apr 18, 2013 at 10:59
  • 1
    $\begingroup$ I'd like to point out for future visitors that (the absolutely excellent) KeyRemap4MacBook, which I have also sworn by for many years, has been renamed to Karabiner. $\endgroup$ Mar 26, 2016 at 7:22
6
$\begingroup$

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).

$\endgroup$
3
  • $\begingroup$ 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. $\endgroup$
    – Kuba
    Jul 19, 2013 at 23:24
  • $\begingroup$ @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 :). $\endgroup$ Jul 19, 2013 at 23:29
  • $\begingroup$ I'm looking forward to see it :) Don't miss the bounty deadline :) Goodnight. $\endgroup$
    – Kuba
    Jul 19, 2013 at 23:31
5
$\begingroup$

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[]}]; 

KeyBindings`OnTab[] := Module[{nb, type, range}, 
    nb = SelectedNotebook[];
    {type, range} = {"CellSelectionType", "CharacterRange"} /. FrontEndExecute @ FrontEnd`UndocumentedGetSelectionPacket[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 *)
KeyBindings`LastCharacter[string_String] := Last@Characters@string;
KeyBindings`LastCharacter[RowBox[{___, last_}]] := 
  KeyBindings`LastCharacter@last;
KeyBindings`LastCharacter[_] := None;


KeyBindings`Desurround[] := Module[{nb, selection, range, first, last},
  nb = SelectedNotebook[];
  selection = NotebookRead[nb];
  range = 
   "CharacterRange" /. 
    FrontEndExecute@FrontEnd`UndocumentedGetSelectionPacket[nb];
  first = KeyBindings`FirstCharacter@selection;
  last = KeyBindings`LastCharacter@selection;
  If[(first === "[" && last === "]") ||
    (first === "{" && last === "}") ||
    (first === "(" && last === ")") ||
    (first === "\"" && last === "\"") ||
    (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],
    
        "ContentSelection",
        SelectionMove[nb, All, Cell];
        NotebookDelete[nb],
        
        "BelowCell" | "AboveCell",
        KeyBindings`SelectUpCell[];
        NotebookDelete[nb],
        
        "OnCell" | "CellRangeSelection",
        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 === "`" || prevChar === ".",
       (* Then do this *)
       (*Print@"backtick or period";*)
       
       FrontEndExecute @ FrontEndToken[nb, "SelectPreviousWord"],
       
       (* Is it a commma *)
       prevChar === ",",
       (* Then do this *)
       (*Print@"comma";*)
       
       KeyBindings`SelectLeftWord[],
       
       (* Is it a word *)
       
       MatchQ[prevChar, 
        Except["]" | "}" | ")" | "\[RightAssociation]" | ">" | "\"" | 
          "\[RightDoubleBracket]", _String]],
       (* If yes then do this *)
       (*Print@"word";*)
       
       FrontEndExecute @ FrontEndToken[nb, "MoveNext"];
       FrontEndExecute @ FrontEndToken[nb, "ExpandSelection"];
       If[
        NotebookRead[nb] == ".",
        FrontEndExecute @ FrontEndToken[nb, "MovePrevious"];
        FrontEndExecute @ FrontEndToken[nb, "SelectPreviousWord"]
        ];
       If[
        NotebookRead[nb] == " ",
        FrontEndExecute @ FrontEndToken[nb, "SelectPreviousWord"]
        ],
       
       (* Is it a ] or \[RightDoubleBracket] *)
       
       prevChar === "]" || prevChar === "\[RightDoubleBracket]",
       (* If yes then do this *)
       (*Print@"] or \[RightDoubleBracket]";*)
    
          FrontEndExecute @ FrontEndToken[nb, "ExpandSelection"];
       FrontEndExecute @ FrontEndToken[nb, "MovePrevious"];
       FrontEndExecute @ FrontEndToken[nb, "SelectNext"];
       If[NotebookRead[nb] === "[",
        FrontEndExecute @ FrontEndToken[nb, "ExpandSelection"],
        FrontEndExecute @ FrontEndToken[nb, "MoveNext"];
        FrontEndExecute @ FrontEndToken[nb, "MoveNextWord"]
        ],
       
       (* Is it a box thing *)
       MatchQ[prevChar, Except[_String]],
       (* If yes then do this *)
       (*Print@"box thing";*)
       Null (* So MMA doesn't get mad on startup *),
       
       (* Then it must be one of } ) \[RightAssociation] > "*)
       True,
       (*Print@"one of } ) \[RightAssociation] > \"";*)
       
       FrontEndExecute @ FrontEndToken[nb, "ExpandSelection"];
       If[
        MatchQ[NotebookRead[nb], "*)" | "|>"],
        FrontEndExecute @ FrontEndToken[nb, "ExpandSelection"]
        ]
       ];
      range2 = 
       "CharacterRange" /. 
        FrontEndExecute @ FrontEnd`UndocumentedGetSelectionPacket[nb];
      SelectionMove[nb, All, Character, range[[2]] - range2 [[1]]]
      ]
);

KeyBindings`SelectRightNext[] := (
    Quiet @ Module[{nb, range, nextChar, range2},
      nb = SelectedNotebook[];
      range = "CharacterRange" /. 
        FrontEndExecute @ FrontEnd`UndocumentedGetSelectionPacket[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 === "`" || nextChar === ".",
       (* Then do this *)
       (*Print@"backtick or period";*)
       
       FrontEndExecute @ FrontEndToken[nb, "SelectNextWord"],
       
       (* Is it a commma a space or an indenting newline *)
       
       nextChar === "," || nextChar === " " || nextChar === "\[IndentingNewLine]",
       (* Then do this *)
       (*Print@"comma space or indenting newline";*)
    
          KeyBindings`SelectRightWord[],
       
       (* Is it a word *)
       
       MatchQ[nextChar, 
        Except["[" | "{" | "(" | "\[LeftAssociation]" | "<" | "\"" | 
          "\[LeftDoubleBracket]", _String]],
       (* If yes then do this *)
       (*Print@"word";*)
       
       FrontEndExecute @ FrontEndToken[nb, "MoveNext"];
       FrontEndExecute @ FrontEndToken[nb, "ExpandSelection"],
       
       (* Is it a [ or \[LeftDoubleBracket] *)
       
       nextChar === "[" || nextChar === "\[LeftDoubleBracket]",
       (* If yes then do this *)
       (*Print@"[ or \[LeftDoubleBracket]";*)
    
       FrontEndExecute @ FrontEndToken[nb, "ExpandSelection"];
       FrontEndExecute @ FrontEndToken[nb, "MoveNext"];
       KeyBindings`SelectLeftWord[],
       
       (* Is it a box thing *)
       MatchQ[nextChar, Except[_String]],
       (* If yes then do this *)
       (*Print@"box thing";*)
       Null (* So MMA doesn't get mad on startup *),
       
       (* Then it must be one of { ( \[LeftAssociation] < " *)
       True,
       (*Print@"one of { ( \[LeftAssociation] < \"";*)
       
       FrontEndExecute @ FrontEndToken[nb, "ExpandSelection"];
       If[
        MatchQ[NotebookRead[nb], "(*" | "<|"],
        FrontEndExecute @ FrontEndToken[nb, "ExpandSelection"]
        ]
       ];
      range2 = "CharacterRange" /. 
        FrontEndExecute @ FrontEnd`UndocumentedGetSelectionPacket[nb];
      SelectionMove[nb, Before, CellContents, AutoScroll -> False];
      SelectionMove[nb, Next, Character, range[[1]], AutoScroll -> False];
      SelectionMove[nb, All, Character, range2[[2]] - range [[1]], AutoScroll -> 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.

$\endgroup$
3
$\begingroup$

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

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.